diff --git a/publish/changeLog.md b/publish/changeLog.md index 492d1486bd..8ef6e45aab 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -6,6 +6,7 @@ ### 优化 +- 优化正常播放结束时的下一首歌曲播放衔接度,在歌曲即将播放结束时将预获取下一首歌曲的播放链接,减少自动切歌时的等待时间 - 修正搜索歌曲提示框文案(#2050) - 优化播放详情页UI,歌曲名字、歌手等文字过长时被截断的问题(#2049) - Scheme URL 的播放歌曲允许更长的专辑名称 diff --git a/src/renderer/core/music/utils.ts b/src/renderer/core/music/utils.ts index eb89772b51..dc861cdadb 100644 --- a/src/renderer/core/music/utils.ts +++ b/src/renderer/core/music/utils.ts @@ -14,6 +14,7 @@ import { apis } from '@renderer/utils/musicSdk/api-source' const getOtherSourcePromises = new Map() +const otherSourceCache = new Map() export const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/ export const getOtherSource = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh = false): Promise => { @@ -21,6 +22,7 @@ export const getOtherSource = async(musicInfo: LX.Music.MusicInfo | LX.Download. // const cachedInfo = await getOtherSourceFromStore(musicInfo.id) // if (cachedInfo.length) return cachedInfo // } + if (otherSourceCache.has(musicInfo)) return otherSourceCache.get(musicInfo)! let key: string let searchMusicInfo: { name: string @@ -56,7 +58,10 @@ export const getOtherSource = async(musicInfo: LX.Music.MusicInfo | LX.Download. reject(new Error('find music timeout')) }, 15_000) musicSdk.findMusic(searchMusicInfo).then((otherSource) => { - resolve(otherSource.map(toNewMusicInfo) as LX.Music.MusicInfoOnline[]) + if (otherSourceCache.size > 100) otherSourceCache.clear() + const source = otherSource.map(toNewMusicInfo) as LX.Music.MusicInfoOnline[] + otherSourceCache.set(musicInfo, source) + resolve(source) }).catch(reject).finally(() => { if (timeout) clearTimeout(timeout) }) diff --git a/src/renderer/core/player/action.ts b/src/renderer/core/player/action.ts index fa8c661015..1f009ee93c 100644 --- a/src/renderer/core/player/action.ts +++ b/src/renderer/core/player/action.ts @@ -188,6 +188,7 @@ const handleRestorePlay = async(restorePlayInfo: LX.Player.SavedPlayInfo) => { const handlePlay = () => { window.lx.isPlayedStop &&= false + resetRandomNextMusicInfo() if (window.lx.restorePlayInfo) { void handleRestorePlay(window.lx.restorePlayInfo) window.lx.restorePlayInfo = null @@ -253,6 +254,104 @@ const handleToggleStop = () => { }) } +const randomNextMusicInfo = { + info: null as LX.Player.PlayMusicInfo | null, + index: -1, +} +export const resetRandomNextMusicInfo = () => { + if (randomNextMusicInfo.info) { + randomNextMusicInfo.info = null + randomNextMusicInfo.index = -1 + } +} + +export const getNextPlayMusicInfo = async(): Promise => { + if (tempPlayList.length) { // 如果稍后播放列表存在歌曲则直接播放改列表的歌曲 + const playMusicInfo = tempPlayList[0] + return playMusicInfo + } + + if (playMusicInfo.musicInfo == null) return null + + if (randomNextMusicInfo.info) return randomNextMusicInfo.info + + // console.log(playInfo.playerListId) + const currentListId = playInfo.playerListId + if (!currentListId) return null + const currentList = getList(currentListId) + + if (playedList.length) { // 移除已播放列表内不存在原列表的歌曲 + let currentId: string + if (playMusicInfo.isTempPlay) { + const musicInfo = currentList[playInfo.playerPlayIndex] + if (musicInfo) currentId = musicInfo.id + } else { + currentId = playMusicInfo.musicInfo.id + } + // 从已播放列表移除播放列表已删除的歌曲 + let index + for (index = playedList.findIndex(m => m.musicInfo.id === currentId) + 1; index < playedList.length; index++) { + const playMusicInfo = playedList[index] + const currentId = playMusicInfo.musicInfo.id + if (playMusicInfo.listId == currentListId && !currentList.some(m => m.id === currentId)) { + removePlayedList(index) + continue + } + break + } + + if (index < playedList.length) return playedList[index] + } + // const isCheckFile = findNum > 2 // 针对下载列表,如果超过两次都碰到无效歌曲,则过滤整个列表内的无效歌曲 + let { filteredList, playerIndex } = await filterList({ // 过滤已播放歌曲 + listId: currentListId, + list: currentList, + playedList, + playerMusicInfo: currentList[playInfo.playerPlayIndex], + isNext: true, + }) + + if (!filteredList.length) return null + // let currentIndex: number = filteredList.indexOf(currentList[playInfo.playerPlayIndex]) + if (playerIndex == -1 && filteredList.length) playerIndex = 0 + let nextIndex = playerIndex + + let togglePlayMethod = appSetting['player.togglePlayMethod'] + switch (togglePlayMethod) { + case 'listLoop': + nextIndex = playerIndex === filteredList.length - 1 ? 0 : playerIndex + 1 + break + case 'random': + nextIndex = getRandom(0, filteredList.length) + break + case 'list': + nextIndex = playerIndex === filteredList.length - 1 ? -1 : playerIndex + 1 + break + case 'singleLoop': + break + default: + return null + } + if (nextIndex < 0) return null + + const nextPlayMusicInfo = { + musicInfo: filteredList[nextIndex], + listId: currentListId, + isTempPlay: false, + } + + if (togglePlayMethod == 'random') { + randomNextMusicInfo.info = nextPlayMusicInfo + randomNextMusicInfo.index = nextIndex + } + return nextPlayMusicInfo +} + +const handlePlayNext = (playMusicInfo: LX.Player.PlayMusicInfo) => { + pause() + setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay) + handlePlay() +} /** * 下一曲 * @param isAutoToggle 是否自动切换 @@ -263,9 +362,7 @@ export const playNext = async(isAutoToggle = false): Promise => { if (tempPlayList.length) { // 如果稍后播放列表存在歌曲则直接播放改列表的歌曲 const playMusicInfo = tempPlayList[0] removeTempPlayList(0) - pause() - setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay) - handlePlay() + handlePlayNext(playMusicInfo) console.log('play temp list') return } @@ -306,14 +403,15 @@ export const playNext = async(isAutoToggle = false): Promise => { } if (index < playedList.length) { - const playMusicInfo = playedList[index] - pause() - setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay) - handlePlay() + handlePlayNext(playedList[index]) console.log('play played list') return } } + if (randomNextMusicInfo.info) { + handlePlayNext(randomNextMusicInfo.info) + return + } // const isCheckFile = findNum > 2 // 针对下载列表,如果超过两次都碰到无效歌曲,则过滤整个列表内的无效歌曲 let { filteredList, playerIndex } = await filterList({ // 过滤已播放歌曲 listId: currentListId, @@ -363,15 +461,11 @@ export const playNext = async(isAutoToggle = false): Promise => { return } - const nextPlayMusicInfo = { + handlePlayNext({ musicInfo: filteredList[nextIndex], listId: currentListId, isTempPlay: false, - } - - pause() - setPlayMusicInfo(nextPlayMusicInfo.listId, nextPlayMusicInfo.musicInfo) - handlePlay() + }) } /** @@ -411,10 +505,7 @@ export const playPrev = async(isAutoToggle = false): Promise => { } if (index > -1) { - const playMusicInfo = playedList[index] - pause() - setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay) - handlePlay() + handlePlayNext(playedList[index]) return } } @@ -462,15 +553,11 @@ export const playPrev = async(isAutoToggle = false): Promise => { if (nextIndex < 0) return } - const nextPlayMusicInfo = { + handlePlayNext({ musicInfo: filteredList[nextIndex], listId: currentListId, isTempPlay: false, - } - - pause() - setPlayMusicInfo(nextPlayMusicInfo.listId, nextPlayMusicInfo.musicInfo) - handlePlay() + }) } /** diff --git a/src/renderer/core/useApp/usePlayer/usePlayer.ts b/src/renderer/core/useApp/usePlayer/usePlayer.ts index 833424755e..dea92eb97b 100644 --- a/src/renderer/core/useApp/usePlayer/usePlayer.ts +++ b/src/renderer/core/useApp/usePlayer/usePlayer.ts @@ -34,6 +34,7 @@ import usePlaybackRate from './usePlaybackRate' import useSoundEffect from './useSoundEffect' import useMaxOutputChannelCount from './useMaxOutputChannelCount' import { setPowerSaveBlocker } from '@renderer/core/player/utils' +import usePreloadNextMusic from './usePreloadNextMusic' export default () => { @@ -48,6 +49,7 @@ export default () => { useSoundEffect() usePlaybackRate() useWatchList() + usePreloadNextMusic() const handlePlayNext = () => { void playNext() diff --git a/src/renderer/core/useApp/usePlayer/usePreloadNextMusic.ts b/src/renderer/core/useApp/usePlayer/usePreloadNextMusic.ts new file mode 100644 index 0000000000..7b16b3b393 --- /dev/null +++ b/src/renderer/core/useApp/usePlayer/usePreloadNextMusic.ts @@ -0,0 +1,75 @@ +import { onBeforeUnmount, watch } from '@common/utils/vueTools' +import { onTimeupdate, getCurrentTime } from '@renderer/plugins/player' +import { playProgress } from '@renderer/store/player/playProgress' +import { musicInfo } from '@renderer/store/player/state' +// import { getList } from '@renderer/store/utils' +import { getNextPlayMusicInfo, resetRandomNextMusicInfo } from '@renderer/core/player' +import { getMusicUrl } from '@renderer/core/music' +import { checkUrl } from '@renderer/utils/request' +import { appSetting } from '@renderer/store/setting' + +const preloadMusicInfo = { + isLoading: false, + preProgress: 0, + info: null as LX.Player.PlayMusicInfo | null, +} +const resetPreloadInfo = () => { + preloadMusicInfo.preProgress = 0 + preloadMusicInfo.info = null + preloadMusicInfo.isLoading = false +} +const preloadNextMusicUrl = async(curTime: number) => { + if (preloadMusicInfo.isLoading || curTime - preloadMusicInfo.preProgress < 3) return + preloadMusicInfo.isLoading = true + console.log('preload next music url') + const info = await getNextPlayMusicInfo() + if (info) { + preloadMusicInfo.info = info + const url = await getMusicUrl({ musicInfo: info.musicInfo }).catch(() => '') + if (url) { + console.log('preload url', url) + const result = await checkUrl(url).then(() => true).catch(() => false) + if (!result) { + const url = await getMusicUrl({ musicInfo: info.musicInfo, isRefresh: true }).catch(() => '') + console.log('preload url refresh', url) + } + } + } + preloadMusicInfo.isLoading = false +} + +export default () => { + const setProgress = (time: number) => { + if (!musicInfo.id) return + preloadMusicInfo.preProgress = time + } + + const handleSetPlayInfo = () => { + resetPreloadInfo() + } + + watch(() => appSetting['player.togglePlayMethod'], () => { + if (!preloadMusicInfo.info || preloadMusicInfo.info.isTempPlay) return + resetRandomNextMusicInfo() + preloadMusicInfo.info = null + preloadMusicInfo.preProgress = playProgress.nowPlayTime + }) + + window.app_event.on('setProgress', setProgress) + window.app_event.on('musicToggled', handleSetPlayInfo) + + const rOnTimeupdate = onTimeupdate(() => { + const time = getCurrentTime() + const duration = playProgress.maxPlayTime + if (duration > 10 && duration - time < 10 && !preloadMusicInfo.info) { + void preloadNextMusicUrl(time) + } + }) + + + onBeforeUnmount(() => { + rOnTimeupdate() + window.app_event.off('setProgress', setProgress) + window.app_event.off('musicToggled', handleSetPlayInfo) + }) +} diff --git a/src/renderer/utils/request.js b/src/renderer/utils/request.js index 3373cc4a08..6a30ee359f 100644 --- a/src/renderer/utils/request.js +++ b/src/renderer/utils/request.js @@ -307,3 +307,16 @@ const fetchData = async(url, method, { callback(null, resp, body) }) } + +export const checkUrl = (url, options = {}) => { + return new Promise((resolve, reject) => { + fetchData(url, 'head', options, (err, resp) => { + if (err) return reject(err) + if (resp.statusCode === 200) { + resolve() + } else { + reject(new Error(resp.statusCode)) + } + }) + }) +}