FreeTube/src/renderer/store/modules/utils.js

957 lines
28 KiB
JavaScript

import fs from 'fs/promises'
import path from 'path'
import i18n from '../../i18n/index'
import { set as vueSet } from 'vue'
import { IpcChannels } from '../../../constants'
import { pathExists } from '../../helpers/filesystem'
import {
CHANNEL_HANDLE_REGEX,
createWebURL,
getVideoParamsFromUrl,
openExternalLink,
replaceFilenameForbiddenChars,
searchFiltersMatch,
showExternalPlayerUnsupportedActionToast,
showSaveDialog,
showToast
} from '../../helpers/utils'
const state = {
isSideNavOpen: false,
outlinesHidden: true,
sessionSearchHistory: [],
popularCache: null,
trendingCache: {
default: null,
music: null,
gaming: null,
movies: null
},
cachedPlaylist: null,
deArrowCache: {},
showProgressBar: false,
showAddToPlaylistPrompt: false,
showCreatePlaylistPrompt: false,
progressBarPercentage: 0,
toBeAddedToPlaylistVideoList: [],
newPlaylistDefaultProperties: {},
newPlaylistVideoObject: [],
regionNames: [],
regionValues: [],
recentBlogPosts: [],
searchSettings: {
sortBy: 'relevance',
time: '',
type: 'all',
duration: ''
},
externalPlayerNames: [],
externalPlayerValues: [],
externalPlayerCmdArguments: {},
lastVideoRefreshTimestampByProfile: {},
lastShortRefreshTimestampByProfile: {},
lastLiveRefreshTimestampByProfile: {},
lastCommunityRefreshTimestampByProfile: {},
lastPopularRefreshTimestamp: '',
lastTrendingRefreshTimestamp: '',
}
const getters = {
getIsSideNavOpen () {
return state.isSideNavOpen
},
getOutlinesHidden() {
return state.outlinesHidden
},
getCurrentVolume () {
return state.currentVolume
},
getSessionSearchHistory () {
return state.sessionSearchHistory
},
getDeArrowCache: (state) => {
return state.deArrowCache
},
getPopularCache () {
return state.popularCache
},
getTrendingCache () {
return state.trendingCache
},
getCachedPlaylist() {
return state.cachedPlaylist
},
getSearchSettings () {
return state.searchSettings
},
getShowAddToPlaylistPrompt () {
return state.showAddToPlaylistPrompt
},
getShowCreatePlaylistPrompt () {
return state.showCreatePlaylistPrompt
},
getToBeAddedToPlaylistVideoList () {
return state.toBeAddedToPlaylistVideoList
},
getNewPlaylistDefaultProperties () {
return state.newPlaylistDefaultProperties
},
getNewPlaylistVideoObject () {
return state.newPlaylistVideoObject
},
getShowProgressBar () {
return state.showProgressBar
},
getProgressBarPercentage () {
return state.progressBarPercentage
},
getRegionNames () {
return state.regionNames
},
getRegionValues () {
return state.regionValues
},
getRecentBlogPosts () {
return state.recentBlogPosts
},
getExternalPlayerNames () {
return state.externalPlayerNames
},
getExternalPlayerValues () {
return state.externalPlayerValues
},
getExternalPlayerCmdArguments () {
return state.externalPlayerCmdArguments
},
getLastTrendingRefreshTimestamp() {
return state.lastTrendingRefreshTimestamp
},
getLastPopularRefreshTimestamp() {
return state.lastPopularRefreshTimestamp
},
getLastCommunityRefreshTimestampByProfile: (state) => (profileId) => {
return state.lastCommunityRefreshTimestampByProfile[profileId]
},
getLastShortRefreshTimestampByProfile: (state) => (profileId) => {
return state.lastShortRefreshTimestampByProfile[profileId]
},
getLastLiveRefreshTimestampByProfile: (state) => (profileId) => {
return state.lastLiveRefreshTimestampByProfile[profileId]
},
getLastVideoRefreshTimestampByProfile: (state) => (profileId) => {
return state.lastVideoRefreshTimestampByProfile[profileId]
}
}
const actions = {
showOutlines({ commit }) {
commit('setOutlinesHidden', false)
},
hideOutlines({ commit }) {
commit('setOutlinesHidden', true)
},
async downloadMedia({ rootState }, { url, title, extension, fallingBackPath }) {
if (!process.env.IS_ELECTRON) {
openExternalLink(url)
return
}
const fileName = `${replaceFilenameForbiddenChars(title)}.${extension}`
const errorMessage = i18n.t('Downloading failed', { videoTitle: title })
const askFolderPath = rootState.settings.downloadAskPath
let folderPath = rootState.settings.downloadFolderPath
if (askFolderPath) {
const options = {
defaultPath: fileName,
filters: [
{
name: extension.toUpperCase(),
extensions: [extension]
}
]
}
const response = await showSaveDialog(options)
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
return
}
folderPath = response.filePath
} else {
if (!(await pathExists(folderPath))) {
try {
await fs.mkdir(folderPath, { recursive: true })
} catch (err) {
console.error(err)
showToast(err)
return
}
}
folderPath = path.join(folderPath, fileName)
}
showToast(i18n.t('Starting download', { videoTitle: title }))
const response = await fetch(url).catch((error) => {
console.error(error)
showToast(errorMessage)
})
const reader = response.body.getReader()
const chunks = []
const handleError = (err) => {
console.error(err)
showToast(errorMessage)
}
const processText = async ({ done, value }) => {
if (done) {
return
}
chunks.push(value)
// Can be used in the future to determine download percentage
// const contentLength = response.headers.get('Content-Length')
// const receivedLength = value.length
// const percentage = receivedLength / contentLength
await reader.read().then(processText).catch(handleError)
}
await reader.read().then(processText).catch(handleError)
const blobFile = new Blob(chunks)
const buffer = await blobFile.arrayBuffer()
try {
await fs.writeFile(folderPath, new DataView(buffer))
showToast(i18n.t('Downloading has completed', { videoTitle: title }))
} catch (err) {
console.error(err)
showToast(errorMessage)
}
},
parseScreenshotCustomFileName: function({ rootState }, payload) {
return new Promise((resolve, reject) => {
const { pattern = rootState.settings.screenshotFilenamePattern, date, playerTime, videoId } = payload
const keywords = [
['%Y', date.getFullYear()], // year 4 digits
['%M', (date.getMonth() + 1).toString().padStart(2, '0')], // month 2 digits
['%D', date.getDate().toString().padStart(2, '0')], // day 2 digits
['%H', date.getHours().toString().padStart(2, '0')], // hour 2 digits
['%N', date.getMinutes().toString().padStart(2, '0')], // minute 2 digits
['%S', date.getSeconds().toString().padStart(2, '0')], // second 2 digits
['%T', date.getMilliseconds().toString().padStart(3, '0')], // millisecond 3 digits
['%s', parseInt(playerTime)], // video position second n digits
['%t', (playerTime % 1).toString().slice(2, 5) || '000'], // video position millisecond 3 digits
['%i', videoId] // video id
]
let parsedString = pattern
for (const [key, value] of keywords) {
parsedString = parsedString.replaceAll(key, value)
}
if (parsedString !== replaceFilenameForbiddenChars(parsedString)) {
reject(new Error(i18n.t('Settings.Player Settings.Screenshot.Error.Forbidden Characters')))
}
let filename
if (parsedString.indexOf(path.sep) !== -1) {
const lastIndex = parsedString.lastIndexOf(path.sep)
filename = parsedString.substring(lastIndex + 1)
} else {
filename = parsedString
}
if (!filename) {
reject(new Error(i18n.t('Settings.Player Settings.Screenshot.Error.Empty File Name')))
}
resolve(parsedString)
})
},
showAddToPlaylistPromptForManyVideos ({ commit }, { videos: videoObjectArray, newPlaylistDefaultProperties }) {
let videoDataValid = true
if (!Array.isArray(videoObjectArray)) {
videoDataValid = false
}
let missingKeys = []
if (videoDataValid) {
const requiredVideoKeys = [
'videoId',
'title',
'author',
'authorId',
'lengthSeconds',
// `timeAdded` should be generated when videos are added
// Not when a prompt is displayed
// 'timeAdded',
// `playlistItemId` should be generated anyway
// 'playlistItemId',
// `type` should be added in action anyway
// 'type',
]
// Using `every` to loop and `return false` to break
videoObjectArray.every((video) => {
const videoPropertyKeys = Object.keys(video)
const missingKeysHere = requiredVideoKeys.filter(x => !videoPropertyKeys.includes(x))
if (missingKeysHere.length > 0) {
videoDataValid = false
missingKeys = missingKeysHere
return false
}
// Return true to continue loop
return true
})
}
if (!videoDataValid) {
// Print error and abort
const errorMsgText = 'Incorrect videos data passed when opening playlist prompt'
console.error(errorMsgText)
console.error({
videoObjectArray,
missingKeys,
})
throw new Error(errorMsgText)
}
commit('setShowAddToPlaylistPrompt', true)
commit('setToBeAddedToPlaylistVideoList', videoObjectArray)
if (newPlaylistDefaultProperties != null) {
commit('setNewPlaylistDefaultProperties', newPlaylistDefaultProperties)
}
},
hideAddToPlaylistPrompt ({ commit }) {
commit('setShowAddToPlaylistPrompt', false)
// The default value properties are only valid until prompt is closed
commit('resetNewPlaylistDefaultProperties')
},
showCreatePlaylistPrompt ({ commit }, data) {
commit('setShowCreatePlaylistPrompt', true)
commit('setNewPlaylistVideoObject', data)
},
hideCreatePlaylistPrompt ({ commit }) {
commit('setShowCreatePlaylistPrompt', false)
},
updateShowProgressBar ({ commit }, value) {
commit('setShowProgressBar', value)
},
async getRegionData ({ commit }, { locale }) {
const localePathExists = process.env.GEOLOCATION_NAMES.includes(locale)
// Exclude __dirname from path if not in electron
const fileLocation = `${process.env.IS_ELECTRON ? process.env.NODE_ENV === 'development' ? '.' : __dirname : ''}/static/geolocations/`
const pathName = `${fileLocation}${localePathExists ? locale : 'en-US'}.json`
const countries = process.env.IS_ELECTRON ? JSON.parse(await fs.readFile(pathName)) : await (await fetch(createWebURL(pathName))).json()
const regionNames = countries.map((entry) => { return entry.name })
const regionValues = countries.map((entry) => { return entry.code })
commit('setRegionNames', regionNames)
commit('setRegionValues', regionValues)
},
async getYoutubeUrlInfo({ rootState, state }, urlStr) {
// Returns
// - urlType [String] `video`, `playlist`
//
// If `urlType` is "video"
// - videoId [String]
// - timestamp [String]
//
// If `urlType` is "playlist"
// - playlistId [String]
// - query [Object]
//
// If `urlType` is "search"
// - searchQuery [String]
// - query [Object]
//
// If `urlType` is "hashtag"
// Nothing else
//
// If `urlType` is "channel"
// - channelId [String]
//
// If `urlType` is "unknown"
// Nothing else
//
// If `urlType` is "invalid_url"
// Nothing else
if (CHANNEL_HANDLE_REGEX.test(urlStr)) {
urlStr = `https://www.youtube.com/${urlStr}`
}
const { videoId, timestamp, playlistId } = getVideoParamsFromUrl(urlStr)
if (videoId) {
return {
urlType: 'video',
videoId,
playlistId,
timestamp
}
}
let url
try {
url = new URL(urlStr)
} catch {
return {
urlType: 'invalid_url'
}
}
let urlType = 'unknown'
const channelPattern =
/^\/(?:(?:channel|user|c)\/)?(?<channelId>[^/]+)(?:\/(?<tab>join|featured|videos|shorts|live|streams|podcasts|releases|playlists|about|community|channels))?\/?$/
const hashtagPattern = /^\/hashtag\/(?<tag>[^#&/?]+)$/
const typePatterns = new Map([
['playlist', /^(\/playlist\/?|\/embed(\/?videoseries)?)$/],
['search', /^\/results|search\/?$/],
['hashtag', hashtagPattern],
['channel', channelPattern]
])
for (const [type, pattern] of typePatterns) {
const matchFound = pattern.test(url.pathname)
if (matchFound) {
urlType = type
break
}
}
switch (urlType) {
case 'playlist': {
if (!url.searchParams.has('list')) {
throw new Error('Playlist: "list" field not found')
}
const playlistId = url.searchParams.get('list')
url.searchParams.delete('list')
const query = {}
for (const [param, value] of url.searchParams) {
query[param] = value
}
return {
urlType: 'playlist',
playlistId,
query
}
}
case 'search': {
let searchQuery = null
if (url.searchParams.has('search_query')) {
// https://www.youtube.com/results?search_query={QUERY}
searchQuery = url.searchParams.get('search_query')
url.searchParams.delete('search_query')
}
if (url.searchParams.has('q')) {
// https://redirect.invidious.io/search?q={QUERY}
searchQuery = url.searchParams.get('q')
url.searchParams.delete('q')
}
if (searchQuery == null) {
throw new Error('Search: "search_query" field not found')
}
const searchSettings = state.searchSettings
const query = {
sortBy: searchSettings.sortBy,
time: searchSettings.time,
type: searchSettings.type,
duration: searchSettings.duration
}
for (const [param, value] of url.searchParams) {
query[param] = value
}
return {
urlType: 'search',
searchQuery,
query
}
}
case 'hashtag': {
const match = url.pathname.match(hashtagPattern)
const hashtag = match.groups.tag
return {
urlType: 'hashtag',
hashtag
}
}
/*
Using RegExp named capture groups from ES2018
To avoid access to specific captured value broken
Channel URL (ID-based)
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/about
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/channels
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/community
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/featured
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/join
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/playlists
https://www.youtube.com/channel/UCfMJ2MchTSW2kWaT0kK94Yw/videos
Custom URL
https://www.youtube.com/c/YouTubeCreators
https://www.youtube.com/c/YouTubeCreators/about
etc.
Legacy Username URL
https://www.youtube.com/user/ufoludek
https://www.youtube.com/user/ufoludek/about
etc.
*/
case 'channel': {
const match = url.pathname.match(channelPattern)
const channelId = match.groups.channelId
if (!channelId) {
throw new Error('Channel: could not extract id')
}
let subPath = null
switch (match.groups.tab) {
case 'shorts':
subPath = 'shorts'
break
case 'live':
case 'streams':
subPath = 'live'
break
case 'playlists':
subPath = 'playlists'
break
case 'podcasts':
subPath = 'podcasts'
break
case 'releases':
subPath = 'releases'
break
case 'channels':
case 'about':
subPath = 'about'
break
case 'community':
subPath = 'community'
break
default:
subPath = 'videos'
break
}
return {
urlType: 'channel',
channelId,
subPath,
// The original URL could be from Invidious.
// We need to make sure it starts with youtube.com, so that YouTube's resolve endpoint can recognise it
url: `https://www.youtube.com${url.pathname}`
}
}
default: {
// Unknown URL type
return {
urlType: 'unknown'
}
}
}
},
clearSessionSearchHistory ({ commit }) {
commit('setSessionSearchHistory', [])
},
async getExternalPlayerCmdArgumentsData ({ commit }, payload) {
const fileName = 'external-player-map.json'
/* eslint-disable-next-line n/no-path-concat */
const fileLocation = process.env.NODE_ENV === 'development' ? './static/' : `${__dirname}/static/`
const fileData = await fs.readFile(`${fileLocation}${fileName}`)
const externalPlayerMap = JSON.parse(fileData).map((entry) => {
return { name: entry.name, value: entry.value, cmdArguments: entry.cmdArguments }
})
// Sort external players alphabetically & case-insensitive, keep default entry at the top
const playerNone = externalPlayerMap.shift()
externalPlayerMap.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }))
externalPlayerMap.unshift(playerNone)
const externalPlayerNames = externalPlayerMap.map((entry) => { return entry.name })
const externalPlayerValues = externalPlayerMap.map((entry) => { return entry.value })
const externalPlayerCmdArguments = externalPlayerMap.reduce((result, item) => {
result[item.value] = item.cmdArguments
return result
}, {})
commit('setExternalPlayerNames', externalPlayerNames)
commit('setExternalPlayerValues', externalPlayerValues)
commit('setExternalPlayerCmdArguments', externalPlayerCmdArguments)
},
openInExternalPlayer ({ state, rootState }, payload) {
const args = []
const externalPlayer = rootState.settings.externalPlayer
const cmdArgs = state.externalPlayerCmdArguments[externalPlayer]
const executable = rootState.settings.externalPlayerExecutable !== ''
? rootState.settings.externalPlayerExecutable
: cmdArgs.defaultExecutable
const ignoreWarnings = rootState.settings.externalPlayerIgnoreWarnings
const ignoreDefaultArgs = rootState.settings.externalPlayerIgnoreDefaultArgs
const customArgs = rootState.settings.externalPlayerCustomArgs
if (ignoreDefaultArgs) {
if (typeof customArgs === 'string' && customArgs !== '') {
const custom = customArgs.split(';')
args.push(...custom)
}
if (payload.videoId != null) args.push(`${cmdArgs.videoUrl}https://www.youtube.com/watch?v=${payload.videoId}`)
} else {
// Append custom user-defined arguments,
// or use the default ones specified for the external player.
if (typeof customArgs === 'string' && customArgs !== '') {
const custom = customArgs.split(';')
args.push(...custom)
} else if (typeof cmdArgs.defaultCustomArguments === 'string' && cmdArgs.defaultCustomArguments !== '') {
const defaultCustomArguments = cmdArgs.defaultCustomArguments.split(';')
args.push(...defaultCustomArguments)
}
if (payload.watchProgress > 0 && payload.watchProgress < payload.videoLength - 10) {
if (typeof cmdArgs.startOffset === 'string') {
if (cmdArgs.defaultExecutable.startsWith('mpc')) {
// For mpc-hc and mpc-be, which require startOffset to be in milliseconds
args.push(cmdArgs.startOffset, (Math.trunc(payload.watchProgress) * 1000))
} else if (cmdArgs.startOffset.endsWith('=')) {
// For players using `=` in arguments
// e.g. vlc --start-time=xxxxx
args.push(`${cmdArgs.startOffset}${payload.watchProgress}`)
} else {
// For players using space in arguments
// e.g. smplayer -start xxxxx
args.push(cmdArgs.startOffset, Math.trunc(payload.watchProgress))
}
} else if (!ignoreWarnings) {
showExternalPlayerUnsupportedActionToast(externalPlayer, i18n.t('Video.External Player.Unsupported Actions.starting video at offset'))
}
}
if (payload.playbackRate != null) {
if (typeof cmdArgs.playbackRate === 'string') {
args.push(`${cmdArgs.playbackRate}${payload.playbackRate}`)
} else if (!ignoreWarnings) {
showExternalPlayerUnsupportedActionToast(externalPlayer, i18n.t('Video.External Player.Unsupported Actions.setting a playback rate'))
}
}
// Check whether the video is in a playlist
if (typeof cmdArgs.playlistUrl === 'string' && payload.playlistId != null && payload.playlistId !== '') {
if (payload.playlistIndex != null) {
if (typeof cmdArgs.playlistIndex === 'string') {
args.push(`${cmdArgs.playlistIndex}${payload.playlistIndex}`)
} else if (!ignoreWarnings) {
showExternalPlayerUnsupportedActionToast(externalPlayer, i18n.t('Video.External Player.Unsupported Actions.opening specific video in a playlist (falling back to opening the video)'))
}
}
if (payload.playlistReverse) {
if (typeof cmdArgs.playlistReverse === 'string') {
args.push(cmdArgs.playlistReverse)
} else if (!ignoreWarnings) {
showExternalPlayerUnsupportedActionToast(externalPlayer, i18n.t('Video.External Player.Unsupported Actions.reversing playlists'))
}
}
if (payload.playlistShuffle) {
if (typeof cmdArgs.playlistShuffle === 'string') {
args.push(cmdArgs.playlistShuffle)
} else if (!ignoreWarnings) {
showExternalPlayerUnsupportedActionToast(externalPlayer, i18n.t('Video.External Player.Unsupported Actions.shuffling playlists'))
}
}
if (payload.playlistLoop) {
if (typeof cmdArgs.playlistLoop === 'string') {
args.push(cmdArgs.playlistLoop)
} else if (!ignoreWarnings) {
showExternalPlayerUnsupportedActionToast(externalPlayer, i18n.t('Video.External Player.Unsupported Actions.looping playlists'))
}
}
// If the player supports opening playlists but not indexes, send only the video URL if an index is specified
if (cmdArgs.playlistIndex == null && payload.playlistIndex != null && payload.playlistIndex !== '') {
args.push(`${cmdArgs.videoUrl}https://youtube.com/watch?v=${payload.videoId}`)
} else {
args.push(`${cmdArgs.playlistUrl}https://youtube.com/playlist?list=${payload.playlistId}`)
}
} else {
if (payload.playlistId != null && payload.playlistId !== '' && !ignoreWarnings) {
showExternalPlayerUnsupportedActionToast(externalPlayer, i18n.t('Video.External Player.Unsupported Actions.opening playlists'))
}
if (payload.videoId != null) {
args.push(`${cmdArgs.videoUrl}https://www.youtube.com/watch?v=${payload.videoId}`)
}
}
}
const videoOrPlaylist = payload.playlistId != null && payload.playlistId !== ''
? i18n.t('Video.External Player.playlist')
: i18n.t('Video.External Player.video')
showToast(i18n.t('Video.External Player.OpeningTemplate', { videoOrPlaylist, externalPlayer }))
if (process.env.IS_ELECTRON) {
const { ipcRenderer } = require('electron')
ipcRenderer.send(IpcChannels.OPEN_IN_EXTERNAL_PLAYER, { executable, args })
}
},
updateLastCommunityRefreshTimestampByProfile ({ commit }, payload) {
commit('updateLastCommunityRefreshTimestampByProfile', payload)
},
updateLastShortRefreshTimestampByProfile ({ commit }, payload) {
commit('updateLastShortRefreshTimestampByProfile', payload)
},
updateLastLiveRefreshTimestampByProfile ({ commit }, payload) {
commit('updateLastLiveRefreshTimestampByProfile', payload)
},
updateLastVideoRefreshTimestampByProfile ({ commit }, payload) {
commit('updateLastVideoRefreshTimestampByProfile', payload)
}
}
const mutations = {
toggleSideNav (state) {
state.isSideNavOpen = !state.isSideNavOpen
},
setOutlinesHidden(state, value) {
state.outlinesHidden = value
},
setShowProgressBar (state, value) {
state.showProgressBar = value
},
setProgressBarPercentage (state, value) {
state.progressBarPercentage = value
},
setSessionSearchHistory (state, history) {
state.sessionSearchHistory = history
},
setDeArrowCache (state, cache) {
state.deArrowCache = cache
},
addVideoToDeArrowCache (state, payload) {
const sameVideo = state.deArrowCache[payload.videoId]
if (!sameVideo) {
// setting properties directly doesn't trigger watchers in Vue 2,
// so we need to use Vue's set function
vueSet(state.deArrowCache, payload.videoId, payload)
}
},
addThumbnailToDeArrowCache (state, payload) {
vueSet(state.deArrowCache, payload.videoId, payload)
},
addToSessionSearchHistory (state, payload) {
const sameSearch = state.sessionSearchHistory.findIndex((search) => {
return search.query === payload.query && searchFiltersMatch(payload.searchSettings, search.searchSettings)
})
if (sameSearch !== -1) {
state.sessionSearchHistory[sameSearch].data = payload.data
if (payload.nextPageRef) {
// Local API
state.sessionSearchHistory[sameSearch].nextPageRef = payload.nextPageRef
} else if (payload.searchPage) {
// Invidious API
state.sessionSearchHistory[sameSearch].searchPage = payload.searchPage
}
} else {
state.sessionSearchHistory.push(payload)
}
},
setShowAddToPlaylistPrompt (state, payload) {
state.showAddToPlaylistPrompt = payload
},
setShowCreatePlaylistPrompt (state, payload) {
state.showCreatePlaylistPrompt = payload
},
setToBeAddedToPlaylistVideoList (state, payload) {
state.toBeAddedToPlaylistVideoList = payload
},
setNewPlaylistDefaultProperties (state, payload) {
state.newPlaylistDefaultProperties = payload
},
resetNewPlaylistDefaultProperties (state) {
state.newPlaylistDefaultProperties = {}
},
setNewPlaylistVideoObject (state, payload) {
state.newPlaylistVideoObject = payload
},
setPopularCache (state, value) {
state.popularCache = value
},
setTrendingCache (state, { value, page }) {
state.trendingCache[page] = value
},
setLastTrendingRefreshTimestamp (state, timestamp) {
state.lastTrendingRefreshTimestamp = timestamp
},
setLastPopularRefreshTimestamp (state, timestamp) {
state.lastPopularRefreshTimestamp = timestamp
},
updateLastCommunityRefreshTimestampByProfile (state, { profileId, timestamp }) {
vueSet(state.lastCommunityRefreshTimestampByProfile, profileId, timestamp)
},
updateLastShortRefreshTimestampByProfile (state, { profileId, timestamp }) {
vueSet(state.lastShortRefreshTimestampByProfile, profileId, timestamp)
},
updateLastLiveRefreshTimestampByProfile (state, { profileId, timestamp }) {
vueSet(state.lastLiveRefreshTimestampByProfile, profileId, timestamp)
},
updateLastVideoRefreshTimestampByProfile (state, { profileId, timestamp }) {
vueSet(state.lastVideoRefreshTimestampByProfile, profileId, timestamp)
},
clearTrendingCache(state) {
state.trendingCache = {
default: null,
music: null,
gaming: null,
movies: null
}
},
setCachedPlaylist(state, value) {
state.cachedPlaylist = value
},
setSearchSortBy (state, value) {
state.searchSettings.sortBy = value
},
setSearchTime (state, value) {
state.searchSettings.time = value
},
setSearchType (state, value) {
state.searchSettings.type = value
},
setSearchDuration (state, value) {
state.searchSettings.duration = value
},
setRegionNames (state, value) {
state.regionNames = value
},
setRegionValues (state, value) {
state.regionValues = value
},
setRecentBlogPosts (state, value) {
state.recentBlogPosts = value
},
setExternalPlayerNames (state, value) {
state.externalPlayerNames = value
},
setExternalPlayerValues (state, value) {
state.externalPlayerValues = value
},
setExternalPlayerCmdArguments (state, value) {
state.externalPlayerCmdArguments = value
}
}
export default {
state,
getters,
actions,
mutations
}