v2.0.0
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
// import api from 'api/connom'
|
||||
import { httpGet } from '../utils/request'
|
||||
import { author, name } from '../../../package.json'
|
||||
|
||||
export default {
|
||||
getVersionInfo(state, retryNum = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
httpGet(`https://raw.githubusercontent.com/${author.name}/${name}/master/publish/version.json`, {
|
||||
timeout: 20000,
|
||||
}, (err, resp, body) => {
|
||||
if (err) {
|
||||
return ++retryNum > 3
|
||||
? this.dispatch('getVersionInfo2').then(resolve).catch(reject)
|
||||
: this.dispatch('getVersionInfo', retryNum).then(resolve).catch(reject)
|
||||
}
|
||||
resolve(body)
|
||||
})
|
||||
})
|
||||
},
|
||||
getVersionInfo2(state, retryNum = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
httpGet('https://gitee.com/lyswhut/lx-music-desktop-versions/raw/master/version.json', {
|
||||
timeout: 20000,
|
||||
}, (err, resp, body) => {
|
||||
if (!err && !body.version) err = new Error(JSON.stringify(body))
|
||||
if (err) {
|
||||
return ++retryNum > 3
|
||||
? this.dispatch('getVersionInfo3').then(resolve).catch(reject)
|
||||
: this.dispatch('getVersionInfo2', retryNum).then(resolve).catch(reject)
|
||||
}
|
||||
resolve(body)
|
||||
})
|
||||
})
|
||||
},
|
||||
getVersionInfo3(state, retryNum = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
httpGet('https://cdn.stsky.cn/lx-music/desktop/version.json', {
|
||||
timeout: 20000,
|
||||
}, (err, resp, body) => {
|
||||
if (err) {
|
||||
return ++retryNum > 3
|
||||
? reject(err)
|
||||
: this.dispatch('getVersionInfo3', retryNum).then(resolve).catch(reject)
|
||||
}
|
||||
resolve(body)
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
import {
|
||||
downloadTasksGet,
|
||||
// downloadListClear,
|
||||
downloadTasksCreate,
|
||||
downloadTasksRemove,
|
||||
downloadTasksUpdate,
|
||||
} from '@renderer/utils/ipc'
|
||||
import {
|
||||
downloadList,
|
||||
} from './state'
|
||||
import { markRaw, toRaw } from '@common/utils/vueTools'
|
||||
import { getMusicUrl, getPicUrl, getLyricInfo } from '@renderer/core/music/online'
|
||||
import { appSetting } from '../setting'
|
||||
import { qualityList } from '..'
|
||||
import { proxyCallback } from '@renderer/worker/utils'
|
||||
import { joinPath } from '@renderer/utils'
|
||||
import { DOWNLOAD_STATUS } from '@common/constants'
|
||||
|
||||
const waitingUpdateTasks = new Map<string, LX.Download.ListItem>()
|
||||
let timer: NodeJS.Timeout | null = null
|
||||
const throttleUpdateTask = (tasks: LX.Download.ListItem[]) => {
|
||||
for (const task of tasks) waitingUpdateTasks.set(task.id, toRaw(task))
|
||||
if (timer) return
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
void downloadTasksUpdate(Array.from(waitingUpdateTasks.values()))
|
||||
waitingUpdateTasks.clear()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const runingTask = new Map<string, LX.Download.ListItem>()
|
||||
|
||||
// const initDownloadList = (list: LX.Download.ListItem[]) => {
|
||||
// downloadList.splice(0, downloadList.length, ...list)
|
||||
// }
|
||||
|
||||
export const getDownloadList = async(): Promise<LX.Download.ListItem[]> => {
|
||||
if (!downloadList.length) {
|
||||
const list = await downloadTasksGet()
|
||||
for (const downloadInfo of list) {
|
||||
markRaw(downloadInfo.metadata)
|
||||
switch (downloadInfo.status) {
|
||||
case DOWNLOAD_STATUS.RUN:
|
||||
case DOWNLOAD_STATUS.WAITING:
|
||||
downloadInfo.status = DOWNLOAD_STATUS.PAUSE
|
||||
downloadInfo.statusText = window.i18n.t('download___status_paused')
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
downloadList.push(...list)
|
||||
}
|
||||
return downloadList
|
||||
}
|
||||
|
||||
const addTasks = async(list: LX.Download.ListItem[]) => {
|
||||
const addMusicLocationType = appSetting['list.addMusicLocationType']
|
||||
|
||||
await downloadTasksCreate(list.map(i => toRaw(i)), addMusicLocationType)
|
||||
|
||||
if (addMusicLocationType === 'top') {
|
||||
downloadList.unshift(...list)
|
||||
} else {
|
||||
downloadList.push(...list)
|
||||
}
|
||||
window.app_event.downloadListUpdate()
|
||||
}
|
||||
|
||||
const setStatusText = (downloadInfo: LX.Download.ListItem, text: string) => { // 设置状态文本
|
||||
downloadInfo.statusText = text
|
||||
throttleUpdateTask([downloadInfo])
|
||||
}
|
||||
|
||||
const setUrl = (downloadInfo: LX.Download.ListItem, url: string) => {
|
||||
downloadInfo.metadata.url = url
|
||||
throttleUpdateTask([downloadInfo])
|
||||
}
|
||||
|
||||
const updateFilePath = (downloadInfo: LX.Download.ListItem, filePath: string) => {
|
||||
downloadInfo.metadata.filePath = filePath
|
||||
throttleUpdateTask([downloadInfo])
|
||||
}
|
||||
|
||||
const setProgress = (downloadInfo: LX.Download.ListItem, progress: LX.Download.ProgressInfo) => {
|
||||
downloadInfo.progress = progress.progress
|
||||
downloadInfo.total = progress.total
|
||||
downloadInfo.downloaded = progress.downloaded
|
||||
downloadInfo.speed = progress.speed
|
||||
throttleUpdateTask([downloadInfo])
|
||||
}
|
||||
|
||||
const setStatus = (downloadInfo: LX.Download.ListItem, status: LX.Download.DownloadTaskStatus, statusText?: string) => { // 设置状态及状态文本
|
||||
if (statusText == null) {
|
||||
switch (status) {
|
||||
case DOWNLOAD_STATUS.RUN:
|
||||
statusText = window.i18n.t('download___status_runing')
|
||||
break
|
||||
case DOWNLOAD_STATUS.WAITING:
|
||||
statusText = window.i18n.t('download___status_wating')
|
||||
break
|
||||
case DOWNLOAD_STATUS.PAUSE:
|
||||
statusText = window.i18n.t('download___status_paused')
|
||||
break
|
||||
case DOWNLOAD_STATUS.ERROR:
|
||||
statusText = window.i18n.t('download___status_error')
|
||||
break
|
||||
case DOWNLOAD_STATUS.COMPLETED:
|
||||
statusText = window.i18n.t('download___status_complated')
|
||||
break
|
||||
default:
|
||||
statusText = ''
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadInfo.statusText == statusText && downloadInfo.status == status) return
|
||||
|
||||
if (status == DOWNLOAD_STATUS.COMPLETED) downloadInfo.isComplate = true
|
||||
downloadInfo.statusText = statusText
|
||||
downloadInfo.status = status
|
||||
throttleUpdateTask([downloadInfo])
|
||||
}
|
||||
|
||||
// 修复 1.1.x版本 酷狗源歌词格式
|
||||
const fixKgLyric = (lrc: string) => /\[00:\d\d:\d\d.\d+\]/.test(lrc) ? lrc.replace(/(?:\[00:(\d\d:\d\d.\d+\]))/gm, '[$1') : lrc
|
||||
|
||||
/**
|
||||
* 设置歌曲meta信息
|
||||
* @param downloadInfo 下载任务信息
|
||||
*/
|
||||
const saveMeta = (downloadInfo: LX.Download.ListItem) => {
|
||||
if (downloadInfo.metadata.quality === 'ape') return
|
||||
const isUseOtherSource = appSetting['download.isUseOtherSource']
|
||||
const tasks: [Promise<string | null>, Promise<LX.Player.LyricInfo | null>] = [
|
||||
appSetting['download.isEmbedPic']
|
||||
? downloadInfo.metadata.musicInfo.meta.picUrl
|
||||
? Promise.resolve(downloadInfo.metadata.musicInfo.meta.picUrl)
|
||||
: getPicUrl({ musicInfo: downloadInfo.metadata.musicInfo, isRefresh: false, allowToggleSource: isUseOtherSource }).catch(err => {
|
||||
console.log(err)
|
||||
return null
|
||||
})
|
||||
: Promise.resolve(null),
|
||||
appSetting['download.isEmbedLyric']
|
||||
? getLyricInfo({ musicInfo: downloadInfo.metadata.musicInfo, isRefresh: false, allowToggleSource: isUseOtherSource }).catch(err => {
|
||||
console.log(err)
|
||||
return null
|
||||
})
|
||||
: Promise.resolve(null),
|
||||
]
|
||||
void Promise.all(tasks).then(([imgUrl, lyrics]) => {
|
||||
if (lyrics?.lyric) lyrics.lyric = fixKgLyric(lyrics.lyric)
|
||||
void window.lx.worker.download.writeMeta(downloadInfo.metadata.filePath, {
|
||||
title: downloadInfo.metadata.musicInfo.name,
|
||||
artist: downloadInfo.metadata.musicInfo.singer,
|
||||
album: downloadInfo.metadata.musicInfo.meta.albumName,
|
||||
APIC: imgUrl,
|
||||
lyrics: lyrics?.lyric ?? null,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存歌词文件
|
||||
* @param downloadInfo 下载任务信息
|
||||
*/
|
||||
const downloadLyric = (downloadInfo: LX.Download.ListItem) => {
|
||||
if (!appSetting['download.isDownloadLrc']) return
|
||||
void getLyricInfo({
|
||||
musicInfo: downloadInfo.metadata.musicInfo,
|
||||
isRefresh: false,
|
||||
allowToggleSource: appSetting['download.isUseOtherSource'],
|
||||
}).then(lrcs => {
|
||||
if (lrcs.lyric) {
|
||||
lrcs.lyric = fixKgLyric(lrcs.lyric)
|
||||
let lyric = lrcs.lyric
|
||||
if (appSetting['download.isDownloadTLrc'] && lrcs.tlyric) lyric += '\n' + lrcs.tlyric + '\n'
|
||||
if (appSetting['download.isDownloadRLrc'] && lrcs.rlyric) lyric += '\n' + lrcs.rlyric + '\n'
|
||||
void window.lx.worker.download.saveLrc(downloadInfo.metadata.filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'),
|
||||
lyric, appSetting['download.lrcFormat'])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getUrl = async(downloadInfo: LX.Download.ListItem, isRefresh: boolean = false) => {
|
||||
return getMusicUrl({
|
||||
musicInfo: downloadInfo.metadata.musicInfo,
|
||||
isRefresh: false,
|
||||
quality: downloadInfo.metadata.quality,
|
||||
allowToggleSource: appSetting['download.isUseOtherSource'],
|
||||
}).catch(() => '')
|
||||
}
|
||||
const handleRefreshUrl = (downloadInfo: LX.Download.ListItem) => {
|
||||
setStatusText(downloadInfo, window.i18n.t('download_status_error_refresh_url'))
|
||||
getMusicUrl({
|
||||
musicInfo: downloadInfo.metadata.musicInfo,
|
||||
isRefresh: true,
|
||||
quality: downloadInfo.metadata.quality,
|
||||
allowToggleSource: appSetting['download.isUseOtherSource'],
|
||||
})
|
||||
.then(url => {
|
||||
// commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
|
||||
setUrl(downloadInfo, url)
|
||||
void window.lx.worker.download.updateUrl(downloadInfo.id, url)
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
handleError(downloadInfo, err.message)
|
||||
})
|
||||
}
|
||||
const handleError = (downloadInfo: LX.Download.ListItem, message?: string) => {
|
||||
setStatus(downloadInfo, DOWNLOAD_STATUS.ERROR, message)
|
||||
void window.lx.worker.download.removeTask(downloadInfo.id)
|
||||
runingTask.delete(downloadInfo.id)
|
||||
void checkStartTask()
|
||||
}
|
||||
|
||||
const handleStartTask = async(downloadInfo: LX.Download.ListItem) => {
|
||||
if (!downloadInfo.metadata.url) {
|
||||
setStatusText(downloadInfo, window.i18n.t('download_status_url_geting'))
|
||||
const url = await getUrl(downloadInfo)
|
||||
if (!url) {
|
||||
handleError(downloadInfo, window.i18n.t('download_status_error_url_failed'))
|
||||
return
|
||||
}
|
||||
setUrl(downloadInfo, url)
|
||||
if (downloadInfo.status != DOWNLOAD_STATUS.RUN) return
|
||||
}
|
||||
|
||||
const filePath = joinPath(appSetting['download.savePath'], downloadInfo.metadata.fileName)
|
||||
if (downloadInfo.metadata.filePath != filePath) updateFilePath(downloadInfo, filePath)
|
||||
|
||||
setStatusText(downloadInfo, window.i18n.t('download_status_start'))
|
||||
|
||||
await window.lx.worker.download.startTask(toRaw(downloadInfo), appSetting['download.savePath'], proxyCallback((event: LX.Download.DownloadTaskActions) => {
|
||||
// console.log(event)
|
||||
switch (event.action) {
|
||||
case 'start':
|
||||
setStatus(downloadInfo, DOWNLOAD_STATUS.RUN)
|
||||
break
|
||||
case 'complete':
|
||||
saveMeta(downloadInfo)
|
||||
downloadLyric(downloadInfo)
|
||||
void window.lx.worker.download.removeTask(downloadInfo.id)
|
||||
runingTask.delete(downloadInfo.id)
|
||||
setStatus(downloadInfo, DOWNLOAD_STATUS.COMPLETED)
|
||||
void checkStartTask()
|
||||
break
|
||||
case 'refreshUrl':
|
||||
handleRefreshUrl(downloadInfo)
|
||||
break
|
||||
case 'statusText':
|
||||
setStatusText(downloadInfo, event.data)
|
||||
break
|
||||
case 'progress':
|
||||
setProgress(downloadInfo, event.data)
|
||||
break
|
||||
case 'error':
|
||||
handleError(downloadInfo, event.data.error == null
|
||||
? event.data.message ?? undefined
|
||||
// @ts-expect-error
|
||||
: window.i18n.t(event.data.error) + (event.data.message ?? ''))
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}))
|
||||
}
|
||||
const startTask = async(downloadInfo: LX.Download.ListItem) => {
|
||||
setStatus(downloadInfo, DOWNLOAD_STATUS.RUN)
|
||||
runingTask.set(downloadInfo.id, downloadInfo)
|
||||
void handleStartTask(downloadInfo)
|
||||
}
|
||||
|
||||
const getStartTask = (list: LX.Download.ListItem[]): LX.Download.ListItem | null => {
|
||||
let downloadCount = 0
|
||||
const waitList = list.filter(item => {
|
||||
if (item.status == DOWNLOAD_STATUS.WAITING) return true
|
||||
if (item.status == DOWNLOAD_STATUS.RUN) ++downloadCount
|
||||
return false
|
||||
})
|
||||
// console.log(downloadCount, waitList)
|
||||
return downloadCount < appSetting['download.maxDownloadNum'] ? waitList.shift() ?? null : null
|
||||
}
|
||||
|
||||
const checkStartTask = async() => {
|
||||
if (runingTask.size >= appSetting['download.maxDownloadNum']) return
|
||||
let result = getStartTask(downloadList)
|
||||
// console.log(result)
|
||||
while (result) {
|
||||
await startTask(result)
|
||||
result = getStartTask(downloadList)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤重复任务
|
||||
* @param list
|
||||
*/
|
||||
const filterTask = (list: LX.Download.ListItem[]) => {
|
||||
const set = new Set<string>()
|
||||
for (const item of downloadList) set.add(item.id)
|
||||
return list.filter(item => {
|
||||
if (set.has(item.id)) return false
|
||||
markRaw(item.metadata)
|
||||
set.add(item.id)
|
||||
return true
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 创建下载任务
|
||||
* @param list 要下载的歌曲
|
||||
* @param quality 下载音质
|
||||
*/
|
||||
export const createDownloadTasks = async(list: LX.Music.MusicInfoOnline[], quality: LX.Quality) => {
|
||||
if (!list.length) return
|
||||
const tasks = filterTask(await window.lx.worker.download.createDownloadTasks(list, quality,
|
||||
appSetting['download.savePath'],
|
||||
appSetting['download.fileName'],
|
||||
toRaw(qualityList.value)),
|
||||
)
|
||||
|
||||
if (tasks.length) await addTasks(tasks)
|
||||
void checkStartTask()
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始下载任务
|
||||
* @param list
|
||||
*/
|
||||
export const startDownloadTasks = async(list: LX.Download.ListItem[]) => {
|
||||
for (const downloadInfo of list) {
|
||||
switch (downloadInfo.status) {
|
||||
case DOWNLOAD_STATUS.PAUSE:
|
||||
case DOWNLOAD_STATUS.ERROR:
|
||||
if (runingTask.size < appSetting['download.maxDownloadNum']) void startTask(downloadInfo)
|
||||
else setStatus(downloadInfo, DOWNLOAD_STATUS.WAITING)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
void checkStartTask()
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停下载任务
|
||||
* @param list
|
||||
*/
|
||||
export const pauseDownloadTasks = async(list: LX.Download.ListItem[]) => {
|
||||
for (const downloadInfo of list) {
|
||||
switch (downloadInfo.status) {
|
||||
case DOWNLOAD_STATUS.RUN:
|
||||
void window.lx.worker.download.pauseTask(downloadInfo.id)
|
||||
runingTask.delete(downloadInfo.id)
|
||||
case DOWNLOAD_STATUS.WAITING:
|
||||
case DOWNLOAD_STATUS.ERROR:
|
||||
setStatus(downloadInfo, DOWNLOAD_STATUS.PAUSE)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
void checkStartTask()
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除下载任务
|
||||
* @param ids 要移除的任务Id
|
||||
*/
|
||||
export const removeDownloadTasks = async(ids: string[]) => {
|
||||
await downloadTasksRemove(ids)
|
||||
ids = [...ids]
|
||||
for (let i = downloadList.length; i--;) {
|
||||
const item = downloadList[i]
|
||||
const index = ids.indexOf(item.id)
|
||||
if (index < 0) continue
|
||||
ids.splice(index, 1)
|
||||
downloadList.splice(i, 1)
|
||||
if (runingTask.has(item.id)) {
|
||||
void window.lx.worker.download.removeTask(item.id)
|
||||
runingTask.delete(item.id)
|
||||
}
|
||||
}
|
||||
void checkStartTask()
|
||||
window.app_event.downloadListUpdate()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { reactive, ref, markRaw } from '@common/utils/vueTools'
|
||||
import { DOWNLOAD_STATUS } from '@common/constants'
|
||||
|
||||
export const isInitedList = ref(false)
|
||||
|
||||
export const setInited = () => {
|
||||
isInitedList.value = true
|
||||
}
|
||||
|
||||
export const downloadList = reactive<LX.Download.ListItem[]>([])
|
||||
// export const downloadListMap = new Map<string, LX.Download.ListItem>()
|
||||
|
||||
export const downloadStatus = markRaw(DOWNLOAD_STATUS)
|
||||
@@ -1,75 +0,0 @@
|
||||
import music from '../utils/music'
|
||||
import { themes, windowSizeList, themeShouldUseDarkColors } from '@renderer/core/share'
|
||||
|
||||
export default {
|
||||
theme(state) {
|
||||
const themeId = state.setting.theme.id == 'auto'
|
||||
? themeShouldUseDarkColors.value
|
||||
? state.setting.theme.darkId
|
||||
: state.setting.theme.lightId
|
||||
: state.setting.theme.id
|
||||
let theme = themes.find(theme => theme.id == themeId) ?? themes[0]
|
||||
return theme.className
|
||||
},
|
||||
font(state) {
|
||||
return state.setting.font
|
||||
},
|
||||
themes(state) {
|
||||
return {
|
||||
active: state.setting.theme.id,
|
||||
lightId: state.setting.theme.lightId,
|
||||
darkId: state.setting.theme.darkId,
|
||||
list: themes,
|
||||
}
|
||||
},
|
||||
source(state) {
|
||||
const source = music.sources.find(s => s.id === state.setting.sourceId) || music.sources[0]
|
||||
return source
|
||||
},
|
||||
sources(state) {
|
||||
return {
|
||||
active: state.setting.sourceId,
|
||||
list: music.sources,
|
||||
}
|
||||
},
|
||||
sourceNames(state) {
|
||||
let prefix = 'source_'
|
||||
if (state.setting.sourceNameType == 'alias') prefix += 'alias_'
|
||||
const sources = {}
|
||||
for (const source of music.sources) {
|
||||
sources[source.id] = window.i18n.t(prefix + source.id)
|
||||
}
|
||||
sources.all = window.i18n.t(prefix + 'all')
|
||||
return sources
|
||||
},
|
||||
userInfo(state) {
|
||||
return state.userInfo
|
||||
},
|
||||
setting(state) {
|
||||
return state.setting
|
||||
},
|
||||
settingVersion(state) {
|
||||
return state.settingVersion
|
||||
},
|
||||
version(state) {
|
||||
return state.version
|
||||
},
|
||||
route(state) {
|
||||
return state.route
|
||||
},
|
||||
windowSizeList() {
|
||||
return windowSizeList
|
||||
},
|
||||
windowSizeActive(state) {
|
||||
return windowSizeList.find(i => i.id === state.setting.windowSizeId) || windowSizeList[0]
|
||||
},
|
||||
pactModalVisible(state) {
|
||||
return !state.setting.isAgreePact
|
||||
},
|
||||
isShowAnimation(state) {
|
||||
return state.setting.isShowAnimation
|
||||
},
|
||||
playDetailSetting(state) {
|
||||
return state.setting.playDetail
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { reactive, markRaw } from '@common/utils/vueTools'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
|
||||
// import { deduplicationList } from '@common/utils/renderer'
|
||||
|
||||
export type Source = LX.OnlineSource | 'all'
|
||||
|
||||
interface SourceLists extends Partial<Record<LX.OnlineSource, string[]>> {
|
||||
'all': string[]
|
||||
}
|
||||
|
||||
export const sources: Source[] = markRaw([])
|
||||
|
||||
export const sourceList: SourceLists = markRaw({
|
||||
all: reactive<string[]>([]),
|
||||
})
|
||||
|
||||
|
||||
for (const source of music.sources) {
|
||||
if (!music[source.id as LX.OnlineSource].hotSearch) continue
|
||||
sources.push(source.id as LX.OnlineSource)
|
||||
sourceList[source.id as LX.OnlineSource] = reactive<string[]>([])
|
||||
}
|
||||
sources.push('all')
|
||||
|
||||
|
||||
const setList = (source: LX.OnlineSource, list: string[]): string[] => {
|
||||
return sourceList[source] = list.slice(0, 20)
|
||||
}
|
||||
|
||||
const setLists = (lists: Array<{ source: LX.OnlineSource, list: string[] }>): string[] => {
|
||||
let wordsMap = new Map<string, number>()
|
||||
for (const { source, list } of lists) {
|
||||
if (!sourceList[source]?.length) sourceList[source] = list.slice(0, 20)
|
||||
for (let item of list) {
|
||||
item = item.trim()
|
||||
wordsMap.set(item, (wordsMap.get(item) ?? 0) + 1)
|
||||
}
|
||||
}
|
||||
const wordsMapArr = Array.from(wordsMap)
|
||||
wordsMapArr.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
wordsMapArr.sort((a, b) => b[1] - a[1])
|
||||
const words = wordsMapArr.map(item => item[0])
|
||||
return sourceList.all = words.slice(0, sources.length * 10)
|
||||
}
|
||||
|
||||
export const getList = async(source: Source): Promise<string[]> => {
|
||||
if (source == 'all') {
|
||||
let task = []
|
||||
for (const source of sources) {
|
||||
if (source == 'all') continue
|
||||
task.push(
|
||||
sourceList[source]?.length
|
||||
? Promise.resolve({ source, list: sourceList[source] })
|
||||
: music[source].hotSearch.getList().catch((err: any) => {
|
||||
console.log(err)
|
||||
return { source, list: [] }
|
||||
}),
|
||||
)
|
||||
}
|
||||
return Promise.all(task).then((results: any[]) => {
|
||||
return setLists(results)
|
||||
})
|
||||
} else {
|
||||
if (sourceList[source]?.length) return Promise.resolve(sourceList[source] as string[])
|
||||
if (!music[source].hotSearch) {
|
||||
setList(source, [])
|
||||
return Promise.resolve([])
|
||||
}
|
||||
return music[source].hotSearch.getList().then(data => setList(source, data.list))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const clearList = (source: Source) => {
|
||||
sourceList[source] = []
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { createStore } from 'vuex'
|
||||
// import { createPersistedState, createSharedMutations } from 'vuex-electron'
|
||||
|
||||
import defaultState from './state'
|
||||
import mutations from './mutations'
|
||||
import modules from './modules'
|
||||
import getters from './getters'
|
||||
import actions from './actions'
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
const store = createStore({
|
||||
strict: isDev,
|
||||
state: defaultState,
|
||||
modules,
|
||||
mutations,
|
||||
getters,
|
||||
actions,
|
||||
// plugins: [createPersistedState(), createSharedMutations()],
|
||||
})
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept([
|
||||
'./state',
|
||||
'./mutations',
|
||||
'./actions',
|
||||
'./getters',
|
||||
], () => {
|
||||
const newState = require('./state').default
|
||||
const newMutations = require('./mutations').default
|
||||
const newActions = require('./actions').default
|
||||
const newGetters = require('./getters').default
|
||||
|
||||
store.hotUpdate({
|
||||
state: newState,
|
||||
mutations: newMutations,
|
||||
getters: newGetters,
|
||||
actions: newActions,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default store
|
||||
@@ -0,0 +1,134 @@
|
||||
import { ref, reactive, shallowRef, markRaw, computed } from '@common/utils/vueTools'
|
||||
import { windowSizeList as configWindowSizeList } from '@common/config'
|
||||
import { appSetting } from './setting'
|
||||
import pkg from '../../../package.json'
|
||||
import { ProgressInfo } from 'electron-updater'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
process.versions.app = pkg.version
|
||||
|
||||
export const apiSource = ref<string | null>(null)
|
||||
export const proxy: {
|
||||
enable: boolean
|
||||
host: string
|
||||
port: string
|
||||
username: string
|
||||
password: string
|
||||
|
||||
envProxy?: {
|
||||
host: string
|
||||
port: string
|
||||
}
|
||||
} = {
|
||||
enable: false,
|
||||
host: '',
|
||||
port: '',
|
||||
username: '',
|
||||
password: '',
|
||||
}
|
||||
export const sync: {
|
||||
enable: boolean
|
||||
port: string
|
||||
isShowSyncMode: boolean
|
||||
deviceName: string
|
||||
status: {
|
||||
status: boolean
|
||||
message: string
|
||||
address: string[]
|
||||
code: string
|
||||
devices: LX.Sync.KeyInfo[]
|
||||
}
|
||||
} = reactive({
|
||||
enable: false,
|
||||
port: '',
|
||||
isShowSyncMode: false,
|
||||
deviceName: '',
|
||||
status: {
|
||||
status: false,
|
||||
message: '',
|
||||
address: [],
|
||||
code: '',
|
||||
devices: [],
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
export const windowSizeActive = computed(() => {
|
||||
return windowSizeList.find(i => i.id === appSetting['common.windowSizeId']) ?? windowSizeList[0]
|
||||
})
|
||||
|
||||
export const sourceNames = computed(() => {
|
||||
const prefix = appSetting['common.sourceNameType'] == 'real' ? 'source_' : 'source_alias_'
|
||||
const sourceNames: Record<LX.OnlineSource | 'all', string> = {
|
||||
kw: 'kw',
|
||||
tx: 'tx',
|
||||
kg: 'kg',
|
||||
mg: 'mg',
|
||||
wy: 'wy',
|
||||
all: window.i18n.t(prefix + 'all' as any),
|
||||
}
|
||||
for (const { id } of music.sources) {
|
||||
sourceNames[id as LX.OnlineSource] = window.i18n.t(prefix + id as any)
|
||||
}
|
||||
|
||||
return sourceNames
|
||||
})
|
||||
|
||||
export const windowSizeList = markRaw(configWindowSizeList)
|
||||
|
||||
export const isShowPact = ref(false)
|
||||
|
||||
export const versionInfo = window.lxData.versionInfo = reactive<{
|
||||
version: string
|
||||
newVersion: {
|
||||
version: string
|
||||
desc: string
|
||||
history?: LX.VersionInfo[]
|
||||
} | null
|
||||
showModal: boolean
|
||||
isError: boolean
|
||||
isTimeOut: boolean
|
||||
isUnknow: boolean
|
||||
isDownloaded: boolean
|
||||
isDownloading: boolean
|
||||
isLatestVer: boolean
|
||||
downloadProgress: ProgressInfo | null
|
||||
}>({
|
||||
version: pkg.version,
|
||||
newVersion: null,
|
||||
showModal: false,
|
||||
isError: false,
|
||||
isTimeOut: false,
|
||||
isUnknow: false,
|
||||
isDownloaded: false,
|
||||
isDownloading: false,
|
||||
isLatestVer: false,
|
||||
downloadProgress: null,
|
||||
})
|
||||
export const userApi = reactive<{
|
||||
list: LX.UserApi.UserApiInfo[]
|
||||
status: boolean
|
||||
message?: string
|
||||
apis: Partial<LX.UserApi.UserApiSources>
|
||||
}>({
|
||||
list: [],
|
||||
status: false,
|
||||
message: 'initing',
|
||||
apis: {},
|
||||
})
|
||||
|
||||
export const isFullscreen = ref(false)
|
||||
|
||||
export const themeShouldUseDarkColors = ref(window.shouldUseDarkColors)
|
||||
|
||||
|
||||
export const qualityList = shallowRef<LX.QualityList>({})
|
||||
export const setQualityList = (_qualityList: LX.QualityList) => {
|
||||
qualityList.value = _qualityList
|
||||
}
|
||||
|
||||
export const themeId = ref('green')
|
||||
export const themeInfo: LX.ThemeInfo = {
|
||||
themes: [],
|
||||
userThemes: [],
|
||||
dataPath: '',
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// import { getLeaderboardSetting } from '@renderer/utils/data'
|
||||
import { deduplicationList, toNewMusicInfo } from '@renderer/utils'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import { markRaw, markRawList } from '@common/utils/vueTools'
|
||||
import { boards, Board, listDetailInfo, ListDetailInfo } from './state'
|
||||
|
||||
const cache = new Map<string, any>()
|
||||
|
||||
export const setBoard = (board: Board, source: LX.OnlineSource) => {
|
||||
boards[source] = markRaw(board)
|
||||
}
|
||||
|
||||
export const setListDetail = (result: ListDetailInfo, id: string, page: number) => {
|
||||
listDetailInfo.list = markRaw([...result.list])
|
||||
listDetailInfo.id = id
|
||||
listDetailInfo.source = result.source
|
||||
listDetailInfo.total = result.total
|
||||
listDetailInfo.limit = result.limit
|
||||
listDetailInfo.page = page
|
||||
|
||||
if (result.list.length) listDetailInfo.noItemLabel = ''
|
||||
else listDetailInfo.noItemLabel = window.i18n.t('no_item')
|
||||
}
|
||||
export const clearListDetail = () => {
|
||||
listDetailInfo.list = []
|
||||
listDetailInfo.id = ''
|
||||
listDetailInfo.source = null
|
||||
listDetailInfo.total = 0
|
||||
listDetailInfo.limit = 30
|
||||
listDetailInfo.page = 1
|
||||
listDetailInfo.key = null
|
||||
listDetailInfo.noItemLabel = ''
|
||||
}
|
||||
|
||||
export const getBoardsList = async(source: LX.OnlineSource) => {
|
||||
// const source = (await getLeaderboardSetting()).source as LX.OnlineSource
|
||||
return musicSdk[source]?.leaderboard.getBoards() as Promise<Board>
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排行榜内单页歌曲
|
||||
* @param id 排行榜id {souce}__{id}
|
||||
* @param isRefresh 是否跳过缓存
|
||||
* @returns
|
||||
*/
|
||||
export const getListDetail = async(id: string, page: number, isRefresh = false): Promise<ListDetailInfo> => {
|
||||
// let [source, bangId] = tabId.split('__')
|
||||
// if (!bangId) return
|
||||
let key = `${id}__${page}`
|
||||
|
||||
if (!isRefresh && cache.has(key)) return cache.get(key)
|
||||
|
||||
const [source, bangId] = id.split('__') as [LX.OnlineSource, string]
|
||||
|
||||
return musicSdk[source]?.leaderboard.getList(bangId, page).then((result: ListDetailInfo) => {
|
||||
result.list = markRawList(deduplicationList(result.list.map(m => toNewMusicInfo(m))))
|
||||
cache.set(key, result)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取排行榜内全部歌曲
|
||||
* @param id 排行榜id {souce}__{id}
|
||||
* @param isRefresh 是否跳过缓存
|
||||
* @returns
|
||||
*/
|
||||
export const getListDetailAll = async(id: string, isRefresh = false): Promise<LX.Music.MusicInfoOnline[]> => {
|
||||
const [source, bangId] = id.split('__') as [LX.OnlineSource, string]
|
||||
// console.log(source, id)
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
const loadData = async(id: string, page: number): Promise<ListDetailInfo> => {
|
||||
let key = `${source}__${id}__${page}`
|
||||
if (!isRefresh && cache.has(key)) return cache.get(key)
|
||||
|
||||
return musicSdk[source]?.leaderboard.getList(id, page).then((result: ListDetailInfo) => {
|
||||
result.list = markRawList(deduplicationList(result.list.map(m => toNewMusicInfo(m))))
|
||||
cache.set(key, result)
|
||||
return result
|
||||
}) ?? Promise.reject(new Error('source not found'))
|
||||
}
|
||||
return await loadData(bangId, 1).then((result: ListDetailInfo) => {
|
||||
if (result.total <= result.limit) return result.list
|
||||
|
||||
let maxPage = Math.ceil(result.total / result.limit)
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
const loadDetail = (loadPage = 2): Promise<ListDetailInfo['list']> => {
|
||||
return loadPage == maxPage
|
||||
? loadData(bangId, loadPage).then((result: ListDetailInfo) => result.list)
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
: loadData(bangId, loadPage).then((result1: ListDetailInfo) => loadDetail(++loadPage).then((result2: ListDetailInfo['list']) => [...result1.list, ...result2]))
|
||||
}
|
||||
return loadDetail().then(result2 => [...result.list, ...result2])
|
||||
}).then((list: ListDetailInfo['list']) => deduplicationList(list))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取并设置排行榜内单页歌曲
|
||||
* @param id 排行榜id {souce}__{id}
|
||||
* @param isRefresh 是否跳过缓存
|
||||
* @returns
|
||||
*/
|
||||
export const getAndSetListDetail = async(id: string, page: number, isRefresh = false) => {
|
||||
// let [source, bangId] = tabId.split('__')
|
||||
// if (!bangId) return
|
||||
let key = `${id}__${page}`
|
||||
|
||||
if (!isRefresh && listDetailInfo.key == key && listDetailInfo.list.length) return
|
||||
|
||||
listDetailInfo.key = key
|
||||
listDetailInfo.noItemLabel = window.i18n.t('list__loading')
|
||||
|
||||
return getListDetail(id, page, isRefresh).then((result: ListDetailInfo) => {
|
||||
if (key != listDetailInfo.key) return
|
||||
setListDetail(result, id, page)
|
||||
}).catch((error: any) => {
|
||||
clearListDetail()
|
||||
listDetailInfo.noItemLabel = window.i18n.t('list__load_failed')
|
||||
console.log(error)
|
||||
throw error
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { reactive, markRaw, shallowReactive } from '@common/utils/vueTools'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
|
||||
export declare type Source = LX.OnlineSource
|
||||
|
||||
export const sources: LX.OnlineSource[] = markRaw([])
|
||||
|
||||
for (const source of music.sources) {
|
||||
if (!music[source.id as LX.OnlineSource].leaderboard?.getBoards) continue
|
||||
sources.push(source.id as LX.OnlineSource)
|
||||
}
|
||||
|
||||
export declare interface BoardItem {
|
||||
id: string
|
||||
name: string
|
||||
bangid: string
|
||||
}
|
||||
export declare interface Board {
|
||||
list: BoardItem[]
|
||||
source: LX.OnlineSource
|
||||
}
|
||||
type Boards = Partial<Record<LX.OnlineSource, Board>>
|
||||
|
||||
export const boards = shallowReactive<Boards>({})
|
||||
|
||||
export declare interface ListDetailInfo {
|
||||
list: LX.Music.MusicInfoOnline[]
|
||||
total: number
|
||||
page: number
|
||||
source: LX.OnlineSource | null
|
||||
limit: number
|
||||
key: string | null
|
||||
id: string
|
||||
noItemLabel: string
|
||||
}
|
||||
|
||||
export const listDetailInfo = reactive<ListDetailInfo>({
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 30,
|
||||
key: null,
|
||||
source: null,
|
||||
id: '',
|
||||
noItemLabel: '',
|
||||
})
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// import { } from '@renderer/utils/ipc'
|
||||
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { fetchingListStatus, listUpdateTimes, allMusicList, userLists, tempListMeta } from './state'
|
||||
import {
|
||||
registerListAction,
|
||||
createUserList as createUserListAction,
|
||||
addListMusics as addListMusicsAction,
|
||||
moveListMusics as moveListMusicsAction,
|
||||
overwriteListMusics,
|
||||
} from '@renderer/store/list/listManage'
|
||||
import { toRaw } from '@common/utils/vueTools'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
export const registerAction = (onListChanged: (listIds: string[]) => void) => {
|
||||
return registerListAction(appSetting, onListChanged)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存获取列表内歌曲,前提是知道列表之前已被获取过,否则返回空数组
|
||||
* @param listId 列表ID
|
||||
* @returns
|
||||
*/
|
||||
export const getListMusicsFromCache = (listId: string | null): LX.Music.MusicInfo[] => {
|
||||
if (!listId) return []
|
||||
if (allMusicList.has(listId)) return allMusicList.get(listId) as LX.Music.MusicInfo[]
|
||||
return []
|
||||
}
|
||||
|
||||
export const setFetchingListStatus = (id: string, status: boolean) => {
|
||||
fetchingListStatus[id] = status
|
||||
}
|
||||
|
||||
export const setUpdateTime = (id: string, time: string) => {
|
||||
listUpdateTimes[id] = time
|
||||
}
|
||||
|
||||
export const addListMusics = async(id: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType?: LX.AddMusicLocationType) => {
|
||||
return addListMusicsAction({
|
||||
id,
|
||||
musicInfos: toRaw(musicInfos),
|
||||
addMusicLocationType: addMusicLocationType ?? appSetting['list.addMusicLocationType'],
|
||||
})
|
||||
}
|
||||
|
||||
export const moveListMusics = async(fromId: string, toId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType?: LX.AddMusicLocationType) => {
|
||||
return moveListMusicsAction({
|
||||
fromId,
|
||||
toId,
|
||||
musicInfos: toRaw(musicInfos),
|
||||
addMusicLocationType: addMusicLocationType ?? appSetting['list.addMusicLocationType'],
|
||||
})
|
||||
}
|
||||
|
||||
export const createUserList = async({ name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, position = -1 }: {
|
||||
name?: string
|
||||
id?: string
|
||||
list?: LX.Music.MusicInfo[]
|
||||
source?: LX.Source
|
||||
sourceListId?: string
|
||||
position?: number
|
||||
}) => {
|
||||
await createUserListAction({
|
||||
position: position < 0 ? userLists.length : position,
|
||||
listInfos: [
|
||||
{
|
||||
id,
|
||||
name: name ?? 'list',
|
||||
source,
|
||||
sourceListId,
|
||||
locationUpdateTime: position < 0 ? null : Date.now(),
|
||||
},
|
||||
],
|
||||
})
|
||||
if (list) await addListMusics(id, list)
|
||||
}
|
||||
|
||||
|
||||
export const setTempList = async(id: string, list: LX.Music.MusicInfoOnline[]) => {
|
||||
tempListMeta.id = id
|
||||
await overwriteListMusics({
|
||||
listId: LIST_IDS.TEMP,
|
||||
musicInfos: list,
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
addListMusicsAction,
|
||||
moveListMusicsAction,
|
||||
}
|
||||
|
||||
export {
|
||||
getUserLists,
|
||||
removeUserList,
|
||||
updateUserList,
|
||||
updateUserListPosition,
|
||||
getListMusics,
|
||||
removeListMusics,
|
||||
updateListMusics,
|
||||
updateListMusicsPosition,
|
||||
overwriteListMusics,
|
||||
clearListMusics,
|
||||
overwriteListFull,
|
||||
checkListExistMusic,
|
||||
getMusicExistListIds,
|
||||
} from '@renderer/store/list/listManage'
|
||||
@@ -0,0 +1,290 @@
|
||||
import { shallowReactive, markRaw, markRawList, toRaw } from '@common/utils/vueTools'
|
||||
import {
|
||||
allMusicList,
|
||||
defaultList,
|
||||
loveList,
|
||||
tempList,
|
||||
userLists,
|
||||
} from './state'
|
||||
import { overwriteListPosition, overwriteListUpdateInfo, removeListPosition, removeListUpdateInfo } from '@renderer/utils/data'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
export const setUserLists = (lists: LX.List.UserListInfo[]) => {
|
||||
userLists.splice(0, userLists.length, ...lists)
|
||||
return userLists
|
||||
}
|
||||
|
||||
export const setMusicList = (listId: string, musicList: LX.Music.MusicInfo[]) => {
|
||||
const list = shallowReactive(markRawList(musicList))
|
||||
allMusicList.set(listId, shallowReactive(musicList))
|
||||
return list
|
||||
}
|
||||
|
||||
const overwriteMusicList = (id: string, list: LX.Music.MusicInfo[]) => {
|
||||
// console.log(id, list)
|
||||
markRawList(list)
|
||||
let targetList = allMusicList.get(id)
|
||||
if (targetList) {
|
||||
targetList.splice(0, targetList.length, ...list)
|
||||
} else {
|
||||
allMusicList.set(id, shallowReactive(list))
|
||||
}
|
||||
}
|
||||
const removeMusicList = (id: string) => {
|
||||
allMusicList.delete(id)
|
||||
}
|
||||
|
||||
const createUserList = ({
|
||||
name,
|
||||
id,
|
||||
source,
|
||||
sourceListId,
|
||||
locationUpdateTime,
|
||||
}: LX.List.UserListInfo, position: number) => {
|
||||
if (position < 0 || position >= userLists.length) {
|
||||
userLists.push({
|
||||
name,
|
||||
id,
|
||||
source,
|
||||
sourceListId,
|
||||
locationUpdateTime,
|
||||
})
|
||||
} else {
|
||||
userLists.splice(position, 0, {
|
||||
name,
|
||||
id,
|
||||
source,
|
||||
sourceListId,
|
||||
locationUpdateTime,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const updateList = ({
|
||||
name,
|
||||
id,
|
||||
source,
|
||||
sourceListId,
|
||||
meta,
|
||||
locationUpdateTime,
|
||||
}: LX.List.UserListInfo & { meta?: { id?: string } }) => {
|
||||
let targetList
|
||||
switch (id) {
|
||||
case defaultList.id:
|
||||
case loveList.id:
|
||||
break
|
||||
case tempList.id:
|
||||
tempList.meta = meta ?? {}
|
||||
break
|
||||
default:
|
||||
targetList = userLists.find(l => l.id == id)
|
||||
if (!targetList) return
|
||||
targetList.name = name
|
||||
targetList.source = source
|
||||
targetList.sourceListId = sourceListId
|
||||
targetList.locationUpdateTime = locationUpdateTime
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const removeUserList = (id: string) => {
|
||||
const index = userLists.findIndex(l => l.id == id)
|
||||
if (index < 0) return
|
||||
userLists.splice(index, 1)
|
||||
// removeMusicList(id)
|
||||
}
|
||||
|
||||
const overwriteUserList = (lists: LX.List.UserListInfo[]) => {
|
||||
userLists.splice(0, lists.length, ...lists)
|
||||
}
|
||||
|
||||
|
||||
// const sendMyListUpdateEvent = (ids: string[]) => {
|
||||
// window.app_event.myListUpdate(ids)
|
||||
// }
|
||||
|
||||
|
||||
export const listDataOverwrite = ({ defaultList, loveList, userList, tempList }: MakeOptional<LX.List.ListDataFull, 'tempList'>): string[] => {
|
||||
const updatedListIds: string[] = []
|
||||
overwriteUserList(userList.map(({ list, ...listInfo }) => {
|
||||
if (allMusicList.has(listInfo.id)) {
|
||||
overwriteMusicList(listInfo.id, list)
|
||||
updatedListIds.push(listInfo.id)
|
||||
}
|
||||
return listInfo
|
||||
}))
|
||||
if (allMusicList.has(LIST_IDS.DEFAULT)) {
|
||||
overwriteMusicList(LIST_IDS.DEFAULT, defaultList)
|
||||
updatedListIds.push(LIST_IDS.DEFAULT)
|
||||
}
|
||||
if (allMusicList.has(LIST_IDS.LOVE)) {
|
||||
overwriteMusicList(LIST_IDS.LOVE, loveList)
|
||||
}
|
||||
updatedListIds.push(LIST_IDS.LOVE)
|
||||
|
||||
if (tempList && allMusicList.has(LIST_IDS.TEMP)) {
|
||||
overwriteMusicList(LIST_IDS.TEMP, tempList)
|
||||
updatedListIds.push(LIST_IDS.TEMP)
|
||||
}
|
||||
const newIds = [LIST_IDS.DEFAULT, LIST_IDS.LOVE, ...userList.map(l => l.id)]
|
||||
if (tempList) newIds.push(LIST_IDS.TEMP)
|
||||
overwriteListPosition(newIds)
|
||||
overwriteListUpdateInfo(newIds)
|
||||
return updatedListIds
|
||||
}
|
||||
|
||||
export const userListCreate = ({ name, id, source, sourceListId, position, locationUpdateTime }: {
|
||||
name: string
|
||||
id: string
|
||||
source?: LX.Source
|
||||
sourceListId?: string
|
||||
position: number
|
||||
locationUpdateTime: number | null
|
||||
}) => {
|
||||
if (userLists.some(item => item.id == id)) return
|
||||
const newList: LX.List.UserListInfo = {
|
||||
name,
|
||||
id,
|
||||
source,
|
||||
sourceListId,
|
||||
locationUpdateTime,
|
||||
}
|
||||
createUserList(newList, position)
|
||||
}
|
||||
|
||||
export const userListsRemove = (ids: string[]) => {
|
||||
const changedIds = []
|
||||
for (const id of ids) {
|
||||
removeUserList(id)
|
||||
if (!allMusicList.has(id)) continue
|
||||
removeMusicList(id)
|
||||
removeListPosition(id)
|
||||
removeListUpdateInfo(id)
|
||||
changedIds.push(id)
|
||||
}
|
||||
|
||||
return changedIds
|
||||
}
|
||||
|
||||
export const userListsUpdate = (listInfos: LX.List.UserListInfo[]) => {
|
||||
for (const info of listInfos) {
|
||||
updateList(info)
|
||||
}
|
||||
}
|
||||
|
||||
export const userListsUpdatePosition = (position: number, ids: string[]) => {
|
||||
const newUserLists = [...userLists]
|
||||
|
||||
// console.log(position, ids)
|
||||
|
||||
const updateLists: LX.List.UserListInfo[] = []
|
||||
|
||||
for (let i = newUserLists.length; i--;) {
|
||||
if (ids.includes(newUserLists[i].id)) {
|
||||
const list = newUserLists.splice(i, 1)[0]
|
||||
list.locationUpdateTime = Date.now()
|
||||
updateLists.push(list)
|
||||
}
|
||||
}
|
||||
position = Math.min(newUserLists.length, position)
|
||||
|
||||
newUserLists.splice(position, 0, ...updateLists)
|
||||
setUserLists(newUserLists)
|
||||
}
|
||||
|
||||
export const listMusicOverwrite = (listId: string, musicInfos: LX.Music.MusicInfo[]): string[] => {
|
||||
const isExist = allMusicList.has(listId)
|
||||
overwriteMusicList(listId, musicInfos)
|
||||
return isExist || listId == loveList.id ? [listId] : []
|
||||
}
|
||||
|
||||
export const listMusicAdd = (id: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType): string[] => {
|
||||
const targetList = allMusicList.get(id)
|
||||
if (!targetList) return id == loveList.id ? [id] : []
|
||||
|
||||
const listSet = new Set<string>()
|
||||
for (const item of targetList) listSet.add(item.id)
|
||||
musicInfos = musicInfos.filter(item => {
|
||||
if (listSet.has(item.id)) return false
|
||||
markRaw(item)
|
||||
listSet.add(item.id)
|
||||
return true
|
||||
})
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
targetList.unshift(...musicInfos)
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
targetList.push(...musicInfos)
|
||||
break
|
||||
}
|
||||
|
||||
return [id]
|
||||
}
|
||||
|
||||
export const listMusicMove = (fromId: string, toId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType): string[] => {
|
||||
return [
|
||||
...listMusicRemove(fromId, musicInfos.map(musicInfo => musicInfo.id)),
|
||||
...listMusicAdd(toId, musicInfos, addMusicLocationType),
|
||||
]
|
||||
}
|
||||
|
||||
export const listMusicRemove = (listId: string, ids: string[]): string[] => {
|
||||
let targetList = allMusicList.get(listId)
|
||||
if (!targetList) return listId == loveList.id ? [listId] : []
|
||||
|
||||
ids = [...ids]
|
||||
for (let i = targetList.length - 1; i > -1; i--) {
|
||||
const item = targetList[i]
|
||||
const index = ids.indexOf(item.id)
|
||||
if (index < 0) continue
|
||||
ids.splice(index, 1)
|
||||
targetList.splice(i, 1)
|
||||
}
|
||||
return [listId]
|
||||
}
|
||||
|
||||
export const listMusicUpdateInfo = (musicInfos: LX.List.ListActionMusicUpdate): string[] => {
|
||||
const updateListIds = new Set<string>()
|
||||
for (const { id, musicInfo } of musicInfos) {
|
||||
const targetList = allMusicList.get(id)
|
||||
if (!targetList) continue
|
||||
const index = targetList.findIndex(l => l.id == musicInfo.id)
|
||||
if (index < 0) continue
|
||||
const info: LX.Music.MusicInfo = { ...targetList[index] }
|
||||
Object.assign(info, {
|
||||
name: musicInfo.name,
|
||||
singer: musicInfo.singer,
|
||||
source: musicInfo.source,
|
||||
interval: musicInfo.interval,
|
||||
meta: musicInfo.meta,
|
||||
})
|
||||
targetList.splice(index, 1, markRaw(info))
|
||||
updateListIds.add(id)
|
||||
}
|
||||
return Array.from(updateListIds)
|
||||
}
|
||||
|
||||
export const listMusicUpdatePosition = async(listId: string, position: number, ids: string[]): Promise<string[]> => {
|
||||
let targetList = allMusicList.get(listId)
|
||||
if (!targetList) return listId == loveList.id ? [listId] : []
|
||||
|
||||
|
||||
// const infos = Array(ids.length)
|
||||
// for (let i = targetList.length; i--;) {
|
||||
// const item = targetList[i]
|
||||
// const index = ids.indexOf(item.id)
|
||||
// if (index < 0) continue
|
||||
// infos.splice(index, 1, targetList.splice(i, 1)[0])
|
||||
// }
|
||||
// targetList.splice(Math.min(position, targetList.length - 1), 0, ...infos)
|
||||
|
||||
// console.time('ts')
|
||||
|
||||
const list = await window.lx.worker.main.createSortedList(toRaw(targetList), position, ids)
|
||||
markRawList(list)
|
||||
targetList.splice(0, targetList.length, ...list)
|
||||
// console.timeEnd('ts')
|
||||
return [listId]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './rendererListManage'
|
||||
export * from './state'
|
||||
@@ -0,0 +1,249 @@
|
||||
import { toRaw } from '@common/utils/vueTools'
|
||||
import { rendererInvoke, rendererOff, rendererOn } from '@common/rendererIpc'
|
||||
import { PLAYER_EVENT_NAME } from '@common/ipcNames'
|
||||
import {
|
||||
userListCreate,
|
||||
listDataOverwrite,
|
||||
userListsRemove,
|
||||
userListsUpdate,
|
||||
userListsUpdatePosition,
|
||||
listMusicAdd,
|
||||
listMusicMove,
|
||||
listMusicRemove,
|
||||
listMusicOverwrite,
|
||||
listMusicUpdateInfo,
|
||||
listMusicUpdatePosition,
|
||||
setMusicList,
|
||||
setUserLists,
|
||||
} from './action'
|
||||
import { allMusicList } from './state'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @returns 所有用户列表
|
||||
*/
|
||||
export const getUserLists = async() => {
|
||||
const lists = await rendererInvoke<LX.List.UserListInfo[]>(PLAYER_EVENT_NAME.list_get)
|
||||
return setUserLists(lists)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户列表
|
||||
* @param data
|
||||
*/
|
||||
export const createUserList = async(data: LX.List.ListActionAdd) => {
|
||||
data.listInfos = data.listInfos.map(info => toRaw(info))
|
||||
await rendererInvoke<LX.List.ListActionAdd>(PLAYER_EVENT_NAME.list_add, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除用户列表及列表内歌曲
|
||||
* @param data
|
||||
*/
|
||||
export const removeUserList = async(data: LX.List.ListActionRemove) => {
|
||||
await rendererInvoke<LX.List.ListActionRemove>(PLAYER_EVENT_NAME.list_remove, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户列表
|
||||
* @param data
|
||||
*/
|
||||
export const updateUserList = async(data: LX.List.ListActionUpdate) => {
|
||||
data = data.map(info => toRaw(info))
|
||||
await rendererInvoke<LX.List.ListActionUpdate>(PLAYER_EVENT_NAME.list_update, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量移动用户列表位置
|
||||
* @param data
|
||||
*/
|
||||
export const updateUserListPosition = async(data: LX.List.ListActionUpdatePosition) => {
|
||||
await rendererInvoke<LX.List.ListActionUpdatePosition>(PLAYER_EVENT_NAME.list_update_position, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取列表内的歌曲
|
||||
* @param listId
|
||||
*/
|
||||
export const getListMusics = async(listId: string | null): Promise<LX.Music.MusicInfo[]> => {
|
||||
if (!listId) return []
|
||||
if (allMusicList.has(listId)) return allMusicList.get(listId) as LX.Music.MusicInfo[]
|
||||
const list = await rendererInvoke<string, LX.Music.MusicInfo[]>(PLAYER_EVENT_NAME.list_music_get, listId)
|
||||
return setMusicList(listId, list)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加歌曲到列表
|
||||
* @param data
|
||||
*/
|
||||
export const addListMusics = async(data: LX.List.ListActionMusicAdd) => {
|
||||
await rendererInvoke<LX.List.ListActionMusicAdd>(PLAYER_EVENT_NAME.list_music_add, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨列表批量移动歌曲
|
||||
* @param data
|
||||
*/
|
||||
export const moveListMusics = async(data: LX.List.ListActionMusicMove) => {
|
||||
await rendererInvoke<LX.List.ListActionMusicMove>(PLAYER_EVENT_NAME.list_music_move, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除列表内歌曲
|
||||
* @param data
|
||||
*/
|
||||
export const removeListMusics = async(data: LX.List.ListActionMusicRemove) => {
|
||||
await rendererInvoke<LX.List.ListActionMusicRemove>(PLAYER_EVENT_NAME.list_music_remove, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新列表内歌曲
|
||||
* @param data
|
||||
*/
|
||||
export const updateListMusics = async(data: LX.List.ListActionMusicUpdate) => {
|
||||
await rendererInvoke<LX.List.ListActionMusicUpdate>(PLAYER_EVENT_NAME.list_music_update, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量移动列表内歌曲的位置
|
||||
* @param data
|
||||
*/
|
||||
export const updateListMusicsPosition = async(data: LX.List.ListActionMusicUpdatePosition) => {
|
||||
await rendererInvoke<LX.List.ListActionMusicUpdatePosition>(PLAYER_EVENT_NAME.list_music_update_position, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖列表内的歌曲
|
||||
* @param data
|
||||
*/
|
||||
export const overwriteListMusics = async(data: LX.List.ListActionMusicOverwrite) => {
|
||||
await rendererInvoke<LX.List.ListActionMusicOverwrite>(PLAYER_EVENT_NAME.list_music_overwrite, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空列表内的歌曲
|
||||
* @param listId
|
||||
*/
|
||||
export const clearListMusics = async(listId: LX.List.ListActionMusicClear) => {
|
||||
await rendererInvoke<LX.List.ListActionMusicClear>(PLAYER_EVENT_NAME.list_music_clear, listId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖全部列表数据
|
||||
* @param data
|
||||
*/
|
||||
export const overwriteListFull = async(data: LX.List.ListActionDataOverwrite) => {
|
||||
data.defaultList = toRaw(data.defaultList)
|
||||
data.loveList = toRaw(data.loveList)
|
||||
if (data.tempList) {
|
||||
data.tempList = toRaw(data.tempList)
|
||||
}
|
||||
data.userList = data.userList.map(info => {
|
||||
return {
|
||||
...info,
|
||||
list: toRaw(info.list),
|
||||
}
|
||||
})
|
||||
|
||||
await rendererInvoke<LX.List.ListActionDataOverwrite>(PLAYER_EVENT_NAME.list_data_overwire, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查音乐是否存在列表中
|
||||
* @param listId
|
||||
* @param musicInfoId
|
||||
*/
|
||||
export const checkListExistMusic = async(listId: string, musicInfoId: string): Promise<boolean> => {
|
||||
return await rendererInvoke<LX.List.ListActionCheckMusicExistList, boolean>(PLAYER_EVENT_NAME.list_music_check_exist, { listId, musicInfoId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有存在该音乐的列表id
|
||||
* @param musicInfoId
|
||||
*/
|
||||
export const getMusicExistListIds = async(musicInfoId: string): Promise<string[]> => {
|
||||
return await rendererInvoke<string, string[]>(PLAYER_EVENT_NAME.list_music_get_list_ids, musicInfoId)
|
||||
}
|
||||
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
|
||||
export const registerListAction = (appSetting: LX.AppSetting, onListChanged: (listIds: string[]) => void = noop) => {
|
||||
const list_data_overwrite = ({ params: datas }: LX.IpcRendererEventParams<LX.List.ListActionDataOverwrite>) => {
|
||||
const updatedListIds = listDataOverwrite(datas)
|
||||
if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
}
|
||||
const list_create = ({ params: { position, listInfos } }: LX.IpcRendererEventParams<LX.List.ListActionAdd>) => {
|
||||
for (const list of listInfos) {
|
||||
userListCreate({ ...list, position })
|
||||
}
|
||||
}
|
||||
const list_remove = ({ params: ids }: LX.IpcRendererEventParams<LX.List.ListActionRemove>) => {
|
||||
const updatedListIds = userListsRemove(ids)
|
||||
if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
}
|
||||
const list_update = ({ params: listInfos }: LX.IpcRendererEventParams<LX.List.ListActionUpdate>) => {
|
||||
userListsUpdate(listInfos)
|
||||
}
|
||||
const list_update_position = ({ params: { position, ids } }: LX.IpcRendererEventParams<LX.List.ListActionUpdatePosition>) => {
|
||||
userListsUpdatePosition(position, ids)
|
||||
}
|
||||
const list_music_add = ({ params: { id, musicInfos, addMusicLocationType } }: LX.IpcRendererEventParams<LX.List.ListActionMusicAdd>) => {
|
||||
if (!addMusicLocationType) addMusicLocationType = appSetting['list.addMusicLocationType']
|
||||
const updatedListIds = listMusicAdd(id, musicInfos, addMusicLocationType)
|
||||
if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
}
|
||||
const list_music_move = ({ params: { fromId, toId, musicInfos, addMusicLocationType } }: LX.IpcRendererEventParams<LX.List.ListActionMusicMove>) => {
|
||||
if (!addMusicLocationType) addMusicLocationType = appSetting['list.addMusicLocationType']
|
||||
const updatedListIds = listMusicMove(fromId, toId, musicInfos, addMusicLocationType)
|
||||
if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
}
|
||||
const list_music_remove = ({ params: { listId, ids } }: LX.IpcRendererEventParams<LX.List.ListActionMusicRemove>) => {
|
||||
// console.log(listId, ids)
|
||||
const updatedListIds = listMusicRemove(listId, ids)
|
||||
if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
}
|
||||
const list_music_update = ({ params: musicInfos }: LX.IpcRendererEventParams<LX.List.ListActionMusicUpdate>) => {
|
||||
listMusicUpdateInfo(musicInfos)
|
||||
// const updatedListIds = listMusicUpdateInfo(musicInfos)
|
||||
// if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
}
|
||||
const list_music_update_position = ({ params: { listId, position, ids } }: LX.IpcRendererEventParams<LX.List.ListActionMusicUpdatePosition>) => {
|
||||
void listMusicUpdatePosition(listId, position, ids).then(updatedListIds => {
|
||||
if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
})
|
||||
}
|
||||
const list_music_overwrite = ({ params: { listId, musicInfos } }: LX.IpcRendererEventParams<LX.List.ListActionMusicOverwrite>) => {
|
||||
const updatedListIds = listMusicOverwrite(listId, musicInfos)
|
||||
if (updatedListIds.length) onListChanged(updatedListIds)
|
||||
}
|
||||
|
||||
rendererOn(PLAYER_EVENT_NAME.list_data_overwire, list_data_overwrite)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_add, list_create)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_remove, list_remove)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_update, list_update)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_update_position, list_update_position)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_music_add, list_music_add)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_music_move, list_music_move)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_music_remove, list_music_remove)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_music_update, list_music_update)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_music_update_position, list_music_update_position)
|
||||
rendererOn(PLAYER_EVENT_NAME.list_music_overwrite, list_music_overwrite)
|
||||
// rendererOn(PLAYER_EVENT_NAME.list_data_overwire, list_music_clear)
|
||||
|
||||
return () => {
|
||||
rendererOff(PLAYER_EVENT_NAME.list_data_overwire, list_data_overwrite)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_add, list_create)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_remove, list_remove)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_update, list_update)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_update_position, list_update_position)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_music_add, list_music_add)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_music_move, list_music_move)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_music_remove, list_music_remove)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_music_update, list_music_update)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_music_update_position, list_music_update_position)
|
||||
rendererOff(PLAYER_EVENT_NAME.list_music_overwrite, list_music_overwrite)
|
||||
// rendererOff(PLAYER_EVENT_NAME.list_data_overwire, list_music_clear)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
import { markRaw, reactive } from '@common/utils/vueTools'
|
||||
|
||||
export const allMusicList: Map<string, LX.Music.MusicInfo[]> = markRaw(new Map())
|
||||
|
||||
export const defaultList = markRaw<LX.List.MyDefaultListInfo>({
|
||||
id: LIST_IDS.DEFAULT,
|
||||
name: '试听列表',
|
||||
})
|
||||
|
||||
export const loveList = markRaw<LX.List.MyLoveListInfo>({
|
||||
id: LIST_IDS.LOVE,
|
||||
name: '我的收藏',
|
||||
})
|
||||
export const tempList = markRaw<LX.List.MyTempListInfo>({
|
||||
id: LIST_IDS.TEMP,
|
||||
name: '临时列表',
|
||||
meta: {},
|
||||
})
|
||||
|
||||
export const userLists: LX.List.UserListInfo[] = reactive([])
|
||||
@@ -0,0 +1,43 @@
|
||||
import { reactive } from '@common/utils/vueTools'
|
||||
|
||||
export {
|
||||
allMusicList,
|
||||
defaultList,
|
||||
loveList,
|
||||
tempList,
|
||||
userLists,
|
||||
} from '@renderer/store/list/listManage'
|
||||
// import { reactive, ref, markRaw, Ref } from '@common/utils/vueTools'
|
||||
|
||||
// // const TEMP_LIST = 'TEMP_LIST'
|
||||
|
||||
// export const isInitedList: Ref<boolean> = ref(false)
|
||||
|
||||
// export const allList: Map<string, LX.Music.MusicInfo[]> = window.lxData.allList = markRaw(new Map())
|
||||
|
||||
// export const defaultList: Omit<LX.List.MyDefaultListInfo, 'list'> = reactive({
|
||||
// id: 'default',
|
||||
// name: '试听列表',
|
||||
// })
|
||||
|
||||
// export const loveList: Omit<LX.List.MyLoveListInfo, 'list'> = reactive({
|
||||
// id: 'love',
|
||||
// name: '我的收藏',
|
||||
// })
|
||||
|
||||
// export const tempList: Omit<LX.List.MyTempListInfo, 'list'> = reactive({
|
||||
// id: 'temp',
|
||||
// name: '临时列表',
|
||||
// meta: {},
|
||||
// })
|
||||
|
||||
export const tempListMeta = {
|
||||
id: '',
|
||||
}
|
||||
|
||||
|
||||
// export const userLists: LX.List.UserListInfo[] = window.lxData.userLists = reactive([])
|
||||
|
||||
export const fetchingListStatus = reactive<Record<string, boolean>>({})
|
||||
|
||||
export const listUpdateTimes = reactive<Record<string, string>>({})
|
||||
@@ -0,0 +1,31 @@
|
||||
import { setListUpdateTime } from '@renderer/utils/data'
|
||||
import { setFetchingListStatus, overwriteListMusics, setUpdateTime } from './action'
|
||||
import { getListDetailAll } from '@renderer/store/songList/action'
|
||||
import { getListDetailAll as getBoardListAll } from '@renderer/store/leaderboard/action'
|
||||
import { dateFormat } from '@common/utils/common'
|
||||
|
||||
const fetchList = async(id: string, source: LX.OnlineSource, sourceListId: string) => {
|
||||
setFetchingListStatus(id, true)
|
||||
|
||||
let promise
|
||||
if (/^board__/.test(sourceListId)) {
|
||||
const id = sourceListId.replace(/^board__/, '')
|
||||
promise = id ? getBoardListAll(id, true) : Promise.reject(new Error('id not defined: ' + sourceListId))
|
||||
} else {
|
||||
promise = getListDetailAll(sourceListId, source, true)
|
||||
}
|
||||
return promise.finally(() => {
|
||||
setFetchingListStatus(id, false)
|
||||
})
|
||||
}
|
||||
|
||||
export default async(targetListInfo: LX.List.UserListInfo) => {
|
||||
// console.log(targetListInfo)
|
||||
if (!targetListInfo.source || !targetListInfo.sourceListId) return
|
||||
const list = await fetchList(targetListInfo.id, targetListInfo.source as LX.OnlineSource, targetListInfo.sourceListId)
|
||||
// console.log(list)
|
||||
void overwriteListMusics({ listId: targetListInfo.id, musicInfos: list })
|
||||
const now = Date.now()
|
||||
void setListUpdateTime(targetListInfo.id, now)
|
||||
setUpdateTime(targetListInfo.id, dateFormat(now))
|
||||
}
|
||||
@@ -1,810 +0,0 @@
|
||||
import download from '../../utils/download'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import music from '../../utils/music'
|
||||
import { getMusicType } from '../../utils/music/utils'
|
||||
import {
|
||||
setMeta,
|
||||
saveLrc,
|
||||
getLyric as getLyricFromStorage,
|
||||
setLyric,
|
||||
getMusicUrl as getMusicUrlFormStorage,
|
||||
setMusicUrl,
|
||||
assertApiSupport,
|
||||
filterFileName,
|
||||
} from '../../utils'
|
||||
import { NAMES, rendererInvoke } from '@common/ipc'
|
||||
import { initDownloadList, downloadList, downloadListMap, downloadStatus } from '@renderer/core/share/download'
|
||||
import { markRaw } from '@renderer/utils/vueTools'
|
||||
import { download as eventDownloadNames } from '@renderer/event/names'
|
||||
|
||||
// window.downloadList = []
|
||||
// window.downloadListFull = []
|
||||
// window.downloadListFullMap = new Map()
|
||||
// state
|
||||
const state = {
|
||||
// list: window.downloadList,
|
||||
// downloadStatus: {
|
||||
// RUN: 'run',
|
||||
// WAITING: 'waiting',
|
||||
// PAUSE: 'pause',
|
||||
// ERROR: 'error',
|
||||
// COMPLETED: 'completed',
|
||||
// },
|
||||
}
|
||||
|
||||
|
||||
const dls = {}
|
||||
const tryNum = {}
|
||||
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
// list: state => downloadList || [],
|
||||
// downloadStatus: state => downloadStatus,
|
||||
}
|
||||
|
||||
const checkPath = path => new Promise((resolve, reject) => {
|
||||
fs.access(path, fs.constants.F_OK | fs.constants.W_OK, err => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
fs.mkdir(path, { recursive: true }, err => {
|
||||
if (err) return reject(err)
|
||||
resolve()
|
||||
})
|
||||
return
|
||||
}
|
||||
return reject(err)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
const getExt = type => {
|
||||
switch (type) {
|
||||
case '128k':
|
||||
case '192k':
|
||||
case '320k':
|
||||
return 'mp3'
|
||||
case 'ape':
|
||||
return 'ape'
|
||||
case 'flac':
|
||||
case 'flac32bit':
|
||||
return 'flac'
|
||||
case 'wav':
|
||||
return 'wav'
|
||||
}
|
||||
}
|
||||
|
||||
const checkList = (list, musicInfo, type, ext) => list.some(s => s.songmid === musicInfo.songmid && (s.metadata.type === type || s.metadata.ext === ext))
|
||||
|
||||
const getStartTask = (list, downloadStatus, maxDownloadNum) => {
|
||||
let downloadCount = 0
|
||||
const waitList = list.filter(item => item.status == downloadStatus.WAITING ? true : (item.status === downloadStatus.RUN && ++downloadCount && false))
|
||||
// console.log(downloadCount, waitList)
|
||||
return downloadCount < maxDownloadNum ? waitList.shift() || null : false
|
||||
}
|
||||
|
||||
const handleGetMusicUrl = function(musicInfo, type, retryedSource = [], originMusic) {
|
||||
// console.log(musicInfo.source)
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = music[musicInfo.source].getMusicUrl(musicInfo, type).promise
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.catch(err => {
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
return this.dispatch('list/getOtherSource', originMusic).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source) || !assertApiSupport(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return handleGetMusicUrl.call(this, item, type, retryedSource, originMusic)
|
||||
}
|
||||
}
|
||||
return Promise.reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getMusicUrl = async function(downloadInfo, isUseOtherSource, isRefresh) {
|
||||
const cachedUrl = await getMusicUrlFormStorage(downloadInfo.metadata.musicInfo, downloadInfo.metadata.type)
|
||||
if (!downloadInfo.metadata.musicInfo._types[downloadInfo.metadata.type]) {
|
||||
// 兼容旧版酷我源搜索列表过滤128k音质的bug
|
||||
if (!(downloadInfo.metadata.musicInfo.source == 'kw' && downloadInfo.metadata.type == '128k')) throw new Error('该歌曲没有可下载的音频')
|
||||
|
||||
// return Promise.reject(new Error('该歌曲没有可下载的音频'))
|
||||
}
|
||||
return cachedUrl && !isRefresh
|
||||
? cachedUrl
|
||||
: (
|
||||
isUseOtherSource
|
||||
? handleGetMusicUrl.call(this, downloadInfo.metadata.musicInfo, downloadInfo.metadata.type)
|
||||
: music[downloadInfo.metadata.musicInfo.source].getMusicUrl(downloadInfo.metadata.musicInfo, downloadInfo.metadata.type).promise
|
||||
).then(({ url }) => {
|
||||
setMusicUrl(downloadInfo.metadata.musicInfo, downloadInfo.metadata.type, url)
|
||||
return url
|
||||
})
|
||||
}
|
||||
const getPic = function(musicInfo, retryedSource = [], originMusic) {
|
||||
// console.log(musicInfo.source)
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = music[musicInfo.source].getPic(musicInfo).promise
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.catch(err => {
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
return this.dispatch('list/getOtherSource', originMusic).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return getPic.call(this, item, retryedSource, originMusic)
|
||||
}
|
||||
}
|
||||
return Promise.reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||
const handleGetLyric = function(musicInfo, retryedSource = [], originMusic) {
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = music[musicInfo.source].getLyric(musicInfo).promise
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.then(lyricInfo => {
|
||||
return existTimeExp.test(lyricInfo.lyric) ? lyricInfo : Promise.reject(new Error('failed'))
|
||||
}).catch(err => {
|
||||
// console.log(err)
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
return this.dispatch('list/getOtherSource', originMusic).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return handleGetLyric.call(this, item, retryedSource, originMusic)
|
||||
}
|
||||
}
|
||||
return Promise.reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getLyric = function(musicInfo, isUseOtherSource, isS2t) {
|
||||
return getLyricFromStorage(musicInfo).then(lrcInfo => {
|
||||
return (
|
||||
existTimeExp.test(lrcInfo.lyric)
|
||||
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '', rlyric: lrcInfo.rlyric || '', lxlyric: lrcInfo.lxlyric || '' })
|
||||
: (
|
||||
isUseOtherSource
|
||||
? handleGetLyric.call(this, musicInfo)
|
||||
: music[musicInfo.source].getLyric(musicInfo).promise
|
||||
).then(({ lyric, tlyric, rlyric, lxlyric }) => {
|
||||
setLyric(musicInfo, { lyric, tlyric, rlyric, lxlyric })
|
||||
return { lyric, tlyric, rlyric, lxlyric }
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
return null
|
||||
})
|
||||
).then(lrcs => {
|
||||
if (!lrcs || !isS2t) return lrcs
|
||||
return rendererInvoke(NAMES.mainWindow.lang_s2t, Buffer.from(lrcs.lyric).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString()).then(lyric => {
|
||||
lrcs.lyric = lyric
|
||||
return lrcs
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 修复 1.1.x版本 酷狗源歌词格式
|
||||
const fixKgLyric = lrc => /\[00:\d\d:\d\d.\d+\]/.test(lrc) ? lrc.replace(/(?:\[00:(\d\d:\d\d.\d+\]))/gm, '[$1') : lrc
|
||||
|
||||
/**
|
||||
* 设置歌曲meta信息
|
||||
* @param {*} downloadInfo
|
||||
* @param {*} filePath
|
||||
* @param {*} isEmbedPic // 是否嵌入图片
|
||||
*/
|
||||
const saveMeta = function({ downloadInfo, filePath, isUseOtherSource, isEmbedPic, isEmbedLyric, isS2t }) {
|
||||
if (downloadInfo.metadata.type === 'ape') return
|
||||
const tasks = [
|
||||
isEmbedPic
|
||||
? downloadInfo.metadata.musicInfo.img
|
||||
? Promise.resolve(downloadInfo.metadata.musicInfo.img)
|
||||
: (
|
||||
isUseOtherSource
|
||||
? getPic.call(this, downloadInfo.metadata.musicInfo)
|
||||
: music[downloadInfo.metadata.musicInfo.source].getPic(downloadInfo.metadata.musicInfo).promise
|
||||
).catch(err => {
|
||||
console.log(err)
|
||||
return null
|
||||
})
|
||||
: Promise.resolve(),
|
||||
isEmbedLyric
|
||||
? getLyric.call(this, downloadInfo.metadata.musicInfo, isUseOtherSource, isS2t)
|
||||
: Promise.resolve(),
|
||||
]
|
||||
Promise.all(tasks).then(([imgUrl, lyrics = {}]) => {
|
||||
if (lyrics?.lyric) lyrics.lyric = fixKgLyric(lyrics.lyric)
|
||||
setMeta(filePath, {
|
||||
title: downloadInfo.metadata.musicInfo.name,
|
||||
artist: downloadInfo.metadata.musicInfo.singer,
|
||||
album: downloadInfo.metadata.musicInfo.albumName,
|
||||
APIC: imgUrl,
|
||||
lyrics: lyrics.lyric,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存歌词
|
||||
* @param {*} downloadInfo
|
||||
* @param {*} filePath
|
||||
*/
|
||||
const downloadLyric = function({ downloadInfo, isUseOtherSource, filePath, lrcFormat, isS2t }) {
|
||||
getLyric.call(this, downloadInfo.metadata.musicInfo, isUseOtherSource, isS2t).then(lrcs => {
|
||||
if (lrcs?.lyric) {
|
||||
lrcs.lyric = fixKgLyric(lrcs.lyric)
|
||||
saveLrc(filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'), lrcs.lyric, lrcFormat)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const refreshUrl = function(commit, downloadInfo, isUseOtherSource) {
|
||||
commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
|
||||
getMusicUrl.call(this, downloadInfo, isUseOtherSource, true).then(url => {
|
||||
commit('updateUrl', { downloadInfo, url })
|
||||
commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
|
||||
const dl = dls[downloadInfo.key]
|
||||
if (!dl) return
|
||||
dl.refreshUrl(url)
|
||||
dl.start().catch(err => {
|
||||
commit('onError', { downloadInfo, errorMsg: err.message })
|
||||
commit('setStatusText', { downloadInfo, text: err.message })
|
||||
this.dispatch('download/startTask')
|
||||
})
|
||||
}).catch(err => {
|
||||
// console.log(err)
|
||||
commit('onError', { downloadInfo, errorMsg: err.message })
|
||||
commit('setStatusText', { downloadInfo, text: err.message })
|
||||
this.dispatch('download/startTask')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param {*} path
|
||||
*/
|
||||
const deleteFile = path => new Promise((resolve, reject) => {
|
||||
fs.access(path, fs.constants.F_OK, err => {
|
||||
if (err) return err.code == 'ENOENT' ? resolve() : reject(err)
|
||||
fs.unlink(path, err => {
|
||||
if (err) return reject(err)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const createDownloadInfo = ({ musicInfo, type, list, fileName, savePath }) => {
|
||||
type = getMusicType(musicInfo, type)
|
||||
let ext = getExt(type)
|
||||
const key = `${musicInfo.songmid}${ext}`
|
||||
if (checkList(list, musicInfo, type, ext)) return null
|
||||
const downloadInfo = {
|
||||
isComplate: false,
|
||||
status: downloadStatus.WAITING,
|
||||
statusText: '待下载',
|
||||
songmid: musicInfo.songmid,
|
||||
progress: {
|
||||
downloaded: 0,
|
||||
total: 0,
|
||||
progress: 0,
|
||||
},
|
||||
name: `${musicInfo.name} - ${musicInfo.singer}`,
|
||||
key,
|
||||
metadata: {
|
||||
musicInfo,
|
||||
url: null,
|
||||
type,
|
||||
ext,
|
||||
fileName: filterFileName(`${fileName
|
||||
.replace('歌名', musicInfo.name)
|
||||
.replace('歌手', musicInfo.singer)}.${ext}`),
|
||||
},
|
||||
}
|
||||
downloadInfo.metadata.filePath = path.join(savePath, downloadInfo.metadata.fileName)
|
||||
// commit('addTask', downloadInfo)
|
||||
|
||||
// 删除同路径下的同名文件
|
||||
deleteFile(downloadInfo.metadata.filePath)
|
||||
// .catch(err => {
|
||||
// if (err.code !== 'ENOENT') return commit('setStatusText', { downloadInfo, text: '文件删除失败' })
|
||||
// })
|
||||
|
||||
if (dls[downloadInfo.key]) {
|
||||
const dl = dls[downloadInfo.key]
|
||||
delete dls[downloadInfo.key]
|
||||
dl.stop()
|
||||
}
|
||||
|
||||
return downloadInfo
|
||||
}
|
||||
|
||||
// let waitingUpdateTasks = {}
|
||||
// const delayUpdateProgress = throttle(function(commit) {
|
||||
// commit('setProgressDelay')
|
||||
// }, 1000)
|
||||
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
async createDownload({ state, rootState, commit, dispatch }, { musicInfo, type }) {
|
||||
const downloadInfo = createDownloadInfo({
|
||||
musicInfo,
|
||||
type,
|
||||
fileName: rootState.setting.download.fileName,
|
||||
savePath: rootState.setting.download.savePath,
|
||||
list: downloadList,
|
||||
})
|
||||
if (!downloadInfo) return
|
||||
commit('addTask', { downloadInfo, addMusicLocationType: rootState.setting.list.addMusicLocationType })
|
||||
let result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
console.log(result)
|
||||
while (result) {
|
||||
dispatch('startTask', result)
|
||||
result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
}
|
||||
},
|
||||
createDownloadMultiple({ state, rootState, commit, dispatch }, { list, type }) {
|
||||
if (!list.length) return
|
||||
const taskList = list.map(musicInfo => {
|
||||
return createDownloadInfo({
|
||||
musicInfo,
|
||||
type,
|
||||
fileName: rootState.setting.download.fileName,
|
||||
savePath: rootState.setting.download.savePath,
|
||||
list: downloadList,
|
||||
})
|
||||
}).filter(task => task)
|
||||
commit('addTasks', { list: taskList, addMusicLocationType: rootState.setting.list.addMusicLocationType })
|
||||
let result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
while (result) {
|
||||
dispatch('startTask', result)
|
||||
result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
}
|
||||
},
|
||||
async handleStartTask({ commit, dispatch, rootState }, downloadInfo) {
|
||||
// 开始任务
|
||||
commit('onStart', downloadInfo)
|
||||
commit('setStatusText', { downloadInfo, text: '任务初始化中' })
|
||||
try {
|
||||
await checkPath(rootState.setting.download.savePath)
|
||||
} catch (error) {
|
||||
commit('onError', { downloadInfo, errorMsg: error.message })
|
||||
commit('setStatusText', { downloadInfo, text: '检查下载目录出错: ' + error.message })
|
||||
await dispatch('startTask')
|
||||
return
|
||||
}
|
||||
const _this = this
|
||||
const options = {
|
||||
url: downloadInfo.metadata.url,
|
||||
path: rootState.setting.download.savePath,
|
||||
fileName: downloadInfo.metadata.fileName,
|
||||
method: 'get',
|
||||
override: true,
|
||||
onCompleted() {
|
||||
// if (downloadInfo.progress.progress != '100.00') {
|
||||
// delete dls[downloadInfo.key]
|
||||
// return dispatch('startTask', downloadInfo)
|
||||
// }
|
||||
commit('onCompleted', downloadInfo)
|
||||
dispatch('startTask')
|
||||
|
||||
saveMeta.call(_this, {
|
||||
downloadInfo,
|
||||
filePath: downloadInfo.metadata.filePath,
|
||||
isUseOtherSource: rootState.setting.download.isUseOtherSource,
|
||||
isEmbedPic: rootState.setting.download.isEmbedPic,
|
||||
isEmbedLyric: rootState.setting.download.isEmbedLyric,
|
||||
isS2t: rootState.setting.player.isS2t,
|
||||
})
|
||||
if (rootState.setting.download.isDownloadLrc) {
|
||||
downloadLyric.call(_this, {
|
||||
downloadInfo,
|
||||
isUseOtherSource: rootState.setting.download.isUseOtherSource,
|
||||
filePath: downloadInfo.metadata.filePath,
|
||||
lrcFormat: rootState.setting.download.lrcFormat,
|
||||
isS2t: rootState.setting.player.isS2t,
|
||||
})
|
||||
}
|
||||
console.log('on complate')
|
||||
},
|
||||
onError(err) {
|
||||
console.log(err)
|
||||
if (err.code == 'EPERM') {
|
||||
commit('onError', { downloadInfo, errorMsg: '歌曲保存位置被占用或没有写入权限,请尝试更改歌曲保存目录或重启软件或重启电脑,错误详情:' + err.message })
|
||||
return
|
||||
}
|
||||
// console.log(tryNum[downloadInfo.key])
|
||||
if (++tryNum[downloadInfo.key] > 2) {
|
||||
commit('onError', { downloadInfo, errorMsg: err.message })
|
||||
dispatch('startTask')
|
||||
return
|
||||
}
|
||||
if (err.message?.startsWith('Resume failed')) {
|
||||
fs.unlink(downloadInfo.metadata.filePath, err => {
|
||||
if (err) return commit('onError', { downloadInfo, errorMsg: '删除不匹配的文件失败:' + err.message })
|
||||
dls[downloadInfo.key].start()
|
||||
commit('setStatusText', { downloadInfo, text: '正在重试' })
|
||||
})
|
||||
return
|
||||
}
|
||||
if (err.code == 'ENOTFOUND') {
|
||||
commit('onError', { downloadInfo, errorMsg: '链接失效' })
|
||||
refreshUrl.call(_this, commit, downloadInfo, rootState.setting.download.isUseOtherSource)
|
||||
} else {
|
||||
console.log('Download failed, Attempting Retry')
|
||||
dls[downloadInfo.key].start()
|
||||
commit('setStatusText', { downloadInfo, text: '正在重试' })
|
||||
}
|
||||
},
|
||||
onFail(response) {
|
||||
if (++tryNum[downloadInfo.key] > 2) {
|
||||
commit('onError', { downloadInfo, errorMsg: '下载失败' })
|
||||
dispatch('startTask')
|
||||
return
|
||||
}
|
||||
switch (response.statusCode) {
|
||||
case 401:
|
||||
case 403:
|
||||
case 410:
|
||||
commit('onError', { downloadInfo, errorMsg: '链接失效' })
|
||||
refreshUrl.call(_this, commit, downloadInfo, rootState.setting.download.isUseOtherSource)
|
||||
break
|
||||
default:
|
||||
dls[downloadInfo.key].start()
|
||||
commit('setStatusText', { downloadInfo, text: '正在重试' })
|
||||
break
|
||||
}
|
||||
},
|
||||
onStart() {
|
||||
commit('onStart', downloadInfo)
|
||||
console.log('on start')
|
||||
},
|
||||
onProgress(status) {
|
||||
commit('onProgress', { downloadInfo, status })
|
||||
console.log(status)
|
||||
},
|
||||
onStop() {
|
||||
commit('pauseTask', downloadInfo)
|
||||
dispatch('startTask')
|
||||
},
|
||||
}
|
||||
commit('setStatusText', { downloadInfo, text: '获取URL中...' })
|
||||
let p = options.url
|
||||
? Promise.resolve()
|
||||
: getMusicUrl.call(this, downloadInfo, rootState.setting.download.isUseOtherSource).then(url => {
|
||||
commit('updateUrl', { downloadInfo, url })
|
||||
if (!url) return Promise.reject(new Error('获取URL失败'))
|
||||
options.url = url
|
||||
})
|
||||
p.then(() => {
|
||||
tryNum[downloadInfo.key] = 0
|
||||
dls[downloadInfo.key] = download(options)
|
||||
}).catch(err => {
|
||||
// console.log(err.message)
|
||||
commit('onError', { downloadInfo, errorMsg: err.message })
|
||||
commit('setStatusText', { downloadInfo, text: err.message })
|
||||
dispatch('startTask')
|
||||
})
|
||||
},
|
||||
async removeTask({ commit, state, dispatch }, item) {
|
||||
if (dls[item.key]) {
|
||||
if (item.status == downloadStatus.RUN) {
|
||||
try {
|
||||
await dls[item.key].stop()
|
||||
} catch (_) {}
|
||||
}
|
||||
delete dls[item.key]
|
||||
}
|
||||
commit('removeTask', item)
|
||||
// 没有未完成、已下载大于1k
|
||||
if (item.status != downloadStatus.COMPLETED && item.progress.total && item.progress.downloaded > 1024) {
|
||||
try {
|
||||
await deleteFile(item.metadata.filePath)
|
||||
} catch (_) {}
|
||||
}
|
||||
switch (item.status) {
|
||||
case downloadStatus.RUN:
|
||||
case downloadStatus.WAITING:
|
||||
await dispatch('startTask')
|
||||
}
|
||||
},
|
||||
removeTasks({ rootState, commit, dispatch }, list) {
|
||||
for (const item of list) {
|
||||
if (dls[item.key]) {
|
||||
if (item.status == downloadStatus.RUN) {
|
||||
dls[item.key].stop().finally(() => {
|
||||
delete dls[item.key]
|
||||
})
|
||||
} else {
|
||||
delete dls[item.key]
|
||||
}
|
||||
}
|
||||
// 没有未完成、已下载大于1k
|
||||
if (item.status != downloadStatus.COMPLETED && item.progress.total && item.progress.downloaded > 1024) {
|
||||
deleteFile(item.metadata.filePath).catch(_ => _)
|
||||
}
|
||||
}
|
||||
commit('removeTasks', list)
|
||||
let result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
while (result) {
|
||||
dispatch('startTask', result)
|
||||
result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
}
|
||||
},
|
||||
async startTask({ state, rootState, commit, dispatch }, downloadInfo) {
|
||||
// 检查是否可以开始任务
|
||||
if (downloadInfo && !downloadInfo.isComplate && downloadInfo.status != downloadStatus.RUN) {
|
||||
const result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
if (result === false) {
|
||||
commit('setStatus', { downloadInfo, status: downloadStatus.WAITING })
|
||||
return
|
||||
}
|
||||
} else {
|
||||
const result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
if (!result) return
|
||||
downloadInfo = result
|
||||
}
|
||||
commit('setStatus', { downloadInfo, status: downloadStatus.RUN })
|
||||
|
||||
let dl = dls[downloadInfo.key]
|
||||
if (dl) {
|
||||
commit('updateFilePath', {
|
||||
downloadInfo,
|
||||
filePath: path.join(rootState.setting.download.savePath, downloadInfo.metadata.fileName),
|
||||
})
|
||||
dl.updateSaveInfo(rootState.setting.download.savePath, downloadInfo.metadata.fileName)
|
||||
if (tryNum[downloadInfo.key]) tryNum[downloadInfo.key] = 0
|
||||
try {
|
||||
await dl.start()
|
||||
} catch (error) {
|
||||
commit('onError', { downloadInfo, errorMsg: error.message })
|
||||
commit('setStatusText', error.message)
|
||||
await dispatch('startTask')
|
||||
}
|
||||
} else {
|
||||
await dispatch('handleStartTask', downloadInfo)
|
||||
}
|
||||
},
|
||||
startTasks({ commit, rootState, dispatch }, list) {
|
||||
list = list.filter(item => !(item.isComplate || item.status == downloadStatus.RUN || item.status == downloadStatus.WAITING))
|
||||
commit('setStatus', { list, status: downloadStatus.WAITING })
|
||||
let result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
while (result) {
|
||||
dispatch('startTask', result)
|
||||
result = getStartTask(downloadList, downloadStatus, rootState.setting.download.maxDownloadNum)
|
||||
}
|
||||
},
|
||||
async pauseTask({ commit }, item) {
|
||||
if (item.isComplate) return
|
||||
let dl = dls[item.key]
|
||||
if (dl) dl.stop()
|
||||
commit('setStatus', { downloadInfo: item, status: downloadStatus.PAUSE })
|
||||
},
|
||||
pauseTasks({ commit, rootState, dispatch }, list) {
|
||||
const waitingTasks = list.filter(item => item.status == downloadStatus.WAITING)
|
||||
commit('setStatus', { list: waitingTasks, status: downloadStatus.PAUSE })
|
||||
const runningTasks = list.filter(item => item.status == downloadStatus.RUN)
|
||||
for (const item of runningTasks) {
|
||||
if (item.isComplate) return
|
||||
let dl = dls[item.key]
|
||||
if (dl) dl.stop()
|
||||
}
|
||||
commit('setStatus', { list: runningTasks, status: downloadStatus.PAUSE })
|
||||
},
|
||||
}
|
||||
|
||||
// mitations
|
||||
const mutations = {
|
||||
addTask(state, { downloadInfo, addMusicLocationType }) {
|
||||
downloadListMap.set(downloadInfo.key, downloadInfo)
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
downloadList.unshift(downloadInfo)
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
downloadList.push(downloadInfo)
|
||||
break
|
||||
}
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
addTasks(state, { list, addMusicLocationType }) {
|
||||
let newList
|
||||
const map = {}
|
||||
const fullMap = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...list, ...downloadList]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.key]) continue
|
||||
ids.unshift(item.key)
|
||||
map[item.key] = item
|
||||
fullMap[item.key] = newList[i]
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...downloadList, ...list]
|
||||
newList.forEach((item, index) => {
|
||||
if (map[item.key]) return
|
||||
ids.push(item.key)
|
||||
map[item.key] = item
|
||||
fullMap[item.key] = newList[index]
|
||||
})
|
||||
break
|
||||
}
|
||||
initDownloadList(ids.map(id => map[id]))
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
removeTask(state, downloadInfo) {
|
||||
const index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
if (index < 0) return
|
||||
downloadListMap.delete(downloadInfo.key)
|
||||
downloadList.splice(index, 1)
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
removeTasks(state, list) {
|
||||
let set = new Set()
|
||||
let ids = []
|
||||
for (const item of downloadList) {
|
||||
ids.push(item.key)
|
||||
set.add(item.key)
|
||||
}
|
||||
for (const { key } of list) {
|
||||
if (set.has(key)) delete set.delete(key)
|
||||
}
|
||||
let newList = []
|
||||
for (const id of ids) {
|
||||
if (set.has(id)) {
|
||||
newList.push(downloadListMap.get(id))
|
||||
}
|
||||
}
|
||||
initDownloadList(newList)
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
pauseTask(state, downloadInfo) {
|
||||
const index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.status = downloadStatus.PAUSE
|
||||
downloadInfo.statusText = '暂停下载'
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
setStatusText(state, { downloadInfo, index, text }) { // 设置状态文本
|
||||
if (downloadInfo) {
|
||||
index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
}
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.statusText = text
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
setStatus(state, { downloadInfo, index, status, list }) { // 设置状态及状态文本
|
||||
let text
|
||||
switch (status) {
|
||||
case downloadStatus.RUN:
|
||||
text = '正在下载'
|
||||
break
|
||||
case downloadStatus.WAITING:
|
||||
text = '等待下载'
|
||||
break
|
||||
case downloadStatus.PAUSE:
|
||||
text = '暂停下载'
|
||||
break
|
||||
case downloadStatus.ERROR:
|
||||
text = '任务出错'
|
||||
break
|
||||
case downloadStatus.COMPLETED:
|
||||
text = '下载完成'
|
||||
break
|
||||
}
|
||||
if (list) {
|
||||
for (let downloadInfo of list) {
|
||||
const index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.statusText = text
|
||||
downloadInfo.status = status
|
||||
}
|
||||
} else {
|
||||
if (downloadInfo) {
|
||||
index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
}
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.statusText = text
|
||||
downloadInfo.status = status
|
||||
}
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
onCompleted(state, downloadInfo) {
|
||||
const index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
// console.log(index)
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.isComplate = true
|
||||
downloadInfo.status = downloadStatus.COMPLETED
|
||||
downloadInfo.statusText = '下载完成'
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
onError(state, { downloadInfo, errorMsg }) {
|
||||
const index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.status = downloadStatus.ERROR
|
||||
downloadInfo.statusText = errorMsg || '任务出错'
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
onStart(state, downloadInfo) {
|
||||
const index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.status = downloadStatus.RUN
|
||||
downloadInfo.statusText = '正在下载'
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
onProgress(state, { downloadInfo, status }) {
|
||||
const index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.progress.progress = status.progress
|
||||
downloadInfo.progress.downloaded = status.downloaded
|
||||
downloadInfo.progress.total = status.total
|
||||
},
|
||||
initDownloadList(state, list) {
|
||||
for (const task of list) {
|
||||
markRaw(task.metadata)
|
||||
}
|
||||
initDownloadList(list)
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
updateUrl(state, { downloadInfo, url }) {
|
||||
const index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.metadata.url = url
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
updateFilePath(state, { downloadInfo, filePath }) {
|
||||
if (downloadInfo.metadata.filePath === filePath) return
|
||||
const index = downloadList.findIndex(m => m.key == downloadInfo.key)
|
||||
if (index < 0) return
|
||||
downloadInfo = downloadList[index]
|
||||
downloadInfo.metadata.filePath = filePath
|
||||
window.eventHub.emit(eventDownloadNames.listChange)
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import music from '../../utils/music'
|
||||
const sources = []
|
||||
const sourceList = {}
|
||||
for (const source of music.sources) {
|
||||
const hotSearch = music[source.id].hotSearch
|
||||
if (!hotSearch) continue
|
||||
sources.push(source)
|
||||
sourceList[source.id] = []
|
||||
}
|
||||
|
||||
sources.push({
|
||||
id: 'all',
|
||||
name: '聚合搜索',
|
||||
})
|
||||
|
||||
// state
|
||||
const state = {
|
||||
list: {
|
||||
...sourceList,
|
||||
all: [],
|
||||
},
|
||||
currentSource: null,
|
||||
}
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
sources(state, getters, rootState, { sourceNames }) {
|
||||
return sources.map(item => ({ id: item.id, name: sourceNames[item.id] }))
|
||||
},
|
||||
list: state => state.list,
|
||||
}
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
getList({ state, commit }, source) {
|
||||
if (source == 'all') {
|
||||
let task = []
|
||||
for (const source of sources) {
|
||||
if (source.id == 'all') continue
|
||||
task.push(
|
||||
state.list[source.id].length
|
||||
? Promise.resolve({ source: source.id, list: state.list[source.id] })
|
||||
: music[source.id].hotSearch.getList().catch(() => ({ source: source.id, list: [] })),
|
||||
)
|
||||
}
|
||||
Promise.all(task).then(results => commit('setLists', results))
|
||||
} else {
|
||||
if (!music[source].hotSearch) {
|
||||
commit('setList', { source, data: null })
|
||||
return Promise.resolve()
|
||||
}
|
||||
return music[source].hotSearch.getList()
|
||||
.then(data => commit('setList', { source, data }))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// mitations
|
||||
const mutations = {
|
||||
setList(state, { source, data }) {
|
||||
state.list[source] = data ? data.list.slice(0, 20) : []
|
||||
},
|
||||
setLists(state, lists) {
|
||||
let list = new Map()
|
||||
for (const source of lists) {
|
||||
if (!state.list[source.source].length) state.list[source.source] = source.list.slice(0, 20)
|
||||
const sourceList = source.list.slice(0, 10)
|
||||
for (let item of sourceList) {
|
||||
item = item.trim()
|
||||
list.set(item, (list.has(item) ? list.get(item) : 0) + 1)
|
||||
}
|
||||
}
|
||||
list = Array.from(list)
|
||||
list.sort((a, b) => a[0].charCodeAt(0) - b[0].charCodeAt(0))
|
||||
list.sort((a, b) => b[1] - a[1])
|
||||
list = list.map(item => item[0])
|
||||
state.list.all = list
|
||||
},
|
||||
clearList(state, source) {
|
||||
state.list[source] = []
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// https://vuex.vuejs.org/en/modules.html
|
||||
|
||||
const requireModule = require.context('./', true, /\.js$/)
|
||||
const modules = {}
|
||||
|
||||
requireModule.keys().forEach(fileName => {
|
||||
if (fileName === './index.js') return
|
||||
const path = fileName.replace(/(\.\/|\.js)/g, '')
|
||||
|
||||
if (/\//.test(path)) {
|
||||
// Replace ./ and .js
|
||||
const [moduleName, imported] = path.split('/')
|
||||
|
||||
if (!modules[moduleName]) {
|
||||
modules[moduleName] = {
|
||||
namespaced: true,
|
||||
}
|
||||
}
|
||||
|
||||
modules[moduleName][imported] = requireModule(fileName).default
|
||||
} else {
|
||||
modules[path] = requireModule(fileName).default
|
||||
}
|
||||
})
|
||||
|
||||
export default modules
|
||||
@@ -1,116 +0,0 @@
|
||||
import music from '../../utils/music'
|
||||
import { markRawList } from '@renderer/utils/vueTools'
|
||||
import { deduplicationList } from '@renderer/utils'
|
||||
|
||||
const sourceList = {}
|
||||
const sources = []
|
||||
const cache = new Map()
|
||||
for (const source of music.sources) {
|
||||
const leaderboard = music[source.id].leaderboard
|
||||
if (!leaderboard || !leaderboard.getBoards) continue
|
||||
sourceList[source.id] = []
|
||||
sources.push(source)
|
||||
}
|
||||
|
||||
const listInfo = {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 30,
|
||||
key: null,
|
||||
}
|
||||
|
||||
// state
|
||||
const state = {
|
||||
boards: sourceList,
|
||||
}
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
sources(state, getters, rootState, { sourceNames }) {
|
||||
return sources.map(item => ({ id: item.id, name: sourceNames[item.id] }))
|
||||
},
|
||||
sourceIds() {
|
||||
return sources.map(item => item.id)
|
||||
},
|
||||
boards(state) {
|
||||
return state.boards
|
||||
},
|
||||
}
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
getBoardsList({ state, rootState, commit }) {
|
||||
// if (state.boards.length)
|
||||
let source = rootState.setting.leaderboard.source
|
||||
// let tabId = rootState.setting.leaderboard.tabId
|
||||
// let key = `${source}${tabId}${page}`
|
||||
// if (state.list.length && state.key == key) return true
|
||||
if (state.boards[source].length) return
|
||||
return music[source].leaderboard.getBoards().then(result => commit('setBoardsList', { boards: result, source }))
|
||||
},
|
||||
getList({ state, rootState, commit }, page) {
|
||||
// let source = rootState.setting.leaderboard.source
|
||||
let tabId = rootState.setting.leaderboard.tabId
|
||||
let [source, bangId] = tabId.split('__')
|
||||
let key = `${source}${tabId}${page}`
|
||||
if (listInfo.list.length && listInfo.key == key) return Promise.resolve(listInfo)
|
||||
// commit('clearList')
|
||||
// return (
|
||||
// cache.has(key)
|
||||
// ? Promise.resolve(cache.get(key))
|
||||
// : music[source].leaderboard.getList(bangId, page)
|
||||
// ).then(result => commit('setList', { result, key }))
|
||||
return music[source].leaderboard.getList(bangId, page).then(result => {
|
||||
result.list = deduplicationList(result.list)
|
||||
cache.set(key, result)
|
||||
listInfo.list = markRawList(result.list)
|
||||
listInfo.total = result.total
|
||||
listInfo.limit = result.limit
|
||||
listInfo.page = result.page
|
||||
listInfo.key = key
|
||||
return listInfo
|
||||
})
|
||||
},
|
||||
getListAll({ state, rootState }, { id, isRefresh = false }) {
|
||||
// console.log(source, id)
|
||||
let [source, bangId] = id.split('__')
|
||||
const loadData = (id, page) => {
|
||||
let key = `${source}${id}${page}`
|
||||
if (isRefresh && cache.has(key)) cache.delete(key)
|
||||
return cache.has(key)
|
||||
? Promise.resolve(cache.get(key))
|
||||
: music[source].leaderboard.getList(bangId, page).then(result => {
|
||||
result.list = markRawList(deduplicationList(result.list))
|
||||
cache.set(key, result)
|
||||
return result
|
||||
})
|
||||
}
|
||||
return loadData(id, 1).then(result => {
|
||||
if (result.total <= result.limit) return result.list
|
||||
|
||||
let maxPage = Math.ceil(result.total / result.limit)
|
||||
const load = (loadPage = 2) => {
|
||||
return loadPage == maxPage
|
||||
? loadData(id, loadPage).then(result => result.list)
|
||||
: loadData(id, loadPage).then(result1 => load(++loadPage).then(result2 => [...result1.list, ...result2]))
|
||||
}
|
||||
return load().then(result2 => [...result.list, ...result2])
|
||||
}).then(list => deduplicationList(list))
|
||||
},
|
||||
}
|
||||
|
||||
// mitations
|
||||
const mutations = {
|
||||
setBoardsList(state, { boards, source }) {
|
||||
state.boards[source] = boards.list
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
import musicSdk from '../../utils/music'
|
||||
import { clearLyric, clearMusicUrl } from '../../utils'
|
||||
import { sync as eventSyncName, list as eventListNames } from '@renderer/event/names'
|
||||
import { removeListPosition, setListPrevSelectId, removeListUpdateInfo, getListPositionAll, setListPositionAll, getListUpdateInfo, setListUpdateInfo } from '@renderer/utils/data'
|
||||
import { markRawList, toRaw, markRaw } from '@renderer/utils/vueTools'
|
||||
import { allList, allListInit, setInited, removeUserList, addUserList, updateList, defaultList, loveList, userLists } from '@renderer/core/share/list'
|
||||
|
||||
// import { allListInit } from '@renderer/core/share/list'
|
||||
|
||||
// state
|
||||
const state = window.lists = {
|
||||
// isInitedList: false,
|
||||
}
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
// isInitedList: state => state.isInitedList,
|
||||
// defaultList: state => state.defaultList || {},
|
||||
// loveList: state => state.loveList || {},
|
||||
// userList: state => state.userList,
|
||||
// allList: () => allList,
|
||||
}
|
||||
|
||||
const getOtherSourcePromises = new Map()
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
getOtherSource({ state, commit }, musicInfo) {
|
||||
if (musicInfo.otherSource?.length) return Promise.resolve(musicInfo.otherSource)
|
||||
let key = `${musicInfo.source}_${musicInfo.songmid}`
|
||||
if (getOtherSourcePromises.has(key)) return getOtherSourcePromises.get(key)
|
||||
const promise = musicSdk.findMusic(musicInfo).then(otherSource => {
|
||||
commit('setOtherSource', { musicInfo, otherSource })
|
||||
return otherSource
|
||||
}).finally(() => {
|
||||
if (getOtherSourcePromises.has(key)) getOtherSourcePromises.delete(key)
|
||||
})
|
||||
getOtherSourcePromises.set(key, promise)
|
||||
return promise
|
||||
},
|
||||
}
|
||||
|
||||
const updateListMetaData = () => {
|
||||
const listUpdateInfos = getListUpdateInfo()
|
||||
const newListUpdateInfos = {}
|
||||
|
||||
const listPositions = getListPositionAll()
|
||||
const newListPositions = {}
|
||||
|
||||
for (const list of [defaultList, loveList, ...userLists]) {
|
||||
if (listPositions[list.id] != null) {
|
||||
newListPositions[list.id] = listPositions[list.id]
|
||||
}
|
||||
if (listUpdateInfos[list.id] != null) {
|
||||
newListUpdateInfos[list.id] = listUpdateInfos[list.id]
|
||||
}
|
||||
}
|
||||
setListPositionAll(newListPositions)
|
||||
setListUpdateInfo(newListUpdateInfos)
|
||||
}
|
||||
|
||||
|
||||
// mitations
|
||||
const mutations = {
|
||||
initList(state, { defaultList, loveList, userList, tempList }) {
|
||||
if (defaultList != null) {
|
||||
markRawList(defaultList.list)
|
||||
}
|
||||
if (loveList != null) {
|
||||
markRawList(loveList.list)
|
||||
}
|
||||
if (tempList != null) {
|
||||
markRawList(tempList.list)
|
||||
}
|
||||
if (userList != null) {
|
||||
for (const list of userList) if (list.list) markRawList(list.list)
|
||||
}
|
||||
allListInit({ defaultList, loveList, tempList, userList })
|
||||
window.eventHub.emit(eventListNames.listChange, [defaultList.id, loveList.id, tempList.id, ...userList.map(l => l.id)])
|
||||
// state.isInitedList = true
|
||||
setInited()
|
||||
|
||||
updateListMetaData()
|
||||
|
||||
// if (!isSync) {
|
||||
// window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
// action: 'init_list',
|
||||
// data: { defaultList, loveList, userList },
|
||||
// })
|
||||
// }
|
||||
},
|
||||
setSyncListData(state, { defaultList, loveList, userList }) {
|
||||
if (defaultList != null) markRawList(defaultList.list)
|
||||
if (loveList != null) markRawList(loveList.list)
|
||||
if (userList != null) for (const list of userList) markRawList(list.list)
|
||||
allListInit({ defaultList, loveList, userList })
|
||||
window.eventHub.emit(eventListNames.listChange, [defaultList.id, loveList.id, ...userList.map(l => l.id)])
|
||||
},
|
||||
setList(state, { id, list, name, source, sourceListId, isSync }) {
|
||||
markRawList(list)
|
||||
const targetList = id == defaultList.id
|
||||
? defaultList
|
||||
: id == loveList.id
|
||||
? loveList
|
||||
: userLists.find(l => l.id == id)
|
||||
if (targetList) {
|
||||
if (name && targetList.name === name) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'set_list',
|
||||
data: { id, list: toRaw(list), name, source, sourceListId },
|
||||
})
|
||||
}
|
||||
updateList({
|
||||
id,
|
||||
list,
|
||||
name,
|
||||
source,
|
||||
sourceListId,
|
||||
})
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
return
|
||||
}
|
||||
|
||||
id += '_' + Math.random()
|
||||
}
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'set_list',
|
||||
data: { id, list: toRaw(list), name, source, sourceListId },
|
||||
})
|
||||
}
|
||||
let newList = {
|
||||
name,
|
||||
id,
|
||||
list,
|
||||
source,
|
||||
sourceListId,
|
||||
}
|
||||
addUserList(newList)
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
},
|
||||
listAdd(state, { id, musicInfo, addMusicLocationType, isSync }) {
|
||||
if (!addMusicLocationType) addMusicLocationType = this.state.setting.list.addMusicLocationType
|
||||
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'list_add',
|
||||
data: { id, musicInfo: toRaw(musicInfo), addMusicLocationType },
|
||||
})
|
||||
}
|
||||
|
||||
const targetList = allList[id]
|
||||
markRaw(musicInfo)
|
||||
if (!targetList) return
|
||||
if (targetList.some(s => s.songmid === musicInfo.songmid)) return
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
targetList.unshift(musicInfo)
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
targetList.push(musicInfo)
|
||||
break
|
||||
}
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
},
|
||||
listMove(state, { fromId, musicInfo, toId, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'list_move',
|
||||
data: { fromId, musicInfo: toRaw(musicInfo), toId },
|
||||
})
|
||||
}
|
||||
|
||||
const fromList = allList[fromId]
|
||||
const toList = allList[toId]
|
||||
if (!fromList || !toList) return
|
||||
fromList.splice(fromList.findIndex(s => s.songmid === musicInfo.songmid), 1)
|
||||
let index = toList.findIndex(s => s.songmid === musicInfo.songmid)
|
||||
if (index < 0) {
|
||||
switch (this.state.setting.list.addMusicLocationType) {
|
||||
case 'top':
|
||||
toList.unshift(musicInfo)
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
toList.push(musicInfo)
|
||||
break
|
||||
}
|
||||
}
|
||||
window.eventHub.emit(eventListNames.listChange, [fromId, toId])
|
||||
},
|
||||
listAddMultiple(state, { id, list, addMusicLocationType, isSync }) {
|
||||
if (!addMusicLocationType) addMusicLocationType = this.state.setting.list.addMusicLocationType
|
||||
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'list_add_multiple',
|
||||
data: { id, list: toRaw(list), addMusicLocationType },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[id]
|
||||
if (!targetList) return
|
||||
markRawList(list)
|
||||
let newList
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...list, ...targetList]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid]) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...targetList, ...list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid]) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
targetList.splice(0, targetList.length, ...ids.map(id => map[id]))
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
},
|
||||
// { fromId, toId, list }
|
||||
listMoveMultiple(state, { fromId, toId, list, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'list_move_multiple',
|
||||
data: { fromId, toId, list: toRaw(list) },
|
||||
})
|
||||
}
|
||||
|
||||
// console.log(state.commit)
|
||||
this.commit('list/listRemoveMultiple', { listId: fromId, ids: list.map(s => s.songmid), isSync: true })
|
||||
this.commit('list/listAddMultiple', { id: toId, list, isSync: true })
|
||||
},
|
||||
listRemove(state, { listId, id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'list_remove',
|
||||
data: { listId, id },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[listId]
|
||||
if (!targetList) return
|
||||
const index = targetList.findIndex(item => item.songmid == id)
|
||||
if (index < 0) return
|
||||
targetList.splice(index, 1)
|
||||
window.eventHub.emit(eventListNames.listChange, [listId])
|
||||
},
|
||||
listRemoveMultiple(state, { listId, ids: musicIds, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'list_remove_multiple',
|
||||
data: { listId, ids: musicIds },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[listId]
|
||||
if (!targetList) return
|
||||
let map = {}
|
||||
let ids = []
|
||||
for (const item of targetList) {
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
for (const songmid of musicIds) {
|
||||
if (map[songmid]) delete map[songmid]
|
||||
}
|
||||
let newList = []
|
||||
for (const id of ids) if (map[id]) newList.push(map[id])
|
||||
|
||||
targetList.splice(0, targetList.length, ...newList)
|
||||
window.eventHub.emit(eventListNames.listChange, [listId])
|
||||
},
|
||||
listClear(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'list_clear',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[id]
|
||||
if (!targetList) return
|
||||
targetList.splice(0, targetList.length)
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
},
|
||||
updateMusicInfo(state, { listId, id, data, musicInfo, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'update_music_info',
|
||||
data: { listId, id, data, musicInfo: toRaw(musicInfo) },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[listId]
|
||||
if (!targetList) {
|
||||
if (musicInfo) Object.assign(musicInfo, data)
|
||||
return
|
||||
}
|
||||
const targetMusicInfo = targetList.find(item => item.songmid == id)
|
||||
if (!targetMusicInfo) return
|
||||
Object.assign(targetMusicInfo, data)
|
||||
const targetListInfo = [defaultList, loveList, ...userLists].find(l => l.id == listId)
|
||||
if (!targetListInfo) return
|
||||
window.eventHub.emit(eventListNames.musicInfoChange, targetListInfo)
|
||||
},
|
||||
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, position, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'create_user_list',
|
||||
data: { name, id, list: toRaw(list), source, sourceListId, position },
|
||||
})
|
||||
}
|
||||
|
||||
markRawList(list)
|
||||
let newList = userLists.find(item => item.id === id)
|
||||
if (!newList) {
|
||||
newList = {
|
||||
name,
|
||||
id,
|
||||
list: [],
|
||||
source,
|
||||
position,
|
||||
sourceListId,
|
||||
}
|
||||
if (position) newList.locationUpdateTime = Date.now()
|
||||
addUserList(newList)
|
||||
}
|
||||
this.commit('list/listAddMultiple', { id, list, isSync: true })
|
||||
},
|
||||
removeUserList(state, { id, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'remove_user_list',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
const index = userLists.findIndex(l => l.id === id)
|
||||
if (index < 0) return
|
||||
removeUserList(id)
|
||||
removeListPosition(id)
|
||||
removeListUpdateInfo(id)
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
},
|
||||
setUserListName(state, { id, name, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'set_user_list_name',
|
||||
data: { id, name },
|
||||
})
|
||||
}
|
||||
|
||||
let list = userLists.find(l => l.id === id)
|
||||
if (!list) return
|
||||
list.name = name
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
},
|
||||
setUserListPosition(state, { id, position, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'set_user_list_position',
|
||||
data: { id, position },
|
||||
})
|
||||
}
|
||||
const index = userLists.findIndex(l => l.id == id)
|
||||
if (index < 0) return
|
||||
let targetList = userLists[index]
|
||||
userLists.splice(index, 1)
|
||||
userLists.splice(Math.max(Math.min(position, userLists.length), 0), 0, targetList)
|
||||
targetList.locationUpdateTime = Date.now()
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
},
|
||||
setMusicPosition(state, { id, position, list, isSync }) {
|
||||
if (!isSync) {
|
||||
window.eventHub.emit(eventSyncName.send_action_list, {
|
||||
action: 'set_music_position',
|
||||
data: { id, position, list: toRaw(list) },
|
||||
})
|
||||
}
|
||||
|
||||
let targetList = allList[id]
|
||||
this.commit('list/listRemoveMultiple', { listId: id, ids: list.map(m => m.songmid), isSync: true })
|
||||
|
||||
targetList.splice(position - 1, 0, ...list)
|
||||
window.eventHub.emit(eventListNames.listChange, [id])
|
||||
},
|
||||
clearCache() {
|
||||
const lists = Object.values(allList)
|
||||
for (const list of lists) {
|
||||
for (const item of list) {
|
||||
if (item.otherSource) item.otherSource = null
|
||||
if (item.typeUrl['128k']) delete item.typeUrl['128k']
|
||||
if (item.typeUrl['320k']) delete item.typeUrl['320k']
|
||||
if (item.typeUrl.flac) delete item.typeUrl.flac
|
||||
if (item.typeUrl.wav) delete item.typeUrl.wav
|
||||
|
||||
// v1.8.2以前的Lyric
|
||||
if (item.lxlrc) delete item.lxlrc
|
||||
if (item.lrc) delete item.lrc
|
||||
if (item.tlrc) delete item.tlrc
|
||||
}
|
||||
}
|
||||
clearMusicUrl()
|
||||
clearLyric()
|
||||
window.eventHub.emit(eventListNames.listChange, Object.keys(allList))
|
||||
},
|
||||
setOtherSource(state, { musicInfo, otherSource }) {
|
||||
musicInfo.otherSource = otherSource
|
||||
},
|
||||
setPrevSelectListId(state, val) {
|
||||
setListPrevSelectId(val)
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
||||
@@ -1,510 +0,0 @@
|
||||
import path from 'path'
|
||||
import music from '../../utils/music'
|
||||
import {
|
||||
getRandom,
|
||||
checkPath,
|
||||
getLyric as getStoreLyric,
|
||||
getLyricRaw as getStoreLyricRaw,
|
||||
setLyric,
|
||||
setMusicUrl,
|
||||
getMusicUrl as getStoreMusicUrl,
|
||||
assertApiSupport,
|
||||
} from '@renderer/utils'
|
||||
import { player as eventPlayerNames, list as eventListNames } from '@renderer/event/names'
|
||||
import {
|
||||
setPlayList,
|
||||
setPlayMusicInfo,
|
||||
playedList,
|
||||
addPlayedList,
|
||||
removePlayedList,
|
||||
clearPlayedList,
|
||||
tempPlayList,
|
||||
addTempPlayList,
|
||||
removeTempPlayList,
|
||||
clearTempPlayeList,
|
||||
playInfo,
|
||||
playMusicInfo,
|
||||
} from '@renderer/core/share/player'
|
||||
import { tempList, updateList } from '@renderer/core/share/list'
|
||||
import { getList } from '@renderer/core/share/utils'
|
||||
|
||||
// state
|
||||
const state = {
|
||||
}
|
||||
|
||||
const playMusic = () => {
|
||||
if (global.isPlayedStop) global.isPlayedStop = false
|
||||
window.eventHub.emit(eventPlayerNames.playMusic)
|
||||
}
|
||||
|
||||
const filterList = async({ playedList, listInfo, savePath, commit, isCheckFile }) => {
|
||||
// if (this.list.listName === null) return
|
||||
// console.log(isCheckFile)
|
||||
if (isCheckFile) {
|
||||
let list
|
||||
let canPlayList = []
|
||||
const filteredPlayedList = playedList.filter(({ listId, isTempPlay }) => listInfo.id === listId && !isTempPlay).map(({ musicInfo }) => musicInfo)
|
||||
if (listInfo.id == 'download') {
|
||||
list = []
|
||||
for (const item of listInfo.list) {
|
||||
const filePath = path.join(savePath, item.metadata.fileName)
|
||||
if (!await checkPath(filePath) || !item.isComplate || /\.ape$/.test(filePath)) continue
|
||||
|
||||
canPlayList.push(item)
|
||||
|
||||
// 排除已播放音乐
|
||||
let index = filteredPlayedList.findIndex(m => m.songmid == item.songmid)
|
||||
if (index > -1) {
|
||||
filteredPlayedList.splice(index, 1)
|
||||
continue
|
||||
}
|
||||
list.push(item)
|
||||
}
|
||||
} else {
|
||||
list = listInfo.list.filter(s => {
|
||||
// if (!assertApiSupport(s.source)) return false
|
||||
canPlayList.push(s)
|
||||
|
||||
let index = filteredPlayedList.findIndex(m => m.songmid == s.songmid)
|
||||
if (index > -1) {
|
||||
filteredPlayedList.splice(index, 1)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
if (!list.length && playedList.length) {
|
||||
commit('clearPlayedList')
|
||||
return canPlayList
|
||||
}
|
||||
return list
|
||||
} else {
|
||||
let canPlayList = []
|
||||
const filteredPlayedList = playedList.filter(({ listId, isTempPlay }) => listInfo.id === listId && !isTempPlay).map(({ musicInfo }) => musicInfo)
|
||||
|
||||
const list = listInfo.list.filter(s => {
|
||||
// if (!assertApiSupport(s.source)) return false
|
||||
canPlayList.push(s)
|
||||
|
||||
let index = filteredPlayedList.findIndex(m => m.songmid == s.songmid)
|
||||
if (index > -1) {
|
||||
filteredPlayedList.splice(index, 1)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if (!list.length && playedList.length) {
|
||||
commit('clearPlayedList')
|
||||
return canPlayList
|
||||
}
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
const getMusicUrl = function(musicInfo, type, onToggleSource, retryedSource = [], originMusic) {
|
||||
// console.log(musicInfo.source)
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = music[musicInfo.source].getMusicUrl(musicInfo, type).promise
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.catch(err => {
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
onToggleSource()
|
||||
return this.dispatch('list/getOtherSource', originMusic).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source) || !assertApiSupport(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return getMusicUrl.call(this, item, type, onToggleSource, retryedSource, originMusic)
|
||||
}
|
||||
}
|
||||
return Promise.reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getPic = function(musicInfo, retryedSource = [], originMusic) {
|
||||
// console.log(musicInfo.source)
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = music[musicInfo.source].getPic(musicInfo).promise
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.catch(err => {
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
return this.dispatch('list/getOtherSource', originMusic).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return getPic.call(this, item, retryedSource, originMusic)
|
||||
}
|
||||
}
|
||||
return Promise.reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||
const getLyric = function(musicInfo, retryedSource = [], originMusic) {
|
||||
if (!originMusic) originMusic = musicInfo
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = music[musicInfo.source].getLyric(musicInfo).promise
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.then(lyricInfo => {
|
||||
return existTimeExp.test(lyricInfo.lyric) ? lyricInfo : Promise.reject(new Error('failed'))
|
||||
}).catch(err => {
|
||||
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
|
||||
return this.dispatch('list/getOtherSource', originMusic).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
for (const item of otherSource) {
|
||||
if (retryedSource.includes(item.source)) continue
|
||||
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
|
||||
return getLyric.call(this, item, retryedSource, originMusic)
|
||||
}
|
||||
}
|
||||
return Promise.reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const buildLyricInfo = async(lyricInfo, musicInfo) => {
|
||||
const lyricRawInfo = await getStoreLyricRaw(musicInfo)
|
||||
return {
|
||||
...lyricInfo,
|
||||
rawInfo: lyricRawInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
|
||||
}
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
async getUrl({ commit, state }, { musicInfo, type, isRefresh, onToggleSource = () => {} }) {
|
||||
// if (!musicInfo._types[type]) {
|
||||
// // 兼容旧版酷我源搜索列表过滤128k音质的bug
|
||||
// if (!(musicInfo.source == 'kw' && type == '128k')) throw new Error('该歌曲没有可播放的音频')
|
||||
|
||||
// // return Promise.reject(new Error('该歌曲没有可播放的音频'))
|
||||
// }
|
||||
const cachedUrl = await getStoreMusicUrl(musicInfo, type)
|
||||
if (cachedUrl && !isRefresh) return cachedUrl
|
||||
|
||||
return getMusicUrl.call(this, musicInfo, type, onToggleSource).then(({ url }) => {
|
||||
commit('setUrl', { musicInfo, url, type })
|
||||
return url
|
||||
}).catch(err => {
|
||||
return Promise.reject(err)
|
||||
})
|
||||
},
|
||||
getPic({ commit, state }, { musicInfo, listId }) {
|
||||
// if (picRequest && picRequest.cancelHttp) picRequest.cancelHttp()
|
||||
// picRequest = music[musicInfo.source].getPic(musicInfo)
|
||||
return getPic.call(this, musicInfo).then(url => {
|
||||
// picRequest = null
|
||||
commit('setPic', { musicInfo, url, listId })
|
||||
}).catch(err => {
|
||||
// picRequest = null
|
||||
return Promise.reject(err)
|
||||
})
|
||||
},
|
||||
async getLrc({ commit, state }, musicInfo) {
|
||||
let lrcInfo = await getStoreLyric(musicInfo)
|
||||
// lrcInfo = {}
|
||||
// if (lrcRequest && lrcRequest.cancelHttp) lrcRequest.cancelHttp()
|
||||
if (existTimeExp.test(lrcInfo.lyric) && lrcInfo.tlyric != null) {
|
||||
// if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
|
||||
// let str = musicInfo.lrc.replace('\ufeff[id:$00000000]\n', '')
|
||||
// commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
|
||||
// } else if (musicInfo.lrc.startsWith('[id:$00000000]')) {
|
||||
// let str = musicInfo.lrc.replace('[id:$00000000]\n', '')
|
||||
// commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
|
||||
// }
|
||||
|
||||
if (lrcInfo.lxlyric == null) {
|
||||
switch (musicInfo.source) {
|
||||
case 'kg':
|
||||
case 'kw':
|
||||
case 'mg':
|
||||
break
|
||||
default:
|
||||
return buildLyricInfo(lrcInfo, musicInfo)
|
||||
}
|
||||
} else if (lrcInfo.rlyric == null) {
|
||||
if (musicInfo.source != 'wy') return buildLyricInfo(lrcInfo, musicInfo)
|
||||
} else return buildLyricInfo(lrcInfo, musicInfo)
|
||||
}
|
||||
|
||||
// lrcRequest = music[musicInfo.source].getLyric(musicInfo)
|
||||
return getLyric.call(this, musicInfo).then(({ lyric, tlyric, rlyric, lxlyric }) => {
|
||||
// lrcRequest = null
|
||||
commit('setLrc', { musicInfo, lyric, tlyric, rlyric, lxlyric })
|
||||
return buildLyricInfo({ lyric, tlyric, rlyric, lxlyric }, musicInfo)
|
||||
}).catch(err => {
|
||||
// lrcRequest = null
|
||||
return Promise.reject(err)
|
||||
})
|
||||
},
|
||||
|
||||
async playPrev({ state, rootState, commit, getters }, { findNum = 0, excludeList = [] } = {}) {
|
||||
const currentListId = playInfo.playListId
|
||||
const currentList = getList(currentListId)
|
||||
if (playedList.length) {
|
||||
let currentSongmid
|
||||
if (playMusicInfo.isTempPlay) {
|
||||
const musicInfo = currentList[playInfo.listPlayIndex]
|
||||
if (musicInfo) currentSongmid = musicInfo.songmid
|
||||
} else {
|
||||
currentSongmid = playMusicInfo.musicInfo.songmid
|
||||
}
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = playedList.findIndex(m => m.musicInfo.songmid === currentSongmid) - 1; index > -1; index--) {
|
||||
const playMusicInfo = playedList[index]
|
||||
const currentSongmid = playMusicInfo.musicInfo.songmid
|
||||
if (playMusicInfo.listId == currentListId && !currentList.some(m => m.songmid === currentSongmid)) {
|
||||
commit('removePlayedList', index)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (index > -1) {
|
||||
commit('setPlayMusicInfo', playedList[index])
|
||||
playMusic()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const isCheckFile = findNum > 2
|
||||
let filteredList = await filterList({
|
||||
listInfo: { id: currentListId, list: currentList },
|
||||
playedList: excludeList.length ? [...playedList, ...excludeList] : playedList,
|
||||
savePath: rootState.setting.download.savePath,
|
||||
commit,
|
||||
isCheckFile,
|
||||
})
|
||||
if (!filteredList.length) return commit('setPlayMusicInfo', { listId: null, musicInfo: null })
|
||||
|
||||
let currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
|
||||
if (currentIndex == -1 && filteredList.length) currentIndex = 0
|
||||
let nextIndex = currentIndex
|
||||
if (!playMusicInfo.isTempPlay) {
|
||||
switch (rootState.setting.player.togglePlayMethod) {
|
||||
case 'random':
|
||||
nextIndex = getRandom(0, filteredList.length)
|
||||
break
|
||||
case 'listLoop':
|
||||
case 'list':
|
||||
nextIndex = currentIndex === 0 ? filteredList.length - 1 : currentIndex - 1
|
||||
break
|
||||
case 'singleLoop':
|
||||
break
|
||||
default:
|
||||
nextIndex = -1
|
||||
return
|
||||
}
|
||||
if (nextIndex < 0) return
|
||||
}
|
||||
|
||||
const nextPlayMusicInfo = {
|
||||
musicInfo: filteredList[nextIndex],
|
||||
listId: currentListId,
|
||||
}
|
||||
if (currentListId == 'download' && !isCheckFile) {
|
||||
if (!await checkPath(path.join(rootState.setting.download.savePath, nextPlayMusicInfo.musicInfo.metadata.fileName)) || !nextPlayMusicInfo.musicInfo.isComplate || /\.ape$/.test(nextPlayMusicInfo.musicInfo.metadata.fileName)) {
|
||||
excludeList.push(nextPlayMusicInfo)
|
||||
// console.log('findNum', findNum)
|
||||
return this.dispatch('player/playPrev', { findNum: findNum + 1, excludeList })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
commit('setPlayMusicInfo', nextPlayMusicInfo)
|
||||
playMusic()
|
||||
},
|
||||
async playNext({ state, rootState, commit, getters }, { findNum = 0, excludeList = [] } = {}) {
|
||||
if (tempPlayList.length) { // 如果稍后播放列表存在歌曲则直接播放改列表的歌曲
|
||||
const playMusicInfo = tempPlayList[0]
|
||||
commit('removeTempPlayList', 0)
|
||||
commit('setPlayMusicInfo', playMusicInfo)
|
||||
playMusic()
|
||||
return
|
||||
}
|
||||
|
||||
// console.log(playInfo.playListId)
|
||||
const currentListId = playInfo.playListId
|
||||
const currentList = getList(currentListId)
|
||||
|
||||
if (playedList.length) { // 移除已播放列表内不存在原列表的歌曲
|
||||
let currentSongmid
|
||||
if (playMusicInfo.isTempPlay) {
|
||||
const musicInfo = currentList[playInfo.listPlayIndex]
|
||||
if (musicInfo) currentSongmid = musicInfo.songmid
|
||||
} else {
|
||||
currentSongmid = playMusicInfo.musicInfo.songmid
|
||||
}
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = playedList.findIndex(m => m.musicInfo.songmid === currentSongmid) + 1; index < playedList.length; index++) {
|
||||
const playMusicInfo = playedList[index]
|
||||
const currentSongmid = playMusicInfo.musicInfo.songmid
|
||||
if (playMusicInfo.listId == currentListId && !currentList.some(m => m.songmid === currentSongmid)) {
|
||||
commit('removePlayedList', index)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (index < playedList.length) {
|
||||
commit('setPlayMusicInfo', playedList[index])
|
||||
playMusic()
|
||||
return
|
||||
}
|
||||
}
|
||||
const isCheckFile = findNum > 2 // 针对下载列表,如果超过两次都碰到无效歌曲,则过滤整个列表内的无效歌曲
|
||||
let filteredList = await filterList({ // 过滤已播放歌曲
|
||||
listInfo: { id: currentListId, list: currentList },
|
||||
playedList: excludeList.length ? [...playedList, ...excludeList] : playedList,
|
||||
savePath: rootState.setting.download.savePath,
|
||||
commit,
|
||||
isCheckFile,
|
||||
})
|
||||
|
||||
if (!filteredList.length) return commit('setPlayMusicInfo', { listId: null, musicInfo: null })
|
||||
let currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
|
||||
if (currentIndex == -1 && filteredList.length) currentIndex = 0
|
||||
let nextIndex = currentIndex
|
||||
|
||||
switch (rootState.setting.player.togglePlayMethod) {
|
||||
case 'listLoop':
|
||||
nextIndex = currentIndex === filteredList.length - 1 ? 0 : currentIndex + 1
|
||||
break
|
||||
case 'random':
|
||||
nextIndex = getRandom(0, filteredList.length)
|
||||
break
|
||||
case 'list':
|
||||
nextIndex = currentIndex === filteredList.length - 1 ? -1 : currentIndex + 1
|
||||
break
|
||||
case 'singleLoop':
|
||||
break
|
||||
default:
|
||||
nextIndex = -1
|
||||
return
|
||||
}
|
||||
if (nextIndex < 0) return
|
||||
|
||||
const nextPlayMusicInfo = {
|
||||
musicInfo: filteredList[nextIndex],
|
||||
listId: currentListId,
|
||||
}
|
||||
if (currentListId == 'download' && !isCheckFile) { // 针对下载列表,检查文件是否可以播放
|
||||
if (!await checkPath(path.join(rootState.setting.download.savePath, nextPlayMusicInfo.musicInfo.metadata.fileName)) || !nextPlayMusicInfo.musicInfo.isComplate || /\.ape$/.test(nextPlayMusicInfo.musicInfo.metadata.fileName)) {
|
||||
excludeList.push(nextPlayMusicInfo)
|
||||
// console.log('findNum', findNum)
|
||||
return this.dispatch('player/playNext', { findNum: findNum + 1, excludeList })
|
||||
}
|
||||
}
|
||||
|
||||
commit('setPlayMusicInfo', nextPlayMusicInfo)
|
||||
playMusic()
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// mitations
|
||||
const mutations = {
|
||||
setUrl(state, { musicInfo, type, url }) {
|
||||
setMusicUrl(musicInfo, type, url)
|
||||
},
|
||||
setPic(state, datas) {
|
||||
datas.musicInfo.img = datas.url
|
||||
this.commit('list/updateMusicInfo', {
|
||||
listId: datas.listId,
|
||||
id: datas.musicInfo.songmid,
|
||||
data: { img: datas.url },
|
||||
musicInfo: datas.musicInfo,
|
||||
})
|
||||
},
|
||||
setLrc(state, datas) {
|
||||
// datas.musicInfo.lrc = datas.lyric
|
||||
// datas.musicInfo.tlrc = datas.tlyric
|
||||
// datas.musicInfo.lxlrc = datas.lxlyric
|
||||
setLyric(datas.musicInfo, {
|
||||
lyric: datas.lyric,
|
||||
tlyric: datas.tlyric,
|
||||
rlyric: datas.rlyric,
|
||||
lxlyric: datas.lxlyric,
|
||||
})
|
||||
},
|
||||
setList(state, { listId, index }) {
|
||||
const list = getList(listId)
|
||||
const musicInfo = list[index]
|
||||
if (!musicInfo) return
|
||||
setPlayList(listId)
|
||||
setPlayMusicInfo(listId, musicInfo)
|
||||
if (playedList.length) this.commit('player/clearPlayedList')
|
||||
if (tempPlayList.length) this.commit('player/clearTempPlayeList')
|
||||
playMusic()
|
||||
},
|
||||
setTempList(state, { list, id, index }) {
|
||||
updateList({ id: tempList.id, meta: { id }, list })
|
||||
this.commit('player/setList', {
|
||||
listId: tempList.id,
|
||||
index,
|
||||
})
|
||||
window.eventHub.emit(eventListNames.listChange, [tempList.id])
|
||||
},
|
||||
updateTempList(state, { id, list }) {
|
||||
updateList({ id: tempList.id, meta: { id }, list })
|
||||
window.eventHub.emit(eventListNames.listChange, [tempList.id])
|
||||
},
|
||||
// setPlayIndex(state, index) {
|
||||
// state.playIndex = index
|
||||
// },
|
||||
setPlayedList(state, item) {
|
||||
// console.log(item)
|
||||
addPlayedList(item)
|
||||
},
|
||||
removePlayedList(state, index) {
|
||||
removePlayedList(index)
|
||||
},
|
||||
clearPlayedList(state) {
|
||||
clearPlayedList()
|
||||
},
|
||||
setTempPlayList(state, list) {
|
||||
addTempPlayList(list)
|
||||
if (!playMusicInfo.musicInfo) this.dispatch('player/playNext')
|
||||
},
|
||||
removeTempPlayList(state, index) {
|
||||
removeTempPlayList(index)
|
||||
},
|
||||
clearTempPlayeList(state) {
|
||||
clearTempPlayeList()
|
||||
},
|
||||
|
||||
setPlayMusicInfo(state, { listId, musicInfo, isTempPlay }) {
|
||||
setPlayMusicInfo(listId, musicInfo, isTempPlay)
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
import music from '../../utils/music'
|
||||
import { markRawList } from '@renderer/utils/vueTools'
|
||||
import { deduplicationList } from '@renderer/utils'
|
||||
|
||||
const sources = []
|
||||
const sourceList = {}
|
||||
const sourceMaxPage = {}
|
||||
for (const source of music.sources) {
|
||||
const musicSearch = music[source.id].musicSearch
|
||||
if (!musicSearch) continue
|
||||
sources.push(source)
|
||||
sourceList[source.id] = {
|
||||
page: 1,
|
||||
allPage: 0,
|
||||
limit: 30,
|
||||
total: 0,
|
||||
list: [],
|
||||
}
|
||||
sourceMaxPage[source.id] = 0
|
||||
}
|
||||
|
||||
// https://blog.csdn.net/xcxy2015/article/details/77164126#comments
|
||||
const similar = (a, b) => {
|
||||
if (!a || !b) return 0
|
||||
if (a.length > b.length) { // 保证 a <= b
|
||||
let t = b
|
||||
b = a
|
||||
a = t
|
||||
}
|
||||
let al = a.length
|
||||
let bl = b.length
|
||||
let mp = [] // 一个表
|
||||
let i, j, ai, lt, tmp // ai:字符串a的第i个字符。 lt:左上角的值。 tmp:暂存新的值。
|
||||
for (i = 0; i <= bl; i++) mp[i] = i
|
||||
for (i = 1; i <= al; i++) {
|
||||
ai = a.charAt(i - 1)
|
||||
lt = mp[0]
|
||||
mp[0] = mp[0] + 1
|
||||
for (j = 1; j <= bl; j++) {
|
||||
tmp = Math.min(mp[j] + 1, mp[j - 1] + 1, lt + (ai == b.charAt(j - 1) ? 0 : 1))
|
||||
lt = mp[j]
|
||||
mp[j] = tmp
|
||||
}
|
||||
}
|
||||
return 1 - (mp[bl] / bl)
|
||||
}
|
||||
|
||||
const sortInsert = (arr, data) => {
|
||||
let key = data.num
|
||||
let left = 0
|
||||
let right = arr.length - 1
|
||||
|
||||
while (left <= right) {
|
||||
let middle = parseInt((left + right) / 2)
|
||||
if (key == arr[middle]) {
|
||||
left = middle
|
||||
break
|
||||
} else if (key < arr[middle].num) {
|
||||
right = middle - 1
|
||||
} else {
|
||||
left = middle + 1
|
||||
}
|
||||
}
|
||||
while (left > 0) {
|
||||
if (arr[left - 1].num != key) break
|
||||
left--
|
||||
}
|
||||
|
||||
arr.splice(left, 0, data)
|
||||
}
|
||||
|
||||
const handleSortList = (list, keyword) => {
|
||||
let arr = []
|
||||
for (const item of list) {
|
||||
sortInsert(arr, {
|
||||
num: similar(keyword, `${item.name} ${item.singer}`),
|
||||
data: item,
|
||||
})
|
||||
}
|
||||
return arr.map(item => item.data).reverse()
|
||||
}
|
||||
|
||||
sources.push({
|
||||
id: 'all',
|
||||
name: '聚合搜索',
|
||||
})
|
||||
|
||||
// state
|
||||
const state = window.state = {
|
||||
sourceList,
|
||||
list: [],
|
||||
text: '',
|
||||
page: 1,
|
||||
limit: 30,
|
||||
allPage: 1,
|
||||
total: 0,
|
||||
sourceMaxPage,
|
||||
historyList: [],
|
||||
}
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
sources(state, getters, rootState, { sourceNames }) {
|
||||
return sources.map(item => ({ id: item.id, name: sourceNames[item.id] }))
|
||||
},
|
||||
sourceList: state => state.sourceList || [],
|
||||
searchText: state => state.text,
|
||||
historyList: state => state.historyList,
|
||||
allList: state => ({ list: state.list, allPage: state.allPage, page: state.page, total: state.total, limit: state.limit, sourceMaxPage: state.sourceMaxPage }),
|
||||
}
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
search({ commit, rootState }, { text, page, limit }) {
|
||||
commit('setText', text)
|
||||
commit('addHistory', text)
|
||||
if (rootState.setting.search.searchSource == 'all') {
|
||||
let task = []
|
||||
for (const source of sources) {
|
||||
if (source.id == 'all') continue
|
||||
task.push(music[source.id].musicSearch.search(text, page).catch(error => {
|
||||
console.log(error)
|
||||
return {
|
||||
allPage: 1,
|
||||
limit: 30,
|
||||
list: [],
|
||||
source: source.id,
|
||||
total: 0,
|
||||
}
|
||||
}))
|
||||
}
|
||||
return Promise.all(task).then(results => commit('setLists', { results, page }))
|
||||
} else {
|
||||
return music[rootState.setting.search.searchSource].musicSearch.search(text, page, limit).catch(error => {
|
||||
console.log(error)
|
||||
return {
|
||||
allPage: 1,
|
||||
limit: 30,
|
||||
list: [],
|
||||
source: rootState.setting.search.searchSource,
|
||||
total: 0,
|
||||
}
|
||||
}).then(data => commit('setList', { page, ...data }))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// mitations
|
||||
const mutations = {
|
||||
setText(state, text) {
|
||||
state.text = text
|
||||
},
|
||||
setList(state, datas) {
|
||||
if (!state.text) return
|
||||
let source = state.sourceList[datas.source]
|
||||
datas.list = deduplicationList(datas.list)
|
||||
source.list = markRawList(datas.list)
|
||||
source.total = datas.total
|
||||
source.allPage = datas.allPage
|
||||
source.page = datas.page
|
||||
source.limit = datas.limit
|
||||
},
|
||||
setLists(state, { results, page }) {
|
||||
if (!state.text) return
|
||||
let pages = []
|
||||
let total = 0
|
||||
let limit = 0
|
||||
let list = []
|
||||
for (const source of results) {
|
||||
state.sourceMaxPage[source.source] = source.allPage
|
||||
if (source.allPage < page) continue
|
||||
list.push(...markRawList(source.list))
|
||||
pages.push(source.allPage)
|
||||
total += source.total
|
||||
// limit = Math.max(source.limit, limit)
|
||||
}
|
||||
list = deduplicationList(list)
|
||||
state.allPage = Math.max(...pages)
|
||||
state.total = total
|
||||
state.limit = limit
|
||||
state.page = page
|
||||
state.list = handleSortList(list, state.text)
|
||||
},
|
||||
clearList(state) {
|
||||
for (const source of Object.keys(state.sourceList)) {
|
||||
state.sourceList[source].list = []
|
||||
state.sourceList[source].page = 0
|
||||
state.sourceList[source].allPage = 0
|
||||
state.sourceList[source].total = 0
|
||||
state.sourceMaxPage[source] = 0
|
||||
}
|
||||
state.list = []
|
||||
state.page = 0
|
||||
state.allPage = 0
|
||||
state.total = 0
|
||||
state.text = ''
|
||||
},
|
||||
addHistory(state, text) {
|
||||
let index = state.historyList.indexOf(text)
|
||||
if (index > -1) state.historyList.splice(index, 1)
|
||||
if (state.historyList.length >= 15) state.historyList = state.historyList.slice(0, 14)
|
||||
state.historyList.unshift(text)
|
||||
},
|
||||
removeHistory(state, index) {
|
||||
state.historyList.splice(index, 1)
|
||||
},
|
||||
clearHistory(state) {
|
||||
state.historyList = []
|
||||
},
|
||||
setHistory(state, list) {
|
||||
state.historyList = list
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import music from '../../utils/music'
|
||||
import { markRawList } from '@renderer/utils/vueTools'
|
||||
import { deduplicationList } from '@renderer/utils'
|
||||
|
||||
const sortList = {}
|
||||
const sources = []
|
||||
const cache = new Map()
|
||||
for (const source of music.sources) {
|
||||
const songList = music[source.id].songList
|
||||
if (!songList) continue
|
||||
sortList[source.id] = songList.sortList
|
||||
sources.push(source)
|
||||
}
|
||||
|
||||
const filterList = list => {
|
||||
const keys = new Set()
|
||||
return list.filter(item => {
|
||||
if (keys.has(item.songmid)) return false
|
||||
keys.add(item.songmid)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// state
|
||||
const state = {
|
||||
tags: {},
|
||||
list: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 30,
|
||||
key: null,
|
||||
},
|
||||
listDetail: {
|
||||
list: [],
|
||||
desc: null,
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 30,
|
||||
key: null,
|
||||
info: {},
|
||||
},
|
||||
selectListInfo: {},
|
||||
isVisibleListDetail: false,
|
||||
}
|
||||
|
||||
sources.forEach(source => {
|
||||
state.tags[source.id] = null
|
||||
})
|
||||
|
||||
|
||||
// getters
|
||||
const getters = {
|
||||
sourceInfo(state, getters, rootState, { sourceNames }) {
|
||||
return { sourceIds: sources.map(item => item.id), sources: sources.map(item => ({ id: item.id, name: sourceNames[item.id] })), sortList }
|
||||
},
|
||||
tags: state => state.tags,
|
||||
isVisibleListDetail: state => state.isVisibleListDetail,
|
||||
selectListInfo: state => state.selectListInfo,
|
||||
listData(state) {
|
||||
return state.list
|
||||
},
|
||||
listDetail(state) {
|
||||
return state.listDetail
|
||||
},
|
||||
}
|
||||
|
||||
let loadId = null
|
||||
|
||||
// actions
|
||||
const actions = {
|
||||
getTags({ state, rootState, commit }) {
|
||||
let source = rootState.setting.songList.source
|
||||
return music[source]?.songList.getTags().then(result => commit('setTags', { tags: result, source }))
|
||||
},
|
||||
getList({ state, rootState, commit }, page) {
|
||||
let source = rootState.setting.songList.source
|
||||
let tabId = rootState.setting.songList.tagInfo.id
|
||||
let sortId = rootState.setting.songList.sortId
|
||||
// console.log(sortId)
|
||||
let key = loadId = `slist__${source}__${sortId}__${tabId}__${page}`
|
||||
if (state.list.list.length && state.list.key == key) return
|
||||
if (cache.has(key)) return Promise.resolve(cache.get(key)).then(result => commit('setList', { result, key, page }))
|
||||
commit('clearList')
|
||||
return music[source]?.songList.getList(sortId, tabId, page).then(result => {
|
||||
if (loadId != key) return
|
||||
commit('setList', { result, key, page })
|
||||
})
|
||||
},
|
||||
getListDetail({ state, commit }, { id, source, page, isRefresh = false }) {
|
||||
let key = `sdetail__${source}__${id}__${page}`
|
||||
if (state.listDetail.list.length && state.listDetail.key == key) return Promise.resolve(state.listDetail.list)
|
||||
commit('clearListDetail')
|
||||
if (isRefresh && cache.has(key)) cache.delete(key)
|
||||
return (
|
||||
cache.has(key)
|
||||
? Promise.resolve(cache.get(key))
|
||||
: music[source]?.songList.getListDetail(id, page).then(result => ({ ...result, list: filterList(result.list) }))
|
||||
).then(result => {
|
||||
result.list = markRawList(deduplicationList(result.list))
|
||||
commit('setListDetail', { result, key, source, id, page })
|
||||
return result.list
|
||||
})
|
||||
},
|
||||
getListDetailAll({ state, rootState }, { source, id, isRefresh = false }) {
|
||||
// console.log(source, id)
|
||||
const loadData = (id, page) => {
|
||||
let key = `sdetail__${source}__${id}__${page}`
|
||||
if (isRefresh && cache.has(key)) cache.delete(key)
|
||||
return cache.has(key)
|
||||
? Promise.resolve(cache.get(key))
|
||||
: music[source]?.songList.getListDetail(id, page).then(result => {
|
||||
result.list = markRawList(deduplicationList(result.list))
|
||||
cache.set(key, result)
|
||||
return result
|
||||
}) ?? Promise.reject(new Error('source not found'))
|
||||
}
|
||||
return loadData(id, 1).then(result => {
|
||||
if (result.total <= result.limit) return filterList(result.list)
|
||||
|
||||
let maxPage = Math.ceil(result.total / result.limit)
|
||||
const loadDetail = (loadPage = 2) => {
|
||||
return loadPage == maxPage
|
||||
? loadData(id, loadPage).then(result => result.list)
|
||||
: loadData(id, loadPage).then(result1 => loadDetail(++loadPage).then(result2 => [...result1.list, ...result2]))
|
||||
}
|
||||
return loadDetail().then(result2 => [...result.list, ...result2]).then(list => filterList(list))
|
||||
}).then(list => deduplicationList(list))
|
||||
},
|
||||
}
|
||||
|
||||
// mitations
|
||||
const mutations = {
|
||||
setTags(state, { tags, source }) {
|
||||
state.tags[source] = tags
|
||||
},
|
||||
clearList(state) {
|
||||
state.list.list = []
|
||||
state.list.total = 0
|
||||
},
|
||||
setList(state, { result, key, page }) {
|
||||
state.list.list = markRawList(result.list)
|
||||
state.list.total = result.total
|
||||
state.list.limit = result.limit
|
||||
state.list.page = page
|
||||
state.list.key = key
|
||||
cache.set(key, result)
|
||||
},
|
||||
setListDetail(state, { result, key, source, id, page }) {
|
||||
state.listDetail.list = result.list
|
||||
state.listDetail.id = id
|
||||
state.listDetail.source = source
|
||||
state.listDetail.total = result.total
|
||||
state.listDetail.limit = result.limit
|
||||
state.listDetail.page = page
|
||||
state.listDetail.key = key
|
||||
state.listDetail.info = result.info || {
|
||||
name: state.selectListInfo.name,
|
||||
img: state.selectListInfo.img,
|
||||
desc: state.selectListInfo.desc,
|
||||
author: state.selectListInfo.author,
|
||||
play_count: state.selectListInfo.play_count,
|
||||
}
|
||||
cache.set(key, result)
|
||||
},
|
||||
setVisibleListDetail(state, bool) {
|
||||
state.isVisibleListDetail = bool
|
||||
},
|
||||
setSelectListInfo(state, info) {
|
||||
state.selectListInfo = info
|
||||
},
|
||||
clearListDetail(state) {
|
||||
state.listDetail = {
|
||||
id: null,
|
||||
source: null,
|
||||
list: [],
|
||||
desc: null,
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 30,
|
||||
key: null,
|
||||
info: {},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
export default {
|
||||
setTheme(state, val) {
|
||||
state.setting.theme.id = val
|
||||
},
|
||||
setSearchSource(state, { searchSource, tempSearchSource }) {
|
||||
console.log(searchSource, tempSearchSource)
|
||||
if (searchSource != null) state.setting.search.searchSource = searchSource
|
||||
if (tempSearchSource != null) state.setting.search.tempSearchSource = tempSearchSource
|
||||
},
|
||||
setSetting(state, val) {
|
||||
state.setting = val
|
||||
},
|
||||
setSettingVersion(state, val) {
|
||||
state.settingVersion = val
|
||||
},
|
||||
setAgreePact(state) {
|
||||
state.setting.isAgreePact = true
|
||||
},
|
||||
setLeaderboard(state, { tabId, source }) {
|
||||
if (tabId != null) state.setting.leaderboard.tabId = tabId
|
||||
if (source != null) state.setting.leaderboard.source = source
|
||||
},
|
||||
setSongList(state, { sortId, tagInfo, source }) {
|
||||
if (tagInfo != null) state.setting.songList.tagInfo = tagInfo
|
||||
if (sortId != null) state.setting.songList.sortId = sortId
|
||||
if (source != null) state.setting.songList.source = source
|
||||
},
|
||||
setIgnoreVersion(state, version) {
|
||||
state.setting.ignoreVersion = version
|
||||
},
|
||||
setVolume(state, val) {
|
||||
if (typeof val == 'boolean') {
|
||||
state.setting.player.isMute = val
|
||||
} else {
|
||||
state.setting.player.volume = val
|
||||
}
|
||||
},
|
||||
setPlayDetailLyricAlign(state, val) {
|
||||
state.setting.playDetail.style.align = val
|
||||
},
|
||||
setPlayDetailLyricFont(state, val) {
|
||||
state.setting.playDetail.style.fontSize = val
|
||||
},
|
||||
setPlayNextMode(state, val) {
|
||||
state.setting.player.togglePlayMethod = val
|
||||
},
|
||||
setVisibleDesktopLyric(state, val) {
|
||||
state.setting.desktopLyric.enable = val
|
||||
},
|
||||
setLockDesktopLyric(state, val) {
|
||||
state.setting.desktopLyric.isLock = val
|
||||
},
|
||||
setMediaDeviceId(state, val) {
|
||||
state.setting.player.mediaDeviceId = val
|
||||
},
|
||||
setDesktopLyricConfig(state, config) {
|
||||
state.setting.desktopLyric = Object.assign(state.setting.desktopLyric, config)
|
||||
},
|
||||
setApiSource(state, apiSource) {
|
||||
state.setting.apiSource = apiSource
|
||||
},
|
||||
setProxyEnable(state, val) {
|
||||
state.setting.network.proxy.enable = val
|
||||
},
|
||||
setAudioVisualization(state, val) {
|
||||
state.setting.player.audioVisualization = val
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
// import { reactive, ref, shallowRef } from '@common/utils/vueTools'
|
||||
import {
|
||||
PlayerMusicInfo,
|
||||
musicInfo,
|
||||
isPlay,
|
||||
status,
|
||||
statusText,
|
||||
isShowPlayerDetail,
|
||||
isShowPlayComment,
|
||||
isShowLrcSelectContent,
|
||||
playInfo,
|
||||
playMusicInfo,
|
||||
playedList,
|
||||
tempPlayList,
|
||||
} from './state'
|
||||
import { getListMusicsFromCache } from '@renderer/store/list/action'
|
||||
import { downloadList } from '@renderer/store/download/state'
|
||||
import { setProgress } from './playProgress'
|
||||
import { playNext } from '@renderer/core/player'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
import { toRaw } from '@common/utils/vueTools'
|
||||
|
||||
|
||||
type PlayerMusicInfoKeys = keyof typeof musicInfo
|
||||
|
||||
const musicInfoKeys: PlayerMusicInfoKeys[] = Object.keys(musicInfo) as PlayerMusicInfoKeys[]
|
||||
|
||||
export const setMusicInfo = (_musicInfo: Partial<PlayerMusicInfo>) => {
|
||||
for (const key of musicInfoKeys) {
|
||||
const val = _musicInfo[key]
|
||||
if (val !== undefined) {
|
||||
// @ts-expect-error
|
||||
musicInfo[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const setPlay = (val: boolean) => {
|
||||
isPlay.value = val
|
||||
}
|
||||
|
||||
export const setStatus = (val: string) => {
|
||||
console.log('setStatus', val)
|
||||
status.value = val
|
||||
}
|
||||
|
||||
|
||||
export const setStatusText = (val: string) => {
|
||||
statusText.value = val
|
||||
}
|
||||
|
||||
export const setAllStatus = (val: string) => {
|
||||
console.log('setAllStatus', val)
|
||||
status.value = val
|
||||
statusText.value = val
|
||||
}
|
||||
|
||||
|
||||
export const setShowPlayerDetail = (val: boolean) => {
|
||||
isShowPlayerDetail.value = val
|
||||
}
|
||||
|
||||
export const setShowPlayComment = (val: boolean) => {
|
||||
isShowPlayComment.value = val
|
||||
}
|
||||
|
||||
export const setShowPlayLrcSelectContentLrc = (val: boolean) => {
|
||||
isShowLrcSelectContent.value = val
|
||||
}
|
||||
|
||||
export const setPlayListId = (listId: string | null) => {
|
||||
playInfo.playerListId = listId
|
||||
}
|
||||
|
||||
export const getList = (listId: string | null): LX.Music.MusicInfo[] | LX.Download.ListItem[] => {
|
||||
return listId == LIST_IDS.DOWNLOAD ? downloadList : getListMusicsFromCache(listId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新播放位置
|
||||
* @returns 播放位置
|
||||
*/
|
||||
export const updatePlayIndex = () => {
|
||||
const indexInfo = getPlayIndex(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
// console.log(indexInfo)
|
||||
playInfo.playIndex = indexInfo.playIndex
|
||||
playInfo.playerPlayIndex = indexInfo.playerPlayIndex
|
||||
|
||||
return indexInfo
|
||||
}
|
||||
|
||||
export const getPlayIndex = (listId: string | null, musicInfo: LX.Download.ListItem | LX.Music.MusicInfo | null, isTempPlay: boolean): {
|
||||
playIndex: number
|
||||
playerPlayIndex: number
|
||||
} => {
|
||||
const playerList = getList(playInfo.playerListId)
|
||||
|
||||
// if (listIndex < 0) throw new Error('music info not found')
|
||||
// playInfo.playIndex = listIndex
|
||||
|
||||
let playIndex = -1
|
||||
let playerPlayIndex = -1
|
||||
if (playerList.length) {
|
||||
playerPlayIndex = Math.min(playInfo.playerPlayIndex, playerList.length - 1)
|
||||
}
|
||||
|
||||
const list = getList(listId)
|
||||
if (list.length && musicInfo) {
|
||||
const currentId = musicInfo.id
|
||||
playIndex = list.findIndex(m => m.id == currentId)
|
||||
if (!isTempPlay) {
|
||||
if (playIndex < 0) {
|
||||
playerPlayIndex = playerPlayIndex < 1 ? (list.length - 1) : (playerPlayIndex - 1)
|
||||
} else {
|
||||
playerPlayIndex = playIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
playIndex,
|
||||
playerPlayIndex,
|
||||
}
|
||||
}
|
||||
|
||||
export const resetPlayerMusicInfo = () => {
|
||||
setMusicInfo({
|
||||
id: null,
|
||||
pic: null,
|
||||
lrc: null,
|
||||
tlrc: null,
|
||||
rlrc: null,
|
||||
lxlrc: null,
|
||||
rawlrc: null,
|
||||
name: '',
|
||||
singer: '',
|
||||
album: '',
|
||||
})
|
||||
}
|
||||
|
||||
const setPlayerMusicInfo = (musicInfo: LX.Music.MusicInfo | LX.Download.ListItem | null) => {
|
||||
if (musicInfo) {
|
||||
setMusicInfo('progress' in musicInfo ? {
|
||||
id: musicInfo.id,
|
||||
pic: musicInfo.metadata.musicInfo.meta.picUrl,
|
||||
name: musicInfo.metadata.musicInfo.name,
|
||||
singer: musicInfo.metadata.musicInfo.singer,
|
||||
album: musicInfo.metadata.musicInfo.meta.albumName,
|
||||
lrc: null,
|
||||
tlrc: null,
|
||||
rlrc: null,
|
||||
lxlrc: null,
|
||||
rawlrc: null,
|
||||
} : {
|
||||
id: musicInfo.id,
|
||||
pic: musicInfo.meta.picUrl,
|
||||
name: musicInfo.name,
|
||||
singer: musicInfo.singer,
|
||||
album: musicInfo.meta.albumName,
|
||||
lrc: null,
|
||||
tlrc: null,
|
||||
rlrc: null,
|
||||
lxlrc: null,
|
||||
rawlrc: null,
|
||||
})
|
||||
} else resetPlayerMusicInfo()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前播放歌曲的信息
|
||||
* @param listId 歌曲所属的列表id
|
||||
* @param musicInfo 歌曲信息
|
||||
* @param isTempPlay 是否临时播放
|
||||
*/
|
||||
export const setPlayMusicInfo = (listId: string | null, musicInfo: LX.Download.ListItem | LX.Music.MusicInfo | null, isTempPlay: boolean = false) => {
|
||||
musicInfo = toRaw(musicInfo)
|
||||
|
||||
playMusicInfo.listId = listId
|
||||
playMusicInfo.musicInfo = musicInfo
|
||||
playMusicInfo.isTempPlay = isTempPlay
|
||||
|
||||
setPlayerMusicInfo(musicInfo)
|
||||
|
||||
setProgress(0, 0)
|
||||
|
||||
if (musicInfo == null) {
|
||||
playInfo.playIndex = -1
|
||||
playInfo.playerListId = null
|
||||
playInfo.playerPlayIndex = -1
|
||||
} else {
|
||||
const { playIndex, playerPlayIndex } = getPlayIndex(listId, musicInfo, isTempPlay)
|
||||
|
||||
playInfo.playIndex = playIndex
|
||||
playInfo.playerPlayIndex = playerPlayIndex
|
||||
window.app_event.musicToggled()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将歌曲添加到已播放列表
|
||||
* @param playMusicInfo playMusicInfo对象
|
||||
*/
|
||||
export const addPlayedList = (playMusicInfo: LX.Player.PlayMusicInfo) => {
|
||||
if (playedList.some(m => m.musicInfo === playMusicInfo.musicInfo)) return
|
||||
playedList.push(playMusicInfo)
|
||||
}
|
||||
/**
|
||||
* 将歌曲从已播放列表移除
|
||||
* @param index 歌曲位置
|
||||
*/
|
||||
export const removePlayedList = (index: number) => {
|
||||
playedList.splice(index, 1)
|
||||
}
|
||||
/**
|
||||
* 清空已播放列表
|
||||
*/
|
||||
export const clearPlayedList = () => {
|
||||
playedList.splice(0, playedList.length)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加歌曲到稍后播放列表
|
||||
* @param list 歌曲列表
|
||||
*/
|
||||
export const addTempPlayList = (list: LX.Player.TempPlayListItem[]) => {
|
||||
const topList: Array<{ listId: string, musicInfo: LX.Music.MusicInfo | LX.Download.ListItem }> = []
|
||||
const bottomList = list.filter(({ isTop, ...musicInfo }) => {
|
||||
if (isTop) {
|
||||
topList.push(musicInfo)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if (topList.length) tempPlayList.unshift(...topList.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true })))
|
||||
if (bottomList.length) tempPlayList.push(...bottomList.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true })))
|
||||
|
||||
if (!playMusicInfo.musicInfo) void playNext()
|
||||
}
|
||||
/**
|
||||
* 从稍后播放列表移除歌曲
|
||||
* @param index 歌曲位置
|
||||
*/
|
||||
export const removeTempPlayList = (index: number) => {
|
||||
tempPlayList.splice(index, 1)
|
||||
}
|
||||
/**
|
||||
* 清空稍后播放列表
|
||||
*/
|
||||
export const clearTempPlayeList = () => {
|
||||
tempPlayList.splice(0, tempPlayList.length)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { reactive } from '@common/utils/vueTools'
|
||||
|
||||
interface Line {
|
||||
text: string
|
||||
time: number
|
||||
extendedLyrics: string[]
|
||||
dom_line: HTMLDivElement
|
||||
}
|
||||
|
||||
export const lyric = reactive<{
|
||||
lines: Line[]
|
||||
text: string
|
||||
line: number
|
||||
offset: number // 歌词延迟
|
||||
tempOffset: number // 歌词临时延迟
|
||||
}>({
|
||||
lines: [],
|
||||
text: '',
|
||||
line: 0,
|
||||
offset: 0, // 歌词延迟
|
||||
tempOffset: 0, // 歌词临时延迟
|
||||
})
|
||||
|
||||
export const setLines = (lines: Line[]) => {
|
||||
if (!lines.length && !lyric.lines.length) return
|
||||
lyric.lines = lines
|
||||
}
|
||||
export const setText = (text: string, line: number) => {
|
||||
lyric.text = text
|
||||
lyric.line = line
|
||||
}
|
||||
export const setOffset = (offset: number) => {
|
||||
lyric.offset = offset
|
||||
}
|
||||
export const setTempOffset = (offset: number) => {
|
||||
lyric.tempOffset = offset
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { reactive } from '@common/utils/vueTools'
|
||||
import { formatPlayTime2 } from '@common/utils/common'
|
||||
|
||||
export const playProgress = reactive({
|
||||
nowPlayTime: 0,
|
||||
maxPlayTime: 0,
|
||||
progress: 0,
|
||||
nowPlayTimeStr: '00:00',
|
||||
maxPlayTimeStr: '00:00',
|
||||
})
|
||||
|
||||
export const setNowPlayTime = (time: number) => {
|
||||
playProgress.nowPlayTime = time
|
||||
playProgress.nowPlayTimeStr = formatPlayTime2(time)
|
||||
playProgress.progress = playProgress.maxPlayTime ? time / playProgress.maxPlayTime : 0
|
||||
}
|
||||
|
||||
export const setMaxplayTime = (time: number) => {
|
||||
playProgress.maxPlayTime = time
|
||||
playProgress.maxPlayTimeStr = formatPlayTime2(time)
|
||||
playProgress.progress = time ? playProgress.nowPlayTime / time : 0
|
||||
}
|
||||
|
||||
export const setProgress = (currentTime: number, totalTime: number) => {
|
||||
setMaxplayTime(totalTime)
|
||||
setNowPlayTime(currentTime)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { reactive, shallowReactive, ref } from '@common/utils/vueTools'
|
||||
|
||||
export interface PlayerMusicInfo {
|
||||
id: string | null
|
||||
pic: string | null
|
||||
lrc: string | null
|
||||
tlrc: string | null
|
||||
rlrc: string | null
|
||||
lxlrc: string | null
|
||||
rawlrc: string | null
|
||||
// url: string | null
|
||||
name: string
|
||||
singer: string
|
||||
album: string
|
||||
}
|
||||
|
||||
export const musicInfo = window.lxData.musicInfo = reactive<PlayerMusicInfo>({
|
||||
id: null,
|
||||
pic: null,
|
||||
lrc: null,
|
||||
tlrc: null,
|
||||
rlrc: null,
|
||||
lxlrc: null,
|
||||
rawlrc: null,
|
||||
// url: null,
|
||||
name: '',
|
||||
singer: '',
|
||||
album: '',
|
||||
})
|
||||
|
||||
export const isPlay = ref(false)
|
||||
|
||||
export const status = window.lxData.status = ref('')
|
||||
|
||||
export const statusText = ref('')
|
||||
|
||||
export const isShowPlayerDetail = ref(false)
|
||||
|
||||
export const isShowPlayComment = ref(false)
|
||||
|
||||
export const isShowLrcSelectContent = ref(false)
|
||||
|
||||
export const playMusicInfo = shallowReactive<{
|
||||
/**
|
||||
* 当前播放歌曲的列表 id
|
||||
*/
|
||||
musicInfo: LX.Player.PlayMusicInfo['musicInfo'] | null
|
||||
/**
|
||||
* 当前播放歌曲的列表 id
|
||||
*/
|
||||
listId: LX.Player.PlayMusicInfo['listId'] | null
|
||||
/**
|
||||
* 是否属于 “稍后播放”
|
||||
*/
|
||||
isTempPlay: boolean
|
||||
}>({
|
||||
listId: null,
|
||||
musicInfo: null,
|
||||
isTempPlay: false,
|
||||
})
|
||||
export const playInfo = shallowReactive<LX.Player.PlayInfo>({
|
||||
playIndex: -1,
|
||||
playerListId: null,
|
||||
playerPlayIndex: -1,
|
||||
})
|
||||
|
||||
|
||||
export const playedList = window.lxData.playedList = shallowReactive<LX.Player.PlayMusicInfo[]>([])
|
||||
|
||||
export const tempPlayList = shallowReactive<LX.Player.PlayMusicInfo[]>([])
|
||||
|
||||
window.lxData.playInfo = playInfo
|
||||
window.lxData.playMusicInfo = playMusicInfo
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
|
||||
|
||||
export const volume = ref(0)
|
||||
export const isMute = ref(false)
|
||||
|
||||
export const setVolume = (num: number) => {
|
||||
volume.value = num
|
||||
}
|
||||
|
||||
export const setMute = (flag: boolean) => {
|
||||
isMute.value = flag
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
import { throttle } from '@common/utils/common'
|
||||
import { toRaw } from '@common/utils/vueTools'
|
||||
import {
|
||||
getSearchHistoryList,
|
||||
saveSearchHistoryList,
|
||||
} from '@renderer/utils/ipc'
|
||||
import { appSetting } from '../setting'
|
||||
import { searchText, historyList } from './state'
|
||||
|
||||
|
||||
export const setSearchText = (text: string) => {
|
||||
searchText.value = text
|
||||
}
|
||||
|
||||
let isInitedSearchHistory = false
|
||||
const saveSearchHistoryListThrottle = throttle((list: LX.List.SearchHistoryList) => {
|
||||
saveSearchHistoryList(list)
|
||||
}, 500)
|
||||
|
||||
|
||||
export const getHistoryList = async() => {
|
||||
if (isInitedSearchHistory) return
|
||||
historyList.push(...(await getSearchHistoryList() ?? []))
|
||||
isInitedSearchHistory = true
|
||||
}
|
||||
export const addHistoryWord = async(word: string) => {
|
||||
if (!appSetting['search.isShowHistorySearch']) return
|
||||
if (!isInitedSearchHistory) await getHistoryList()
|
||||
let index = historyList.indexOf(word)
|
||||
if (index > -1) historyList.splice(index, 1)
|
||||
if (historyList.length >= 15) historyList.splice(14, historyList.length - 14)
|
||||
historyList.unshift(word)
|
||||
saveSearchHistoryListThrottle(toRaw(historyList))
|
||||
}
|
||||
export const removeHistoryWord = (index: number) => {
|
||||
historyList.splice(index, 1)
|
||||
saveSearchHistoryListThrottle(toRaw(historyList))
|
||||
}
|
||||
export const clearHistoryList = (id: string) => {
|
||||
historyList.splice(0, historyList.length)
|
||||
saveSearchHistoryList([])
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { markRaw } from '@common/utils/vueTools'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
import { deduplicationList, toNewMusicInfo } from '@renderer/utils'
|
||||
import { sortInsert, similar } from '@common/utils/common'
|
||||
|
||||
import { sources, maxPages, listInfos, ListInfo } from './state'
|
||||
|
||||
interface SearchResult {
|
||||
list: LX.Music.MusicInfo[]
|
||||
allPage: number
|
||||
limit: number
|
||||
total: number
|
||||
source: LX.OnlineSource
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 按搜索关键词重新排序列表
|
||||
* @param list 歌曲列表
|
||||
* @param keyword 搜索关键词
|
||||
* @returns 排序后的列表
|
||||
*/
|
||||
const handleSortList = (list: LX.Music.MusicInfo[], keyword: string) => {
|
||||
let arr: any[] = []
|
||||
for (const item of list) {
|
||||
sortInsert(arr, {
|
||||
num: similar(keyword, `${item.name} ${item.singer}`),
|
||||
data: item,
|
||||
})
|
||||
}
|
||||
return arr.map(item => item.data).reverse()
|
||||
}
|
||||
|
||||
|
||||
const setLists = (results: SearchResult[], page: number, text: string): LX.Music.MusicInfo[] => {
|
||||
let pages = []
|
||||
let total = 0
|
||||
// let limit = 0
|
||||
let list = []
|
||||
for (const source of results) {
|
||||
maxPages[source.source] = source.allPage
|
||||
if (source.allPage < page) continue
|
||||
list.push(...source.list)
|
||||
pages.push(source.allPage)
|
||||
total += source.total
|
||||
// limit = Math.max(source.limit, limit)
|
||||
}
|
||||
list = deduplicationList(list.map(s => markRaw(toNewMusicInfo(s))))
|
||||
let listInfo = listInfos.all
|
||||
listInfo.maxPage = Math.max(...pages)
|
||||
listInfo.total = total
|
||||
// listInfo.limit = limit
|
||||
listInfo.page = page
|
||||
listInfo.list = handleSortList(list, text)
|
||||
if (text && !list.length) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else listInfo.noItemLabel = ''
|
||||
return listInfo.list
|
||||
}
|
||||
|
||||
const setList = (datas: SearchResult, page: number, text: string): LX.Music.MusicInfo[] => {
|
||||
// console.log(datas.source, datas.list)
|
||||
let listInfo = listInfos[datas.source] as ListInfo
|
||||
listInfo.list = deduplicationList(datas.list.map(s => markRaw(toNewMusicInfo(s))))
|
||||
listInfo.total = datas.total
|
||||
listInfo.maxPage = datas.allPage
|
||||
listInfo.page = page
|
||||
listInfo.limit = datas.limit
|
||||
if (text && !datas.list.length) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else listInfo.noItemLabel = ''
|
||||
return listInfo.list
|
||||
}
|
||||
|
||||
export const resetListInfo = (sourceId: LX.OnlineSource | 'all'): [] => {
|
||||
let listInfo = listInfos[sourceId] as ListInfo
|
||||
listInfo.list = []
|
||||
listInfo.page = 0
|
||||
listInfo.maxPage = 0
|
||||
listInfo.total = 0
|
||||
listInfo.noItemLabel = ''
|
||||
return []
|
||||
}
|
||||
|
||||
export const search = async(text: string, page: number, sourceId: LX.OnlineSource | 'all'): Promise<LX.Music.MusicInfo[]> => {
|
||||
const listInfo = listInfos[sourceId]
|
||||
if (!text) return resetListInfo(sourceId)
|
||||
const key = `${page}__${text}`
|
||||
if (sourceId == 'all') {
|
||||
listInfo!.noItemLabel = window.i18n.t('list__loading')
|
||||
listInfo!.key = key
|
||||
let task = []
|
||||
for (const source of sources) {
|
||||
if (source == 'all') continue
|
||||
task.push(music[source].musicSearch.search(text, page, listInfos.all.limit).catch((error: any) => {
|
||||
console.log(error)
|
||||
return {
|
||||
allPage: 1,
|
||||
limit: 30,
|
||||
list: [],
|
||||
source,
|
||||
total: 0,
|
||||
}
|
||||
}))
|
||||
}
|
||||
return await Promise.all(task).then((results: SearchResult[]) => {
|
||||
if (key != listInfo!.key) return []
|
||||
return setLists(results, page, text)
|
||||
})
|
||||
} else {
|
||||
if (listInfo?.key == key && listInfo?.list.length) return listInfo?.list
|
||||
listInfo!.noItemLabel = window.i18n.t('list__loading')
|
||||
listInfo!.key = key
|
||||
return music[sourceId].musicSearch.search(text, page, listInfo!.limit).then((data: SearchResult) => {
|
||||
if (key != listInfo!.key) return []
|
||||
return setList(data, page, text)
|
||||
}).catch((error: any) => {
|
||||
resetListInfo(sourceId)
|
||||
listInfo!.noItemLabel = window.i18n.t('list__load_failed')
|
||||
console.log(error)
|
||||
throw error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
export * from './action'
|
||||
export * from './state'
|
||||
@@ -0,0 +1,48 @@
|
||||
import { reactive, markRaw } from '@common/utils/vueTools'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
|
||||
// import { deduplicationList } from '@common/utils/renderer'
|
||||
|
||||
export declare interface ListInfo {
|
||||
list: LX.Music.MusicInfo[]
|
||||
total: number
|
||||
page: number
|
||||
maxPage: number
|
||||
limit: number
|
||||
key: string | null
|
||||
noItemLabel: string
|
||||
}
|
||||
|
||||
interface ListInfos extends Partial<Record<LX.OnlineSource, ListInfo>> {
|
||||
'all': ListInfo
|
||||
}
|
||||
|
||||
export const sources: Array<LX.OnlineSource | 'all'> = markRaw([])
|
||||
|
||||
export const listInfos: ListInfos = markRaw({
|
||||
all: reactive<ListInfo>({
|
||||
page: 1,
|
||||
maxPage: 0,
|
||||
limit: 30,
|
||||
total: 0,
|
||||
list: [],
|
||||
key: null,
|
||||
noItemLabel: '',
|
||||
}),
|
||||
})
|
||||
export const maxPages: Partial<Record<LX.OnlineSource, number>> = {}
|
||||
for (const source of music.sources) {
|
||||
if (!music[source.id as LX.OnlineSource].musicSearch) continue
|
||||
sources.push(source.id as LX.OnlineSource)
|
||||
listInfos[source.id as LX.OnlineSource] = reactive<ListInfo>({
|
||||
page: 1,
|
||||
maxPage: 0,
|
||||
limit: 30,
|
||||
total: 0,
|
||||
list: [],
|
||||
key: '',
|
||||
noItemLabel: '',
|
||||
})
|
||||
maxPages[source.id as LX.OnlineSource] = 0
|
||||
}
|
||||
sources.push('all')
|
||||
@@ -0,0 +1,123 @@
|
||||
import { markRawList } from '@common/utils/vueTools'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
import { sortInsert, similar } from '@common/utils/common'
|
||||
|
||||
import { sources, maxPages, listInfos, ListInfoItem, SearchListInfo } from './state'
|
||||
|
||||
interface SearchResult {
|
||||
list: ListInfoItem[]
|
||||
limit: number
|
||||
total: number
|
||||
source: LX.OnlineSource
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 按搜索关键词重新排序列表
|
||||
* @param list 歌曲列表
|
||||
* @param keyword 搜索关键词
|
||||
* @returns 排序后的列表
|
||||
*/
|
||||
const handleSortList = (list: ListInfoItem[], keyword: string) => {
|
||||
let arr: any[] = []
|
||||
for (const item of list) {
|
||||
sortInsert(arr, {
|
||||
num: similar(keyword, item.name),
|
||||
data: item,
|
||||
})
|
||||
}
|
||||
return arr.map(item => item.data).reverse()
|
||||
}
|
||||
|
||||
|
||||
let maxTotals: Partial<Record<LX.OnlineSource, number>> = {
|
||||
|
||||
}
|
||||
const setLists = (results: SearchResult[], page: number, text: string): ListInfoItem[] => {
|
||||
let totals = []
|
||||
// let limit = 0
|
||||
let list = []
|
||||
for (const source of results) {
|
||||
list.push(...source.list)
|
||||
totals.push(source.total)
|
||||
maxTotals[source.source] = source.total
|
||||
maxPages[source.source] = Math.ceil(source.total / source.limit)
|
||||
// limit = Math.max(source.limit, limit)
|
||||
}
|
||||
markRawList(list)
|
||||
|
||||
let listInfo = listInfos.all
|
||||
listInfo.total = Math.max(...totals)
|
||||
listInfo.page = page
|
||||
listInfo.list = handleSortList(list, text)
|
||||
if (text && !list.length) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else listInfo.noItemLabel = ''
|
||||
return listInfo.list
|
||||
}
|
||||
|
||||
const setList = (datas: SearchResult, page: number, text: string): ListInfoItem[] => {
|
||||
// console.log(datas.source, datas.list)
|
||||
let listInfo = listInfos[datas.source] as SearchListInfo
|
||||
listInfo.list = markRawList(datas.list)
|
||||
listInfo.total = datas.total
|
||||
listInfo.page = page
|
||||
listInfo.limit = datas.limit
|
||||
if (text && !datas.list.length) listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
else listInfo.noItemLabel = ''
|
||||
return listInfo.list
|
||||
}
|
||||
|
||||
export const resetListInfo = (sourceId: LX.OnlineSource | 'all'): [] => {
|
||||
let listInfo = listInfos[sourceId] as SearchListInfo
|
||||
listInfo.page = 1
|
||||
listInfo.limit = 20
|
||||
listInfo.total = 0
|
||||
listInfo.list = []
|
||||
listInfo.key = null
|
||||
listInfo.noItemLabel = ''
|
||||
listInfo.tagId = ''
|
||||
listInfo.sortId = ''
|
||||
return []
|
||||
}
|
||||
|
||||
export const search = async(text: string, page: number, sourceId: LX.OnlineSource | 'all'): Promise<ListInfoItem[]> => {
|
||||
const listInfo = listInfos[sourceId] as SearchListInfo
|
||||
if (!text) return resetListInfo(sourceId)
|
||||
const key = `${page}__${sourceId}__${text}`
|
||||
if (listInfo.key == key && listInfo.list.length) return listInfo.list
|
||||
if (sourceId == 'all') {
|
||||
listInfo.noItemLabel = window.i18n.t('list__loading')
|
||||
listInfo.key = key
|
||||
let task = []
|
||||
for (const source of sources) {
|
||||
if (source == 'all' || (page > 1 && page > (maxPages[source] as number))) continue
|
||||
task.push(music[source].songList.search(text, page, listInfos.all.limit).catch((error: any) => {
|
||||
console.log(error)
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
limit: listInfos.all.limit,
|
||||
source,
|
||||
}
|
||||
}))
|
||||
}
|
||||
return await Promise.all(task).then((results: SearchResult[]) => {
|
||||
if (key != listInfo.key) return []
|
||||
return setLists(results, page, text)
|
||||
})
|
||||
} else {
|
||||
if (listInfo?.key == key && listInfo?.list.length) return listInfo?.list
|
||||
listInfo.noItemLabel = window.i18n.t('list__loading')
|
||||
listInfo.key = key
|
||||
return music[sourceId].songList.search(text, page, listInfo.limit).then((data: SearchResult) => {
|
||||
if (key != listInfo.key) return []
|
||||
return setList(data, page, text)
|
||||
}).catch((error: any) => {
|
||||
resetListInfo(sourceId)
|
||||
listInfo.noItemLabel = window.i18n.t('list__load_failed')
|
||||
console.log(error)
|
||||
throw error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
export * from './action'
|
||||
export * from './state'
|
||||
@@ -0,0 +1,48 @@
|
||||
import { reactive, markRaw } from '@common/utils/vueTools'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
|
||||
// import { deduplicationList } from '@common/utils/renderer'
|
||||
|
||||
import { ListInfo } from '@renderer/store/songList/state'
|
||||
|
||||
export { ListInfoItem } from '@renderer/store/songList/state'
|
||||
|
||||
export const sources: Array<LX.OnlineSource | 'all'> = markRaw([])
|
||||
|
||||
export type SearchListInfo = Omit<ListInfo, 'source'>
|
||||
|
||||
|
||||
interface ListInfos extends Partial<Record<LX.OnlineSource, SearchListInfo>> {
|
||||
'all': SearchListInfo
|
||||
}
|
||||
|
||||
|
||||
export const listInfos: ListInfos = markRaw({
|
||||
all: reactive<SearchListInfo>({
|
||||
page: 1,
|
||||
limit: 15,
|
||||
total: 0,
|
||||
list: [],
|
||||
key: null,
|
||||
noItemLabel: '',
|
||||
tagId: '',
|
||||
sortId: '',
|
||||
}),
|
||||
})
|
||||
export const maxPages: Partial<Record<LX.OnlineSource, number>> = {}
|
||||
for (const source of music.sources) {
|
||||
if (!music[source.id as LX.OnlineSource].songList?.search) continue
|
||||
sources.push(source.id as LX.OnlineSource)
|
||||
listInfos[source.id as LX.OnlineSource] = reactive<SearchListInfo>({
|
||||
page: 1,
|
||||
limit: 18,
|
||||
total: 0,
|
||||
list: [],
|
||||
key: null,
|
||||
noItemLabel: '',
|
||||
tagId: '',
|
||||
sortId: '',
|
||||
})
|
||||
maxPages[source.id as LX.OnlineSource] = 0
|
||||
}
|
||||
sources.push('all')
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ref, shallowReactive } from '@common/utils/vueTools'
|
||||
|
||||
|
||||
export const searchText = ref('')
|
||||
|
||||
export declare type onlineSource = LX.OnlineSource
|
||||
|
||||
|
||||
export const historyList = shallowReactive<string[]>([])
|
||||
@@ -0,0 +1,115 @@
|
||||
import { reactive, computed } from '@common/utils/vueTools'
|
||||
import defaultSetting from '@common/defaultSetting'
|
||||
import { updateSetting as saveSetting } from '@renderer/utils/ipc'
|
||||
|
||||
export const appSetting = window.lxData.appSetting = reactive<LX.AppSetting>(defaultSetting)
|
||||
|
||||
export const isShowAnimation = computed(() => {
|
||||
return appSetting['common.isShowAnimation']
|
||||
})
|
||||
|
||||
|
||||
export const initSetting = (newSetting: LX.AppSetting) => {
|
||||
mergeSetting(newSetting)
|
||||
}
|
||||
|
||||
export const mergeSetting = (newSetting: Partial<LX.AppSetting>) => {
|
||||
for (const [key, value] of Object.entries(newSetting)) {
|
||||
// @ts-expect-error
|
||||
appSetting[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
export const updateSetting = window.lxData.updateSetting = (setting: Partial<LX.AppSetting>) => {
|
||||
console.warn(setting)
|
||||
void saveSetting(setting)
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存是否同意协议
|
||||
* @param isAgreePact 是否同意协议
|
||||
*/
|
||||
export const saveAgreePact = (isAgreePact: boolean) => {
|
||||
updateSetting({ 'common.isAgreePact': isAgreePact })
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存音频输出id
|
||||
* @param id 媒体驱动id
|
||||
*/
|
||||
export const saveMediaDeviceId = (id: string) => {
|
||||
updateSetting({ 'player.mediaDeviceId': id })
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存音量大小
|
||||
* @param volume 音量
|
||||
*/
|
||||
export const saveVolume = (volume: number) => {
|
||||
updateSetting({ 'player.volume': volume })
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否静音
|
||||
* @param isMute 是否静音
|
||||
*/
|
||||
export const saveVolumeIsMute = (isMute: boolean) => {
|
||||
updateSetting({ 'player.isMute': isMute })
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否开启桌面歌词
|
||||
* @param enabled
|
||||
*/
|
||||
export const setVisibleDesktopLyric = (enabled: boolean) => {
|
||||
updateSetting({ 'desktopLyric.enable': enabled })
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否锁定桌面歌词
|
||||
* @param isLock
|
||||
*/
|
||||
export const setLockDesktopLyric = (isLock: boolean) => {
|
||||
updateSetting({ 'desktopLyric.isLock': isLock })
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置切歌模式
|
||||
* @param mode
|
||||
*/
|
||||
export const setTogglePlayMode = (mode: LX.AppSetting['player.togglePlayMethod']) => {
|
||||
updateSetting({ 'player.togglePlayMethod': mode })
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置API id
|
||||
* @param sourceId
|
||||
*/
|
||||
export const setApiSource = (sourceId: string) => {
|
||||
updateSetting({ 'common.apiSource': sourceId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置播放详情页歌词字体大小
|
||||
* @param size 字体大小
|
||||
*/
|
||||
export const setPlayDetailLyricFont = (size: number) => {
|
||||
updateSetting({ 'playDetail.style.fontSize': size })
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置播放详情页歌词对齐方式
|
||||
* @param align 对齐方式
|
||||
*/
|
||||
export const setPlayDetailLyricAlign = (align: LX.AppSetting['playDetail.style.align']) => {
|
||||
updateSetting({ 'playDetail.style.align': align })
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置播放详情页音频可视化
|
||||
* @param enable 是否启用
|
||||
*/
|
||||
export const setEnableAudioVisualization = (enable: boolean) => {
|
||||
updateSetting({ 'player.audioVisualization': enable })
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// import { getSongListSetting } from '@renderer/utils/data'
|
||||
import { deduplicationList, toNewMusicInfo } from '@renderer/utils'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import { markRaw, markRawList } from '@common/utils/vueTools'
|
||||
import {
|
||||
tags,
|
||||
TagInfo,
|
||||
listInfo,
|
||||
listDetailInfo,
|
||||
ListDetailInfo,
|
||||
ListInfoItem,
|
||||
ListInfo,
|
||||
selectListInfo,
|
||||
isVisibleListDetail,
|
||||
openSongListInputInfo,
|
||||
} from './state'
|
||||
|
||||
const cache = new Map<string, any>()
|
||||
|
||||
export const setTags = (tagInfo: TagInfo, source: LX.OnlineSource) => {
|
||||
tags[source] = markRaw(tagInfo)
|
||||
}
|
||||
|
||||
export const clearList = () => {
|
||||
listInfo.list = []
|
||||
listInfo.total = 0
|
||||
listInfo.noItemLabel = ''
|
||||
listInfo.page = 1
|
||||
listInfo.key = ''
|
||||
}
|
||||
|
||||
export const setList = (result: ListInfo, tagId: string, sortId: string, page: number) => {
|
||||
listInfo.list = markRaw([...result.list])
|
||||
listInfo.total = result.total
|
||||
listInfo.limit = result.limit
|
||||
listInfo.page = page
|
||||
listInfo.source = result.source
|
||||
listInfo.tagId = tagId
|
||||
listInfo.sortId = sortId
|
||||
if (result.list.length) listInfo.noItemLabel = ''
|
||||
else listInfo.noItemLabel = window.i18n.t('no_item')
|
||||
}
|
||||
export const setListDetail = (result: ListDetailInfo, id: string, page: number) => {
|
||||
listDetailInfo.list = markRaw([...result.list])
|
||||
listDetailInfo.id = id
|
||||
listDetailInfo.source = result.source
|
||||
listDetailInfo.total = result.total
|
||||
listDetailInfo.limit = result.limit
|
||||
listDetailInfo.page = page
|
||||
listDetailInfo.info = markRaw({ ...result.info })
|
||||
if (result.list.length) listDetailInfo.noItemLabel = ''
|
||||
else listDetailInfo.noItemLabel = window.i18n.t('no_item')
|
||||
}
|
||||
|
||||
export const setSelectListInfo = (info: ListInfoItem) => {
|
||||
selectListInfo.author = info.author
|
||||
selectListInfo.desc = info.desc
|
||||
selectListInfo.id = info.id
|
||||
selectListInfo.img = info.img
|
||||
selectListInfo.name = info.name
|
||||
selectListInfo.play_count = info.play_count
|
||||
selectListInfo.source = info.source
|
||||
}
|
||||
export const clearListDetail = () => {
|
||||
listDetailInfo.list = []
|
||||
listDetailInfo.id = ''
|
||||
listDetailInfo.source = 'kw'
|
||||
listDetailInfo.total = 0
|
||||
listDetailInfo.limit = 30
|
||||
listDetailInfo.page = 1
|
||||
listDetailInfo.key = null
|
||||
listDetailInfo.info = {}
|
||||
listDetailInfo.noItemLabel = ''
|
||||
}
|
||||
|
||||
export const getTags = async<T extends LX.OnlineSource>(source: T) => {
|
||||
return musicSdk[source]?.songList.getTags() as Promise<TagInfo<T>>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取歌单列表
|
||||
* @param source 歌单源
|
||||
* @param tabId 类型id
|
||||
* @param sortId 排序
|
||||
* @param page 页数
|
||||
* @param isRefresh 是否跳过缓存
|
||||
* @returns
|
||||
*/
|
||||
export const getAndSetList = async(source: LX.OnlineSource, tabId: string, sortId: string, page: number, isRefresh = false) => {
|
||||
// let source = rootState.setting.songList.source
|
||||
// let tabId = rootState.setting.songList.tagInfo.id
|
||||
// let sortId = rootState.setting.songList.sortId
|
||||
// console.log(sortId)
|
||||
let key = `slist__${source}__${sortId}__${tabId}__${page}`
|
||||
// if (state.list.list.length && state.list.key == key) return
|
||||
if (!isRefresh) {
|
||||
if (listInfo.key == key && listInfo.list.length) return
|
||||
if (cache.has(key)) {
|
||||
listInfo.key = key
|
||||
setList(cache.get(key), tabId, sortId, page)
|
||||
return
|
||||
}
|
||||
}
|
||||
listInfo.noItemLabel = window.i18n.t('list__loading')
|
||||
listInfo.key = key
|
||||
// clearList()
|
||||
return musicSdk[source]?.songList.getList(sortId, tabId, page).then((result: ListInfo) => {
|
||||
cache.set(key, result)
|
||||
if (key != listInfo.key) return
|
||||
setList(result, tabId, sortId, page)
|
||||
}).catch((error: any) => {
|
||||
clearList()
|
||||
listInfo.noItemLabel = window.i18n.t('list__load_failed')
|
||||
console.log(error)
|
||||
throw error
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取歌单内单页歌曲
|
||||
* @param id 歌单id
|
||||
* @param source 歌单源
|
||||
* @param isRefresh 是否跳过缓存
|
||||
* @returns
|
||||
*/
|
||||
export const getListDetail = async(id: string, source: LX.OnlineSource, page: number, isRefresh = false): Promise<ListDetailInfo> => {
|
||||
let key = `sdetail__${source}__${id}__${page}`
|
||||
if (!isRefresh && cache.has(key)) return cache.get(key)
|
||||
|
||||
return musicSdk[source]?.songList.getListDetail(id, page).then((result: ListDetailInfo) => {
|
||||
result.list = markRawList(deduplicationList(result.list.map(m => toNewMusicInfo(m))))
|
||||
cache.set(key, result)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取歌单内全部歌曲
|
||||
* @param id 歌单id
|
||||
* @param source 歌单源
|
||||
* @param isRefresh 是否跳过缓存
|
||||
* @returns
|
||||
*/
|
||||
export const getListDetailAll = async(id: string, source: LX.OnlineSource, isRefresh = false): Promise<LX.Music.MusicInfoOnline[]> => {
|
||||
// console.log(source, id)
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
const loadData = (id: string, page: number): Promise<ListDetailInfo> => {
|
||||
let key = `sdetail__${source}__${id}__${page}`
|
||||
if (isRefresh && cache.has(key)) cache.delete(key)
|
||||
return cache.has(key)
|
||||
? Promise.resolve(cache.get(key))
|
||||
: musicSdk[source]?.songList.getListDetail(id, page).then((result: ListDetailInfo) => {
|
||||
result.list = markRawList(deduplicationList(result.list.map(m => toNewMusicInfo(m))))
|
||||
cache.set(key, result)
|
||||
return result
|
||||
}) ?? Promise.reject(new Error('source not found'))
|
||||
}
|
||||
return await loadData(id, 1).then((result: ListDetailInfo) => {
|
||||
if (result.total <= result.limit) return result.list
|
||||
|
||||
let maxPage = Math.ceil(result.total / result.limit)
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
const loadDetail = (loadPage = 2): Promise<ListDetailInfo['list']> => {
|
||||
return loadPage == maxPage
|
||||
? loadData(id, loadPage).then((result: ListDetailInfo) => result.list)
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
: loadData(id, loadPage).then((result1: ListDetailInfo) => loadDetail(++loadPage).then((result2: ListDetailInfo['list']) => [...result1.list, ...result2]))
|
||||
}
|
||||
return loadDetail().then(result2 => [...result.list, ...result2])
|
||||
}).then((list: ListDetailInfo['list']) => deduplicationList(list))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取并设置歌单内单页歌曲
|
||||
* @param id 歌单id
|
||||
* @param source 歌单源
|
||||
* @param isRefresh 是否跳过缓存
|
||||
* @returns
|
||||
*/
|
||||
export const getAndSetListDetail = async(id: string, source: LX.OnlineSource, page: number, isRefresh = false) => {
|
||||
let key = `sdetail__${source}__${id}__${page}`
|
||||
|
||||
if (!isRefresh && listDetailInfo.key == key && listDetailInfo.list.length) return
|
||||
|
||||
listDetailInfo.key = key
|
||||
listDetailInfo.noItemLabel = window.i18n.t('list__loading')
|
||||
|
||||
return getListDetail(id, source, page, isRefresh).then((result: ListDetailInfo) => {
|
||||
if (key != listDetailInfo.key) return
|
||||
setListDetail(result, id, page)
|
||||
}).catch((error: any) => {
|
||||
clearListDetail()
|
||||
listDetailInfo.noItemLabel = window.i18n.t('list__load_failed')
|
||||
console.log(error)
|
||||
throw error
|
||||
})
|
||||
}
|
||||
|
||||
export const setVisibleListDetail = (visible: boolean) => {
|
||||
isVisibleListDetail.value = visible
|
||||
}
|
||||
|
||||
export const setOpenSongListInputInfo = (text: string, source: string) => {
|
||||
openSongListInputInfo.text = text
|
||||
openSongListInputInfo.source = source
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { reactive, markRaw, ref, shallowReactive } from '@common/utils/vueTools'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
|
||||
export declare interface SortInfo {
|
||||
name: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export const sources: LX.OnlineSource[] = markRaw([])
|
||||
export const sortList = markRaw<Partial<Record<LX.OnlineSource, SortInfo[]>>>({})
|
||||
|
||||
for (const source of music.sources) {
|
||||
const songList = music[source.id as LX.OnlineSource].songList
|
||||
if (!songList) continue
|
||||
sources.push(source.id as LX.OnlineSource)
|
||||
sortList[source.id as LX.OnlineSource] = songList.sortList as SortInfo[]
|
||||
}
|
||||
|
||||
export declare interface TagInfoItem<T extends LX.OnlineSource = LX.OnlineSource> {
|
||||
parent_id: string
|
||||
parent_name: string
|
||||
id: string
|
||||
name: string
|
||||
source: T
|
||||
}
|
||||
export declare interface TagInfoTypeItem<T extends LX.OnlineSource = LX.OnlineSource> {
|
||||
name: string
|
||||
list: Array<TagInfoItem<T>>
|
||||
}
|
||||
export declare interface TagInfo<Source extends LX.OnlineSource = LX.OnlineSource> {
|
||||
tags: Array<TagInfoTypeItem<Source>>
|
||||
hotTag: Array<TagInfoItem<Source>>
|
||||
source: Source
|
||||
}
|
||||
|
||||
type Tags = Partial<Record<LX.OnlineSource, TagInfo>>
|
||||
|
||||
export const tags = shallowReactive<Tags>({})
|
||||
|
||||
export declare interface ListInfoItem {
|
||||
play_count: string
|
||||
id: string
|
||||
author: string
|
||||
name: string
|
||||
// time: basic.publish_time,
|
||||
img: string
|
||||
// grade: basic.favorcnt / 10,
|
||||
desc: string | null
|
||||
source: LX.OnlineSource
|
||||
total?: string
|
||||
}
|
||||
export declare interface ListInfo {
|
||||
list: ListInfoItem[]
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
key: string | null
|
||||
noItemLabel: string
|
||||
source?: LX.OnlineSource
|
||||
tagId: string
|
||||
sortId: string
|
||||
}
|
||||
|
||||
export declare interface ListDetailInfo {
|
||||
list: LX.Music.MusicInfoOnline[]
|
||||
source: LX.OnlineSource
|
||||
desc: string | null
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
key: string | null
|
||||
id: string
|
||||
info: {
|
||||
name?: string
|
||||
img?: string
|
||||
desc?: string
|
||||
author?: string
|
||||
play_count?: string
|
||||
}
|
||||
noItemLabel: string
|
||||
}
|
||||
|
||||
export const listInfo = reactive<ListInfo>({
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 30,
|
||||
key: null,
|
||||
noItemLabel: '',
|
||||
source: 'kw',
|
||||
tagId: '',
|
||||
sortId: '',
|
||||
})
|
||||
|
||||
export const listDetailInfo = reactive<ListDetailInfo>({
|
||||
list: [],
|
||||
id: '',
|
||||
desc: null,
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 30,
|
||||
key: null,
|
||||
source: 'kw',
|
||||
info: {},
|
||||
noItemLabel: '',
|
||||
})
|
||||
|
||||
export const selectListInfo = markRaw<ListInfoItem>({
|
||||
play_count: '',
|
||||
id: '',
|
||||
author: '',
|
||||
name: '',
|
||||
// time: basic.publish_time,
|
||||
img: '',
|
||||
// grade: basic.favorcnt / 10,
|
||||
desc: '',
|
||||
source: 'kw',
|
||||
})
|
||||
|
||||
export const isVisibleListDetail = ref(false)
|
||||
export const openSongListInputInfo = markRaw({
|
||||
text: '',
|
||||
source: '',
|
||||
})
|
||||
@@ -1,7 +0,0 @@
|
||||
// const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
export default {
|
||||
userInfo: null,
|
||||
setting: null,
|
||||
settingVersion: null,
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// import { getListFromState } from './list'
|
||||
// import { downloadList } from './download'
|
||||
|
||||
|
||||
// export const getList = (listId: string | null): LX.Download.ListItem[] | LX.Music.MusicInfo[] => {
|
||||
// return listId == 'download' ? downloadList : getListFromState(listId)
|
||||
// }
|
||||
import { commonColorNames, commonDarkColorValues, commonLightColorValues } from '@common/config'
|
||||
import { joinPath } from '@common/utils/nodejs'
|
||||
import { markRaw, shallowReactive } from '@common/utils/vueTools'
|
||||
import { getThemes as getTheme } from '@renderer/utils/ipc'
|
||||
import { qualityList, themeInfo, themeShouldUseDarkColors } from './index'
|
||||
|
||||
export const assertApiSupport = (source: LX.Source): boolean => {
|
||||
return source == 'local' || qualityList.value[source] != null
|
||||
}
|
||||
|
||||
export const getThemes = (callback: (themeInfo: LX.ThemeInfo) => void) => {
|
||||
if (themeInfo.themes.length) return callback(themeInfo)
|
||||
void getTheme().then(info => {
|
||||
themeInfo.themes = markRaw(info.themes)
|
||||
themeInfo.userThemes = shallowReactive(info.userThemes)
|
||||
themeInfo.dataPath = info.dataPath
|
||||
callback(themeInfo)
|
||||
})
|
||||
}
|
||||
export const buildThemeColors = (theme: LX.Theme, dataPath: string) => {
|
||||
if (theme.isCustom && theme.config.extInfo['--background-image'] != 'none') {
|
||||
theme = copyTheme(theme)
|
||||
theme.config.extInfo['--background-image'] = `url(file:///${joinPath(dataPath, theme.config.extInfo['--background-image']).replaceAll('\\', '/')})`
|
||||
}
|
||||
const colors: Record<string, string> = {
|
||||
...theme.config.themeColors,
|
||||
...theme.config.extInfo,
|
||||
}
|
||||
|
||||
const vals = theme.isDark ? commonDarkColorValues : commonLightColorValues
|
||||
for (let i = commonColorNames.length; i--;) {
|
||||
colors[commonColorNames[i]] = vals[i]
|
||||
}
|
||||
|
||||
return colors
|
||||
}
|
||||
|
||||
export const copyTheme = (theme: LX.Theme): LX.Theme => {
|
||||
return {
|
||||
...theme,
|
||||
config: {
|
||||
...theme.config,
|
||||
extInfo: { ...theme.config.extInfo },
|
||||
themeColors: { ...theme.config.themeColors },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const findTheme = (themeInfo: LX.ThemeInfo, id: string): LX.Theme | undefined => {
|
||||
let theme = themeInfo.themes.find(theme => theme.id == id)
|
||||
if (theme) return theme
|
||||
theme = themeInfo.userThemes.find(theme => theme.id == id)
|
||||
return theme
|
||||
}
|
||||
|
||||
export const applyTheme = (id: string, lightId: string, darkId: string, dataPath: string) => {
|
||||
getThemes((themeInfo) => {
|
||||
let themeId = id == 'auto'
|
||||
? themeShouldUseDarkColors.value
|
||||
? darkId
|
||||
: lightId
|
||||
: id
|
||||
|
||||
let theme = findTheme(themeInfo, themeId)
|
||||
if (!theme) {
|
||||
themeId = id == 'auto' && themeShouldUseDarkColors.value ? 'black' : 'green'
|
||||
theme = themeInfo.themes.find(theme => theme.id == themeId) as LX.Theme
|
||||
}
|
||||
window.setTheme(buildThemeColors(theme, dataPath))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user