This commit is contained in:
lyswhut
2022-10-29 11:36:35 +08:00
parent 66b965d9f7
commit e45cee6596
715 changed files with 57096 additions and 31771 deletions
-49
View File
@@ -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)
})
})
},
}
+384
View File
@@ -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()
}
+13
View File
@@ -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)
-75
View File
@@ -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
},
}
+77
View File
@@ -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] = []
}
-43
View File
@@ -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
+134
View File
@@ -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: '',
}
+124
View File
@@ -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
})
}
+47
View File
@@ -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: '',
})
+106
View File
@@ -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([])
+43
View File
@@ -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>>({})
+31
View File
@@ -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))
}
-810
View File
@@ -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,
}
-90
View File
@@ -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,
}
-26
View File
@@ -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
-116
View File
@@ -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,
}
-434
View File
@@ -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,
}
-510
View File
@@ -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,
}
-221
View File
@@ -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,
}
-193
View File
@@ -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,
}
-68
View File
@@ -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
},
}
+251
View File
@@ -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)
}
+37
View File
@@ -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
}
+27
View File
@@ -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)
}
+73
View File
@@ -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
+13
View File
@@ -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
}
+43
View File
@@ -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([])
}
+123
View File
@@ -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
})
}
}
+3
View File
@@ -0,0 +1,3 @@
export * from './action'
export * from './state'
+48
View File
@@ -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')
+9
View File
@@ -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[]>([])
+115
View File
@@ -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 })
}
+208
View File
@@ -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
}
+124
View File
@@ -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: '',
})
-7
View File
@@ -1,7 +0,0 @@
// const isDev = process.env.NODE_ENV === 'development'
export default {
userInfo: null,
setting: null,
settingVersion: null,
}
+78
View File
@@ -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))
})
}