mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 16:27:23 +08:00
fix(lyric): 重启后桌面歌词显示无歌词
playerCore.playMusic 整体替换 (id 不变) 时, lyric watcher 的响应式追踪不可靠
点击播放走 playTrack 流程也未必能可靠触发解析
- 提取 ensureLyricsLoaded 到 module 级别, 直接读 playMusic.value 解析
- openLyric 入口主动调用 (重启首次打开桌面歌词场景)
- onLyricWindowReady 兜底 (窗口异步就绪时 lyric 字段可能刚到位)
- audioService.on('play') 兜底 (重启后首次点击播放场景)
- 在线歌曲 lyric 字段缺失时, 主动调 getMusicLrc API 兜底
This commit is contained in:
+127
-126
@@ -149,124 +149,125 @@ const parseLyricsString = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置音乐相关的监听器
|
// 解析当前 playMusic.lyric 写入 lrcArray, 供 watcher / openLyric / onLyricWindowReady 共用
|
||||||
|
const ensureLyricsLoaded = async (force = false) => {
|
||||||
|
const songId = playMusic.value?.id;
|
||||||
|
if (!songId) {
|
||||||
|
lrcArray.value = [];
|
||||||
|
lrcTimeArray.value = [];
|
||||||
|
nowIndex.value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!force && lrcArray.value.length > 0) return;
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const lyricData = playMusic.value.lyric;
|
||||||
|
if (lyricData && typeof lyricData === 'string') {
|
||||||
|
const {
|
||||||
|
lrcArray: parsedLrcArray,
|
||||||
|
lrcTimeArray: parsedTimeArray,
|
||||||
|
hasWordByWord
|
||||||
|
} = await parseLyricsString(lyricData);
|
||||||
|
lrcArray.value = parsedLrcArray;
|
||||||
|
lrcTimeArray.value = parsedTimeArray;
|
||||||
|
|
||||||
|
if (playMusic.value.lyric && typeof playMusic.value.lyric === 'object') {
|
||||||
|
playMusic.value.lyric.hasWordByWord = hasWordByWord;
|
||||||
|
}
|
||||||
|
} else if (lyricData && typeof lyricData === 'object' && lyricData.lrcArray?.length > 0) {
|
||||||
|
const rawLrc = lyricData.lrcArray || [];
|
||||||
|
lrcTimeArray.value = lyricData.lrcTimeArray || [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { translateLyrics } = await import('@/services/lyricTranslation');
|
||||||
|
lrcArray.value = await translateLyrics(rawLrc as any);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('翻译歌词失败,使用原始歌词:', e);
|
||||||
|
lrcArray.value = rawLrc as any;
|
||||||
|
}
|
||||||
|
} else if (isElectron && playMusic.value.playMusicUrl?.startsWith('local:///')) {
|
||||||
|
try {
|
||||||
|
let filePath = decodeURIComponent(playMusic.value.playMusicUrl.replace('local:///', ''));
|
||||||
|
// 处理 Windows 路径:/C:/... → C:/...
|
||||||
|
if (/^\/[a-zA-Z]:\//.test(filePath)) {
|
||||||
|
filePath = filePath.slice(1);
|
||||||
|
}
|
||||||
|
const embeddedLyrics = await window.api.getEmbeddedLyrics(filePath);
|
||||||
|
if (embeddedLyrics) {
|
||||||
|
const {
|
||||||
|
lrcArray: parsedLrcArray,
|
||||||
|
lrcTimeArray: parsedTimeArray,
|
||||||
|
hasWordByWord
|
||||||
|
} = await parseLyricsString(embeddedLyrics);
|
||||||
|
lrcArray.value = parsedLrcArray;
|
||||||
|
lrcTimeArray.value = parsedTimeArray;
|
||||||
|
if (playMusic.value.lyric && typeof playMusic.value.lyric === 'object') {
|
||||||
|
(playMusic.value.lyric as any).hasWordByWord = hasWordByWord;
|
||||||
|
}
|
||||||
|
} else if (typeof songId === 'number') {
|
||||||
|
try {
|
||||||
|
const { getMusicLrc } = await import('@/api/music');
|
||||||
|
const res = await getMusicLrc(songId);
|
||||||
|
if (res?.data?.lrc?.lyric) {
|
||||||
|
const { lrcArray: apiLrcArray, lrcTimeArray: apiTimeArray } = await parseLyricsString(
|
||||||
|
res.data.lrc.lyric
|
||||||
|
);
|
||||||
|
lrcArray.value = apiLrcArray;
|
||||||
|
lrcTimeArray.value = apiTimeArray;
|
||||||
|
}
|
||||||
|
} catch (apiErr) {
|
||||||
|
console.error('API lyrics fallback failed:', apiErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to extract embedded lyrics:', err);
|
||||||
|
}
|
||||||
|
} else if (typeof songId === 'number') {
|
||||||
|
// 在线歌曲但 lyric 字段尚未加载, 主动调 API 兜底
|
||||||
|
try {
|
||||||
|
const { getMusicLrc } = await import('@/api/music');
|
||||||
|
const res = await getMusicLrc(songId);
|
||||||
|
if (res?.data?.lrc?.lyric) {
|
||||||
|
const { lrcArray: apiLrcArray, lrcTimeArray: apiTimeArray } = await parseLyricsString(
|
||||||
|
res.data.lrc.lyric
|
||||||
|
);
|
||||||
|
lrcArray.value = apiLrcArray;
|
||||||
|
lrcTimeArray.value = apiTimeArray;
|
||||||
|
}
|
||||||
|
} catch (apiErr) {
|
||||||
|
console.error('API lyrics fallback failed:', apiErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isElectron && isLyricWindowOpen.value) {
|
||||||
|
sendLyricToWin();
|
||||||
|
setTimeout(() => sendLyricToWin(), 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const setupMusicWatchers = () => {
|
const setupMusicWatchers = () => {
|
||||||
const store = getPlayerStore();
|
const store = getPlayerStore();
|
||||||
|
|
||||||
// 监听 playerStore.playMusic 的变化以更新歌词数据
|
// 切歌时 id 变化, 强制重新解析
|
||||||
watch(
|
watch(
|
||||||
() => store.playMusic.id,
|
() => store.playMusic.id,
|
||||||
async (newId, oldId) => {
|
async (newId, oldId) => {
|
||||||
// 如果没有歌曲ID,清空歌词
|
if (newId !== oldId) nowIndex.value = 0;
|
||||||
if (!newId) {
|
await ensureLyricsLoaded(true);
|
||||||
lrcArray.value = [];
|
|
||||||
lrcTimeArray.value = [];
|
|
||||||
nowIndex.value = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 避免相同ID的重复执行(但允许初始化时执行)
|
|
||||||
if (newId === oldId && lrcArray.value.length > 0) return;
|
|
||||||
|
|
||||||
// 歌曲切换时重置歌词索引
|
|
||||||
if (newId !== oldId) {
|
|
||||||
nowIndex.value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await nextTick(async () => {
|
|
||||||
console.log('歌曲切换,更新歌词数据');
|
|
||||||
|
|
||||||
// 检查是否有原始歌词字符串需要解析
|
|
||||||
const lyricData = playMusic.value.lyric;
|
|
||||||
if (lyricData && typeof lyricData === 'string') {
|
|
||||||
// 如果歌词是字符串格式,使用新的解析器
|
|
||||||
const {
|
|
||||||
lrcArray: parsedLrcArray,
|
|
||||||
lrcTimeArray: parsedTimeArray,
|
|
||||||
hasWordByWord
|
|
||||||
} = await parseLyricsString(lyricData);
|
|
||||||
lrcArray.value = parsedLrcArray;
|
|
||||||
lrcTimeArray.value = parsedTimeArray;
|
|
||||||
|
|
||||||
// 更新歌曲的歌词数据结构
|
|
||||||
if (playMusic.value.lyric && typeof playMusic.value.lyric === 'object') {
|
|
||||||
playMusic.value.lyric.hasWordByWord = hasWordByWord;
|
|
||||||
}
|
|
||||||
} else if (lyricData && typeof lyricData === 'object' && lyricData.lrcArray?.length > 0) {
|
|
||||||
// 使用现有的歌词数据结构
|
|
||||||
const rawLrc = lyricData.lrcArray || [];
|
|
||||||
lrcTimeArray.value = lyricData.lrcTimeArray || [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { translateLyrics } = await import('@/services/lyricTranslation');
|
|
||||||
lrcArray.value = await translateLyrics(rawLrc as any);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('翻译歌词失败,使用原始歌词:', e);
|
|
||||||
lrcArray.value = rawLrc as any;
|
|
||||||
}
|
|
||||||
} else if (isElectron && playMusic.value.playMusicUrl?.startsWith('local:///')) {
|
|
||||||
// 从下载/本地文件的 ID3/FLAC 元数据中提取嵌入歌词
|
|
||||||
try {
|
|
||||||
let filePath = decodeURIComponent(
|
|
||||||
playMusic.value.playMusicUrl.replace('local:///', '')
|
|
||||||
);
|
|
||||||
// 处理 Windows 路径:/C:/... → C:/...
|
|
||||||
if (/^\/[a-zA-Z]:\//.test(filePath)) {
|
|
||||||
filePath = filePath.slice(1);
|
|
||||||
}
|
|
||||||
const embeddedLyrics = await window.api.getEmbeddedLyrics(filePath);
|
|
||||||
if (embeddedLyrics) {
|
|
||||||
const {
|
|
||||||
lrcArray: parsedLrcArray,
|
|
||||||
lrcTimeArray: parsedTimeArray,
|
|
||||||
hasWordByWord
|
|
||||||
} = await parseLyricsString(embeddedLyrics);
|
|
||||||
lrcArray.value = parsedLrcArray;
|
|
||||||
lrcTimeArray.value = parsedTimeArray;
|
|
||||||
if (playMusic.value.lyric && typeof playMusic.value.lyric === 'object') {
|
|
||||||
(playMusic.value.lyric as any).hasWordByWord = hasWordByWord;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 无嵌入歌词 — 若有数字 ID,尝试 API 兜底
|
|
||||||
const songId = playMusic.value.id;
|
|
||||||
if (songId && typeof songId === 'number') {
|
|
||||||
try {
|
|
||||||
const { getMusicLrc } = await import('@/api/music');
|
|
||||||
const res = await getMusicLrc(songId);
|
|
||||||
if (res?.data?.lrc?.lyric) {
|
|
||||||
const { lrcArray: apiLrcArray, lrcTimeArray: apiTimeArray } =
|
|
||||||
await parseLyricsString(res.data.lrc.lyric);
|
|
||||||
lrcArray.value = apiLrcArray;
|
|
||||||
lrcTimeArray.value = apiTimeArray;
|
|
||||||
}
|
|
||||||
} catch (apiErr) {
|
|
||||||
console.error('API lyrics fallback failed:', apiErr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to extract embedded lyrics:', err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 无歌词数据
|
|
||||||
lrcArray.value = [];
|
|
||||||
lrcTimeArray.value = [];
|
|
||||||
}
|
|
||||||
// 当歌词数据更新时,如果歌词窗口打开,则发送数据
|
|
||||||
if (isElectron && isLyricWindowOpen.value) {
|
|
||||||
console.log('歌词窗口已打开,同步最新歌词数据');
|
|
||||||
// 不管歌词数组是否为空,都发送最新数据
|
|
||||||
sendLyricToWin();
|
|
||||||
|
|
||||||
// 再次延迟发送,确保歌词窗口已完全加载
|
|
||||||
setTimeout(() => {
|
|
||||||
sendLyricToWin();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 同一首歌但 lyric 字段后到 (重启 + autoPlay 关闭场景)
|
||||||
|
watch(
|
||||||
|
() => playMusic.value?.lyric,
|
||||||
|
() => {
|
||||||
|
if (lrcArray.value.length === 0 && playMusic.value?.id) {
|
||||||
|
ensureLyricsLoaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupAudioListeners = () => {
|
const setupAudioListeners = () => {
|
||||||
@@ -486,7 +487,10 @@ const setupAudioListeners = () => {
|
|||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
window.api.sendSong(cloneDeep(getPlayerStore().playMusic));
|
window.api.sendSong(cloneDeep(getPlayerStore().playMusic));
|
||||||
}
|
}
|
||||||
// 启动进度更新
|
// 兜底: 重启后首次点播放时 lrcArray 仍为空则主动加载
|
||||||
|
if (lrcArray.value.length === 0 && playMusic.value?.id) {
|
||||||
|
ensureLyricsLoaded();
|
||||||
|
}
|
||||||
startProgressInterval();
|
startProgressInterval();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -893,28 +897,20 @@ const stopLyricSync = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 修改openLyric函数,添加定时同步
|
export const openLyric = async () => {
|
||||||
export const openLyric = () => {
|
|
||||||
if (!isElectron) return;
|
if (!isElectron) return;
|
||||||
|
|
||||||
// 检查是否有播放中的歌曲
|
|
||||||
if (!playMusic.value || !playMusic.value.id) {
|
if (!playMusic.value || !playMusic.value.id) {
|
||||||
console.log('没有正在播放的歌曲,无法打开歌词窗口');
|
console.log('没有正在播放的歌曲,无法打开歌词窗口');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Opening lyric window with current song:', playMusic.value?.name);
|
|
||||||
|
|
||||||
isLyricWindowOpen.value = !isLyricWindowOpen.value;
|
isLyricWindowOpen.value = !isLyricWindowOpen.value;
|
||||||
if (isLyricWindowOpen.value) {
|
if (isLyricWindowOpen.value) {
|
||||||
// 立即打开窗口
|
|
||||||
window.api.openLyric();
|
window.api.openLyric();
|
||||||
|
|
||||||
// 确保有歌词数据,如果没有,则使用默认的"无歌词"提示
|
// 先发"加载中"占位, 防止窗口启动期间显示"无歌词"
|
||||||
if (!lrcArray.value || lrcArray.value.length === 0) {
|
if (!lrcArray.value || lrcArray.value.length === 0) {
|
||||||
// 如果当前播放的歌曲有ID但没有歌词,则尝试加载歌词
|
|
||||||
console.log('尝试加载歌词数据...');
|
|
||||||
// 发送默认的"无歌词"数据
|
|
||||||
const emptyLyricData = {
|
const emptyLyricData = {
|
||||||
type: 'empty',
|
type: 'empty',
|
||||||
nowIndex: 0,
|
nowIndex: 0,
|
||||||
@@ -928,12 +924,15 @@ export const openLyric = () => {
|
|||||||
playMusic: playMusic.value
|
playMusic: playMusic.value
|
||||||
};
|
};
|
||||||
window.api.sendLyric(JSON.stringify(emptyLyricData));
|
window.api.sendLyric(JSON.stringify(emptyLyricData));
|
||||||
|
|
||||||
|
// 关键: 主动加载歌词, 不依赖 watcher
|
||||||
|
// (重启场景下 playerCore.playMusic 整体替换可能未触发 lyric watcher)
|
||||||
|
await ensureLyricsLoaded(true);
|
||||||
} else {
|
} else {
|
||||||
// 发送完整歌词数据
|
|
||||||
sendLyricToWin();
|
sendLyricToWin();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 延迟重发一次,以防窗口加载略慢
|
// 延迟重发, 防窗口加载慢丢消息
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (isLyricWindowOpen.value) {
|
if (isLyricWindowOpen.value) {
|
||||||
sendLyricToWin();
|
sendLyricToWin();
|
||||||
@@ -1055,11 +1054,13 @@ export const initAudioListeners = async () => {
|
|||||||
window.api.onLyricWindowClosed(() => {
|
window.api.onLyricWindowClosed(() => {
|
||||||
isLyricWindowOpen.value = false;
|
isLyricWindowOpen.value = false;
|
||||||
});
|
});
|
||||||
// 歌词窗口 Vue 加载完成后,发送完整歌词数据
|
window.api.onLyricWindowReady(async () => {
|
||||||
window.api.onLyricWindowReady(() => {
|
if (!isLyricWindowOpen.value) return;
|
||||||
if (isLyricWindowOpen.value) {
|
// 窗口加载完成时再兜底加载一次, 防止 openLyric 阶段 lyric 字段尚未到位
|
||||||
sendLyricToWin();
|
if (lrcArray.value.length === 0 && playMusic.value?.id) {
|
||||||
|
await ensureLyricsLoaded(true);
|
||||||
}
|
}
|
||||||
|
sendLyricToWin();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user