Files
AlgerMusicPlayer/src/renderer/hooks/MusicHook.ts
T
alger 61797494cb fix(lyric): 点击歌词跳转不再跳到歌曲末尾触发切歌
根因:MusicHook.parseLyricsString 的 lrcTimeArray 直接使用 yrcParser 的
毫秒时间戳,而 seek/nowTime 全链路以秒为单位。合并播放重构(#712)后
'先播放、歌词后到'使 ensureLyricsLoaded 的 API 兜底分支成为在线歌曲
常规路径,点击歌词 seek(毫秒值) 被钳到音频末尾直接触发 ended 切歌,
歌词高亮同步也一并失效。

修复:
- parseLyricsString 时间数组统一换算为秒,与 usePlayerHooks.parseLyrics 一致
- 完整歌词对象(yrc 逐字/翻译)异步送达时强制重新解析,替换先行的兜底纯 lrc

测试:以真实 LRC/YRC 样本驱动真实 yrcParser 验证 7 项断言全过,
含修复前行为回归复现(seek(28910) 钳到末尾触发切歌)与修复后
全行点击不触发切歌、两解析路径单位一致性。
2026-07-05 16:05:03 +08:00

1081 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { cloneDeep } from 'lodash';
import { computed, type ComputedRef, nextTick, onUnmounted, ref, watch } from 'vue';
import useIndexedDB from '@/hooks/IndexDBHook';
import { audioService } from '@/services/audioService';
import type { usePlayerStore } from '@/store';
import type { Artist, ILyricText, SongResult } from '@/types/music';
import { isElectron } from '@/utils';
import { getTextColors } from '@/utils/linearColor';
import { parseLyrics } from '@/utils/yrcParser';
const windowData = window as any;
// 全局 playerStore 引用,通过 initMusicHook 函数注入
let playerStore: ReturnType<typeof usePlayerStore> | null = null;
// 初始化函数,接受 store 实例
export const initMusicHook = (store: ReturnType<typeof usePlayerStore>) => {
playerStore = store;
// 创建 computed 属性
playMusic = computed(() => getPlayerStore().playMusic as SongResult);
artistList = computed(
() => (getPlayerStore().playMusic.ar || getPlayerStore().playMusic?.song?.artists) as Artist[]
);
// 在 store 注入后初始化需要 store 的功能
setupKeyboardListeners();
setupMusicWatchers();
setupCorrectionTimeWatcher();
setupPlayStateWatcher();
};
// 获取 playerStore 的辅助函数
const getPlayerStore = () => {
if (!playerStore) {
throw new Error('MusicHook not initialized. Call initMusicHook first.');
}
return playerStore;
};
export const lrcArray = ref<ILyricText[]>([]); // 歌词数组
export const lrcTimeArray = ref<number[]>([]); // 歌词时间数组
export const nowTime = ref(0); // 当前播放时间
export const allTime = ref(0); // 总播放时间
export const nowIndex = ref(0); // 当前播放歌词
export const currentLrcProgress = ref(0); // 来存储当前歌词的进度
export const sound = ref<HTMLAudioElement | null>(audioService.getCurrentSound());
export const isLyricWindowOpen = ref(false); // 新增状态
export const textColors = ref<any>(getTextColors());
// 这些 computed 属性需要在初始化后创建
export let playMusic: ComputedRef<SongResult>;
export let artistList: ComputedRef<Artist[]>;
let lastIndex = -1;
// 缓存平台信息,避免每次歌词变化时同步 IPC 调用
const cachedPlatform = isElectron ? window.electron.ipcRenderer.sendSync('get-platform') : 'web';
export const musicDB = await useIndexedDB(
'musicDB',
[
{ name: 'music', keyPath: 'id' },
{ name: 'music_lyric', keyPath: 'id' },
{ name: 'api_cache', keyPath: 'id' },
{ name: 'music_url_cache', keyPath: 'id' },
{ name: 'music_failed_cache', keyPath: 'id' }
],
3
);
// 键盘事件处理器(提取为命名函数,防止重复注册)
const handleKeyUp = (e: KeyboardEvent) => {
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
return;
}
const store = getPlayerStore();
switch (e.code) {
case 'Space':
if (store.playMusic?.id) {
void store.setPlay({ ...store.playMusic });
}
break;
default:
}
};
const setupKeyboardListeners = () => {
document.removeEventListener('keyup', handleKeyUp);
document.addEventListener('keyup', handleKeyUp);
};
let audioListenersInitialized = false;
/**
* 解析歌词字符串并转换为ILyricText格式
* @param lyricsStr 歌词字符串
* @returns 解析后的歌词数据
*/
const parseLyricsString = async (
lyricsStr: string
): Promise<{ lrcArray: ILyricText[]; lrcTimeArray: number[]; hasWordByWord: boolean }> => {
if (!lyricsStr || typeof lyricsStr !== 'string') {
return { lrcArray: [], lrcTimeArray: [], hasWordByWord: false };
}
try {
const parseResult = parseLyrics(lyricsStr);
console.log('parseResult', parseResult);
if (!parseResult.success) {
console.error('歌词解析失败:', parseResult.error.message);
return { lrcArray: [], lrcTimeArray: [], hasWordByWord: false };
}
const { lyrics } = parseResult.data;
const lrcArray: ILyricText[] = [];
const lrcTimeArray: number[] = [];
let hasWordByWord = false;
for (const line of lyrics) {
// 检查是否有逐字歌词
const hasWords = line.words && line.words.length > 0;
if (hasWords) {
hasWordByWord = true;
}
lrcArray.push({
text: line.fullText,
trText: '', // 翻译文本稍后处理
words: hasWords
? line.words.map((word) => ({
...word
}))
: undefined,
hasWordByWord: hasWords,
startTime: line.startTime,
duration: line.duration
});
// yrcParser 的 startTime 是毫秒;lrcTimeArray 全链路(nowTime 对比、
// setAudioTime seek)以秒为单位,必须换算,否则点击歌词会 seek 到
// 远超时长的位置被钳到末尾、直接触发切歌
lrcTimeArray.push(line.startTime / 1000);
}
return { lrcArray, lrcTimeArray, hasWordByWord };
} catch (error) {
console.error('解析歌词时发生错误:', error);
return { lrcArray: [], lrcTimeArray: [], hasWordByWord: false };
}
};
// 解析当前 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 store = getPlayerStore();
// 切歌时 id 变化, 强制重新解析
watch(
() => store.playMusic.id,
async (newId, oldId) => {
if (newId !== oldId) nowIndex.value = 0;
await ensureLyricsLoaded(true);
},
{ immediate: true }
);
// 同一首歌但 lyric 字段后到 (播放后异步加载元数据 / 重启 + autoPlay 关闭场景)
watch(
() => playMusic.value?.lyric,
(newLyric) => {
if (!playMusic.value?.id) return;
// 完整歌词对象(含 yrc 逐字/翻译,时间单位为秒)后到时强制重新解析,
// 替换掉先行的 API 兜底纯 lrc 歌词
const isRichLyric =
!!newLyric && typeof newLyric === 'object' && (newLyric.lrcArray?.length ?? 0) > 0;
if (lrcArray.value.length === 0 || isRichLyric) {
ensureLyricsLoaded(isRichLyric);
}
}
);
};
const setupAudioListeners = () => {
// 监听器只注册一次,避免重复绑定和误清理全局恢复监听器
if (audioListenersInitialized) {
return () => {};
}
audioListenersInitialized = true;
let interval: number | null = null;
// 播放状态恢复定时器:当 interval 因异常被清除时,自动恢复
let recoveryTimer: number | null = null;
let lyricThrottleCounter = 0;
let lastSavedProgress = 0;
const clearInterval = () => {
if (interval) {
window.clearInterval(interval);
interval = null;
}
};
const stopRecovery = () => {
if (recoveryTimer) {
window.clearInterval(recoveryTimer);
recoveryTimer = null;
}
};
/**
* 启动进度更新 interval
* 从 audioService 实时获取 sound 引用,避免闭包中 sound.value 过期
*/
const startProgressInterval = () => {
clearInterval();
interval = window.setInterval(() => {
try {
// 每次从 audioService 获取最新的 sound 引用,而不是依赖闭包中的 sound.value
const currentSound = audioService.getCurrentSound();
if (!currentSound) {
// sound 暂时为空(可能在切歌/重建中),不清除 interval,等待恢复
return;
}
const currentTime = currentSound.currentTime;
if (typeof currentTime !== 'number' || Number.isNaN(currentTime)) {
// 无效时间,跳过本次更新
return;
}
// 同步 sound.value 引用(确保外部也能拿到最新的)
if (sound.value !== currentSound) {
sound.value = currentSound;
}
nowTime.value = currentTime;
allTime.value = currentSound.duration;
// === 歌词索引更新 ===
const newIndex = getLrcIndex(nowTime.value);
if (newIndex !== nowIndex.value) {
nowIndex.value = newIndex;
currentLrcProgress.value = 0; // 换行时重置进度
if (isElectron && isLyricWindowOpen.value) {
sendLyricToWin();
}
}
if (isElectron && lrcArray.value[nowIndex.value]) {
if (lastIndex !== nowIndex.value) {
sendTrayLyric(nowIndex.value);
lastIndex = nowIndex.value;
}
}
// === 逐字歌词行内进度 ===
const { start, end } = currentLrcTiming.value;
if (typeof start === 'number' && typeof end === 'number' && start !== end) {
const elapsed = currentTime - start;
const duration = end - start;
const progress = (elapsed / duration) * 100;
currentLrcProgress.value = Math.min(Math.max(progress, 0), 100);
}
// === 节流发送轻量歌词进度更新(每 ~200ms / 约每 4 个 tick===
lyricThrottleCounter++;
if (isElectron && isLyricWindowOpen.value && lyricThrottleCounter % 4 === 0) {
try {
window.api.sendLyric(
JSON.stringify({
type: 'update',
nowIndex: nowIndex.value,
nowTime: nowTime.value,
isPlay: getPlayerStore().play
})
);
} catch {
// 忽略发送失败
}
}
// === localStorage 进度保存(每 ~2 秒)===
if (
Math.floor(currentTime) % 2 === 0 &&
Math.floor(currentTime) !== Math.floor(lastSavedProgress)
) {
lastSavedProgress = currentTime;
if (getPlayerStore().playMusic?.id) {
localStorage.setItem(
'playProgress',
JSON.stringify({
songId: getPlayerStore().playMusic.id,
progress: currentTime
})
);
}
}
// === MPRIS 进度更新(每 ~1 秒)===
if (isElectron && lyricThrottleCounter % 20 === 0) {
try {
window.electron.ipcRenderer.send('mpris-position-update', currentTime);
} catch {
// 忽略发送失败
}
}
} catch (error) {
console.error('进度更新 interval 出错:', error);
// 出错时不清除 interval,让下一次 tick 继续尝试
}
}, 50);
};
/**
* 启动播放状态恢复监控
* 每 500ms 检查一次:如果 store 认为在播放但 interval 已丢失,则恢复
*/
const startRecoveryMonitor = () => {
stopRecovery();
recoveryTimer = window.setInterval(() => {
try {
const store = getPlayerStore();
if (store.play && !interval) {
const currentSound = audioService.getCurrentSound();
if (currentSound && !currentSound.paused) {
console.warn('[MusicHook] 检测到播放中但 interval 丢失,自动恢复');
startProgressInterval();
}
}
} catch {
// 静默忽略
}
}, 500);
};
// 启动恢复监控
startRecoveryMonitor();
// 监听seek开始事件,立即更新UI
audioService.on('seek_start', (time) => {
// 直接更新显示位置,不检查拖动状态
nowTime.value = time;
});
// 监听seek完成事件
audioService.on('seek', () => {
try {
const currentSound = audioService.getCurrentSound();
if (currentSound) {
// 立即更新显示时间,不进行任何检查
const currentTime = currentSound.currentTime;
if (typeof currentTime === 'number' && !Number.isNaN(currentTime)) {
nowTime.value = currentTime;
// === MPRIS seek 时同步进度 ===
if (isElectron) {
window.electron.ipcRenderer.send('mpris-position-update', currentTime);
}
// 检查是否需要更新歌词
const newIndex = getLrcIndex(nowTime.value);
if (newIndex !== nowIndex.value) {
nowIndex.value = newIndex;
if (isElectron && isLyricWindowOpen.value) {
sendLyricToWin();
}
}
}
}
} catch (error) {
console.error('处理seek事件出错:', error);
}
});
// 立即更新一次时间和进度(解决初始化时进度条不显示问题)
const updateCurrentTimeAndDuration = () => {
const currentSound = audioService.getCurrentSound();
if (currentSound) {
try {
// 更新当前时间和总时长
const currentTime = currentSound.currentTime;
if (typeof currentTime === 'number' && !Number.isNaN(currentTime)) {
nowTime.value = currentTime;
allTime.value = currentSound.duration;
}
} catch (error) {
console.error('初始化时间和进度失败:', error);
}
}
};
// 立即执行一次更新
updateCurrentTimeAndDuration();
// 监听播放
audioService.on('play', () => {
getPlayerStore().setPlayMusic(true);
if (isElectron) {
window.api.sendSong(cloneDeep(getPlayerStore().playMusic));
}
// 兜底: 重启后首次点播放时 lrcArray 仍为空则主动加载
if (lrcArray.value.length === 0 && playMusic.value?.id) {
ensureLyricsLoaded();
}
startProgressInterval();
});
// 监听暂停
audioService.on('pause', () => {
console.log('音频暂停事件触发');
getPlayerStore().setPlayMusic(false);
clearInterval();
if (isElectron && isLyricWindowOpen.value) {
sendLyricToWin();
}
});
const replayMusic = async (retryCount = 0) => {
const MAX_REPLAY_RETRIES = 3;
try {
if (getPlayerStore().playMusicUrl && playMusic.value) {
await audioService.play(getPlayerStore().playMusicUrl, playMusic.value);
sound.value = audioService.getCurrentSound();
setupAudioListeners();
} else {
console.error('单曲循环:无可用 URL 或歌曲数据');
const { usePlaylistStore } = await import('@/store/modules/playlist');
usePlaylistStore().nextPlayOnEnd();
}
} catch (error) {
console.error('单曲循环重播失败:', error);
if (retryCount < MAX_REPLAY_RETRIES) {
setTimeout(() => replayMusic(retryCount + 1), 1000 * (retryCount + 1));
} else {
const { usePlaylistStore } = await import('@/store/modules/playlist');
usePlaylistStore().nextPlayOnEnd();
}
}
};
// 监听结束
audioService.on('end', async () => {
console.log('音频播放结束事件触发');
clearInterval();
if (getPlayerStore().playMode === 1) {
// 单曲循环模式
replayMusic();
return;
}
// 其他模式(FM/顺序/列表循环/随机):交给 playlist store 路由
const { usePlaylistStore } = await import('@/store/modules/playlist');
usePlaylistStore().nextPlayOnEnd();
});
audioService.on('previoustrack', () => {
getPlayerStore().prevPlay();
});
audioService.on('nexttrack', () => {
getPlayerStore().nextPlay();
});
return () => {
clearInterval();
stopRecovery();
};
};
export const play = () => {
const currentSound = audioService.getCurrentSound();
if (currentSound) {
currentSound.play();
}
};
export const pause = () => {
const currentSound = audioService.getCurrentSound();
if (currentSound) {
try {
// 保存当前播放进度
const currentTime = currentSound.currentTime;
if (getPlayerStore().playMusic && getPlayerStore().playMusic.id) {
localStorage.setItem(
'playProgress',
JSON.stringify({
songId: getPlayerStore().playMusic.id,
progress: currentTime
})
);
}
audioService.pause();
} catch (error) {
console.error('暂停播放出错:', error);
}
}
};
// 歌词矫正时间映射(每首歌独立)
const CORRECTION_KEY = 'lyric-correction-map';
const correctionTimeMap = ref<Record<string, number>>({});
// 初始化 correctionTimeMap
const loadCorrectionMap = () => {
try {
const raw = localStorage.getItem(CORRECTION_KEY);
correctionTimeMap.value = raw ? JSON.parse(raw) : {};
} catch {
correctionTimeMap.value = {};
}
};
const saveCorrectionMap = () => {
localStorage.setItem(CORRECTION_KEY, JSON.stringify(correctionTimeMap.value));
};
loadCorrectionMap();
// 歌词矫正时间,当前歌曲
export const correctionTime = ref(0);
// 设置歌词矫正时间的监听器
const setupCorrectionTimeWatcher = () => {
// 切歌时自动读取矫正时间
watch(
() => playMusic.value?.id,
(id) => {
if (!id) return;
correctionTime.value = correctionTimeMap.value[id] ?? 0;
},
{ immediate: true }
);
};
/**
* 调整歌词矫正时间(每首歌独立)
* @param delta 增加/减少的秒数(正为加,负为减)
*/
export const adjustCorrectionTime = (delta: number) => {
const id = playMusic.value?.id;
if (!id) return;
const newVal = Math.max(-10, Math.min(10, (correctionTime.value ?? 0) + delta));
correctionTime.value = newVal;
correctionTimeMap.value[id] = newVal;
saveCorrectionMap();
};
// 获取当前播放歌词
export const isCurrentLrc = (index: number, time: number): boolean => {
const currentTime = lrcTimeArray.value[index];
// 如果是最后一句歌词,只需要判断时间是否大于等于当前句的开始时间
if (index === lrcTimeArray.value.length - 1) {
const correctedTime = time + correctionTime.value;
return correctedTime >= currentTime;
}
// 非最后一句歌词,需要判断时间在当前句和下一句之间
const nextTime = lrcTimeArray.value[index + 1];
const correctedTime = time + correctionTime.value;
return correctedTime >= currentTime && correctedTime < nextTime;
};
// 获取当前播放歌词INDEX
export const getLrcIndex = (time: number): number => {
const correctedTime = time + correctionTime.value;
// 如果歌词数组为空,返回当前索引
if (lrcTimeArray.value.length === 0) {
return nowIndex.value;
}
// 处理最后一句歌词的情况
const lastIndex = lrcTimeArray.value.length - 1;
if (correctedTime >= lrcTimeArray.value[lastIndex]) {
nowIndex.value = lastIndex;
return lastIndex;
}
// 查找当前时间对应的歌词索引
for (let i = 0; i < lrcTimeArray.value.length - 1; i++) {
const currentTime = lrcTimeArray.value[i];
const nextTime = lrcTimeArray.value[i + 1];
if (correctedTime >= currentTime && correctedTime < nextTime) {
nowIndex.value = i;
return i;
}
}
return nowIndex.value;
};
// 获取当前播放歌词进度
const currentLrcTiming = computed(() => {
const start = lrcTimeArray.value[nowIndex.value] || 0;
const end = lrcTimeArray.value[nowIndex.value + 1] || start + 1;
return { start, end };
});
// 获取歌词样式
export const getLrcStyle = (index: number) => {
const currentTime = nowTime.value + correctionTime.value;
const start = lrcTimeArray.value[index];
const end = lrcTimeArray.value[index + 1] ?? start + 1;
if (currentTime >= start && currentTime < end) {
// 当前句,显示进度
const progress = ((currentTime - start) / (end - start)) * 100;
return {
backgroundImage: `linear-gradient(to right, #ffffff ${progress}%, #ffffff8a ${progress}%)`,
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
color: 'transparent',
transition: 'background-image 0.1s linear'
};
}
// 其它句
return {};
};
// 播放进度
export const useLyricProgress = () => {
// 如果已经在全局更新进度,立即返回
return {
getLrcStyle
};
};
// 设置当前播放时间
export const setAudioTime = (index: number) => {
const currentSound = sound.value;
if (!currentSound) return;
audioService.seek(lrcTimeArray.value[index]);
currentSound.play();
};
// 获取当前播放的歌词
export const getCurrentLrc = () => {
const index = getLrcIndex(nowTime.value);
return {
currentLrc: lrcArray.value[index],
nextLrc: lrcArray.value[index + 1]
};
};
// 获取一句歌词播放时间几秒到几秒
export const getLrcTimeRange = (index: number) => ({
currentTime: lrcTimeArray.value[index],
nextTime: lrcTimeArray.value[index + 1]
});
// 监听歌词数组变化,当切换歌曲时重新初始化歌词窗口
watch(
() => lrcArray.value,
(newLrcArray) => {
if (newLrcArray.length > 0 && isElectron && isLyricWindowOpen.value) {
sendLyricToWin();
}
}
);
// 发送歌词更新数据
export const sendLyricToWin = () => {
if (!isElectron || !isLyricWindowOpen.value) {
return;
}
// 检查是否有播放的歌曲
if (!playMusic.value || !playMusic.value.id) {
return;
}
try {
// 记录歌词发送状态
if (lrcArray.value && lrcArray.value.length > 0) {
const nowIndex = getLrcIndex(nowTime.value);
// 构建完整的歌词更新数据
const updateData = {
type: 'full',
nowIndex,
nowTime: nowTime.value,
startCurrentTime: lrcTimeArray.value[nowIndex] || 0,
nextTime: lrcTimeArray.value[nowIndex + 1] || 0,
isPlay: getPlayerStore().play,
lrcArray: lrcArray.value,
lrcTimeArray: lrcTimeArray.value,
allTime: allTime.value,
playMusic: playMusic.value
};
// 发送数据到歌词窗口
window.api.sendLyric(JSON.stringify(updateData));
} else {
console.log('No lyric data available, sending empty lyric message');
// 发送没有歌词的提示
const emptyLyricData = {
type: 'empty',
nowIndex: 0,
nowTime: nowTime.value,
startCurrentTime: 0,
nextTime: 0,
isPlay: getPlayerStore().play,
lrcArray: [{ text: '当前歌曲暂无歌词', trText: '' }],
lrcTimeArray: [0],
allTime: allTime.value,
playMusic: playMusic.value
};
window.api.sendLyric(JSON.stringify(emptyLyricData));
}
} catch (error) {
console.error('Error sending lyric update:', error);
}
};
// 发送歌词到系统托盘歌词(TrayLyric)
const sendTrayLyric = (index: number) => {
if (!isElectron || cachedPlatform !== 'linux') return;
try {
const lyric = lrcArray.value[index];
if (!lyric) return;
const currentTime = lrcTimeArray.value[index] || 0;
const nextTime = lrcTimeArray.value[index + 1] || currentTime + 3;
const duration = nextTime - currentTime;
const lrcObj = JSON.stringify({
content: lyric.text || '',
time: duration.toFixed(1),
sender: 'AlgerMusicPlayer'
});
window.electron.ipcRenderer.send('tray-lyric-update', lrcObj);
} catch (error) {
console.error('[TrayLyric] Failed to send:', error);
}
};
// 歌词同步定时器
let lyricSyncInterval: any = null;
// 开始歌词同步
const startLyricSync = () => {
// 清除已有的定时器
if (lyricSyncInterval) {
clearInterval(lyricSyncInterval);
}
// 每秒同步一次歌词数据
lyricSyncInterval = setInterval(() => {
if (isElectron && isLyricWindowOpen.value && getPlayerStore().play && playMusic.value?.id) {
// 发送当前播放进度的更新
try {
const updateData = {
type: 'update',
nowIndex: getLrcIndex(nowTime.value),
nowTime: nowTime.value,
isPlay: getPlayerStore().play
};
window.api.sendLyric(JSON.stringify(updateData));
} catch (error) {
console.error('发送歌词进度更新失败:', error);
}
}
}, 1000);
};
// 停止歌词同步
const stopLyricSync = () => {
if (lyricSyncInterval) {
clearInterval(lyricSyncInterval);
lyricSyncInterval = null;
}
};
export const openLyric = async () => {
if (!isElectron) return;
if (!playMusic.value || !playMusic.value.id) {
console.log('没有正在播放的歌曲,无法打开歌词窗口');
return;
}
isLyricWindowOpen.value = !isLyricWindowOpen.value;
if (isLyricWindowOpen.value) {
window.api.openLyric();
// 先发"加载中"占位, 防止窗口启动期间显示"无歌词"
if (!lrcArray.value || lrcArray.value.length === 0) {
const emptyLyricData = {
type: 'empty',
nowIndex: 0,
nowTime: nowTime.value,
startCurrentTime: 0,
nextTime: 0,
isPlay: getPlayerStore().play,
lrcArray: [{ text: '加载歌词中...', trText: '' }],
lrcTimeArray: [0],
allTime: allTime.value,
playMusic: playMusic.value
};
window.api.sendLyric(JSON.stringify(emptyLyricData));
// 关键: 主动加载歌词, 不依赖 watcher
// (重启场景下 playerCore.playMusic 整体替换可能未触发 lyric watcher)
await ensureLyricsLoaded(true);
} else {
sendLyricToWin();
}
// 延迟重发, 防窗口加载慢丢消息
setTimeout(() => {
if (isLyricWindowOpen.value) {
sendLyricToWin();
}
}, 500);
// 启动歌词同步
startLyricSync();
} else {
closeLyric();
// 停止歌词同步
stopLyricSync();
}
};
// 修改closeLyric函数,确保停止定时同步
export const closeLyric = () => {
if (!isElectron) return;
isLyricWindowOpen.value = false; // 确保状态更新
windowData.electron.ipcRenderer.send('close-lyric');
// 停止歌词同步
stopLyricSync();
};
// 设置播放状态监听器
const setupPlayStateWatcher = () => {
// 在组件挂载时设置对播放状态的监听
watch(
() => getPlayerStore().play,
(isPlaying) => {
// 如果歌词窗口打开,根据播放状态控制同步
if (isElectron && isLyricWindowOpen.value) {
if (isPlaying) {
startLyricSync();
} else {
// 如果暂停播放,发送一次暂停状态的更新
const pauseData = {
type: 'update',
isPlay: false
};
window.api.sendLyric(JSON.stringify(pauseData));
}
}
}
);
};
// 在组件卸载时清理资源
onUnmounted(() => {
stopLyricSync();
});
// 导出歌词解析函数供外部使用
export { parseLyricsString };
// 添加播放控制命令监听
if (isElectron) {
windowData.electron.ipcRenderer.on('lyric-control-back', (_, command: string) => {
switch (command) {
case 'playpause':
if (getPlayerStore().playMusic?.id) {
void getPlayerStore().setPlay({ ...getPlayerStore().playMusic });
}
break;
case 'prev':
getPlayerStore().prevPlay();
break;
case 'next':
getPlayerStore().nextPlay();
break;
case 'close':
isLyricWindowOpen.value = false; // 确保状态更新
break;
default:
console.log('Unknown command:', command);
break;
}
});
}
// 在组件挂载时设置监听器
export const initAudioListeners = async () => {
try {
// 确保有正在播放的音乐
if (!getPlayerStore().playMusic || !getPlayerStore().playMusic.id) {
console.log('没有正在播放的音乐,跳过音频监听器初始化');
return;
}
// 确保有音频实例
const initialSound = audioService.getCurrentSound();
if (!initialSound) {
console.log('没有音频实例,等待音频加载...');
// 等待音频加载完成
await new Promise<void>((resolve) => {
const checkInterval = setInterval(() => {
const sound = audioService.getCurrentSound();
if (sound) {
clearInterval(checkInterval);
resolve();
}
}, 100);
// 设置超时
setTimeout(() => {
clearInterval(checkInterval);
console.log('等待音频加载超时');
resolve();
}, 5000);
});
}
// 初始化音频监听器
setupAudioListeners();
// 监听歌词窗口事件
if (isElectron) {
window.api.onLyricWindowClosed(() => {
isLyricWindowOpen.value = false;
});
window.api.onLyricWindowReady(async () => {
if (!isLyricWindowOpen.value) return;
// 窗口加载完成时再兜底加载一次, 防止 openLyric 阶段 lyric 字段尚未到位
if (lrcArray.value.length === 0 && playMusic.value?.id) {
await ensureLyricsLoaded(true);
}
sendLyricToWin();
});
}
// 获取最新的音频实例
const finalSound = audioService.getCurrentSound();
if (finalSound) {
// 更新全局 sound 引用
sound.value = finalSound;
} else {
console.warn('无法获取音频实例,跳过进度更新初始化');
}
} catch (error) {
console.error('初始化音频监听器失败:', error);
}
};
// 音频就绪事件处理器(提取为命名函数,防止重复注册)
const handleAudioReady = ((event: CustomEvent) => {
try {
const { sound: newSound } = event.detail;
if (newSound) {
sound.value = audioService.getCurrentSound();
setupAudioListeners();
const currentSound = audioService.getCurrentSound();
if (currentSound) {
const currentPosition = currentSound.currentTime;
if (typeof currentPosition === 'number' && !Number.isNaN(currentPosition)) {
nowTime.value = currentPosition;
}
}
}
} catch (error) {
console.error('处理音频就绪事件出错:', error);
}
}) as EventListener;
// 先移除再注册,防止重复
window.removeEventListener('audio-ready', handleAudioReady);
window.addEventListener('audio-ready', handleAudioReady);