Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 51 additions & 36 deletions electron/main/ipc/ipc-mac-statusbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +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;
Expand All @@ -29,15 +30,15 @@ const stopInterpolation = () => {
/**
* 启动插值计时器
*/
const startInterpolation = (store: ReturnType<typeof useStore>) => {
const startInterpolation = () => {
stopInterpolation(); // 先停止任何已存在的计时器
macLastUpdateTime = Date.now(); // 在启动新的插值计时器时,重置 macLastUpdateTime
interpolationTimer = setInterval(() => {
const now = Date.now();
const elapsedTime = now - macLastUpdateTime;
macCurrentTime += elapsedTime;
macLastUpdateTime = now;
updateMacStatusBarLyric(store);
updateMacStatusBarLyric();
}, LYRIC_UPDATE_INTERVAL);
};

Expand Down Expand Up @@ -65,28 +66,41 @@ const findCurrentLyricIndex = (

/**
* 更新 macOS 状态栏歌词(只在新行时才更新)
* @param forceUpdate 是否强制更新,即便歌词行索引未变化
*/
const updateMacStatusBarLyric = (store: ReturnType<typeof useStore>) => {
const updateMacStatusBarLyric = (
forceUpdate: boolean = false,
) => {
const store = useStore();
Comment on lines +71 to +74
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updateMacStatusBarLyric function calls useStore() every time it's invoked, including during the 50ms interpolation timer interval. While electron-store handles internal singleton behavior, repeatedly instantiating the Store wrapper could have minor performance implications. Consider caching the store instance at the module level (e.g., const store = useStore() at the top of the file) to avoid repeated instantiation, similar to how macLyricLines and other state variables are maintained at the module level.

Copilot uses AI. Check for mistakes.
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 =
Expand All @@ -97,64 +111,68 @@ const updateMacStatusBarLyric = (store: ReturnType<typeof useStore>) => {
.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) {
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?.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);
updateMacStatusBarLyric();
}
break;

case "full-hydration":
// 接收完整的状态,但歌词更新仍然依赖 SYNC_TICK
if (payload.data.lyrics) {
macLyricLines = payload.data.lyrics.lines;
macLastLyricIndex = -1;
Expand All @@ -167,14 +185,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;

Expand All @@ -193,10 +208,10 @@ export const initMacStatusBarIpc = () => {
macOffset = offset;
}
// 收到精确进度或误差较大同步后,立即更新一次歌词显示
updateMacStatusBarLyric(store);
// 如果此时是播放状态,确保插值器运行
updateMacStatusBarLyric(true);
if (macIsPlaying) {
startInterpolation(store);
startInterpolation();
}
});

Expand All @@ -207,4 +222,4 @@ export const initMacStatusBarIpc = () => {
mainWin.webContents.send(TASKBAR_IPC_CHANNELS.REQUEST_DATA);
}
});
};
};
27 changes: 12 additions & 15 deletions electron/main/ipc/ipc-tray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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);
Expand All @@ -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;
34 changes: 9 additions & 25 deletions electron/main/tray/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -108,6 +104,7 @@ const getMenuIcon = (iconName: string): NativeImage | undefined => {

// 托盘菜单
const createTrayMenu = (win: BrowserWindow): MenuItemConstructorOptions[] => {
const store = useStore();
/**
* 获取 {@linkcode RepeatModeType} 对应的显示字符串
* @param mode 重复模式
Expand All @@ -124,6 +121,9 @@ const createTrayMenu = (win: BrowserWindow): MenuItemConstructorOptions[] => {
return "列表循环";
}
};

const isMacosLyricEnabled = store.get("macos.statusBarLyric.enabled") ?? false;

// 菜单
const menu: MenuItemConstructorOptions[] = [
{
Expand Down Expand Up @@ -226,7 +226,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"),
Expand Down Expand Up @@ -293,10 +293,10 @@ class CreateTray implements MainTray {
this._contextMenu = Menu.buildFromTemplate(this._menu);
this.initTrayMenu();
this.initEvents();
this.setTitle(appName);
this._tray.setTitle(appName); // 仅设置托盘标题,不设置窗口标题
}
// 托盘菜单
private initTrayMenu() {
public initTrayMenu() {
this._menu = createTrayMenu(this._win);
this._contextMenu = Menu.buildFromTemplate(this._menu);
this._tray.setContextMenu(this._contextMenu);
Expand All @@ -318,7 +318,6 @@ class CreateTray implements MainTray {
* @param title 标题
*/
setTitle(title: string) {
this._win.setTitle(title);
this._tray.setTitle(title);
this._tray.setToolTip(title);
}
Expand Down Expand Up @@ -383,25 +382,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);
}
}
/**
* 销毁托盘
*/
Expand Down