Files
SPlayer/electron/main/ipcMain.ts
2024-10-09 17:53:44 +08:00

704 lines
20 KiB
TypeScript

import {
app,
ipcMain,
BrowserWindow,
powerSaveBlocker,
screen,
shell,
dialog,
net,
session,
} from "electron";
import { File, Picture, Id3v2Settings } from "node-taglib-sharp";
import { parseFile } from "music-metadata";
import { getFonts } from "font-list";
import { MainTray } from "./tray";
import { Thumbar } from "./thumbar";
import { StoreType } from "./store";
import { isDev, getFileID, getFileMD5 } from "./utils";
import { isShortcutRegistered, registerShortcut, unregisterShortcuts } from "./shortcut";
import { join, basename, resolve } from "path";
import { download } from "electron-dl";
import { checkUpdate, startDownloadUpdate } from "./update";
import fs from "fs/promises";
import log from "../main/logger";
import Store from "electron-store";
import fg from "fast-glob";
// 注册 ipcMain
const initIpcMain = (
win: BrowserWindow | null,
lyricWin: BrowserWindow | null,
loadingWin: BrowserWindow | null,
tray: MainTray | null,
thumbar: Thumbar | null,
store: Store<StoreType>,
) => {
initWinIpcMain(win, loadingWin, lyricWin, store);
initLyricIpcMain(lyricWin, win, store);
initTrayIpcMain(tray, win, lyricWin);
initThumbarIpcMain(thumbar);
initStoreIpcMain(store);
initOtherIpcMain(win);
};
// win
const initWinIpcMain = (
win: BrowserWindow | null,
loadingWin: BrowserWindow | null,
lyricWin: BrowserWindow | null,
store: Store<StoreType>,
) => {
let preventId: number | null = null;
// 当前窗口状态
ipcMain.on("win-state", (ev) => {
ev.returnValue = win?.isMaximized();
});
// 加载完成
ipcMain.on("win-loaded", () => {
if (loadingWin && !loadingWin.isDestroyed()) loadingWin.close();
win?.show();
win?.focus();
});
// 最小化
ipcMain.on("win-min", (ev) => {
ev.preventDefault();
win?.minimize();
});
// 最大化
ipcMain.on("win-max", () => {
win?.maximize();
});
// 还原
ipcMain.on("win-restore", () => {
win?.restore();
});
// 关闭
ipcMain.on("win-close", (ev) => {
ev.preventDefault();
win?.close();
app.quit();
});
// 隐藏
ipcMain.on("win-hide", () => {
win?.hide();
});
// 显示
ipcMain.on("win-show", () => {
win?.show();
});
// 重启
ipcMain.on("win-reload", () => {
app.quit();
app.relaunch();
});
// 显示进度
ipcMain.on("set-bar", (_, val: number | "none" | "indeterminate" | "error" | "paused") => {
switch (val) {
case "none":
win?.setProgressBar(-1);
break;
case "indeterminate":
win?.setProgressBar(2, { mode: "indeterminate" });
break;
case "error":
win?.setProgressBar(1, { mode: "error" });
break;
case "paused":
win?.setProgressBar(1, { mode: "paused" });
break;
default:
if (typeof val === "number") {
win?.setProgressBar(val / 100);
} else {
win?.setProgressBar(-1);
}
break;
}
});
// 开启控制台
ipcMain.on("open-dev-tools", () => {
win?.webContents.openDevTools({
title: "SPlayer DevTools",
mode: isDev ? "right" : "detach",
});
});
// 获取系统全部字体
ipcMain.handle("get-all-fonts", async () => {
try {
const fonts = await getFonts();
return fonts;
} catch (error) {
log.error(`❌ Failed to get all system fonts: ${error}`);
return [];
}
});
// 切换桌面歌词
ipcMain.on("change-desktop-lyric", (_, val: boolean) => {
if (val) {
lyricWin?.show();
lyricWin?.setAlwaysOnTop(true, "screen-saver");
} else lyricWin?.hide();
});
// 是否阻止系统息屏
ipcMain.on("prevent-sleep", (_, val: boolean) => {
if (val) {
preventId = powerSaveBlocker.start("prevent-display-sleep");
log.info("⏾ System sleep prevention started");
} else {
if (preventId !== null) {
powerSaveBlocker.stop(preventId);
log.info("✅ System sleep prevention stopped");
}
}
});
// 默认文件夹
ipcMain.handle(
"get-default-dir",
(_, type: "documents" | "downloads" | "pictures" | "music" | "videos"): string => {
return app.getPath(type);
},
);
// 遍历音乐文件
ipcMain.handle("get-music-files", async (_, dirPath: string) => {
try {
// 查找指定目录下的所有音乐文件
const musicFiles = await fg("**/*.{mp3,wav,flac}", { cwd: dirPath });
// 解析元信息
const metadataPromises = musicFiles.map(async (file) => {
const filePath = join(dirPath, file);
// 处理元信息
const { common, format } = await parseFile(filePath);
// 获取文件大小
const { size } = await fs.stat(filePath);
// 判断音质等级
let quality: string;
if ((format.sampleRate || 0) >= 96000 || (format.bitsPerSample || 0) > 16) {
quality = "Hi-Res";
} else if ((format.sampleRate || 0) >= 44100) {
quality = "HQ";
} else {
quality = "SQ";
}
return {
id: getFileID(filePath),
name: common.title || basename(filePath),
artists: common.artists?.[0] || common.artist,
album: common.album || "",
alia: common.comment?.[0],
duration: (format?.duration ?? 0) * 1000,
size: (size / (1024 * 1024)).toFixed(2),
path: filePath,
quality,
};
});
const metadataArray = await Promise.all(metadataPromises);
return metadataArray;
} catch (error) {
log.error("❌ Error fetching music metadata:", error);
throw error;
}
});
// 获取音乐元信息
ipcMain.handle("get-music-metadata", async (_, path: string) => {
try {
const { common, format } = await parseFile(path);
return {
// 文件名称
fileName: basename(path),
// 文件大小
fileSize: (await fs.stat(path)).size / (1024 * 1024),
// 元信息
common,
// 音质信息
format,
// md5
md5: await getFileMD5(path),
};
} catch (error) {
log.error("❌ Error fetching music metadata:", error);
throw error;
}
});
// 获取音乐歌词
ipcMain.handle("get-music-lyric", async (_, path: string): Promise<string> => {
try {
const { common, native } = await parseFile(path);
const lyric = common?.lyrics;
if (lyric && lyric.length > 0) return String(lyric[0]);
else {
// 尝试读取 UNSYNCEDLYRICS
const nativeTags = native["ID3v2.3"] || native["ID3v2.4"];
const usltTag = nativeTags?.find((tag) => tag.id === "USLT");
if (usltTag) return String(usltTag.value.text);
// 如果歌词数据不存在,尝试读取同名的 lrc 文件
else {
const lrcFilePath = path.replace(/\.[^.]+$/, ".lrc");
try {
await fs.access(lrcFilePath);
const lrcData = await fs.readFile(lrcFilePath, "utf-8");
return lrcData || "";
} catch {
return "";
}
}
}
} catch (error) {
log.error("❌ Error fetching music lyric:", error);
throw error;
}
});
// 获取音乐封面
ipcMain.handle(
"get-music-cover",
async (_, path: string): Promise<{ data: Buffer; format: string } | null> => {
try {
const { common } = await parseFile(path);
// 获取封面数据
const picture = common.picture?.[0];
if (picture) {
return { data: Buffer.from(picture.data), format: picture.format };
} else {
const coverFilePath = path.replace(/\.[^.]+$/, ".jpg");
try {
await fs.access(coverFilePath);
const coverData = await fs.readFile(coverFilePath);
return { data: coverData, format: "image/jpeg" };
} catch {
return null;
}
}
} catch (error) {
console.error("❌ Error fetching music cover:", error);
throw error;
}
},
);
// 删除文件
ipcMain.handle("delete-file", async (_, path: string) => {
try {
// 规范化路径
const resolvedPath = resolve(path);
// 检查文件是否存在
try {
await fs.access(resolvedPath);
} catch {
throw new Error("❌ File not found");
}
// 删除文件
await fs.unlink(resolvedPath);
return true;
} catch (error) {
log.error("❌ File delete error", error);
return false;
}
});
// 打开文件夹
ipcMain.on("open-folder", async (_, path: string) => {
try {
// 规范化路径
const resolvedPath = resolve(path);
// 检查文件夹是否存在
try {
await fs.access(resolvedPath);
} catch {
throw new Error("❌ Folder not found");
}
// 打开文件夹
shell.showItemInFolder(resolvedPath);
} catch (error) {
log.error("❌ Folder open error", error);
throw error;
}
});
// 图片选择窗口
ipcMain.handle("choose-image", async () => {
try {
const { filePaths } = await dialog.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "Images", extensions: ["jpg", "jpeg", "png"] }],
});
if (!filePaths || filePaths.length === 0) return null;
return filePaths[0];
} catch (error) {
log.error("❌ Image choose error", error);
return null;
}
});
// 路径选择窗口
ipcMain.handle("choose-path", async () => {
try {
const { filePaths } = await dialog.showOpenDialog({
title: "选择文件夹",
defaultPath: app.getPath("downloads"),
properties: ["openDirectory", "createDirectory"],
buttonLabel: "选择文件夹",
});
if (!filePaths || filePaths.length === 0) return null;
return filePaths[0];
} catch (error) {
log.error("❌ Path choose error", error);
return null;
}
});
// 修改音乐元信息
ipcMain.handle("set-music-metadata", async (_, path: string, metadata: any) => {
try {
const { name, artist, album, alia, lyric, cover } = metadata;
// 规范化路径
const songPath = resolve(path);
const coverPath = cover ? resolve(cover) : null;
// 读取歌曲文件
const songFile = File.createFromPath(songPath);
// 读取封面文件
const songCover = coverPath ? Picture.fromPath(coverPath) : null;
// 保存元数据
Id3v2Settings.forceDefaultVersion = true;
Id3v2Settings.defaultVersion = 3;
songFile.tag.title = name || "未知曲目";
songFile.tag.performers = [artist || "未知艺术家"];
songFile.tag.album = album || "未知专辑";
songFile.tag.albumArtists = [artist || "未知艺术家"];
songFile.tag.lyrics = lyric || "";
songFile.tag.description = alia || "";
songFile.tag.comment = alia || "";
if (songCover) songFile.tag.pictures = [songCover];
// 保存元信息
songFile.save();
songFile.dispose();
return true;
} catch (error) {
log.error("❌ Error setting music metadata:", error);
throw error;
}
});
// 下载文件
ipcMain.handle(
"download-file",
async (
_,
url: string,
options: {
fileName: string;
fileType: string;
path: string;
downloadMeta?: boolean;
downloadCover?: boolean;
downloadLyric?: boolean;
saveMetaFile?: boolean;
lyric?: string;
songData?: any;
} = {
fileName: "未知文件名",
fileType: "mp3",
path: app.getPath("downloads"),
},
): Promise<boolean> => {
try {
if (!win) return false;
// 获取配置
const {
fileName,
fileType,
path,
lyric,
downloadMeta,
downloadCover,
downloadLyric,
saveMetaFile,
songData,
} = options;
// 规范化路径
const downloadPath = resolve(path);
// 检查文件夹是否存在
try {
await fs.access(downloadPath);
} catch {
throw new Error("❌ Folder not found");
}
// 下载文件
const songDownload = await download(win, url, {
directory: downloadPath,
filename: `${fileName}.${fileType}`,
});
if (!downloadMeta || !songData?.cover) return true;
// 下载封面
const coverUrl = songData?.coverSize?.l || songData.cover;
const coverDownload = await download(win, coverUrl, {
directory: downloadPath,
filename: `${fileName}.jpg`,
});
// 读取歌曲文件
const songFile = File.createFromPath(songDownload.getSavePath());
// 生成图片信息
const songCover = Picture.fromPath(coverDownload.getSavePath());
// 保存修改后的元数据
Id3v2Settings.forceDefaultVersion = true;
Id3v2Settings.defaultVersion = 3;
songFile.tag.title = songData?.name || "未知曲目";
songFile.tag.album = songData?.album?.name || "未知专辑";
songFile.tag.performers = songData?.artists?.map((ar: any) => ar.name) || ["未知艺术家"];
songFile.tag.albumArtists = songData?.artists?.map((ar: any) => ar.name) || ["未知艺术家"];
if (lyric && downloadLyric) songFile.tag.lyrics = lyric;
if (songCover && downloadCover) songFile.tag.pictures = [songCover];
// 保存元信息
songFile.save();
songFile.dispose();
// 创建同名歌词文件
if (lyric && saveMetaFile && downloadLyric) {
const lrcPath = join(downloadPath, `${fileName}.lrc`);
await fs.writeFile(lrcPath, lyric, "utf-8");
}
// 是否删除封面
if (!saveMetaFile || !downloadCover) await fs.unlink(coverDownload.getSavePath());
return true;
} catch (error) {
log.error("❌ Error downloading file:", error);
return false;
}
},
);
// 取消代理
ipcMain.on("remove-proxy", () => {
store.set("proxy", "");
win?.webContents.session.setProxy({ proxyRules: "" });
log.info("✅ Remove proxy successfully");
});
// 配置网络代理
ipcMain.on("set-proxy", (_, config) => {
const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
store.set("proxy", proxyRules);
win?.webContents.session.setProxy({ proxyRules });
log.info("✅ Set proxy successfully:", proxyRules);
});
// 代理测试
ipcMain.handle("test-proxy", async (_, config) => {
const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
try {
// 设置代理
const ses = session.defaultSession;
await ses.setProxy({ proxyRules });
// 测试请求
const request = net.request({ url: "https://www.baidu.com" });
return new Promise((resolve) => {
request.on("response", (response) => {
if (response.statusCode === 200) {
log.info("✅ Proxy test successful");
resolve(true);
} else {
log.error(`❌ Proxy test failed with status code: ${response.statusCode}`);
resolve(false);
}
});
request.on("error", (error) => {
log.error("❌ Error testing proxy:", error);
resolve(false);
});
request.end();
});
} catch (error) {
log.error("❌ Error testing proxy:", error);
return false;
}
});
// 重置全部设置
ipcMain.on("reset-setting", () => {
store.reset();
log.info("✅ Reset setting successfully");
});
// 检查更新
ipcMain.on("check-update", (_, showTip) => checkUpdate(win!, showTip));
// 开始下载更新
ipcMain.on("start-download-update", () => startDownloadUpdate());
};
// lyric
const initLyricIpcMain = (
lyricWin: BrowserWindow | null,
mainWin: BrowserWindow | null,
store: Store<StoreType>,
): void => {
// 音乐名称更改
ipcMain.on("play-song-change", (_, title) => {
if (!title) return;
lyricWin?.webContents.send("play-song-change", title);
});
// 音乐歌词更改
ipcMain.on("play-lyric-change", (_, lyricData) => {
if (!lyricData) return;
lyricWin?.webContents.send("play-lyric-change", lyricData);
});
// 获取窗口位置
ipcMain.handle("get-window-bounds", () => {
return lyricWin?.getBounds();
});
// 获取屏幕尺寸
ipcMain.handle("get-screen-size", () => {
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
return { width, height };
});
// 移动窗口
ipcMain.on("move-window", (_, x, y, width, height) => {
lyricWin?.setBounds({ x, y, width, height });
// 保存配置
store.set("lyric", { ...store.get("lyric"), x, y, width, height });
// 保持置顶
lyricWin?.setAlwaysOnTop(true, "screen-saver");
});
// 更新高度
ipcMain.on("update-window-height", (_, height) => {
if (!lyricWin) return;
const { width } = lyricWin.getBounds();
// 更新窗口高度
lyricWin.setBounds({ width, height });
});
// 获取配置
ipcMain.handle("get-desktop-lyric-option", () => {
return store.get("lyric");
});
// 保存配置
ipcMain.on("set-desktop-lyric-option", (_, option, callback: boolean = false) => {
store.set("lyric", option);
// 触发窗口更新
if (callback && lyricWin) {
lyricWin.webContents.send("desktop-lyric-option-change", option);
}
mainWin?.webContents.send("desktop-lyric-option-change", option);
});
// 发送主程序事件
ipcMain.on("send-main-event", (_, name, val) => {
mainWin?.webContents.send(name, val);
});
// 关闭桌面歌词
ipcMain.on("closeDesktopLyric", () => {
lyricWin?.hide();
mainWin?.webContents.send("closeDesktopLyric");
});
// 锁定/解锁桌面歌词
ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => {
if (!lyricWin) return;
// 是否穿透
if (isLock) {
lyricWin.setIgnoreMouseEvents(true, { forward: true });
} else {
lyricWin.setIgnoreMouseEvents(false);
}
});
};
// tray
const initTrayIpcMain = (
tray: MainTray | null,
win: BrowserWindow | null,
lyricWin: BrowserWindow | null,
): void => {
// 音乐播放状态更改
ipcMain.on("play-status-change", (_, playStatus: boolean) => {
tray?.setPlayState(playStatus ? "play" : "pause");
lyricWin?.webContents.send("play-status-change", playStatus);
});
// 音乐名称更改
ipcMain.on("play-song-change", (_, title) => {
if (!title) return;
// 更改标题
win?.setTitle(title);
tray?.setTitle(title);
tray?.setPlayName(title);
});
// 播放模式切换
ipcMain.on("play-mode-change", (_, mode) => {
tray?.setPlayMode(mode);
});
// 喜欢状态切换
ipcMain.on("like-status-change", (_, likeStatus: boolean) => {
tray?.setLikeState(likeStatus);
});
// 桌面歌词开关
ipcMain.on("change-desktop-lyric", (_, val: boolean) => {
tray?.setDesktopLyricShow(val);
});
// 锁定/解锁桌面歌词
ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => {
tray?.setDesktopLyricLock(isLock);
});
};
// thumbar
const initThumbarIpcMain = (thumbar: Thumbar | null): void => {
if (!thumbar) return;
};
// store
const initStoreIpcMain = (store: Store<StoreType>): void => {
if (!store) return;
};
// other
const initOtherIpcMain = (mainWin: BrowserWindow | null): void => {
// 快捷键是否被注册
ipcMain.handle("is-shortcut-registered", (_, shortcut: string) => isShortcutRegistered(shortcut));
// 注册快捷键
ipcMain.handle("register-all-shortcut", (_, allShortcuts: any): string[] | false => {
if (!mainWin || !allShortcuts) return false;
// 卸载所有快捷键
unregisterShortcuts();
// 注册快捷键
const failedShortcuts: string[] = [];
for (const key in allShortcuts) {
const shortcut = allShortcuts[key].globalShortcut;
if (!shortcut) continue;
// 快捷键回调
const callback = () => mainWin.webContents.send(key);
const isSuccess = registerShortcut(shortcut, callback);
if (!isSuccess) failedShortcuts.push(shortcut);
}
return failedShortcuts;
});
// 卸载所有快捷键
ipcMain.on("unregister-all-shortcut", () => unregisterShortcuts());
};
export default initIpcMain;