refactor: move resource URL refresh from favorites loading to playback/preload

Replace passive resource checking on playlist item mount with active checking
during playback and preload operations. This improves performance by reducing
unnecessary network requests and ensures resources are validated only when needed.

Changes:
- Create songResourceChecker utility for centralized resource validation
- Remove resource checking from PlayListItem component
- Add resource validation in Player component before playback
- Add resource validation in usePlayQueueStore before preload
- Maintain data consistency between play queue and favorites

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Astrian Zheng 2025-06-04 21:18:46 +10:00
parent fcf8362a15
commit dcf13b2f07
Signed by: Astrian
SSH Key Fingerprint: SHA256:rVnhx3DAKjujCwWE13aDl7uV6+9U1MvydLkNRXJrBiA
5 changed files with 142 additions and 32 deletions

View File

@ -2,11 +2,8 @@
import { artistsOrganize } from '../utils'
import { ref } from 'vue'
import { useFavourites } from '../stores/useFavourites'
import apis from '../apis'
import axios from 'axios'
import StarSlashIcon from '../assets/icons/starslash.vue'
import { onMounted } from 'vue'
const favourites = useFavourites()
@ -20,27 +17,6 @@ const props = defineProps<{
const emit = defineEmits<{
(e: 'play', index: number): void
}>()
onMounted(async () => {
try {
//
await axios.head(props.item.song.sourceUrl ?? '', {
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
},
params: {
_t: Date.now() //
}
})
} catch (error) {
//
const updatedSong = await apis.getSong(props.item.song.cid)
console.log('Updated song:', updatedSong)
favourites.updateSongInFavourites(props.item.song.cid, updatedSong)
}
})
</script>
<template>

View File

