mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-28 10:57:23 +08:00
✨ feat: 优化桌面歌词添加歌曲控制 上一首下一首 播放暂停
This commit is contained in:
@@ -66,7 +66,7 @@ function createWindow() {
|
|||||||
store.set('set', setJson);
|
store.set('set', setJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadLyricWindow(ipcMain);
|
loadLyricWindow(ipcMain, mainWin);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 限制只能启动一个应用
|
// 限制只能启动一个应用
|
||||||
|
|||||||
+40
-21
@@ -1,11 +1,11 @@
|
|||||||
const { BrowserWindow, screen } = require('electron');
|
const { BrowserWindow, screen, ipcRenderer } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
|
||||||
let lyricWindow = null;
|
let lyricWindow = null;
|
||||||
let isDragging = false;
|
|
||||||
|
|
||||||
const createWin = () => {
|
const createWin = () => {
|
||||||
|
console.log('Creating lyric window');
|
||||||
lyricWindow = new BrowserWindow({
|
lyricWindow = new BrowserWindow({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 200,
|
height: 200,
|
||||||
@@ -21,16 +21,26 @@ const createWin = () => {
|
|||||||
webSecurity: false,
|
webSecurity: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听窗口关闭事件
|
||||||
|
lyricWindow.on('closed', () => {
|
||||||
|
console.log('Lyric window closed');
|
||||||
|
lyricWindow = null;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadLyricWindow = (ipcMain) => {
|
const loadLyricWindow = (ipcMain, mainWin) => {
|
||||||
ipcMain.on('open-lyric', () => {
|
ipcMain.on('open-lyric', () => {
|
||||||
|
console.log('Received open-lyric request');
|
||||||
if (lyricWindow) {
|
if (lyricWindow) {
|
||||||
|
console.log('Lyric window exists, focusing');
|
||||||
if (lyricWindow.isMinimized()) lyricWindow.restore();
|
if (lyricWindow.isMinimized()) lyricWindow.restore();
|
||||||
lyricWindow.focus();
|
lyricWindow.focus();
|
||||||
lyricWindow.show();
|
lyricWindow.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Creating new lyric window');
|
||||||
createWin();
|
createWin();
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
lyricWindow.webContents.openDevTools({ mode: 'detach' });
|
lyricWindow.webContents.openDevTools({ mode: 'detach' });
|
||||||
@@ -41,26 +51,39 @@ const loadLyricWindow = (ipcMain) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lyricWindow.setMinimumSize(600, 200);
|
lyricWindow.setMinimumSize(600, 200);
|
||||||
|
|
||||||
// 隐藏任务栏
|
|
||||||
lyricWindow.setSkipTaskbar(true);
|
lyricWindow.setSkipTaskbar(true);
|
||||||
|
|
||||||
lyricWindow.show();
|
lyricWindow.once('ready-to-show', () => {
|
||||||
|
console.log('Lyric window ready to show');
|
||||||
|
lyricWindow.show();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('send-lyric', (e, data) => {
|
ipcMain.on('send-lyric', (e, data) => {
|
||||||
if (lyricWindow) {
|
if (lyricWindow && !lyricWindow.isDestroyed()) {
|
||||||
lyricWindow.webContents.send('receive-lyric', data);
|
try {
|
||||||
|
lyricWindow.webContents.send('receive-lyric', data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing lyric data:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Cannot send lyric: window not available or destroyed');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('top-lyric', (e, data) => {
|
ipcMain.on('top-lyric', (e, data) => {
|
||||||
lyricWindow.setAlwaysOnTop(data);
|
if (lyricWindow && !lyricWindow.isDestroyed()) {
|
||||||
|
lyricWindow.setAlwaysOnTop(data);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('close-lyric', () => {
|
ipcMain.on('close-lyric', () => {
|
||||||
lyricWindow.close();
|
if (lyricWindow && !lyricWindow.isDestroyed()) {
|
||||||
lyricWindow = null;
|
lyricWindow.webContents.send('lyric-window-close');
|
||||||
|
mainWin.webContents.send('lyric-control-back', 'close');
|
||||||
|
lyricWindow.close();
|
||||||
|
lyricWindow = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('mouseenter-lyric', () => {
|
ipcMain.on('mouseenter-lyric', () => {
|
||||||
@@ -71,11 +94,6 @@ const loadLyricWindow = (ipcMain) => {
|
|||||||
lyricWindow.setIgnoreMouseEvents(false);
|
lyricWindow.setIgnoreMouseEvents(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 开始拖动
|
|
||||||
ipcMain.on('lyric-drag-start', () => {
|
|
||||||
isDragging = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理拖动移动
|
// 处理拖动移动
|
||||||
ipcMain.on('lyric-drag-move', (e, { deltaX, deltaY }) => {
|
ipcMain.on('lyric-drag-move', (e, { deltaX, deltaY }) => {
|
||||||
if (!lyricWindow) return;
|
if (!lyricWindow) return;
|
||||||
@@ -91,11 +109,6 @@ const loadLyricWindow = (ipcMain) => {
|
|||||||
lyricWindow.setPosition(newX, newY);
|
lyricWindow.setPosition(newX, newY);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 结束拖动
|
|
||||||
ipcMain.on('lyric-drag-end', () => {
|
|
||||||
isDragging = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加鼠标穿透事件处理
|
// 添加鼠标穿透事件处理
|
||||||
ipcMain.on('set-ignore-mouse', (e, shouldIgnore) => {
|
ipcMain.on('set-ignore-mouse', (e, shouldIgnore) => {
|
||||||
if (!lyricWindow) return;
|
if (!lyricWindow) return;
|
||||||
@@ -108,6 +121,12 @@ const loadLyricWindow = (ipcMain) => {
|
|||||||
lyricWindow.setIgnoreMouseEvents(false);
|
lyricWindow.setIgnoreMouseEvents(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 添加播放控制处理
|
||||||
|
ipcMain.on('control-back', (e, command) => {
|
||||||
|
console.log('Received control-back request:', command);
|
||||||
|
mainWin.webContents.send('lyric-control-back', command);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
+76
-59
@@ -17,6 +17,7 @@ export const correctionTime = ref(0.4); // 歌词矫正时间Correction time
|
|||||||
export const currentLrcProgress = ref(0); // 来存储当前歌词的进度
|
export const currentLrcProgress = ref(0); // 来存储当前歌词的进度
|
||||||
export const playMusic = computed(() => store.state.playMusic as SongResult); // 当前播放歌曲
|
export const playMusic = computed(() => store.state.playMusic as SongResult); // 当前播放歌曲
|
||||||
export const sound = ref<Howl | null>(audioService.getCurrentSound());
|
export const sound = ref<Howl | null>(audioService.getCurrentSound());
|
||||||
|
export const isLyricWindowOpen = ref(false); // 新增状态
|
||||||
|
|
||||||
document.onkeyup = (e) => {
|
document.onkeyup = (e) => {
|
||||||
// 检查事件目标是否是输入框元素
|
// 检查事件目标是否是输入框元素
|
||||||
@@ -53,13 +54,18 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => store.state.playMusic,
|
() => store.state.playMusic,
|
||||||
() => {
|
() => {
|
||||||
nextTick(() => {
|
nextTick(async () => {
|
||||||
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.value && isLyricWindowOpen.value && lrcArray.value.length > 0) {
|
||||||
|
sendLyricToWin();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
deep: true,
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -76,8 +82,13 @@ export const audioServiceOn = (audio: typeof audioService) => {
|
|||||||
if (newIndex !== nowIndex.value) {
|
if (newIndex !== nowIndex.value) {
|
||||||
nowIndex.value = newIndex;
|
nowIndex.value = newIndex;
|
||||||
currentLrcProgress.value = 0;
|
currentLrcProgress.value = 0;
|
||||||
|
// 当歌词索引更新时,发送歌词数据
|
||||||
|
if (isElectron.value && isLyricWindowOpen.value) {
|
||||||
|
sendLyricToWin();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isElectron.value) {
|
// 定期发送歌词数据更新
|
||||||
|
if (isElectron.value && isLyricWindowOpen.value) {
|
||||||
sendLyricToWin();
|
sendLyricToWin();
|
||||||
}
|
}
|
||||||
}, 50);
|
}, 50);
|
||||||
@@ -87,11 +98,14 @@ export const audioServiceOn = (audio: typeof audioService) => {
|
|||||||
audio.onPause(() => {
|
audio.onPause(() => {
|
||||||
store.commit('setPlayMusic', false);
|
store.commit('setPlayMusic', false);
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
|
// 暂停时也发送一次状态更新
|
||||||
|
if (isElectron.value && isLyricWindowOpen.value) {
|
||||||
|
sendLyricToWin();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听结束
|
// 监听结束
|
||||||
audio.onEnd(() => {
|
audio.onEnd(() => {
|
||||||
handleEnded();
|
|
||||||
if (store.state.playMode === 1) {
|
if (store.state.playMode === 1) {
|
||||||
// 单曲循环模式
|
// 单曲循环模式
|
||||||
audio.getCurrentSound()?.play();
|
audio.getCurrentSound()?.play();
|
||||||
@@ -213,7 +227,7 @@ export const useLyricProgress = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置当前播放时间
|
// 设置���前播放时间
|
||||||
export const setAudioTime = (index: number) => {
|
export const setAudioTime = (index: number) => {
|
||||||
const currentSound = sound.value;
|
const currentSound = sound.value;
|
||||||
if (!currentSound) return;
|
if (!currentSound) return;
|
||||||
@@ -241,72 +255,33 @@ export const getLrcTimeRange = (index: number) => ({
|
|||||||
watch(
|
watch(
|
||||||
() => lrcArray.value,
|
() => lrcArray.value,
|
||||||
(newLrcArray) => {
|
(newLrcArray) => {
|
||||||
if (newLrcArray.length > 0 && isElectron.value) {
|
if (newLrcArray.length > 0 && isElectron.value && isLyricWindowOpen.value) {
|
||||||
// 重新初始化歌词数据
|
|
||||||
initLyricWindow();
|
|
||||||
// 发送当前状态
|
|
||||||
sendLyricToWin();
|
sendLyricToWin();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// 监听播放状态变化
|
|
||||||
watch(isPlaying, (newIsPlaying) => {
|
|
||||||
if (isElectron.value) {
|
|
||||||
sendLyricToWin(newIsPlaying);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理歌曲结束
|
|
||||||
export const handleEnded = () => {
|
|
||||||
if (isElectron.value) {
|
|
||||||
setTimeout(() => {
|
|
||||||
initLyricWindow();
|
|
||||||
sendLyricToWin();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化歌词数据
|
|
||||||
export const initLyricWindow = () => {
|
|
||||||
if (!isElectron.value) return;
|
|
||||||
try {
|
|
||||||
if (lrcArray.value.length > 0) {
|
|
||||||
console.log('Initializing lyric window with data:', {
|
|
||||||
lrcArray: lrcArray.value,
|
|
||||||
lrcTimeArray: lrcTimeArray.value,
|
|
||||||
allTime: allTime.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
const staticData = {
|
|
||||||
type: 'init',
|
|
||||||
lrcArray: lrcArray.value,
|
|
||||||
lrcTimeArray: lrcTimeArray.value,
|
|
||||||
allTime: allTime.value,
|
|
||||||
};
|
|
||||||
windowData.electronAPI.sendLyric(JSON.stringify(staticData));
|
|
||||||
} else {
|
|
||||||
console.log('No lyrics available for initialization');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error initializing lyric window:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 发送歌词更新数据
|
// 发送歌词更新数据
|
||||||
export const sendLyricToWin = (isPlay: boolean = true) => {
|
export const sendLyricToWin = () => {
|
||||||
if (!isElectron.value) return;
|
if (!isElectron.value || !isLyricWindowOpen.value) {
|
||||||
|
console.log('Cannot send lyric: electron or lyric window not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (lrcArray.value.length > 0) {
|
if (lrcArray.value.length > 0) {
|
||||||
const nowIndex = getLrcIndex(nowTime.value);
|
const nowIndex = getLrcIndex(nowTime.value);
|
||||||
const updateData = {
|
const updateData = {
|
||||||
type: 'update',
|
type: 'full',
|
||||||
nowIndex,
|
nowIndex,
|
||||||
nowTime: nowTime.value,
|
nowTime: nowTime.value,
|
||||||
startCurrentTime: lrcTimeArray.value[nowIndex],
|
startCurrentTime: lrcTimeArray.value[nowIndex],
|
||||||
nextTime: lrcTimeArray.value[nowIndex + 1],
|
nextTime: lrcTimeArray.value[nowIndex + 1],
|
||||||
isPlay,
|
isPlay: isPlaying.value,
|
||||||
|
lrcArray: lrcArray.value,
|
||||||
|
lrcTimeArray: lrcTimeArray.value,
|
||||||
|
allTime: allTime.value,
|
||||||
|
playMusic: playMusic.value,
|
||||||
};
|
};
|
||||||
windowData.electronAPI.sendLyric(JSON.stringify(updateData));
|
windowData.electronAPI.sendLyric(JSON.stringify(updateData));
|
||||||
}
|
}
|
||||||
@@ -317,13 +292,55 @@ export const sendLyricToWin = (isPlay: boolean = true) => {
|
|||||||
|
|
||||||
export const openLyric = () => {
|
export const openLyric = () => {
|
||||||
if (!isElectron.value) return;
|
if (!isElectron.value) return;
|
||||||
console.log('Opening lyric window');
|
console.log('Opening lyric window with current song:', playMusic.value?.name);
|
||||||
windowData.electronAPI.openLyric();
|
windowData.electronAPI.openLyric();
|
||||||
|
isLyricWindowOpen.value = true;
|
||||||
|
|
||||||
// 延迟一下初始化,确保窗口已经创建
|
// 延迟一下初始化,确保窗口已经创建
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('Initializing lyric window after delay');
|
if (isLyricWindowOpen.value) {
|
||||||
initLyricWindow();
|
console.log('Initializing lyric window with data:', {
|
||||||
sendLyricToWin();
|
hasLyrics: lrcArray.value.length > 0,
|
||||||
|
songName: playMusic.value?.name,
|
||||||
|
});
|
||||||
|
sendLyricToWin();
|
||||||
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 添加关闭歌词窗口的方法
|
||||||
|
export const closeLyric = () => {
|
||||||
|
if (!isElectron.value) return;
|
||||||
|
isLyricWindowOpen.value = false;
|
||||||
|
windowData.electron.ipcRenderer.send('close-lyric');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加播放控制命令监听
|
||||||
|
if (isElectron.value) {
|
||||||
|
windowData.electron.ipcRenderer.on('lyric-control-back', (command: string) => {
|
||||||
|
console.log('Received playback control command:', command);
|
||||||
|
switch (command) {
|
||||||
|
case 'playpause':
|
||||||
|
if (store.state.play) {
|
||||||
|
store.commit('setPlayMusic', false);
|
||||||
|
audioService.getCurrentSound()?.pause();
|
||||||
|
} else {
|
||||||
|
store.commit('setPlayMusic', true);
|
||||||
|
audioService.getCurrentSound()?.play();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'prev':
|
||||||
|
store.commit('prevPlay');
|
||||||
|
break;
|
||||||
|
case 'next':
|
||||||
|
store.commit('nextPlay');
|
||||||
|
break;
|
||||||
|
case 'close':
|
||||||
|
closeLyric();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('Unknown command:', command);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,7 +69,11 @@
|
|||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
<n-tooltip v-if="isElectron" class="music-lyric" trigger="hover" :z-index="9999999">
|
<n-tooltip v-if="isElectron" class="music-lyric" trigger="hover" :z-index="9999999">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<i class="iconfont ri-netease-cloud-music-line" @click="openLyric"></i>
|
<i
|
||||||
|
class="iconfont ri-netease-cloud-music-line"
|
||||||
|
:class="{ 'text-green-500': isLyricWindowOpen }"
|
||||||
|
@click="openLyricWindow"
|
||||||
|
></i>
|
||||||
</template>
|
</template>
|
||||||
歌词
|
歌词
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
@@ -113,7 +117,7 @@ import { useTemplateRef } from 'vue';
|
|||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
|
|
||||||
import SongItem from '@/components/common/SongItem.vue';
|
import SongItem from '@/components/common/SongItem.vue';
|
||||||
import { allTime, isElectron, nowTime, openLyric, sound } from '@/hooks/MusicHook';
|
import { allTime, isElectron, isLyricWindowOpen, nowTime, openLyric, sound } from '@/hooks/MusicHook';
|
||||||
import type { SongResult } from '@/type/music';
|
import type { SongResult } from '@/type/music';
|
||||||
import { getImgUrl, secondToMinute, setAnimationClass } from '@/utils';
|
import { getImgUrl, secondToMinute, setAnimationClass } from '@/utils';
|
||||||
|
|
||||||
@@ -253,6 +257,10 @@ const toggleFavorite = async (e: Event) => {
|
|||||||
store.commit('addToFavorite', playMusic.value.id);
|
store.commit('addToFavorite', playMusic.value.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openLyricWindow = () => {
|
||||||
|
openLyric();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
+102
-64
@@ -18,15 +18,28 @@
|
|||||||
<i class="ri-add-line"></i>
|
<i class="ri-add-line"></i>
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-button-group>
|
</n-button-group>
|
||||||
|
<div>{{ staticData.playMusic.name }}</div>
|
||||||
|
</div>
|
||||||
|
<!-- 添加播放控制按钮 -->
|
||||||
|
<div class="play-controls">
|
||||||
|
<div class="control-button" @click="handlePrev">
|
||||||
|
<i class="ri-skip-back-fill"></i>
|
||||||
|
</div>
|
||||||
|
<div class="control-button play-button" @click="handlePlayPause">
|
||||||
|
<i :class="dynamicData.isPlay ? 'ri-pause-fill' : 'ri-play-fill'"></i>
|
||||||
|
</div>
|
||||||
|
<div class="control-button" @click="handleNext">
|
||||||
|
<i class="ri-skip-forward-fill"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-buttons">
|
<div class="control-buttons">
|
||||||
<div class="control-button" @click="checkTheme">
|
<div class="control-button" @click="checkTheme">
|
||||||
<i v-if="lyricSetting.theme === 'light'" class="ri-sun-line"></i>
|
<i v-if="lyricSetting.theme === 'light'" class="ri-sun-line"></i>
|
||||||
<i v-else class="ri-moon-line"></i>
|
<i v-else class="ri-moon-line"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-button" @click="handleTop">
|
<!-- <div class="control-button" @click="handleTop">
|
||||||
<i class="ri-pushpin-line" :class="{ active: lyricSetting.isTop }"></i>
|
<i class="ri-pushpin-line" :class="{ active: lyricSetting.isTop }"></i>
|
||||||
</div>
|
</div> -->
|
||||||
<div id="lyric-lock" class="control-button" @click="handleLock">
|
<div id="lyric-lock" class="control-button" @click="handleLock">
|
||||||
<i v-if="lyricSetting.isLock" class="ri-lock-line"></i>
|
<i v-if="lyricSetting.isLock" class="ri-lock-line"></i>
|
||||||
<i v-else class="ri-lock-unlock-line"></i>
|
<i v-else class="ri-lock-unlock-line"></i>
|
||||||
@@ -46,7 +59,10 @@
|
|||||||
v-for="(line, index) in staticData.lrcArray"
|
v-for="(line, index) in staticData.lrcArray"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="lyric-line"
|
class="lyric-line"
|
||||||
:style="lyricLineStyle"
|
:style="{
|
||||||
|
...lyricLineStyle,
|
||||||
|
display: line.text ? 'flex' : 'none',
|
||||||
|
}"
|
||||||
:class="{
|
:class="{
|
||||||
'lyric-line-current': index === currentIndex,
|
'lyric-line-current': index === currentIndex,
|
||||||
'lyric-line-passed': index < currentIndex,
|
'lyric-line-passed': index < currentIndex,
|
||||||
@@ -71,18 +87,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { SongResult } from '@/type/music';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Lyric',
|
name: 'Lyric',
|
||||||
});
|
});
|
||||||
|
|
||||||
const windowData = window as any;
|
const windowData = window as any;
|
||||||
const containerRef = ref<HTMLElement | null>(null);
|
const containerRef = ref<HTMLElement | null>(null);
|
||||||
const containerHeight = ref(0);
|
const containerHeight = ref(0);
|
||||||
const lineHeight = ref(60);
|
const lineHeight = ref(60);
|
||||||
const currentIndex = ref(0);
|
const currentIndex = ref(0);
|
||||||
const isInitialized = ref(false);
|
|
||||||
// 字体大小控制
|
// 字体大小控制
|
||||||
const fontSize = ref(24); // 默认字体大小
|
const fontSize = ref(24); // 默认字体大小
|
||||||
const fontSizeStep = 2; // 每次整的步长
|
const fontSizeStep = 2; // 每次整的步长
|
||||||
@@ -94,10 +110,12 @@ const staticData = ref<{
|
|||||||
lrcArray: Array<{ text: string; trText: string }>;
|
lrcArray: Array<{ text: string; trText: string }>;
|
||||||
lrcTimeArray: number[];
|
lrcTimeArray: number[];
|
||||||
allTime: number;
|
allTime: number;
|
||||||
|
playMusic: SongResult;
|
||||||
}>({
|
}>({
|
||||||
lrcArray: [],
|
lrcArray: [],
|
||||||
lrcTimeArray: [],
|
lrcTimeArray: [],
|
||||||
allTime: 0,
|
allTime: 0,
|
||||||
|
playMusic: {} as SongResult,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 动态数据
|
// 动态数据
|
||||||
@@ -140,7 +158,6 @@ const clearHideTimer = () => {
|
|||||||
|
|
||||||
// 处理鼠标进入窗口
|
// 处理鼠标进入窗口
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
console.log('handleMouseEnter');
|
|
||||||
if (lyricSetting.value.isLock) {
|
if (lyricSetting.value.isLock) {
|
||||||
isHovering.value = true;
|
isHovering.value = true;
|
||||||
windowData.electron.ipcRenderer.send('set-ignore-mouse', true);
|
windowData.electron.ipcRenderer.send('set-ignore-mouse', true);
|
||||||
@@ -151,7 +168,6 @@ const handleMouseEnter = () => {
|
|||||||
|
|
||||||
// 处理鼠标离开窗口
|
// 处理鼠标离开窗口
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
console.log('handleMouseLeave');
|
|
||||||
if (!lyricSetting.value.isLock) return;
|
if (!lyricSetting.value.isLock) return;
|
||||||
isHovering.value = false;
|
isHovering.value = false;
|
||||||
windowData.electron.ipcRenderer.send('set-ignore-mouse', false);
|
windowData.electron.ipcRenderer.send('set-ignore-mouse', false);
|
||||||
@@ -180,7 +196,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
// 计算歌词滚动位置
|
// 计算歌词滚动位置
|
||||||
const wrapperStyle = computed(() => {
|
const wrapperStyle = computed(() => {
|
||||||
if (!isInitialized.value || !containerHeight.value) {
|
if (!containerHeight.value) {
|
||||||
return {
|
return {
|
||||||
transform: 'translateY(0)',
|
transform: 'translateY(0)',
|
||||||
transition: 'none',
|
transition: 'none',
|
||||||
@@ -208,7 +224,7 @@ const wrapperStyle = computed(() => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
transform: `translateY(${finalOffset}px)`,
|
transform: `translateY(${finalOffset}px)`,
|
||||||
transition: isInitialized.value ? 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)' : 'none',
|
transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -281,8 +297,8 @@ const actualTime = ref(0);
|
|||||||
|
|
||||||
// 计算当前行的进度
|
// 计算当前行的进度
|
||||||
const currentProgress = computed(() => {
|
const currentProgress = computed(() => {
|
||||||
const { startCurrentTime, nextTime, isPlay } = dynamicData.value;
|
const { startCurrentTime, nextTime } = dynamicData.value;
|
||||||
if (!startCurrentTime || !nextTime || !isPlay) return 0;
|
if (!startCurrentTime || !nextTime) return 0;
|
||||||
|
|
||||||
const duration = nextTime - startCurrentTime;
|
const duration = nextTime - startCurrentTime;
|
||||||
const elapsed = actualTime.value - startCurrentTime;
|
const elapsed = actualTime.value - startCurrentTime;
|
||||||
@@ -364,22 +380,34 @@ const handleDataUpdate = (parsedData: {
|
|||||||
nextTime: number;
|
nextTime: number;
|
||||||
isPlay: boolean;
|
isPlay: boolean;
|
||||||
nowIndex: number;
|
nowIndex: number;
|
||||||
|
lrcArray: Array<{ text: string; trText: string }>;
|
||||||
|
lrcTimeArray: number[];
|
||||||
|
allTime: number;
|
||||||
|
playMusic: SongResult;
|
||||||
}) => {
|
}) => {
|
||||||
// 确保数据存在且格式正确
|
// 确保数据存在且格式正确
|
||||||
if (!parsedData || typeof parsedData.nowTime !== 'number') {
|
if (!parsedData) {
|
||||||
console.error('Invalid update data received:', parsedData);
|
console.error('Invalid update data received:', parsedData);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 更新静态数据
|
||||||
|
staticData.value = {
|
||||||
|
lrcArray: parsedData.lrcArray || [],
|
||||||
|
lrcTimeArray: parsedData.lrcTimeArray || [],
|
||||||
|
allTime: parsedData.allTime || 0,
|
||||||
|
playMusic: parsedData.playMusic || {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新动态数据
|
||||||
dynamicData.value = {
|
dynamicData.value = {
|
||||||
nowTime: parsedData.nowTime,
|
nowTime: parsedData.nowTime || 0,
|
||||||
startCurrentTime: parsedData.startCurrentTime,
|
startCurrentTime: parsedData.startCurrentTime || 0,
|
||||||
nextTime: parsedData.nextTime,
|
nextTime: parsedData.nextTime || 0,
|
||||||
isPlay: parsedData.isPlay,
|
isPlay: parsedData.isPlay,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新索引
|
// 更新索引
|
||||||
if (typeof parsedData.nowIndex === 'number' && parsedData.nowIndex !== currentIndex.value) {
|
if (typeof parsedData.nowIndex === 'number') {
|
||||||
currentIndex.value = parsedData.nowIndex;
|
currentIndex.value = parsedData.nowIndex;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -400,33 +428,7 @@ onMounted(() => {
|
|||||||
windowData.electron.ipcRenderer.on('receive-lyric', (data: string) => {
|
windowData.electron.ipcRenderer.on('receive-lyric', (data: string) => {
|
||||||
try {
|
try {
|
||||||
const parsedData = JSON.parse(data);
|
const parsedData = JSON.parse(data);
|
||||||
if (parsedData.type === 'init') {
|
handleDataUpdate(parsedData);
|
||||||
// 初始化重置状态
|
|
||||||
currentIndex.value = 0;
|
|
||||||
isInitialized.value = false;
|
|
||||||
|
|
||||||
// 清理可能存在的动画
|
|
||||||
if (animationFrameId.value) {
|
|
||||||
cancelAnimationFrame(animationFrameId.value);
|
|
||||||
animationFrameId.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保数据格式正确
|
|
||||||
if (Array.isArray(parsedData.lrcArray)) {
|
|
||||||
staticData.value = {
|
|
||||||
lrcArray: parsedData.lrcArray,
|
|
||||||
lrcTimeArray: parsedData.lrcTimeArray || [],
|
|
||||||
allTime: parsedData.allTime || 0,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
console.error('Invalid lyric array format:', parsedData);
|
|
||||||
}
|
|
||||||
nextTick(() => {
|
|
||||||
isInitialized.value = true;
|
|
||||||
});
|
|
||||||
} else if (parsedData.type === 'update') {
|
|
||||||
handleDataUpdate(parsedData);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing lyric data:', error);
|
console.error('Error parsing lyric data:', error);
|
||||||
}
|
}
|
||||||
@@ -467,7 +469,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 });
|
||||||
|
|
||||||
@@ -534,6 +536,19 @@ onMounted(() => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 添加播放控制相关的函数
|
||||||
|
const handlePlayPause = () => {
|
||||||
|
windowData.electron.ipcRenderer.send('control-back', 'playpause');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrev = () => {
|
||||||
|
windowData.electron.ipcRenderer.send('control-back', 'prev');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
windowData.electron.ipcRenderer.send('control-back', 'next');
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -554,7 +569,7 @@ body {
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
.control-bar {
|
.control-bar {
|
||||||
&-show {
|
&-show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -571,7 +586,7 @@ body {
|
|||||||
--text-color: #ffffff;
|
--text-color: #ffffff;
|
||||||
--text-secondary: rgba(255, 255, 255, 0.6);
|
--text-secondary: rgba(255, 255, 255, 0.6);
|
||||||
--highlight-color: #1db954;
|
--highlight-color: #1db954;
|
||||||
--control-bg: rgba(0, 0, 0, 0.3);
|
--control-bg: rgba(124, 124, 124, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.light {
|
&.light {
|
||||||
@@ -584,13 +599,13 @@ body {
|
|||||||
|
|
||||||
.control-bar {
|
.control-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 10px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 40px;
|
height: 80px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: start;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -600,13 +615,28 @@ body {
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
.font-size-controls {
|
.font-size-controls {
|
||||||
margin-right: auto; // 将字体控制放在侧
|
-webkit-app-region: no-drag;
|
||||||
padding-right: 20px;
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
.n-button {
|
.play-button {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
i {
|
i {
|
||||||
font-size: 16px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -623,23 +653,21 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.control-button {
|
.control-button {
|
||||||
width: 32px;
|
width: 36px;
|
||||||
height: 32px;
|
height: 36px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 50%;
|
border-radius: 8px;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--control-bg);
|
background: var(--control-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 18px;
|
font-size: 20px;
|
||||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
@@ -650,7 +678,7 @@ body {
|
|||||||
|
|
||||||
.lyric-container {
|
.lyric-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 40px;
|
top: 80px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -689,8 +717,7 @@ body {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.lyric-line-passed,
|
&.lyric-line-passed {
|
||||||
&.lyric-line-next {
|
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -751,6 +778,10 @@ body {
|
|||||||
.lyric_lock & .font-size-controls {
|
.lyric_lock & .font-size-controls {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lyric_lock & .play-controls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lyric_lock {
|
.lyric_lock {
|
||||||
@@ -758,5 +789,12 @@ body {
|
|||||||
&:hover {
|
&:hover {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#lyric-lock {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 72px;
|
||||||
|
background: var(--control-bg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user