mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-07-03 06:32:10 +08:00
添加歌曲预加载功能
This commit is contained in:
parent
81910915f0
commit
7d29458d71
@ -4,6 +4,7 @@
|
||||
|
||||
### 优化
|
||||
|
||||
- 优化正常播放结束时的下一首歌曲播放衔接度,在歌曲即将播放结束时将预获取下一首歌曲的播放链接,减少自动切歌时的等待时间
|
||||
- 首次使用的提示窗口可以点击背景或者返回键关闭(#577)
|
||||
- 上移 Toast 位置避免遮挡播放模式图标(#603 @sibojia)
|
||||
|
||||
|
@ -4,6 +4,7 @@ import initPlayStatus from './playStatus'
|
||||
import initPlayerEvent from './playerEvent'
|
||||
import initWatchList from './watchList'
|
||||
import initPlayProgress from './playProgress'
|
||||
import initPreloadNextMusic from './preloadNextMusic'
|
||||
import initLyric from './lyric'
|
||||
|
||||
export default async(setting: LX.AppSetting) => {
|
||||
@ -14,4 +15,5 @@ export default async(setting: LX.AppSetting) => {
|
||||
initPlayerEvent()
|
||||
initWatchList()
|
||||
initPlayProgress()
|
||||
initPreloadNextMusic()
|
||||
}
|
||||
|
66
src/core/init/player/preloadNextMusic.ts
Normal file
66
src/core/init/player/preloadNextMusic.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { getMusicUrl } from '@/core/music'
|
||||
import { getNextPlayMusicInfo, resetRandomNextMusicInfo } from '@/core/player/player'
|
||||
import { checkUrl } from '@/utils/request'
|
||||
import playerState from '@/store/player/state'
|
||||
|
||||
|
||||
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 (!playerState.musicInfo.id) return
|
||||
preloadMusicInfo.preProgress = time
|
||||
}
|
||||
|
||||
const handleSetPlayInfo = () => {
|
||||
resetPreloadInfo()
|
||||
}
|
||||
|
||||
const handleConfigUpdated: typeof global.state_event.configUpdated = (keys, settings) => {
|
||||
if (!keys.includes('player.togglePlayMethod')) return
|
||||
if (!preloadMusicInfo.info || preloadMusicInfo.info.isTempPlay) return
|
||||
resetRandomNextMusicInfo()
|
||||
preloadMusicInfo.info = null
|
||||
preloadMusicInfo.preProgress = playerState.progress.nowPlayTime
|
||||
}
|
||||
|
||||
const handlePlayProgressChanged: typeof global.state_event.playProgressChanged = (progress) => {
|
||||
const duration = progress.maxPlayTime
|
||||
if (duration > 10 && duration - progress.nowPlayTime < 10 && !preloadMusicInfo.info) {
|
||||
void preloadNextMusicUrl(progress.nowPlayTime)
|
||||
}
|
||||
}
|
||||
|
||||
global.app_event.on('setProgress', setProgress)
|
||||
global.app_event.on('musicToggled', handleSetPlayInfo)
|
||||
global.state_event.on('configUpdated', handleConfigUpdated)
|
||||
global.state_event.on('playProgressChanged', handlePlayProgressChanged)
|
||||
}
|
@ -71,7 +71,7 @@ const blobToBuffer = (blob) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchData = (url, { timeout = 15000, ...options }) => {
|
||||
export const fetchData = (url, { timeout = 13_000, ...options }) => {
|
||||
// console.log('---start---', url)
|
||||
|
||||
const controller = new global.AbortController()
|
||||
|
@ -15,12 +15,14 @@ import { apis } from '@/utils/musicSdk/api-source'
|
||||
|
||||
const getOtherSourcePromises = new Map()
|
||||
export const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||
const otherSourceCache = new Map<LX.Music.MusicInfo | LX.Download.ListItem, LX.Music.MusicInfoOnline[]>()
|
||||
|
||||
export const getOtherSource = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh = false): Promise<LX.Music.MusicInfoOnline[]> => {
|
||||
// if (!isRefresh) {
|
||||
// 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
|
||||
@ -54,9 +56,12 @@ export const getOtherSource = async(musicInfo: LX.Music.MusicInfo | LX.Download.
|
||||
let timeout: null | number = BackgroundTimer.setTimeout(() => {
|
||||
timeout = null
|
||||
reject(new Error('find music timeout'))
|
||||
}, 15_000)
|
||||
}, 12_000)
|
||||
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) BackgroundTimer.clearTimeout(timeout)
|
||||
})
|
||||
|
@ -238,6 +238,7 @@ const handlePlay = async() => {
|
||||
}
|
||||
|
||||
global.lx.isPlayedStop &&= false
|
||||
resetRandomNextMusicInfo()
|
||||
|
||||
if (global.lx.restorePlayInfo) {
|
||||
void handleRestorePlay(global.lx.restorePlayInfo)
|
||||
@ -284,6 +285,108 @@ const handleToggleStop = async() => {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
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<LX.Player.PlayMusicInfo | null> => {
|
||||
if (playerState.tempPlayList.length) { // 如果稍后播放列表存在歌曲则直接播放改列表的歌曲
|
||||
const playMusicInfo = playerState.tempPlayList[0]
|
||||
return playMusicInfo
|
||||
}
|
||||
|
||||
if (playerState.playMusicInfo.musicInfo == null) return null
|
||||
|
||||
if (randomNextMusicInfo.info) return randomNextMusicInfo.info
|
||||
|
||||
const playMusicInfo = playerState.playMusicInfo
|
||||
const playInfo = playerState.playInfo
|
||||
// console.log(playInfo.playerListId)
|
||||
const currentListId = playInfo.playerListId
|
||||
if (!currentListId) return null
|
||||
const currentList = getList(currentListId)
|
||||
|
||||
const playedList = playerState.playedList
|
||||
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 = settingState.setting['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 = async(playMusicInfo: LX.Player.PlayMusicInfo) => {
|
||||
await pause()
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
await handlePlay()
|
||||
}
|
||||
/**
|
||||
* 下一曲
|
||||
* @param isAutoToggle 是否自动切换
|
||||
@ -293,9 +396,7 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
if (playerState.tempPlayList.length) { // 如果稍后播放列表存在歌曲则直接播放改列表的歌曲
|
||||
const playMusicInfo = playerState.tempPlayList[0]
|
||||
removeTempPlayList(0)
|
||||
await pause()
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
await handlePlay()
|
||||
await handlePlayNext(playMusicInfo)
|
||||
return
|
||||
}
|
||||
|
||||
@ -331,13 +432,14 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
}
|
||||
|
||||
if (index < playedList.length) {
|
||||
const playMusicInfo = playedList[index]
|
||||
await pause()
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
await handlePlay()
|
||||
await handlePlayNext(playedList[index])
|
||||
return
|
||||
}
|
||||
}
|
||||
if (randomNextMusicInfo.info) {
|
||||
await handlePlayNext(randomNextMusicInfo.info)
|
||||
return
|
||||
}
|
||||
// const isCheckFile = findNum > 2 // 针对下载列表,如果超过两次都碰到无效歌曲,则过滤整个列表内的无效歌曲
|
||||
let { filteredList, playerIndex } = await filterList({ // 过滤已播放歌曲
|
||||
listId: currentListId,
|
||||
@ -379,15 +481,11 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
}
|
||||
if (nextIndex < 0) return
|
||||
|
||||
const nextPlayMusicInfo = {
|
||||
await handlePlayNext({
|
||||
musicInfo: filteredList[nextIndex],
|
||||
listId: currentListId,
|
||||
isTempPlay: false,
|
||||
}
|
||||
|
||||
await pause()
|
||||
setPlayMusicInfo(nextPlayMusicInfo.listId, nextPlayMusicInfo.musicInfo)
|
||||
await handlePlay()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -424,10 +522,7 @@ export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
||||
}
|
||||
|
||||
if (index > -1) {
|
||||
const playMusicInfo = playedList[index]
|
||||
await pause()
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
await handlePlay()
|
||||
await handlePlayNext(playedList[index])
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -472,15 +567,12 @@ export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
||||
if (nextIndex < 0) return
|
||||
}
|
||||
|
||||
const nextPlayMusicInfo = {
|
||||
|
||||
await handlePlayNext({
|
||||
musicInfo: filteredList[nextIndex],
|
||||
listId: currentListId,
|
||||
isTempPlay: false,
|
||||
}
|
||||
|
||||
await pause()
|
||||
setPlayMusicInfo(nextPlayMusicInfo.listId, nextPlayMusicInfo.musicInfo)
|
||||
await handlePlay()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -215,3 +215,13 @@ const fetchData = (url, { timeout = 15000, ...options }) => {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const checkUrl = async(url, options = {}) => {
|
||||
return fetchData(url, { method: 'head', ...options }).request.then(resp => {
|
||||
if (resp.statusCode === 200) {
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
throw new Error(resp.statusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user