@ -4,12 +4,14 @@
import { usePlayQueueStore } from '../stores/usePlayQueueStore'
import { useTemplateRef, watch, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useFavourites } from '../stores/useFavourites'
import PlayIcon from '../assets/icons/play.vue'
import LoadingIndicator from '../assets/icons/loadingindicator.vue'
import { audioVisualizer } from '../utils'
import { audioVisualizer, checkAndRefreshSongResource } from '../utils'
const playQueueStore = usePlayQueueStore()
const favourites = useFavourites()
const route = useRoute()
const player = useTemplateRef('playerRef')
@ -53,9 +55,24 @@ watch(() => playQueueStore.currentIndex, async () => {
const songId = track.song.cid
try {
//
console.log('[Player] 检查当前歌曲资源:', track.song.name)
const updatedSong = await checkAndRefreshSongResource(
track.song,
(updated) => {
//
if (playQueueStore.list[playQueueStore.currentIndex]) {
playQueueStore.list[playQueueStore.currentIndex].song = updated
}
//
favourites.updateSongInFavourites(songId, updated)
}
)
// 使
const preloadedAudio = playQueueStore.getPreloadedAudio(songId)
if (preloadedAudio) {
if (preloadedAudio && updatedSong.sourceUrl === track.song.sourceUrl) {
console.log(`[Player] 使用预加载的音频: ${track.song.name}`)
// 使
@ -78,6 +95,11 @@ watch(() => playQueueStore.currentIndex, async () => {
} else {
console.log(`[Player] 正常加载音频: ${track.song.name}`)
playQueueStore.isBuffering = true
//
if (updatedSong.sourceUrl !== track.song.sourceUrl) {
playQueueStore.clearPreloadedAudio(songId)
}
}
} catch (error) {
console.error('[Player] 处理预加载音频时出错:', error)
@ -88,13 +110,21 @@ watch(() => playQueueStore.currentIndex, async () => {
setMetadata()
//
setTimeout(() => {
setTimeout(async () => {
try {
console.log('[Player] 尝试预加载下一首歌')
//
if (typeof playQueueStore.preloadNext === 'function') {
playQueueStore.preloadNext()
await playQueueStore.preloadNext()
//
playQueueStore.list.forEach(item => {
if (favourites.isFavourite(item.song.cid)) {
favourites.updateSongInFavourites(item.song.cid, item.song)
}
})
playQueueStore.limitPreloadCache()
} else {
console.error('[Player] preloadNext 不是一个函数')

View File

@ -1,5 +1,6 @@
import { defineStore } from "pinia"
import { ref, computed } from "vue"
import { checkAndRefreshSongResource } from "../utils"
export const usePlayQueueStore = defineStore('queue', () => {
const list = ref<QueueItem[]>([])
@ -89,6 +90,26 @@ export const usePlayQueueStore = defineStore('queue', () => {
isPreloading.value = true
preloadProgress.value = 0
// 在预加载前检查和刷新资源
console.log('[Store] 预加载前检查资源:', nextSong.song.name)
const updatedSong = await checkAndRefreshSongResource(
nextSong.song,
(updated) => {
// 更新播放队列中的歌曲信息
const actualIndex = playMode.value.shuffle && shuffleList.value.length > 0
? shuffleList.value[nextIndex]
: nextIndex
if (list.value[actualIndex]) {
list.value[actualIndex].song = updated
}
// 如果歌曲在收藏夹中,也更新收藏夹
// 注意:这里不直接导入 favourites store 以避免循环依赖
// 改为触发一个事件或者在调用方处理
console.log('[Store] 预加载时需要更新收藏夹:', updated.name)
}
)
const audio = new Audio()
audio.preload = 'auto'
audio.crossOrigin = 'anonymous'
@ -107,19 +128,21 @@ export const usePlayQueueStore = defineStore('queue', () => {
preloadedAudio.value.set(songId, audio)
isPreloading.value = false
preloadProgress.value = 100
console.log('[Store] 预加载完成:', updatedSong.name)
})
// 监听加载错误
audio.addEventListener('error', (e) => {
console.error(`[Store] 预加载音频失败: ${e}`)
console.error(`[Store] 预加载音频失败: ${updatedSong.name}`, e)
isPreloading.value = false
preloadProgress.value = 0
})
// 设置音频源并开始加载
audio.src = nextSong.song.sourceUrl
// 使用更新后的音频源
audio.src = updatedSong.sourceUrl!
} catch (error) {
console.error('[Store] 预加载过程出错:', error)
isPreloading.value = false
}
}

View File

@ -1,5 +1,6 @@
import artistsOrganize from "./artistsOrganize"
import { audioVisualizer } from "./audioVisualizer"
import cicdInfo from "./cicdInfo"
import { checkAndRefreshSongResource, checkAndRefreshMultipleSongs } from "./songResourceChecker"
export { artistsOrganize, audioVisualizer, cicdInfo }
export { artistsOrganize, audioVisualizer, cicdInfo, checkAndRefreshSongResource, checkAndRefreshMultipleSongs }

View File

@ -0,0 +1,80 @@
import axios from 'axios'
import apis from '../apis'
/**
* URL
* @param song
* @param updateCallback
* @returns
*/
export const checkAndRefreshSongResource = async (
song: Song,
updateCallback?: (updatedSong: Song) => void
): Promise<Song> => {
if (!song.sourceUrl) {
console.warn('[ResourceChecker] 歌曲没有 sourceUrl:', song.name)
return song
}
try {
// 检查资源是否可用
await axios.head(song.sourceUrl, {
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
},
params: {
_t: Date.now() // 添加时间戳参数避免缓存
},
timeout: 5000 // 5秒超时
})
// 资源可用,返回原始歌曲
console.log('[ResourceChecker] 资源可用:', song.name)
return song
} catch (error) {
// 资源不可用,刷新歌曲信息
console.log('[ResourceChecker] 资源不可用,正在刷新:', song.name, error)
try {
const updatedSong = await apis.getSong(song.cid)
console.log('[ResourceChecker] 歌曲信息已刷新:', updatedSong.name)
// 调用更新回调(如果提供)
if (updateCallback) {
updateCallback(updatedSong)
}
return updatedSong
} catch (refreshError) {
console.error('[ResourceChecker] 刷新歌曲信息失败:', refreshError)
// 刷新失败,返回原始歌曲
return song
}
}
}
/**
*
* @param songs
* @param updateCallback
* @returns
*/
export const checkAndRefreshMultipleSongs = async (
songs: Song[],
updateCallback?: (updatedSong: Song, originalIndex: number) => void
): Promise<Song[]> => {
const results: Song[] = []
for (let i = 0; i < songs.length; i++) {
const originalSong = songs[i]
const updatedSong = await checkAndRefreshSongResource(
originalSong,
updateCallback ? (updated) => updateCallback(updated, i) : undefined
)
results.push(updatedSong)
}
return results
}