mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-08-05 15:25:46 +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 };
|
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. 添加到播放历史
|
// 5. 添加到播放历史
|
||||||
try {
|
try {
|
||||||
const playHistoryStore = await getPlayHistoryStore();
|
const playHistoryStore = await getPlayHistoryStore();
|
||||||
@@ -228,6 +254,8 @@ export const playTrack = async (
|
|||||||
playerCore.playMusic = updatedPlayMusic;
|
playerCore.playMusic = updatedPlayMusic;
|
||||||
playerCore.playMusicUrl = updatedPlayMusic.playMusicUrl as string;
|
playerCore.playMusicUrl = updatedPlayMusic.playMusicUrl as string;
|
||||||
music.playMusicUrl = updatedPlayMusic.playMusicUrl as string;
|
music.playMusicUrl = updatedPlayMusic.playMusicUrl as string;
|
||||||
|
// playMusic 被替换为新对象,若元数据已先行到达则重新应用,避免歌词/背景色丢失
|
||||||
|
applyLoadedMetadata();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (gen !== generation) return false;
|
if (gen !== generation) return false;
|
||||||
console.error('[playbackController] 获取歌曲详情失败:', error);
|
console.error('[playbackController] 获取歌曲详情失败:', error);
|
||||||
@@ -251,21 +279,7 @@ export const playTrack = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
// 8. 播放成功后,异步加载元数据(不阻塞)
|
// 8. 元数据已在 4.5 步与播放并行加载,此处无需再处理
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 9. 播放成功
|
// 9. 播放成功
|
||||||
playerCore.playMusic.playLoading = false;
|
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) => {
|
detailedSongs.forEach((song, index) => {
|
||||||
if (song && startIndex + index < playList.value.length) {
|
if (song && startIndex + index < playList.value.length) {
|
||||||
playList.value[startIndex + index] = song;
|
playList.value[startIndex + index] = song;
|
||||||
|
|||||||
Reference in New Issue
Block a user