mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-14 06:30:49 +08:00
✨ feat: 优化歌词窗口交互和同步机制
- 增强歌词窗口数据同步逻辑,支持实时更新和状态管理 - 添加歌词窗口关闭事件监听和状态处理 - 优化无歌词时的默认提示和窗口行为 - 实现歌词窗口定时同步机制,提升用户体验 - 修复歌词窗口打开和关闭时的状态控制 - 国际化支持无歌曲播放时的提示文案
This commit is contained in:
@@ -33,6 +33,7 @@ export default {
|
||||
collapse: 'Collapse Lyrics',
|
||||
like: 'Like',
|
||||
lyric: 'Lyric',
|
||||
noSongPlaying: 'No song playing',
|
||||
eq: 'Equalizer',
|
||||
playList: 'Play List',
|
||||
playMode: {
|
||||
|
||||
@@ -33,6 +33,7 @@ export default {
|
||||
collapse: '收起歌词',
|
||||
like: '喜欢',
|
||||
lyric: '歌词',
|
||||
noSongPlaying: '没有正在播放的歌曲',
|
||||
eq: '均衡器',
|
||||
playList: '播放列表',
|
||||
playMode: {
|
||||
|
||||
@@ -25,9 +25,15 @@ const createWin = () => {
|
||||
const validPosition =
|
||||
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({
|
||||
width: width || 800,
|
||||
height: height || 200,
|
||||
width: validWidth,
|
||||
height: validHeight,
|
||||
x: validPosition ? x : undefined,
|
||||
y: validPosition ? y : undefined,
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -118,6 +135,7 @@ export const loadLyricWindow = (ipcMain: IpcMain, mainWin: BrowserWindow): void
|
||||
if (lyricWindow && !lyricWindow.isDestroyed()) {
|
||||
lyricWindow.webContents.send('lyric-window-close');
|
||||
mainWin.webContents.send('lyric-control-back', 'close');
|
||||
mainWin.webContents.send('lyric-window-closed');
|
||||
lyricWindow.destroy();
|
||||
lyricWindow = null;
|
||||
}
|
||||
@@ -150,12 +168,14 @@ export const loadLyricWindow = (ipcMain: IpcMain, mainWin: BrowserWindow): void
|
||||
|
||||
lyricWindow.setPosition(newX, newY);
|
||||
|
||||
// 保存新位置
|
||||
store.set('lyricWindowBounds', {
|
||||
...lyricWindow.getBounds(),
|
||||
// 保存新位置,但只保存位置信息,不使用getBounds()避免在Windows下引起尺寸变化
|
||||
const bounds = {
|
||||
x: newX,
|
||||
y: newY
|
||||
});
|
||||
y: newY,
|
||||
width: windowWidth, // 使用当前保存的宽度
|
||||
height: windowHeight // 使用当前保存的高度
|
||||
};
|
||||
store.set('lyricWindowBounds', bounds);
|
||||
});
|
||||
|
||||
// 添加鼠标穿透事件处理
|
||||
|
||||
1
src/preload/index.d.ts
vendored
1
src/preload/index.d.ts
vendored
@@ -13,6 +13,7 @@ declare global {
|
||||
miniTray: () => void;
|
||||
restart: () => void;
|
||||
unblockMusic: (id: number, data: any) => Promise<any>;
|
||||
onLyricWindowClosed: (callback: () => void) => void;
|
||||
startDownload: (url: string) => void;
|
||||
onDownloadProgress: (callback: (progress: number, status: string) => void) => void;
|
||||
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void;
|
||||
|
||||
@@ -12,6 +12,10 @@ const api = {
|
||||
openLyric: () => ipcRenderer.send('open-lyric'),
|
||||
sendLyric: (data) => ipcRenderer.send('send-lyric', data),
|
||||
unblockMusic: (id) => ipcRenderer.invoke('unblock-music', id),
|
||||
// 歌词窗口关闭事件
|
||||
onLyricWindowClosed: (callback: () => void) => {
|
||||
ipcRenderer.on('lyric-window-closed', () => callback());
|
||||
},
|
||||
// 更新相关
|
||||
startDownload: (url: string) => ipcRenderer.send('start-download', url),
|
||||
onDownloadProgress: (callback: (progress: number, status: string) => void) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { audioService } from '@/services/audioService';
|
||||
@@ -263,11 +263,21 @@ watch(
|
||||
() => store.state.playMusic,
|
||||
() => {
|
||||
nextTick(async () => {
|
||||
console.log('歌曲切换,更新歌词数据');
|
||||
// 更新歌词数据
|
||||
lrcArray.value = playMusic.value.lyric?.lrcArray || [];
|
||||
lrcTimeArray.value = playMusic.value.lyric?.lrcTimeArray || [];
|
||||
|
||||
// 当歌词数据更新时,如果歌词窗口打开,则发送数据
|
||||
if (isElectron && isLyricWindowOpen.value && lrcArray.value.length > 0) {
|
||||
if (isElectron && isLyricWindowOpen.value) {
|
||||
console.log('歌词窗口已打开,同步最新歌词数据');
|
||||
// 不管歌词数组是否为空,都发送最新数据
|
||||
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);
|
||||
|
||||
@@ -545,54 +553,183 @@ watch(
|
||||
// 发送歌词更新数据
|
||||
export const sendLyricToWin = () => {
|
||||
if (!isElectron || !isLyricWindowOpen.value) {
|
||||
console.log('Cannot send lyric: electron or lyric window not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有播放的歌曲
|
||||
if (!playMusic.value || !playMusic.value.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (lrcArray.value.length > 0) {
|
||||
// 记录歌词发送状态
|
||||
if (lrcArray.value && lrcArray.value.length > 0) {
|
||||
const nowIndex = getLrcIndex(nowTime.value);
|
||||
// 构建完整的歌词更新数据
|
||||
const updateData = {
|
||||
type: 'full',
|
||||
nowIndex,
|
||||
nowTime: nowTime.value,
|
||||
startCurrentTime: lrcTimeArray.value[nowIndex],
|
||||
nextTime: lrcTimeArray.value[nowIndex + 1],
|
||||
isPlay: isPlaying.value,
|
||||
startCurrentTime: lrcTimeArray.value[nowIndex] || 0,
|
||||
nextTime: lrcTimeArray.value[nowIndex + 1] || 0,
|
||||
isPlay: store.state.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: store.state.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);
|
||||
}
|
||||
};
|
||||
|
||||
// 歌词同步定时器
|
||||
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 = () => {
|
||||
if (!isElectron) return;
|
||||
|
||||
// 检查是否有播放中的歌曲
|
||||
if (!playMusic.value || !playMusic.value.id) {
|
||||
console.log('没有正在播放的歌曲,无法打开歌词窗口');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Opening lyric window with current song:', playMusic.value?.name);
|
||||
|
||||
isLyricWindowOpen.value = !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(() => {
|
||||
window.api.openLyric();
|
||||
sendLyricToWin();
|
||||
}, 500);
|
||||
sendLyricToWin();
|
||||
|
||||
// 启动歌词同步
|
||||
startLyricSync();
|
||||
} else {
|
||||
closeLyric();
|
||||
// 停止歌词同步
|
||||
stopLyricSync();
|
||||
}
|
||||
};
|
||||
|
||||
// 添加关闭歌词窗口的方法
|
||||
// 修改closeLyric函数,确保停止定时同步
|
||||
export const closeLyric = () => {
|
||||
if (!isElectron) return;
|
||||
isLyricWindowOpen.value = false; // 确保状态更新
|
||||
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) {
|
||||
windowData.electron.ipcRenderer.on('lyric-control-back', (_, command: string) => {
|
||||
@@ -613,7 +750,7 @@ if (isElectron) {
|
||||
store.commit('nextPlay');
|
||||
break;
|
||||
case 'close':
|
||||
closeLyric();
|
||||
isLyricWindowOpen.value = false; // 确保状态更新
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown command:', command);
|
||||
@@ -627,6 +764,13 @@ onMounted(() => {
|
||||
// 初始化音频监听器
|
||||
setupAudioListeners();
|
||||
|
||||
// 监听歌词窗口关闭事件
|
||||
if (isElectron) {
|
||||
window.api.onLyricWindowClosed(() => {
|
||||
isLyricWindowOpen.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否需要初始化 sound 对象
|
||||
if (!sound.value && audioService.getCurrentSound()) {
|
||||
sound.value = audioService.getCurrentSound();
|
||||
|
||||
@@ -113,11 +113,11 @@
|
||||
<template #trigger>
|
||||
<i
|
||||
class="iconfont ri-netease-cloud-music-line"
|
||||
:class="{ 'text-green-500': isLyricWindowOpen }"
|
||||
@click="openLyricWindow"
|
||||
:class="{ 'text-green-500': isLyricWindowOpen, 'disabled-icon': !playMusic.id }"
|
||||
@click="playMusic.id && openLyricWindow()"
|
||||
></i>
|
||||
</template>
|
||||
{{ t('player.playBar.lyric') }}
|
||||
{{ playMusic.id ? t('player.playBar.lyric') : t('player.playBar.noSongPlaying') }}
|
||||
</n-tooltip>
|
||||
<n-popover
|
||||
trigger="click"
|
||||
@@ -677,6 +677,13 @@ const isEQVisible = ref(false);
|
||||
@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-single-loop {
|
||||
font-size: 1.5rem;
|
||||
|
||||
@@ -378,27 +378,46 @@ watch(
|
||||
|
||||
// 修改数据更新处
|
||||
const handleDataUpdate = (parsedData: {
|
||||
type?: string;
|
||||
nowTime: number;
|
||||
startCurrentTime: number;
|
||||
nextTime: number;
|
||||
isPlay: boolean;
|
||||
nowIndex: number;
|
||||
lrcArray: Array<{ text: string; trText: string }>;
|
||||
lrcTimeArray: number[];
|
||||
allTime: number;
|
||||
playMusic: SongResult;
|
||||
lrcArray?: Array<{ text: string; trText: string }>;
|
||||
lrcTimeArray?: number[];
|
||||
allTime?: number;
|
||||
playMusic?: SongResult;
|
||||
}) => {
|
||||
// 确保数据存在且格式正确
|
||||
if (!parsedData) {
|
||||
console.error('Invalid update data received:', parsedData);
|
||||
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 = {
|
||||
lrcArray: parsedData.lrcArray || [],
|
||||
lrcTimeArray: parsedData.lrcTimeArray || [],
|
||||
allTime: parsedData.allTime || 0,
|
||||
playMusic: parsedData.playMusic || {}
|
||||
playMusic: parsedData.playMusic || ({} as SongResult)
|
||||
};
|
||||
|
||||
// 更新动态数据
|
||||
@@ -472,7 +491,7 @@ watch(
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 添<EFBFBD><EFBFBD>拖动相关变量
|
||||
// 添加拖动相关变量
|
||||
const isDragging = ref(false);
|
||||
const startPosition = ref({ x: 0, y: 0 });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user