feat: 优化歌词窗口交互和同步机制

- 增强歌词窗口数据同步逻辑,支持实时更新和状态管理
- 添加歌词窗口关闭事件监听和状态处理
- 优化无歌词时的默认提示和窗口行为
- 实现歌词窗口定时同步机制,提升用户体验
- 修复歌词窗口打开和关闭时的状态控制
- 国际化支持无歌曲播放时的提示文案
This commit is contained in:
alger
2025-03-08 19:00:50 +08:00
parent e43e85480d
commit f9878ed88a
8 changed files with 226 additions and 29 deletions
+1
View File
@@ -33,6 +33,7 @@ export default {
collapse: 'Collapse Lyrics', collapse: 'Collapse Lyrics',
like: 'Like', like: 'Like',
lyric: 'Lyric', lyric: 'Lyric',
noSongPlaying: 'No song playing',
eq: 'Equalizer', eq: 'Equalizer',
playList: 'Play List', playList: 'Play List',
playMode: { playMode: {
+1
View File
@@ -33,6 +33,7 @@ export default {
collapse: '收起歌词', collapse: '收起歌词',
like: '喜欢', like: '喜欢',
lyric: '歌词', lyric: '歌词',
noSongPlaying: '没有正在播放的歌曲',
eq: '均衡器', eq: '均衡器',
playList: '播放列表', playList: '播放列表',
playMode: { playMode: {
+27 -7
View File
@@ -25,9 +25,15 @@ const createWin = () => {
const validPosition = const validPosition =
x !== undefined && y !== undefined && x >= 0 && y >= 0 && x < screenWidth && y < screenHeight; x !== undefined && y !== undefined && x >= 0 && y >= 0 && x < screenWidth && y < screenHeight;
// 确保宽高合理
const defaultWidth = 800;
const defaultHeight = 200;
const validWidth = width && width > 0 ? width : defaultWidth;
const validHeight = height && height > 0 ? height : defaultHeight;
lyricWindow = new BrowserWindow({ lyricWindow = new BrowserWindow({
width: width || 800, width: validWidth,
height: height || 200, height: validHeight,
x: validPosition ? x : undefined, x: validPosition ? x : undefined,
y: validPosition ? y : undefined, y: validPosition ? y : undefined,
frame: false, frame: false,
@@ -50,6 +56,17 @@ const createWin = () => {
} }
}); });
// 监听窗口大小变化事件,保存新的尺寸
lyricWindow.on('resize', () => {
if (lyricWindow && !lyricWindow.isDestroyed()) {
const [width, height] = lyricWindow.getSize();
const [x, y] = lyricWindow.getPosition();
// 保存窗口位置和大小
store.set('lyricWindowBounds', { x, y, width, height });
}
});
return lyricWindow; return lyricWindow;
}; };
@@ -118,6 +135,7 @@ export const loadLyricWindow = (ipcMain: IpcMain, mainWin: BrowserWindow): void
if (lyricWindow && !lyricWindow.isDestroyed()) { if (lyricWindow && !lyricWindow.isDestroyed()) {
lyricWindow.webContents.send('lyric-window-close'); lyricWindow.webContents.send('lyric-window-close');
mainWin.webContents.send('lyric-control-back', 'close'); mainWin.webContents.send('lyric-control-back', 'close');
mainWin.webContents.send('lyric-window-closed');
lyricWindow.destroy(); lyricWindow.destroy();
lyricWindow = null; lyricWindow = null;
} }
@@ -150,12 +168,14 @@ export const loadLyricWindow = (ipcMain: IpcMain, mainWin: BrowserWindow): void
lyricWindow.setPosition(newX, newY); lyricWindow.setPosition(newX, newY);
// 保存新位置 // 保存新位置,但只保存位置信息,不使用getBounds()避免在Windows下引起尺寸变化
store.set('lyricWindowBounds', { const bounds = {
...lyricWindow.getBounds(),
x: newX, x: newX,
y: newY y: newY,
}); width: windowWidth, // 使用当前保存的宽度
height: windowHeight // 使用当前保存的高度
};
store.set('lyricWindowBounds', bounds);
}); });
// 添加鼠标穿透事件处理 // 添加鼠标穿透事件处理
+1
View File
@@ -13,6 +13,7 @@ declare global {
miniTray: () => void; miniTray: () => void;
restart: () => void; restart: () => void;
unblockMusic: (id: number, data: any) => Promise<any>; unblockMusic: (id: number, data: any) => Promise<any>;
onLyricWindowClosed: (callback: () => void) => void;
startDownload: (url: string) => void; startDownload: (url: string) => void;
onDownloadProgress: (callback: (progress: number, status: string) => void) => void; onDownloadProgress: (callback: (progress: number, status: string) => void) => void;
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void; onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void;
+4
View File
@@ -12,6 +12,10 @@ const api = {
openLyric: () => ipcRenderer.send('open-lyric'), openLyric: () => ipcRenderer.send('open-lyric'),
sendLyric: (data) => ipcRenderer.send('send-lyric', data), sendLyric: (data) => ipcRenderer.send('send-lyric', data),
unblockMusic: (id) => ipcRenderer.invoke('unblock-music', id), unblockMusic: (id) => ipcRenderer.invoke('unblock-music', id),
// 歌词窗口关闭事件
onLyricWindowClosed: (callback: () => void) => {
ipcRenderer.on('lyric-window-closed', () => callback());
},
// 更新相关 // 更新相关
startDownload: (url: string) => ipcRenderer.send('start-download', url), startDownload: (url: string) => ipcRenderer.send('start-download', url),
onDownloadProgress: (callback: (progress: number, status: string) => void) => { onDownloadProgress: (callback: (progress: number, status: string) => void) => {
+157 -13
View File
@@ -1,5 +1,5 @@
import { createDiscreteApi } from 'naive-ui'; import { createDiscreteApi } from 'naive-ui';
import { computed, nextTick, onMounted, ref, watch } from 'vue'; import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import useIndexedDB from '@/hooks/IndexDBHook'; import useIndexedDB from '@/hooks/IndexDBHook';
import { audioService } from '@/services/audioService'; import { audioService } from '@/services/audioService';
@@ -263,11 +263,21 @@ watch(
() => store.state.playMusic, () => store.state.playMusic,
() => { () => {
nextTick(async () => { nextTick(async () => {
console.log('歌曲切换,更新歌词数据');
// 更新歌词数据
lrcArray.value = playMusic.value.lyric?.lrcArray || []; lrcArray.value = playMusic.value.lyric?.lrcArray || [];
lrcTimeArray.value = playMusic.value.lyric?.lrcTimeArray || []; lrcTimeArray.value = playMusic.value.lyric?.lrcTimeArray || [];
// 当歌词数据更新时,如果歌词窗口打开,则发送数据 // 当歌词数据更新时,如果歌词窗口打开,则发送数据
if (isElectron && isLyricWindowOpen.value && lrcArray.value.length > 0) { if (isElectron && isLyricWindowOpen.value) {
console.log('歌词窗口已打开,同步最新歌词数据');
// 不管歌词数组是否为空,都发送最新数据
sendLyricToWin(); sendLyricToWin();
// 再次延迟发送,确保歌词窗口已完全加载
setTimeout(() => {
sendLyricToWin();
}, 500);
} }
}); });
}, },
@@ -451,8 +461,6 @@ export const pause = () => {
} }
}; };
const isPlaying = computed(() => store.state.play as boolean);
// 增加矫正时间 // 增加矫正时间
export const addCorrectionTime = (time: number) => (correctionTime.value += time); export const addCorrectionTime = (time: number) => (correctionTime.value += time);
@@ -545,54 +553,183 @@ watch(
// 发送歌词更新数据 // 发送歌词更新数据
export const sendLyricToWin = () => { export const sendLyricToWin = () => {
if (!isElectron || !isLyricWindowOpen.value) { if (!isElectron || !isLyricWindowOpen.value) {
console.log('Cannot send lyric: electron or lyric window not available'); return;
}
// 检查是否有播放的歌曲
if (!playMusic.value || !playMusic.value.id) {
return; return;
} }
try { try {
if (lrcArray.value.length > 0) { // 记录歌词发送状态
if (lrcArray.value && lrcArray.value.length > 0) {
const nowIndex = getLrcIndex(nowTime.value); const nowIndex = getLrcIndex(nowTime.value);
// 构建完整的歌词更新数据
const updateData = { const updateData = {
type: 'full', type: 'full',
nowIndex, nowIndex,
nowTime: nowTime.value, nowTime: nowTime.value,
startCurrentTime: lrcTimeArray.value[nowIndex], startCurrentTime: lrcTimeArray.value[nowIndex] || 0,
nextTime: lrcTimeArray.value[nowIndex + 1], nextTime: lrcTimeArray.value[nowIndex + 1] || 0,
isPlay: isPlaying.value, isPlay: store.state.play,
lrcArray: lrcArray.value, lrcArray: lrcArray.value,
lrcTimeArray: lrcTimeArray.value, lrcTimeArray: lrcTimeArray.value,
allTime: allTime.value, allTime: allTime.value,
playMusic: playMusic.value playMusic: playMusic.value
}; };
// 发送数据到歌词窗口
window.api.sendLyric(JSON.stringify(updateData)); 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: store.state.play,
lrcArray: [{ text: '当前歌曲暂无歌词', trText: '' }],
lrcTimeArray: [0],
allTime: allTime.value,
playMusic: playMusic.value
};
window.api.sendLyric(JSON.stringify(emptyLyricData));
} }
} catch (error) { } catch (error) {
console.error('Error sending lyric update:', error); console.error('Error sending lyric update:', error);
} }
}; };
// 歌词同步定时器
let lyricSyncInterval: any = null;
// 开始歌词同步
const startLyricSync = () => {
// 清除已有的定时器
if (lyricSyncInterval) {
clearInterval(lyricSyncInterval);
}
// 每秒同步一次歌词数据
lyricSyncInterval = setInterval(() => {
if (isElectron && isLyricWindowOpen.value && store.state.play && playMusic.value?.id) {
// 发送当前播放进度的更新
try {
const updateData = {
type: 'update',
nowIndex: getLrcIndex(nowTime.value),
nowTime: nowTime.value,
isPlay: store.state.play
};
window.api.sendLyric(JSON.stringify(updateData));
} catch (error) {
console.error('发送歌词进度更新失败:', error);
}
}
}, 1000);
};
// 停止歌词同步
const stopLyricSync = () => {
if (lyricSyncInterval) {
clearInterval(lyricSyncInterval);
lyricSyncInterval = null;
}
};
// 修改openLyric函数,添加定时同步
export const openLyric = () => { export const openLyric = () => {
if (!isElectron) return; if (!isElectron) return;
// 检查是否有播放中的歌曲
if (!playMusic.value || !playMusic.value.id) {
console.log('没有正在播放的歌曲,无法打开歌词窗口');
return;
}
console.log('Opening lyric window with current song:', playMusic.value?.name); 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();
// 确保有歌词数据,如果没有,则使用默认的"无歌词"提示
if (!lrcArray.value || lrcArray.value.length === 0) {
// 如果当前播放的歌曲有ID但没有歌词,则尝试加载歌词
console.log('尝试加载歌词数据...');
// 发送默认的"无歌词"数据
const emptyLyricData = {
type: 'empty',
nowIndex: 0,
nowTime: nowTime.value,
startCurrentTime: 0,
nextTime: 0,
isPlay: store.state.play,
lrcArray: [{ text: '加载歌词中...', trText: '' }],
lrcTimeArray: [0],
allTime: allTime.value,
playMusic: playMusic.value
};
window.api.sendLyric(JSON.stringify(emptyLyricData));
} else {
// 发送完整歌词数据
sendLyricToWin();
}
// 设置定时器,确保500ms后再次发送数据,以防窗口加载延迟
setTimeout(() => { setTimeout(() => {
window.api.openLyric();
sendLyricToWin(); sendLyricToWin();
}, 500); }, 500);
sendLyricToWin();
// 启动歌词同步
startLyricSync();
} else { } else {
closeLyric(); closeLyric();
// 停止歌词同步
stopLyricSync();
} }
}; };
// 添加关闭歌词窗口的方法 // 修改closeLyric函数,确保停止定时同步
export const closeLyric = () => { export const closeLyric = () => {
if (!isElectron) return; if (!isElectron) return;
isLyricWindowOpen.value = false; // 确保状态更新
windowData.electron.ipcRenderer.send('close-lyric'); windowData.electron.ipcRenderer.send('close-lyric');
// 停止歌词同步
stopLyricSync();
}; };
// 在组件挂载时设置对播放状态的监听
watch(
() => store.state.play,
(isPlaying) => {
// 如果歌词窗口打开,根据播放状态控制同步
if (isElectron && isLyricWindowOpen.value) {
if (isPlaying) {
startLyricSync();
} else {
// 如果暂停播放,发送一次暂停状态的更新
const pauseData = {
type: 'update',
isPlay: false
};
window.api.sendLyric(JSON.stringify(pauseData));
}
}
}
);
// 在组件卸载时清理资源
onUnmounted(() => {
stopLyricSync();
});
// 添加播放控制命令监听 // 添加播放控制命令监听
if (isElectron) { if (isElectron) {
windowData.electron.ipcRenderer.on('lyric-control-back', (_, command: string) => { windowData.electron.ipcRenderer.on('lyric-control-back', (_, command: string) => {
@@ -613,7 +750,7 @@ if (isElectron) {
store.commit('nextPlay'); store.commit('nextPlay');
break; break;
case 'close': case 'close':
closeLyric(); isLyricWindowOpen.value = false; // 确保状态更新
break; break;
default: default:
console.log('Unknown command:', command); console.log('Unknown command:', command);
@@ -627,6 +764,13 @@ onMounted(() => {
// 初始化音频监听器 // 初始化音频监听器
setupAudioListeners(); setupAudioListeners();
// 监听歌词窗口关闭事件
if (isElectron) {
window.api.onLyricWindowClosed(() => {
isLyricWindowOpen.value = false;
});
}
// 检查是否需要初始化 sound 对象 // 检查是否需要初始化 sound 对象
if (!sound.value && audioService.getCurrentSound()) { if (!sound.value && audioService.getCurrentSound()) {
sound.value = audioService.getCurrentSound(); sound.value = audioService.getCurrentSound();
+10 -3
View File
@@ -113,11 +113,11 @@
<template #trigger> <template #trigger>
<i <i
class="iconfont ri-netease-cloud-music-line" class="iconfont ri-netease-cloud-music-line"
:class="{ 'text-green-500': isLyricWindowOpen }" :class="{ 'text-green-500': isLyricWindowOpen, 'disabled-icon': !playMusic.id }"
@click="openLyricWindow" @click="playMusic.id && openLyricWindow()"
></i> ></i>
</template> </template>
{{ t('player.playBar.lyric') }} {{ playMusic.id ? t('player.playBar.lyric') : t('player.playBar.noSongPlaying') }}
</n-tooltip> </n-tooltip>
<n-popover <n-popover
trigger="click" trigger="click"
@@ -677,6 +677,13 @@ const isEQVisible = ref(false);
@apply text-red-500 hover:text-red-600 !important; @apply text-red-500 hover:text-red-600 !important;
} }
.disabled-icon {
@apply opacity-50 cursor-not-allowed !important;
&:hover {
@apply text-inherit !important;
}
}
.icon-loop, .icon-loop,
.icon-single-loop { .icon-single-loop {
font-size: 1.5rem; font-size: 1.5rem;
+25 -6
View File
@@ -378,27 +378,46 @@ watch(
// 修改数据更新处 // 修改数据更新处
const handleDataUpdate = (parsedData: { const handleDataUpdate = (parsedData: {
type?: string;
nowTime: number; nowTime: number;
startCurrentTime: number; startCurrentTime: number;
nextTime: number; nextTime: number;
isPlay: boolean; isPlay: boolean;
nowIndex: number; nowIndex: number;
lrcArray: Array<{ text: string; trText: string }>; lrcArray?: Array<{ text: string; trText: string }>;
lrcTimeArray: number[]; lrcTimeArray?: number[];
allTime: number; allTime?: number;
playMusic: SongResult; playMusic?: SongResult;
}) => { }) => {
// 确保数据存在且格式正确 // 确保数据存在且格式正确
if (!parsedData) { if (!parsedData) {
console.error('Invalid update data received:', parsedData); console.error('Invalid update data received:', parsedData);
return; return;
} }
// 根据数据类型处理
if (parsedData.type === 'update') {
// 增量更新,只更新动态数据
dynamicData.value = {
...dynamicData.value,
nowTime: parsedData.nowTime || dynamicData.value.nowTime,
isPlay: typeof parsedData.isPlay === 'boolean' ? parsedData.isPlay : dynamicData.value.isPlay
};
// 更新索引(如果提供)
if (typeof parsedData.nowIndex === 'number') {
currentIndex.value = parsedData.nowIndex;
}
return;
}
// 完整更新或空歌词提示
// 更新静态数据 // 更新静态数据
staticData.value = { staticData.value = {
lrcArray: parsedData.lrcArray || [], lrcArray: parsedData.lrcArray || [],
lrcTimeArray: parsedData.lrcTimeArray || [], lrcTimeArray: parsedData.lrcTimeArray || [],
allTime: parsedData.allTime || 0, allTime: parsedData.allTime || 0,
playMusic: parsedData.playMusic || {} playMusic: parsedData.playMusic || ({} as SongResult)
}; };
// 更新动态数据 // 更新动态数据
@@ -472,7 +491,7 @@ watch(
{ deep: true } { deep: true }
); );
// 添拖动相关变量 // 添拖动相关变量
const isDragging = ref(false); const isDragging = ref(false);
const startPosition = ref({ x: 0, y: 0 }); const startPosition = ref({ x: 0, y: 0 });