mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-07-07 18:07:32 +08:00
fix(player): 切歌时封面/歌词/背景色与音频同步就绪
修复 #712 重构引入的体验回退:全屏页点下一首会'先响歌、后换脸'—— 歌词/背景色在播放成功后才开始加载。 - playTrack 的元数据加载(歌词+背景色)提前到与取 URL/加载音频并行: 不阻塞出声,视觉信息通常在起播前就绪;playMusic 被 getSongDetail 替换后重新应用已到达的元数据,避免丢失;generation 拦截过期请求 - 预加载下一首时顺带预热背景色(歌词已有预热),切歌时元数据全部 缓存命中、立即应用 测试:时序状态机模拟 3 场景全过(元数据先到不丢失/后到补上/快速 切歌不污染),typecheck 与构建通过。
This commit is contained in:
@@ -200,6 +200,32 @@ export const playTrack = async (
|
||||
// 保存原始歌曲数据
|
||||
const originalMusic = { ...music };
|
||||
|
||||
// 4.5 立即并行加载元数据(歌词 + 背景色),与取 URL/加载音频同时进行:
|
||||
// 不阻塞出声,但让全屏页的封面/歌词/背景色尽量在音频起播前就绪,
|
||||
// 避免"先响歌、后换脸"的割裂感。预加载过的歌曲两者都是缓存命中,立即应用
|
||||
let loadedMetadata: {
|
||||
lyrics: SongResult['lyric'];
|
||||
backgroundColor: string;
|
||||
primaryColor: string;
|
||||
} | null = null;
|
||||
const applyLoadedMetadata = () => {
|
||||
if (!loadedMetadata || gen !== generation) return;
|
||||
playerCore.playMusic.lyric = loadedMetadata.lyrics;
|
||||
playerCore.playMusic.backgroundColor = loadedMetadata.backgroundColor;
|
||||
playerCore.playMusic.primaryColor = loadedMetadata.primaryColor;
|
||||
// 触发 watcher 更新
|
||||
playerCore.playMusic = { ...playerCore.playMusic };
|
||||
};
|
||||
loadMetadata(originalMusic)
|
||||
.then((result) => {
|
||||
loadedMetadata = result;
|
||||
applyLoadedMetadata();
|
||||
})
|
||||
.catch((error) => {
|
||||
// 元数据加载失败不阻塞播放
|
||||
console.warn('[playbackController] 元数据加载失败:', error);
|
||||
});
|
||||
|
||||
// 5. 添加到播放历史
|
||||
try {
|
||||
const playHistoryStore = await getPlayHistoryStore();
|
||||
@@ -228,6 +254,8 @@ export const playTrack = async (
|
||||
playerCore.playMusic = updatedPlayMusic;
|
||||
playerCore.playMusicUrl = updatedPlayMusic.playMusicUrl as string;
|
||||
music.playMusicUrl = updatedPlayMusic.playMusicUrl as string;
|
||||
// playMusic 被替换为新对象,若元数据已先行到达则重新应用,避免歌词/背景色丢失
|
||||
applyLoadedMetadata();
|
||||
} catch (error) {
|
||||
if (gen !== generation) return false;
|
||||
console.error('[playbackController] 获取歌曲详情失败:', error);
|
||||
@@ -251,21 +279,7 @@ export const playTrack = async (
|
||||
}
|
||||
|
||||
if (success) {
|
||||
// 8. 播放成功后,异步加载元数据(不阻塞)
|
||||
loadMetadata(playerCore.playMusic).then(({ lyrics, backgroundColor, primaryColor }) => {
|
||||
// 检查 generation(确保还是当前歌曲)
|
||||
if (gen !== generation) {
|
||||
console.log(`[playbackController] gen=${gen} 已过期,跳过元数据更新`);
|
||||
return;
|
||||
}
|
||||
playerCore.playMusic.lyric = lyrics;
|
||||
playerCore.playMusic.backgroundColor = backgroundColor;
|
||||
playerCore.playMusic.primaryColor = primaryColor;
|
||||
// 触发 watcher 更新
|
||||
playerCore.playMusic = { ...playerCore.playMusic };
|
||||
}).catch((error) => {
|
||||
console.warn('[playbackController] 元数据加载失败:', error);
|
||||
});
|
||||
// 8. 元数据已在 4.5 步与播放并行加载,此处无需再处理
|
||||
|
||||
// 9. 播放成功
|
||||
playerCore.playMusic.playLoading = false;
|
||||
|
||||
@@ -84,6 +84,21 @@ export const usePlaylistStore = defineStore(
|
||||
}
|
||||
}
|
||||
|
||||
// 预热下一首的背景色:切歌时 playTrack 的元数据加载可直接缓存命中,
|
||||
// 全屏页封面/背景色与音频同步就绪,不再"先响歌、后换背景"
|
||||
if (nextSong?.picUrl && !(nextSong.backgroundColor && nextSong.primaryColor)) {
|
||||
try {
|
||||
const { getImageLinearBackground } = await import('@/utils/linearColor');
|
||||
const { backgroundColor, primaryColor } = await getImageLinearBackground(
|
||||
getImgUrl(nextSong.picUrl, '30y30')
|
||||
);
|
||||
nextSong.backgroundColor = backgroundColor;
|
||||
nextSong.primaryColor = primaryColor;
|
||||
} catch (error) {
|
||||
console.warn('预热背景色失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
detailedSongs.forEach((song, index) => {
|
||||
if (song && startIndex + index < playList.value.length) {
|
||||
playList.value[startIndex + index] = song;
|
||||
|
||||
Reference in New Issue
Block a user