From 8fd0112695e1cb4d911824fbcdfe1468b93c0783 Mon Sep 17 00:00:00 2001 From: Pissofdvpe Date: Sun, 15 Feb 2026 18:12:18 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A6=84refactor(mac/tray/lyric):=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84=20macOS=20=E7=8A=B6=E6=80=81=E6=A0=8F?= =?UTF-8?q?=E6=AD=8C=E8=AF=8D=E5=8A=9F=E8=83=BD=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=A4=9A=E9=A1=B9=E6=98=BE=E7=A4=BA=E4=B8=8E=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E5=85=A8=E9=9D=A2=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E6=80=A7=E4=B8=8E=E7=94=A8=E6=88=B7=E4=BD=93?= =?UTF-8?q?=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一状态管理,解决功能切换竞态条件 - 明确模块职责,消除歌词与歌曲信息覆盖问题 - 优化歌词显示更新机制,解决启动和拖动时的闪烁卡顿 - 提升代码健壮性,解决类型错误并精简冗余代码 --- electron/main/ipc/ipc-mac-statusbar.ts | 82 +++++++++++++++----------- electron/main/ipc/ipc-tray.ts | 27 ++++----- electron/main/tray/index.ts | 41 ++++--------- 3 files changed, 74 insertions(+), 76 deletions(-) diff --git a/electron/main/ipc/ipc-mac-statusbar.ts b/electron/main/ipc/ipc-mac-statusbar.ts index 513553708..b7f18d285 100644 --- a/electron/main/ipc/ipc-mac-statusbar.ts +++ b/electron/main/ipc/ipc-mac-statusbar.ts @@ -4,7 +4,7 @@ import { ipcMain } from "electron"; import { useStore } from "../store"; import { getMainTray } from "../tray"; import mainWindow from "../windows/main-window"; - +import { getCurrentSongTitle } from "./ipc-tray"; // 本地导入,用于关闭时恢复标题 let macLyricLines: LyricLine[] = []; let macCurrentTime = 0; let macOffset = 0; @@ -65,28 +65,42 @@ const findCurrentLyricIndex = ( /** * 更新 macOS 状态栏歌词(只在新行时才更新) + * @param store Pinia store 实例 + * @param forceUpdate 是否强制更新,即便歌词行索引未变化 */ -const updateMacStatusBarLyric = (store: ReturnType) => { +const updateMacStatusBarLyric = ( + store: ReturnType, + forceUpdate: boolean = false, +) => { const tray = getMainTray(); if (!tray) return; + // 检查 macOS 状态栏歌词功能是否启用 + const isMacosLyricEnabled = store.get("macos.statusBarLyric.enabled") ?? false; + if (!isMacosLyricEnabled) { + // 如果功能被禁用,则确保托盘标题显示为当前歌曲名,并立即返回 + tray.setTitle(getCurrentSongTitle()); + return; + } + const showWhenPaused = store.get("taskbar.showWhenPaused") ?? true; if (!macIsPlaying && !showWhenPaused) { - // 如果不显示,则清空标题 - tray.setMacStatusBarLyricTitle(""); + // 如果当前未播放且设置不允许暂停时显示歌词,则清空标题 + tray.setTitle(""); return; } - // 如果歌词为空,则清空标题并返回 + // 如果歌词数据为空,则清空标题并返回 if (macLyricLines.length === 0) { - tray.setMacStatusBarLyricTitle(""); + tray.setTitle(""); return; } const currentLyricIndex = findCurrentLyricIndex(macCurrentTime, macLyricLines, macOffset); - // 如果行索引没有变化,不更新 - if (currentLyricIndex === macLastLyricIndex) return; + // 如果不是强制更新模式,并且歌词行索引没有变化,则跳过更新 + // `forceUpdate` 用于在启动时或拖动进度条时,即使行索引未变,也强制更新 + if (!forceUpdate && currentLyricIndex === macLastLyricIndex) return; macLastLyricIndex = currentLyricIndex; const currentLyric = @@ -97,64 +111,69 @@ const updateMacStatusBarLyric = (store: ReturnType) => { .trim() : ""; - tray.setMacStatusBarLyricTitle(currentLyric); + tray.setTitle(currentLyric); }; export const initMacStatusBarIpc = () => { const store = useStore(); - // 初始化时读取新的 macOS 专属设置 + // 初始化时读取 macOS 专属设置 const isMacosLyricEnabled = store.get("macos.statusBarLyric.enabled") ?? false; const tray = getMainTray(); - tray?.setMacStatusBarLyricShow(isMacosLyricEnabled); // 根据新设置初始化显示状态 + + // 根据初始设置状态更新托盘显示 + // 如果禁用,设置回歌曲标题 + if (isMacosLyricEnabled) { + } else { + tray?.setTitle(getCurrentSongTitle()); + } // 新增 macOS 专属设置切换监听 ipcMain.on("macos-lyric:toggle", (_event, show: boolean) => { - store.set("macos.statusBarLyric.enabled", show); // 更新 store + store.set("macos.statusBarLyric.enabled", show); const tray = getMainTray(); + const mainWin = mainWindow.getWin(); - // 触发 "mac-toggle-statusbar-lyric" 事件,让 ipc-tray 响应 - ipcMain.emit("mac-toggle-statusbar-lyric", null, show); + // 强制更新托盘菜单,以响应新的开启/关闭状态 + (tray as any)?.initTrayMenu(); - const mainWin = mainWindow.getWin(); // 获取主窗口实例 if (mainWin && !mainWin.isDestroyed()) { // 发送更新给渲染进程,同步 Pinia store mainWin.webContents.send("setting:update-macos-lyric-enabled", show); if (show) { - mainWin.webContents.send(TASKBAR_IPC_CHANNELS.REQUEST_DATA); // 请求新数据 + mainWin.webContents.send(TASKBAR_IPC_CHANNELS.REQUEST_DATA); } else { - tray?.setMacStatusBarLyricTitle(""); // 关闭时清空歌词 - stopInterpolation(); // 关闭时停止计时器 + // 关闭时,将标题恢复为歌曲名,并停止歌词插值计时器 + tray?.setTitle(getCurrentSongTitle()); + stopInterpolation(); } } else if (!show) { - // 如果主窗口不可用且正在关闭,也清空歌词 - tray?.setMacStatusBarLyricTitle(""); - stopInterpolation(); // 关闭时停止计时器 + // 如果主窗口不可用且正在关闭,也恢复标题并停止计时器 + tray?.setTitle(getCurrentSongTitle()); + stopInterpolation(); } }); ipcMain.on(TASKBAR_IPC_CHANNELS.SYNC_STATE, (_event, payload: SyncStatePayload) => { switch (payload.type) { case "lyrics-loaded": { + // 仅更新歌词数据,不立即更新状态栏显示 macLyricLines = payload.data.lines; macLastLyricIndex = -1; - // 确保新歌词到达后立即更新状态栏显示 - const mainWin = mainWindow.getWin(); - if (mainWin && !mainWin.isDestroyed()) { - updateMacStatusBarLyric(useStore()); - } break; } case "playback-state": macIsPlaying = payload.data.isPlaying; - if (!macIsPlaying) { + // 不在这里直接更新歌词,依赖 SYNC_TICK 来驱动 + if (!macIsPlaying) { // 如果是暂停状态,则停止插值器并进行一次最终更新 stopInterpolation(); updateMacStatusBarLyric(store); } break; case "full-hydration": + // 接收完整的状态,但歌词更新仍然依赖 SYNC_TICK if (payload.data.lyrics) { macLyricLines = payload.data.lyrics.lines; macLastLyricIndex = -1; @@ -167,14 +186,11 @@ export const initMacStatusBarIpc = () => { macOffset = offset; } } - updateMacStatusBarLyric(store); - if (macIsPlaying) { - startInterpolation(store); - } break; } }); + // macOS 状态栏歌词专用进度更新 ipcMain.on(TASKBAR_IPC_CHANNELS.SYNC_TICK, (_, payload: SyncTickPayload) => { const [currentTime, _duration, offset] = payload; @@ -193,8 +209,8 @@ export const initMacStatusBarIpc = () => { macOffset = offset; } // 收到精确进度或误差较大同步后,立即更新一次歌词显示 - updateMacStatusBarLyric(store); // 如果此时是播放状态,确保插值器运行 + updateMacStatusBarLyric(store, true); if (macIsPlaying) { startInterpolation(store); } @@ -207,4 +223,4 @@ export const initMacStatusBarIpc = () => { mainWin.webContents.send(TASKBAR_IPC_CHANNELS.REQUEST_DATA); } }); -}; +}; \ No newline at end of file diff --git a/electron/main/ipc/ipc-tray.ts b/electron/main/ipc/ipc-tray.ts index ea8c593c0..0d5a8ee01 100644 --- a/electron/main/ipc/ipc-tray.ts +++ b/electron/main/ipc/ipc-tray.ts @@ -3,12 +3,17 @@ import { ipcMain } from "electron"; import { getMainTray } from "../tray"; import { appName, isMac } from "../utils/config"; import lyricWindow from "../windows/lyric-window"; +import { useStore } from "../store"; -// macOS 状态栏歌词开关状态 -let macStatusBarLyricEnabled = false; // 当前歌曲标题 let currentSongTitle = appName; +/** + * 获取当前歌曲标题 + * @returns 当前歌曲标题 + */ +export const getCurrentSongTitle = () => currentSongTitle; + /** * 托盘 IPC */ @@ -25,11 +30,14 @@ const initTrayIpc = (): void => { // 音乐名称更改 ipcMain.on("play-song-change", (_, options) => { + const store = useStore(); + // 从 Store 获取 macOS 状态栏歌词的启用状态 + const isMacLyricEnabled = store.get("macos.statusBarLyric.enabled") ?? false; let title = options?.title; if (!title) title = appName; currentSongTitle = title; - // 更改标题(仅在非 macOS 状态栏歌词模式下更新托盘标题) - if (!isMac || !macStatusBarLyricEnabled) { + // 更改托盘标题:仅在非 macOS 状态栏歌词模式下,或 macOS 歌词未启用时,才更新托盘标题为歌曲名 + if (!isMac || !isMacLyricEnabled) { tray?.setTitle(title); } tray?.setPlayName(title); @@ -54,17 +62,6 @@ const initTrayIpc = (): void => { ipcMain.on("desktop-lyric:toggle-lock", (_, { lock }: { lock: boolean }) => { tray?.setDesktopLyricLock(lock); }); - - // macOS 状态栏歌词开关 - ipcMain.on("mac-toggle-statusbar-lyric", (_, show: boolean) => { - if (!isMac) return; - macStatusBarLyricEnabled = show; - tray?.setMacStatusBarLyricShow(show); - // 如果关闭,恢复显示歌曲标题 - if (!show) { - tray?.setTitle(currentSongTitle); - } - }); }; export default initTrayIpc; diff --git a/electron/main/tray/index.ts b/electron/main/tray/index.ts index efa95860c..91f63c840 100644 --- a/electron/main/tray/index.ts +++ b/electron/main/tray/index.ts @@ -28,9 +28,6 @@ let likeSong: boolean = false; let desktopLyricShow: boolean = false; let desktopLyricLock: boolean = false; let taskbarLyricShow: boolean = false; -// macOS 状态栏歌词 -let macStatusBarLyricShow: boolean = false; -let macStatusBarLyricTitle: string = ""; export interface MainTray { setTitle(title: string): void; @@ -41,8 +38,7 @@ export interface MainTray { setDesktopLyricShow(show: boolean): void; setDesktopLyricLock(lock: boolean): void; setTaskbarLyricShow(show: boolean): void; - setMacStatusBarLyricShow(show: boolean, songTitle?: string): void; - setMacStatusBarLyricTitle(title: string): void; + initTrayMenu(): void; destroyTray(): void; } @@ -107,7 +103,7 @@ const getMenuIcon = (iconName: string): NativeImage | undefined => { }; // 托盘菜单 -const createTrayMenu = (win: BrowserWindow): MenuItemConstructorOptions[] => { +const createTrayMenu = (win: BrowserWindow, store: ReturnType): MenuItemConstructorOptions[] => { /** * 获取 {@linkcode RepeatModeType} 对应的显示字符串 * @param mode 重复模式 @@ -124,6 +120,9 @@ const createTrayMenu = (win: BrowserWindow): MenuItemConstructorOptions[] => { return "列表循环"; } }; + + const isMacosLyricEnabled = store.get("macos.statusBarLyric.enabled") ?? false; + // 菜单 const menu: MenuItemConstructorOptions[] = [ { @@ -226,7 +225,7 @@ const createTrayMenu = (win: BrowserWindow): MenuItemConstructorOptions[] => { }, { id: "toggle-taskbar-lyric", - label: `${(isMac ? macStatusBarLyricShow : taskbarLyricShow) ? "关闭" : "开启"}${isMac ? "状态栏" : "任务栏"}歌词`, + label: `${(isMac ? isMacosLyricEnabled : taskbarLyricShow) ? "关闭" : "开启"}${isMac ? "状态栏" : "任务栏"}歌词`, icon: getMenuIcon("lyric"), visible: isWin || isMac, click: () => win.webContents.send("toggle-taskbar-lyric"), @@ -268,9 +267,11 @@ class CreateTray implements MainTray { // 菜单 private _menu: MenuItemConstructorOptions[]; private _contextMenu: Menu; + private _store: ReturnType; // 添加 store 成员变量以访问全局状态 constructor(win: BrowserWindow) { this._win = win; + this._store = useStore(); if (isWin) { const iconPath = join(__dirname, `../../public/icons/tray/tray.ico`); @@ -289,15 +290,15 @@ class CreateTray implements MainTray { this._tray = new Tray(icon); } - this._menu = createTrayMenu(this._win); + this._menu = createTrayMenu(this._win, this._store); this._contextMenu = Menu.buildFromTemplate(this._menu); this.initTrayMenu(); this.initEvents(); - this.setTitle(appName); + this._tray.setTitle(appName); // 仅设置托盘标题,不设置窗口标题 } // 托盘菜单 - private initTrayMenu() { - this._menu = createTrayMenu(this._win); + public initTrayMenu() { + this._menu = createTrayMenu(this._win, this._store); this._contextMenu = Menu.buildFromTemplate(this._menu); this._tray.setContextMenu(this._contextMenu); } @@ -318,7 +319,6 @@ class CreateTray implements MainTray { * @param title 标题 */ setTitle(title: string) { - this._win.setTitle(title); this._tray.setTitle(title); this._tray.setToolTip(title); } @@ -383,25 +383,10 @@ class CreateTray implements MainTray { setTaskbarLyricShow(show: boolean) { taskbarLyricShow = show; + // 更新菜单 this.initTrayMenu(); } - setMacStatusBarLyricShow(show: boolean, songTitle?: string) { - macStatusBarLyricShow = show; - this.initTrayMenu(); - if (show && macStatusBarLyricTitle) { - this._tray.setTitle(macStatusBarLyricTitle); - } else if (!show) { - this._tray.setTitle(songTitle ?? appName); - } - } - - setMacStatusBarLyricTitle(title: string) { - macStatusBarLyricTitle = title; - if (macStatusBarLyricShow) { - this._tray.setTitle(title); - } - } /** * 销毁托盘 */ From f0968b145c066ce9d4c919ea1f070e5b9ca95b72 Mon Sep 17 00:00:00 2001 From: Pissofdvpe Date: Sun, 15 Feb 2026 20:20:19 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=90=9Efix(mac/tray/lyric):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=A1=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main/ipc/ipc-mac-statusbar.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/electron/main/ipc/ipc-mac-statusbar.ts b/electron/main/ipc/ipc-mac-statusbar.ts index b7f18d285..e2f8d7131 100644 --- a/electron/main/ipc/ipc-mac-statusbar.ts +++ b/electron/main/ipc/ipc-mac-statusbar.ts @@ -123,8 +123,7 @@ export const initMacStatusBarIpc = () => { // 根据初始设置状态更新托盘显示 // 如果禁用,设置回歌曲标题 - if (isMacosLyricEnabled) { - } else { + if (!isMacosLyricEnabled) { tray?.setTitle(getCurrentSongTitle()); } @@ -135,7 +134,7 @@ export const initMacStatusBarIpc = () => { const mainWin = mainWindow.getWin(); // 强制更新托盘菜单,以响应新的开启/关闭状态 - (tray as any)?.initTrayMenu(); + tray?.initTrayMenu(); if (mainWin && !mainWin.isDestroyed()) { // 发送更新给渲染进程,同步 Pinia store From b221f24cbff5e953829e741d836417af972bfa09 Mon Sep 17 00:00:00 2001 From: Pissofdvpe Date: Mon, 16 Feb 2026 00:20:16 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A6=84refactor(mac/ipc,mac/tray):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20macOS=20=E7=8A=B6=E6=80=81=E6=A0=8F?= =?UTF-8?q?=E6=AD=8C=E8=AF=8D=E5=92=8C=E6=89=98=E7=9B=98=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=9A=84=20store=20=E8=AE=BF=E9=97=AE=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 electron/main/ipc/ipc-mac-statusbar.ts 中 updateMacStatusBarLyric 和 startInterpolation 函数的 store 参数,改为函数内部通过 useStore() 获取实例。 - 更新 ipc-mac-statusbar.ts 中所有对 updateMacStatusBarLyric 和 startInterpolation 的调用,移除 store 参数。 - 移除 electron/main/tray/index.ts 中 createTrayMenu 函数的 store 参数,改为函数内部通过 useStore() 获取实例。 - 移除 electron/main/tray/index.ts 中 CreateTray 类冗余的 _store 成员变量。 - 更新 CreateTray 类中所有对 createTrayMenu 的调用,移除 store 参数。 --- electron/main/ipc/ipc-mac-statusbar.ts | 16 ++++++++-------- electron/main/tray/index.ts | 9 ++++----- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/electron/main/ipc/ipc-mac-statusbar.ts b/electron/main/ipc/ipc-mac-statusbar.ts index e2f8d7131..616881bd1 100644 --- a/electron/main/ipc/ipc-mac-statusbar.ts +++ b/electron/main/ipc/ipc-mac-statusbar.ts @@ -4,7 +4,8 @@ import { ipcMain } from "electron"; import { useStore } from "../store"; import { getMainTray } from "../tray"; import mainWindow from "../windows/main-window"; -import { getCurrentSongTitle } from "./ipc-tray"; // 本地导入,用于关闭时恢复标题 +import { getCurrentSongTitle } from "./ipc-tray"; + let macLyricLines: LyricLine[] = []; let macCurrentTime = 0; let macOffset = 0; @@ -29,7 +30,7 @@ const stopInterpolation = () => { /** * 启动插值计时器 */ -const startInterpolation = (store: ReturnType) => { +const startInterpolation = () => { stopInterpolation(); // 先停止任何已存在的计时器 macLastUpdateTime = Date.now(); // 在启动新的插值计时器时,重置 macLastUpdateTime interpolationTimer = setInterval(() => { @@ -37,7 +38,7 @@ const startInterpolation = (store: ReturnType) => { const elapsedTime = now - macLastUpdateTime; macCurrentTime += elapsedTime; macLastUpdateTime = now; - updateMacStatusBarLyric(store); + updateMacStatusBarLyric(); }, LYRIC_UPDATE_INTERVAL); }; @@ -65,13 +66,12 @@ const findCurrentLyricIndex = ( /** * 更新 macOS 状态栏歌词(只在新行时才更新) - * @param store Pinia store 实例 * @param forceUpdate 是否强制更新,即便歌词行索引未变化 */ const updateMacStatusBarLyric = ( - store: ReturnType, forceUpdate: boolean = false, ) => { + const store = useStore(); const tray = getMainTray(); if (!tray) return; @@ -167,7 +167,7 @@ export const initMacStatusBarIpc = () => { // 不在这里直接更新歌词,依赖 SYNC_TICK 来驱动 if (!macIsPlaying) { // 如果是暂停状态,则停止插值器并进行一次最终更新 stopInterpolation(); - updateMacStatusBarLyric(store); + updateMacStatusBarLyric(); } break; @@ -209,9 +209,9 @@ export const initMacStatusBarIpc = () => { } // 收到精确进度或误差较大同步后,立即更新一次歌词显示 // 如果此时是播放状态,确保插值器运行 - updateMacStatusBarLyric(store, true); + updateMacStatusBarLyric(true); if (macIsPlaying) { - startInterpolation(store); + startInterpolation(); } }); diff --git a/electron/main/tray/index.ts b/electron/main/tray/index.ts index 91f63c840..e874c1c2d 100644 --- a/electron/main/tray/index.ts +++ b/electron/main/tray/index.ts @@ -103,7 +103,8 @@ const getMenuIcon = (iconName: string): NativeImage | undefined => { }; // 托盘菜单 -const createTrayMenu = (win: BrowserWindow, store: ReturnType): MenuItemConstructorOptions[] => { +const createTrayMenu = (win: BrowserWindow): MenuItemConstructorOptions[] => { + const store = useStore(); /** * 获取 {@linkcode RepeatModeType} 对应的显示字符串 * @param mode 重复模式 @@ -267,11 +268,9 @@ class CreateTray implements MainTray { // 菜单 private _menu: MenuItemConstructorOptions[]; private _contextMenu: Menu; - private _store: ReturnType; // 添加 store 成员变量以访问全局状态 constructor(win: BrowserWindow) { this._win = win; - this._store = useStore(); if (isWin) { const iconPath = join(__dirname, `../../public/icons/tray/tray.ico`); @@ -290,7 +289,7 @@ class CreateTray implements MainTray { this._tray = new Tray(icon); } - this._menu = createTrayMenu(this._win, this._store); + this._menu = createTrayMenu(this._win); this._contextMenu = Menu.buildFromTemplate(this._menu); this.initTrayMenu(); this.initEvents(); @@ -298,7 +297,7 @@ class CreateTray implements MainTray { } // 托盘菜单 public initTrayMenu() { - this._menu = createTrayMenu(this._win, this._store); + this._menu = createTrayMenu(this._win); this._contextMenu = Menu.buildFromTemplate(this._menu); this._tray.setContextMenu(this._contextMenu); }