🦄 refactor: 主进程重构

修复导航栏不及时响应窗口状态
修复 thumb 展示异常
This commit is contained in:
imsyy
2025-10-25 23:43:54 +08:00
parent 0eaf32b8a7
commit 6a1657cf20
41 changed files with 1806 additions and 1492 deletions

View File

@@ -28,7 +28,6 @@ export default defineConfig(({ command, mode }) => {
input: { input: {
index: resolve(__dirname, "electron/main/index.ts"), index: resolve(__dirname, "electron/main/index.ts"),
lyric: resolve(__dirname, "web/lyric.html"), lyric: resolve(__dirname, "web/lyric.html"),
loading: resolve(__dirname, "web/loading.html"),
}, },
}, },
}, },
@@ -101,6 +100,7 @@ export default defineConfig(({ command, mode }) => {
rollupOptions: { rollupOptions: {
input: { input: {
index: resolve(__dirname, "index.html"), index: resolve(__dirname, "index.html"),
loading: resolve(__dirname, "web/loading/index.html"),
}, },
output: { output: {
manualChunks: { manualChunks: {

View File

@@ -1,204 +1,63 @@
import { app, shell, BrowserWindow, BrowserWindowConstructorOptions } from "electron"; import { app, BrowserWindow } from "electron";
import { electronApp } from "@electron-toolkit/utils"; import { electronApp } from "@electron-toolkit/utils";
import { join } from "path";
import { release, type } from "os"; import { release, type } from "os";
import { isDev, isMac, appName, isLinux } from "./utils"; import { isMac } from "./utils/config";
import { unregisterShortcuts } from "./shortcut"; import { unregisterShortcuts } from "./shortcut";
import { initTray, MainTray } from "./tray"; import { initTray, MainTray } from "./tray";
import { initThumbar, Thumbar } from "./thumbar"; import { processLog } from "./logger";
import { type StoreType, initStore } from "./store";
import Store from "electron-store";
import initAppServer from "../server"; import initAppServer from "../server";
import initIpcMain from "./ipcMain"; import { initSingleLock } from "./utils/single-lock";
import log from "./logger"; import loadWindow from "./windows/load-window";
// icon import mainWindow from "./windows/main-window";
import icon from "../../public/icons/favicon.png?asset"; import lyricWindow from "./windows/lyric-window";
import initIpc from "./ipc";
// 屏蔽报错 // 屏蔽报错
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true"; process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
// 模拟打包
Object.defineProperty(app, "isPackaged", {
get() {
return true;
},
});
// 主进程 // 主进程
class MainProcess { class MainProcess {
// 窗口 // 窗口
mainWindow: BrowserWindow | null = null; mainWindow: BrowserWindow | null = null;
lyricWindow: BrowserWindow | null = null; lyricWindow: BrowserWindow | null = null;
loadingWindow: BrowserWindow | null = null; loadWindow: BrowserWindow | null = null;
// store
store: Store<StoreType> | null = null;
// 托盘 // 托盘
mainTray: MainTray | null = null; mainTray: MainTray | null = null;
// 工具栏
thumbar: Thumbar | null = null;
// 是否退出 // 是否退出
isQuit: boolean = false; isQuit: boolean = false;
constructor() { constructor() {
log.info("🚀 Main process startup"); processLog.info("🚀 Main process startup");
// 程序单例锁
initSingleLock();
// 禁用 Windows 7 的 GPU 加速功能 // 禁用 Windows 7 的 GPU 加速功能
if (release().startsWith("6.1") && type() == "Windows_NT") app.disableHardwareAcceleration(); if (release().startsWith("6.1") && type() == "Windows_NT") app.disableHardwareAcceleration();
// 单例锁 // 监听应用事件
if (!app.requestSingleInstanceLock()) { this.handleAppEvents();
log.error("❌ There is already a program running and this process is terminated"); // Electron 初始化完成后
app.quit(); // 某些API只有在此事件发生后才能使用
process.exit(0); app.whenReady().then(async () => {
} else this.showWindow(); processLog.info("🚀 Application Process Startup");
// 准备就绪
app.on("ready", async () => {
log.info("🚀 Application Process Startup");
// 设置应用程序名称 // 设置应用程序名称
electronApp.setAppUserModelId("com.imsyy.splayer"); electronApp.setAppUserModelId("com.imsyy.splayer");
// 初始化 store
this.store = initStore();
// 启动主服务进程 // 启动主服务进程
await initAppServer(); await initAppServer();
// 启动进程 // 启动窗口
this.createLoadingWindow(); this.loadWindow = loadWindow.create();
this.createMainWindow(); this.mainWindow = mainWindow.create();
this.createLyricsWindow(); this.lyricWindow = lyricWindow.create();
this.handleAppEvents();
this.handleWindowEvents();
// 注册其他服务 // 注册其他服务
this.mainTray = initTray(this.mainWindow!, this.lyricWindow!); this.mainTray = initTray(this.mainWindow!, this.lyricWindow!);
this.thumbar = initThumbar(this.mainWindow!); // 注册 IPC 通信
// 注册主进程事件 initIpc();
initIpcMain(
this.mainWindow,
this.lyricWindow,
this.loadingWindow,
this.mainTray,
this.thumbar,
this.store,
);
}); });
} }
// 创建窗口
createWindow(options: BrowserWindowConstructorOptions = {}): BrowserWindow {
const defaultOptions: BrowserWindowConstructorOptions = {
title: appName,
width: 1280,
height: 720,
frame: false,
center: true,
// 图标
icon,
webPreferences: {
preload: join(__dirname, "../preload/index.mjs"),
// 禁用渲染器沙盒
sandbox: false,
// 禁用同源策略
webSecurity: false,
// 允许 HTTP
allowRunningInsecureContent: true,
// 禁用拼写检查
spellcheck: false,
// 启用 Node.js
nodeIntegration: true,
nodeIntegrationInWorker: true,
// 启用上下文隔离
contextIsolation: false,
},
};
// 合并参数
options = Object.assign(defaultOptions, options);
// 创建窗口
const win = new BrowserWindow(options);
return win;
}
// 创建主窗口
createMainWindow() {
// 窗口配置项
const options: BrowserWindowConstructorOptions = {
width: this.store?.get("window").width,
height: this.store?.get("window").height,
minHeight: 600,
minWidth: 800,
// 菜单栏
titleBarStyle: "customButtonsOnHover",
// 立即显示窗口
show: false,
};
// 初始化窗口
this.mainWindow = this.createWindow(options);
// 渲染路径
if (isDev && process.env["ELECTRON_RENDERER_URL"]) {
this.mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
} else {
const port = Number(import.meta.env["VITE_SERVER_PORT"] || 25884);
this.mainWindow.loadURL(`http://127.0.0.1:${port}`);
}
// 配置网络代理
if (this.store?.get("proxy")) {
this.mainWindow.webContents.session.setProxy({ proxyRules: this.store?.get("proxy") });
}
// 窗口打开处理程序
this.mainWindow.webContents.setWindowOpenHandler((details) => {
const { url } = details;
if (url.startsWith("https://") || url.startsWith("http://")) {
shell.openExternal(url);
}
return { action: "deny" };
});
}
// 创建加载窗口
createLoadingWindow() {
// 初始化窗口
this.loadingWindow = this.createWindow({
width: 800,
height: 560,
maxWidth: 800,
maxHeight: 560,
resizable: false,
});
// 渲染路径
this.loadingWindow.loadFile(join(__dirname, "../main/web/loading.html"));
}
// 创建桌面歌词窗口
createLyricsWindow() {
// 初始化窗口
this.lyricWindow = this.createWindow({
width: this.store?.get("lyric").width || 800,
height: this.store?.get("lyric").height || 180,
minWidth: 440,
minHeight: 120,
maxWidth: 1600,
maxHeight: 300,
// 窗口位置
x: this.store?.get("lyric").x,
y: this.store?.get("lyric").y,
transparent: true,
backgroundColor: "rgba(0, 0, 0, 0)",
alwaysOnTop: true,
resizable: true,
movable: true,
// 不在任务栏显示
skipTaskbar: true,
// 窗口不能最小化
minimizable: false,
// 窗口不能最大化
maximizable: false,
// 窗口不能进入全屏状态
fullscreenable: false,
show: false,
});
// 渲染路径
this.lyricWindow.loadFile(join(__dirname, "../main/web/lyric.html"));
}
// 应用程序事件 // 应用程序事件
handleAppEvents() { handleAppEvents() {
// 窗口被关闭时 // 窗口被关闭时
app.on("window-all-closed", () => { app.on("window-all-closed", () => {
if (!isMac) app.quit(); if (!isMac) app.quit();
this.mainWindow = null; this.mainWindow = null;
this.loadingWindow = null; this.loadWindow = null;
}); });
// 应用被激活 // 应用被激活
@@ -206,19 +65,12 @@ class MainProcess {
const allWindows = BrowserWindow.getAllWindows(); const allWindows = BrowserWindow.getAllWindows();
if (allWindows.length) { if (allWindows.length) {
allWindows[0].focus(); allWindows[0].focus();
} else {
this.createMainWindow();
} }
}); });
// 新增 session
app.on("second-instance", () => {
this.showWindow();
});
// 自定义协议 // 自定义协议
app.on("open-url", (_, url) => { app.on("open-url", (_, url) => {
console.log("Received custom protocol URL:", url); processLog.log("Received custom protocol URL:", url);
}); });
// 将要退出 // 将要退出
@@ -232,82 +84,6 @@ class MainProcess {
this.isQuit = true; this.isQuit = true;
}); });
} }
// 窗口事件
handleWindowEvents() {
this.mainWindow?.on("ready-to-show", () => {
if (!this.mainWindow) return;
this.thumbar = initThumbar(this.mainWindow);
});
this.mainWindow?.on("show", () => {
// this.mainWindow?.webContents.send("lyricsScroll");
});
this.mainWindow?.on("focus", () => {
this.saveBounds();
});
// 移动、缩放、最大化、取消最大化
this.mainWindow?.on("resized", () => {
// 若处于全屏则不保存
if (this.mainWindow?.isFullScreen()) return;
this.saveBounds();
});
this.mainWindow?.on("moved", () => {
this.saveBounds();
});
this.mainWindow?.on("maximize", () => {
this.saveBounds();
});
this.mainWindow?.on("unmaximize", () => {
this.saveBounds();
})
// Linux 无法使用 resized 和 moved
if (isLinux) {
this.mainWindow?.on("resize", () => {
// 若处于全屏则不保存
if (this.mainWindow?.isFullScreen()) return;
this.saveBounds();
})
this.mainWindow?.on("move", () => {
this.saveBounds();
});
}
// 歌词窗口缩放
this.lyricWindow?.on("resized", () => {
const bounds = this.lyricWindow?.getBounds();
if (bounds) {
const { width, height } = bounds;
this.store?.set("lyric", { ...this.store?.get("lyric"), width, height });
}
});
// 窗口关闭
this.mainWindow?.on("close", (event) => {
event.preventDefault();
if (this.isQuit) {
app.exit();
} else {
this.mainWindow?.hide();
}
});
}
// 更新窗口大小
saveBounds() {
if (this.mainWindow?.isFullScreen()) return;
const bounds: any = this.mainWindow?.getBounds();
if (bounds) {
bounds.maximized = this.mainWindow?.isMaximized();
this.store?.set("window", bounds);
}
}
// 显示窗口
showWindow() {
if (this.mainWindow) {
this.mainWindow.show();
if (this.mainWindow.isMinimized()) this.mainWindow.restore();
this.mainWindow.focus();
}
}
} }
export default new MainProcess(); export default new MainProcess();

View File

@@ -0,0 +1,27 @@
import initFileIpc from "./ipc-file";
import initLyricIpc from "./ipc-lyric";
import initShortcutIpc from "./ipc-shortcut";
import initStoreIpc from "./ipc-store";
import initSystemIpc from "./ipc-system";
import initThumbarIpc from "./ipc-thumbar";
import initTrayIpc from "./ipc-tray";
import initUpdateIpc from "./ipc-update";
import initWindowsIpc from "./ipc-window";
/**
* 初始化全部 IPC 通信
* @returns void
*/
const initIpc = (): void => {
initSystemIpc();
initWindowsIpc();
initUpdateIpc();
initFileIpc();
initTrayIpc();
initLyricIpc();
initStoreIpc();
initThumbarIpc();
initShortcutIpc();
};
export default initIpc;

View File

@@ -0,0 +1,376 @@
import { app, BrowserWindow, dialog, ipcMain, shell } from "electron";
import { basename, join, resolve } from "path";
import { access, readFile, stat, unlink, writeFile } from "fs/promises";
import { parseFile } from "music-metadata";
import { getFileID, getFileMD5, metaDataLyricsArrayToLrc } from "../utils/helper";
import { File, Picture, Id3v2Settings } from "node-taglib-sharp";
import { ipcLog } from "../logger";
import FastGlob from "fast-glob";
import { download } from "electron-dl";
/**
* 文件相关 IPC
*/
const initFileIpc = (): void => {
// 默认文件夹
ipcMain.handle(
"get-default-dir",
(_event, type: "documents" | "downloads" | "pictures" | "music" | "videos"): string => {
return app.getPath(type);
},
);
// 遍历音乐文件
ipcMain.handle("get-music-files", async (_, dirPath: string) => {
try {
// 规范化路径
const filePath = resolve(dirPath).replace(/\\/g, "/");
console.info(`📂 Fetching music files from: ${filePath}`);
// 查找指定目录下的所有音乐文件
const musicFiles = await FastGlob("**/*.{mp3,wav,flac}", { cwd: filePath });
// 解析元信息
const metadataPromises = musicFiles.map(async (file) => {
const filePath = join(dirPath, file);
// 处理元信息
const { common, format } = await parseFile(filePath);
// 获取文件大小
const { size } = await 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]?.text || "",
duration: (format?.duration ?? 0) * 1000,
size: (size / (1024 * 1024)).toFixed(2),
path: filePath,
quality,
};
});
const metadataArray = await Promise.all(metadataPromises);
return metadataArray;
} catch (error) {
ipcLog.error("❌ Error fetching music metadata:", error);
throw error;
}
});
// 获取音乐元信息
ipcMain.handle("get-music-metadata", async (_, path: string) => {
try {
const filePath = resolve(path).replace(/\\/g, "/");
const { common, format } = await parseFile(filePath);
return {
// 文件名称
fileName: basename(filePath),
// 文件大小
fileSize: (await stat(filePath)).size / (1024 * 1024),
// 元信息
common,
// 歌词
lyric:
metaDataLyricsArrayToLrc(common?.lyrics?.[0]?.syncText || []) ||
common?.lyrics?.[0]?.text ||
"",
// 音质信息
format,
// md5
md5: await getFileMD5(filePath),
};
} catch (error) {
ipcLog.error("❌ Error fetching music metadata:", error);
throw error;
}
});
// 修改音乐元信息
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) {
ipcLog.error("❌ Error setting music metadata:", error);
throw error;
}
});
// 获取音乐歌词
ipcMain.handle(
"get-music-lyric",
async (
_,
path: string,
): Promise<{
lyric: string;
format: "lrc" | "ttml";
}> => {
try {
const filePath = resolve(path).replace(/\\/g, "/");
const { common } = await parseFile(filePath);
// 尝试获取同名的歌词文件
const filePathWithoutExt = filePath.replace(/\.[^.]+$/, "");
for (const ext of ["ttml", "lrc"] as const) {
const lyricPath = `${filePathWithoutExt}.${ext}`;
ipcLog.info("lyricPath", lyricPath);
try {
await access(lyricPath);
const lyric = await readFile(lyricPath, "utf-8");
if (lyric && lyric != "") return { lyric, format: ext };
} catch {
/* empty */
}
}
// 尝试获取元数据
const lyric = common?.lyrics?.[0]?.syncText;
if (lyric && lyric.length > 0) {
return { lyric: metaDataLyricsArrayToLrc(lyric), format: "lrc" };
} else if (common?.lyrics?.[0]?.text) {
return { lyric: common?.lyrics?.[0]?.text, format: "lrc" };
}
// 没有歌词
return { lyric: "", format: "lrc" };
} catch (error) {
ipcLog.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 access(coverFilePath);
const coverData = await readFile(coverFilePath);
return { data: coverData, format: "image/jpeg" };
} catch {
return null;
}
}
} catch (error) {
console.error("❌ Error fetching music cover:", error);
throw error;
}
},
);
// 读取本地歌词
ipcMain.handle(
"read-local-lyric",
async (_, lyricDir: string, id: number, ext: string): Promise<string> => {
const lyricPath = join(lyricDir, `${id}.${ext}`);
try {
await access(lyricPath);
const lyric = await readFile(lyricPath, "utf-8");
if (lyric) return lyric;
} catch {
/* empty */
}
return "";
},
);
// 删除文件
ipcMain.handle("delete-file", async (_, path: string) => {
try {
// 规范化路径
const resolvedPath = resolve(path);
// 检查文件是否存在
try {
await access(resolvedPath);
} catch {
throw new Error("❌ File not found");
}
// 删除文件
await unlink(resolvedPath);
return true;
} catch (error) {
ipcLog.error("❌ File delete error", error);
return false;
}
});
// 打开文件夹
ipcMain.on("open-folder", async (_, path: string) => {
try {
// 规范化路径
const resolvedPath = resolve(path);
// 检查文件夹是否存在
try {
await access(resolvedPath);
} catch {
throw new Error("❌ Folder not found");
}
// 打开文件夹
shell.showItemInFolder(resolvedPath);
} catch (error) {
ipcLog.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) {
ipcLog.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) {
ipcLog.error("❌ Path choose error", error);
return null;
}
});
// 下载文件
ipcMain.handle(
"download-file",
async (
event,
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 {
// 获取窗口
const win = BrowserWindow.fromWebContents(event.sender);
if (!win) return false;
// 获取配置
const {
fileName,
fileType,
path,
lyric,
downloadMeta,
downloadCover,
downloadLyric,
saveMetaFile,
songData,
} = options;
// 规范化路径
const downloadPath = resolve(path);
// 检查文件夹是否存在
try {
await 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 writeFile(lrcPath, lyric, "utf-8");
}
// 是否删除封面
if (!saveMetaFile || !downloadCover) await unlink(coverDownload.getSavePath());
return true;
} catch (error) {
ipcLog.error("❌ Error downloading file:", error);
return false;
}
},
);
};
export default initFileIpc;

View File

@@ -0,0 +1,111 @@
import { ipcMain, screen } from "electron";
import lyricWindow from "../windows/lyric-window";
import { useStore } from "../store";
import mainWindow from "../windows/main-window";
import { isAbsolute, relative, resolve } from "path";
/**
* 歌词相关 IPC
*/
const initLyricIpc = (): void => {
const store = useStore();
const mainWin = mainWindow.getWin();
const lyricWin = lyricWindow.getWin();
// 切换桌面歌词
ipcMain.on("change-desktop-lyric", (_event, val: boolean) => {
if (val) {
lyricWin?.show();
lyricWin?.setAlwaysOnTop(true, "screen-saver");
} else lyricWin?.hide();
});
// 音乐名称更改
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);
}
});
// 检查是否是子文件夹
ipcMain.handle("check-if-subfolder", (_, localFilesPath: string[], selectedDir: string) => {
const resolvedSelectedDir = resolve(selectedDir);
const allPaths = localFilesPath.map((p) => resolve(p));
return allPaths.some((existingPath) => {
const relativePath = relative(existingPath, resolvedSelectedDir);
return relativePath && !relativePath.startsWith("..") && !isAbsolute(relativePath);
});
});
};
export default initLyricIpc;

View File

@@ -0,0 +1,37 @@
import { ipcMain } from "electron";
import { isShortcutRegistered, registerShortcut, unregisterShortcuts } from "../shortcut";
import mainWindow from "../windows/main-window";
/**
* 初始化快捷键 IPC 主进程
* @returns void
*/
const initShortcutIpc = (): void => {
const mainWin = mainWindow.getWin();
// 快捷键是否被注册
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 initShortcutIpc;

View File

@@ -0,0 +1,11 @@
import { useStore } from "../store";
/**
* 初始化 store IPC 主进程
*/
const initStoreIpc = (): void => {
const store = useStore();
if (!store) return;
};
export default initStoreIpc;

View File

@@ -0,0 +1,95 @@
import { ipcMain, net, powerSaveBlocker, session } from "electron";
import { ipcLog } from "../logger";
import { getFonts } from "font-list";
import { useStore } from "../store";
import mainWindow from "../windows/main-window";
/**
* 初始化系统 IPC 通信
* @returns void
*/
const initSystemIpc = (): void => {
const store = useStore();
const mainWin = mainWindow.getWin();
/** 阻止系统息屏 ID */
let preventId: number | null = null;
// 是否阻止系统息屏
ipcMain.on("prevent-sleep", (_event, val: boolean) => {
if (val) {
preventId = powerSaveBlocker.start("prevent-display-sleep");
ipcLog.info("⏾ System sleep prevention started");
} else {
if (preventId !== null) {
powerSaveBlocker.stop(preventId);
ipcLog.info("✅ System sleep prevention stopped");
}
}
});
// 获取系统全部字体
ipcMain.handle("get-all-fonts", async () => {
try {
const fonts = await getFonts();
return fonts;
} catch (error) {
ipcLog.error(`❌ Failed to get all system fonts: ${error}`);
return [];
}
});
// 取消代理
ipcMain.on("remove-proxy", () => {
store.set("proxy", "");
mainWin?.webContents.session.setProxy({ proxyRules: "" });
ipcLog.info("✅ Remove proxy successfully");
});
// 配置网络代理
ipcMain.on("set-proxy", (_, config) => {
const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
store.set("proxy", proxyRules);
mainWin?.webContents.session.setProxy({ proxyRules });
ipcLog.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) {
ipcLog.info("✅ Proxy test successful");
resolve(true);
} else {
ipcLog.error(`❌ Proxy test failed with status code: ${response.statusCode}`);
resolve(false);
}
});
request.on("error", (error) => {
ipcLog.error("❌ Error testing proxy:", error);
resolve(false);
});
request.end();
});
} catch (error) {
ipcLog.error("❌ Error testing proxy:", error);
return false;
}
});
// 重置全部设置
ipcMain.on("reset-setting", () => {
store.reset();
ipcLog.info("✅ Reset setting successfully");
});
};
export default initSystemIpc;

View File

@@ -0,0 +1,15 @@
import { ipcMain } from "electron";
import { getThumbar } from "../thumbar";
const initThumbarIpc = (): void => {
// 更新工具栏
ipcMain.on("play-status-change", (_, playStatus: boolean) => {
const thumbar = getThumbar();
if (!thumbar) {
return;
}
thumbar.updateThumbar(playStatus);
});
};
export default initThumbarIpc;

View File

@@ -0,0 +1,47 @@
import { ipcMain } from "electron";
import { getMainTray } from "../tray";
import lyricWindow from "../windows/lyric-window";
/**
* 托盘 IPC
*/
const initTrayIpc = (): void => {
const tray = getMainTray();
const lyricWin = lyricWindow.getWin();
// 音乐播放状态更改
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;
// 更改标题
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);
});
};
export default initTrayIpc;

View File

@@ -0,0 +1,15 @@
import { ipcMain } from "electron";
import { checkUpdate, startDownloadUpdate } from "../update";
import mainWindow from "../windows/main-window";
const initUpdateIpc = () => {
const mainWin = mainWindow.getWin();
// 检查更新
ipcMain.on("check-update", (_event, showTip) => checkUpdate(mainWin!, showTip));
// 开始下载更新
ipcMain.on("start-download-update", () => startDownloadUpdate());
};
export default initUpdateIpc;

View File

@@ -0,0 +1,112 @@
import { app, ipcMain } from "electron";
import { useStore } from "../store";
import { isDev } from "../utils/config";
import { initThumbar } from "../thumbar";
import mainWindow from "../windows/main-window";
import loadWindow from "../windows/load-window";
import loginWindow from "../windows/login-window";
/**
* 窗口 IPC 通信
* @returns void
*/
const initWindowsIpc = (): void => {
// 相关窗口
const mainWin = mainWindow.getWin();
const loadWin = loadWindow.getWin();
// store
const store = useStore();
// 当前窗口状态
ipcMain.on("win-state", (event) => {
event.returnValue = mainWin?.isMaximized();
});
// 加载完成
ipcMain.on("win-loaded", () => {
if (loadWin && !loadWin.isDestroyed()) loadWin.destroy();
const isMaximized = store.get("window")?.maximized;
if (isMaximized) mainWin?.maximize();
mainWin?.show();
mainWin?.focus();
// 初始化缩略图工具栏
if (mainWin) initThumbar(mainWin);
});
// 最小化
ipcMain.on("win-min", (event) => {
event.preventDefault();
mainWin?.minimize();
});
// 最大化
ipcMain.on("win-max", () => {
mainWin?.maximize();
});
// 还原
ipcMain.on("win-restore", () => {
mainWin?.restore();
});
// 关闭
ipcMain.on("win-close", (event) => {
event.preventDefault();
mainWin?.close();
app.quit();
});
// 隐藏
ipcMain.on("win-hide", () => {
mainWin?.hide();
});
// 显示
ipcMain.on("win-show", () => {
mainWin?.show();
});
// 重启
ipcMain.on("win-reload", () => {
app.quit();
app.relaunch();
});
// 显示进度
ipcMain.on("set-bar", (_event, val: number | "none" | "indeterminate" | "error" | "paused") => {
switch (val) {
case "none":
mainWin?.setProgressBar(-1);
break;
case "indeterminate":
mainWin?.setProgressBar(2, { mode: "indeterminate" });
break;
case "error":
mainWin?.setProgressBar(1, { mode: "error" });
break;
case "paused":
mainWin?.setProgressBar(1, { mode: "paused" });
break;
default:
if (typeof val === "number") {
mainWin?.setProgressBar(val / 100);
} else {
mainWin?.setProgressBar(-1);
}
break;
}
});
// 开启控制台
ipcMain.on("open-dev-tools", () => {
mainWin?.webContents.openDevTools({
title: "SPlayer DevTools",
mode: isDev ? "right" : "detach",
});
});
// 开启登录窗口
ipcMain.on("open-login-web", () => loginWindow.create(mainWin!));
};
export default initWindowsIpc;

View File

@@ -1,765 +0,0 @@
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, metaDataLyricsArrayToLrc } from "./utils";
import { isShortcutRegistered, registerShortcut, unregisterShortcuts } from "./shortcut";
import { join, basename, resolve, relative, isAbsolute } 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";
import openLoginWin from "./loginWin";
import path from "node:path";
// 注册 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();
const isMaximized = store?.get("window")?.maximized;
if (isMaximized) win?.maximize();
});
// 最小化
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 filePath = resolve(dirPath).replace(/\\/g, "/");
console.info(`📂 Fetching music files from: ${filePath}`);
// 查找指定目录下的所有音乐文件
const musicFiles = await fg("**/*.{mp3,wav,flac}", { cwd: filePath });
// 解析元信息
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]?.text || "",
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 filePath = resolve(path).replace(/\\/g, "/");
const { common, format } = await parseFile(filePath);
return {
// 文件名称
fileName: basename(filePath),
// 文件大小
fileSize: (await fs.stat(filePath)).size / (1024 * 1024),
// 元信息
common,
// 歌词
lyric:
metaDataLyricsArrayToLrc(common?.lyrics?.[0]?.syncText || []) ||
common?.lyrics?.[0]?.text ||
"",
// 音质信息
format,
// md5
md5: await getFileMD5(filePath),
};
} catch (error) {
log.error("❌ Error fetching music metadata:", error);
throw error;
}
});
// 获取音乐歌词
ipcMain.handle(
"get-music-lyric",
async (
_,
path: string,
): Promise<{
lyric: string;
format: "lrc" | "ttml";
}> => {
try {
const filePath = resolve(path).replace(/\\/g, "/");
const { common } = await parseFile(filePath);
// 尝试获取同名的歌词文件
const filePathWithoutExt = filePath.replace(/\.[^.]+$/, "");
for (const ext of ["ttml", "lrc"] as const) {
const lyricPath = `${filePathWithoutExt}.${ext}`;
console.log("lyricPath", lyricPath);
try {
await fs.access(lyricPath);
const lyric = await fs.readFile(lyricPath, "utf-8");
if (lyric && lyric != "") return { lyric, format: ext };
} catch {
/* empty */
}
}
// 尝试获取元数据
const lyric = common?.lyrics?.[0]?.syncText;
if (lyric && lyric.length > 0) {
return { lyric: metaDataLyricsArrayToLrc(lyric), format: "lrc" };
} else if (common?.lyrics?.[0]?.text) {
return { lyric: common?.lyrics?.[0]?.text, format: "lrc" };
}
// 没有歌词
return { lyric: "", format: "lrc" };
} 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(
"read-local-lyric",
async (_, lyricDir: string, id: number, ext: string): Promise<string> => {
const lyricPath = path.join(lyricDir, `${id}.${ext}`);
try {
await fs.access(lyricPath);
const lyric = await fs.readFile(lyricPath, "utf-8");
if (lyric) return lyric;
} catch {
/* empty */
}
return "";
},
);
// 删除文件
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());
// 新建窗口
ipcMain.on("open-login-web", () => openLoginWin(win!));
};
// 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);
}
});
// 检查是否是子文件夹
ipcMain.handle("check-if-subfolder", (_, localFilesPath: string[], selectedDir: string) => {
const resolvedSelectedDir = resolve(selectedDir);
const allPaths = localFilesPath.map((p) => resolve(p));
return allPaths.some((existingPath) => {
const relativePath = relative(existingPath, resolvedSelectedDir);
return relativePath && !relativePath.startsWith("..") && !isAbsolute(relativePath);
});
});
};
// 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;
// 更新工具栏
ipcMain.on("play-status-change", (_, playStatus: boolean) => {
thumbar?.updateThumbar(playStatus);
});
};
// 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;

View File

@@ -1,31 +0,0 @@
// 日志输出
import { join } from "path";
import { app } from "electron";
import { isDev } from "./utils";
import log from "electron-log";
// 绑定事件
Object.assign(console, log.functions);
// 日志配置
log.transports.file.level = "info";
log.transports.file.maxSize = 2 * 1024 * 1024; // 2M
if (log.transports.ipc) log.transports.ipc.level = false;
// 控制台输出
log.transports.console.useStyles = true;
// 文件输出
log.transports.file.format = "{y}-{m}-{d} {h}:{i}:{s}:{ms} {text}";
// 本地输出
if (!isDev) {
log.transports.file.resolvePathFn = () =>
join(app.getPath("documents"), "/SPlayer/SPlayer-log.txt");
} else {
log.transports.file.level = false;
}
log.info("📃 logger initialized");
export default log;

View File

@@ -0,0 +1,47 @@
// 日志输出
import { existsSync, mkdirSync } from "fs";
import { join } from "path";
import { app } from "electron";
import log from "electron-log";
// 日志文件路径
const logDir = join(app.getPath("logs"));
// 是否存在日志目录
if (!existsSync(logDir)) mkdirSync(logDir);
// 获取日期 - YYYY-MM-DD
const dateString = new Date().toISOString().slice(0, 10);
const logFilePath = join(logDir, `${dateString}.log`);
// 配置日志系统
log.transports.console.useStyles = true; // 颜色输出
log.transports.file.level = "info"; // 仅记录 info 及以上级别
log.transports.file.resolvePathFn = (): string => logFilePath; // 日志文件路径
log.transports.file.maxSize = 2 * 1024 * 1024; // 文件最大 2MB
// 日志格式化
// log.transports.file.format = "[{y}-{m}-{d} {h}:{i}:{s}] [{level}] [{scope}] {text}";
// 绑定默认事件
const defaultLog = log.scope("default");
console.log = defaultLog.log;
console.info = defaultLog.info;
console.warn = defaultLog.warn;
console.error = defaultLog.error;
// 分作用域导出
export { defaultLog };
export const ipcLog = log.scope("ipc");
export const trayLog = log.scope("tray");
export const thumbarLog = log.scope("thumbar");
export const storeLog = log.scope("store");
export const updateLog = log.scope("update");
export const systemLog = log.scope("system");
export const configLog = log.scope("config");
export const windowsLog = log.scope("windows");
export const processLog = log.scope("process");
export const preloadLog = log.scope("preload");
export const rendererLog = log.scope("renderer");
export const shortcutLog = log.scope("shortcut");
export const serverLog = log.scope("server");

View File

@@ -1,72 +0,0 @@
import { BrowserWindow, session } from "electron";
import icon from "../../public/icons/favicon.png?asset";
import { join } from "path";
const openLoginWin = async (mainWin: BrowserWindow) => {
let loginTimer: NodeJS.Timeout;
const loginSession = session.fromPartition("persist:login");
// 清除 Cookie
await loginSession.clearStorageData({
storages: ["cookies", "localstorage"],
});
const loginWin = new BrowserWindow({
parent: mainWin,
title: "登录网易云音乐( 若遇到无响应请关闭后重试 ",
width: 1280,
height: 800,
center: true,
autoHideMenuBar: true,
icon,
// resizable: false,
// movable: false,
// minimizable: false,
// maximizable: false,
webPreferences: {
session: loginSession,
sandbox: false,
webSecurity: false,
preload: join(__dirname, "../preload/index.mjs"),
},
});
// 打开网易云
loginWin.loadURL("https://music.163.com/#/login/");
// 阻止新窗口创建
loginWin.webContents.setWindowOpenHandler(() => {
return { action: "deny" };
});
// 检查是否登录
const checkLogin = async () => {
try {
loginWin.webContents.executeJavaScript(
"document.title = '登录网易云音乐( 若遇到无响应请关闭后重试 '",
);
// 是否登录?判断 MUSIC_U
const MUSIC_U = await loginSession.cookies.get({
name: "MUSIC_U",
});
if (MUSIC_U && MUSIC_U?.length > 0) {
if (loginTimer) clearInterval(loginTimer);
const value = `MUSIC_U=${MUSIC_U[0].value};`;
// 发送回主进程
mainWin?.webContents.send("send-cookies", value);
loginWin.destroy();
}
} catch (error) {
console.error(error);
}
};
// 循环检查
loginWin.webContents.once("did-finish-load", () => {
loginWin.show();
loginTimer = setInterval(checkLogin, 1000);
loginWin.on("closed", () => {
clearInterval(loginTimer);
});
});
};
export default openLoginWin;

View File

@@ -1,19 +1,19 @@
import { globalShortcut } from "electron"; import { globalShortcut } from "electron";
import log from "../main/logger"; import { shortcutLog } from "../logger";
// 注册快捷键并检查 // 注册快捷键并检查
export const registerShortcut = (shortcut: string, callback: () => void): boolean => { export const registerShortcut = (shortcut: string, callback: () => void): boolean => {
try { try {
const success = globalShortcut.register(shortcut, callback); const success = globalShortcut.register(shortcut, callback);
if (!success) { if (!success) {
log.error(`❌ Failed to register shortcut: ${shortcut}`); shortcutLog.error(`❌ Failed to register shortcut: ${shortcut}`);
return false; return false;
} else { } else {
log.info(`✅ Shortcut registered: ${shortcut}`); shortcutLog.info(`✅ Shortcut registered: ${shortcut}`);
return true; return true;
} }
} catch (error) { } catch (error) {
log.error(` Error registering shortcut ${shortcut}:`, error); shortcutLog.error(` Error registering shortcut ${shortcut}:`, error);
return false; return false;
} }
}; };
@@ -26,5 +26,5 @@ export const isShortcutRegistered = (shortcut: string): boolean => {
// 卸载所有快捷键 // 卸载所有快捷键
export const unregisterShortcuts = () => { export const unregisterShortcuts = () => {
globalShortcut.unregisterAll(); globalShortcut.unregisterAll();
log.info("🚫 All shortcuts unregistered."); shortcutLog.info("🚫 All shortcuts unregistered.");
}; };

View File

@@ -1,8 +1,8 @@
import Store from "electron-store";
import { screen } from "electron"; import { screen } from "electron";
import log from "./logger"; import { storeLog } from "../logger";
import Store from "electron-store";
log.info("🌱 Store init"); storeLog.info("🌱 Store init");
export interface StoreType { export interface StoreType {
window: { window: {
@@ -25,8 +25,11 @@ export interface StoreType {
proxy: string; proxy: string;
} }
// 初始化仓库 /**
export const initStore = () => { * 使 Store
* @returns Store<StoreType>
*/
export const useStore = () => {
return new Store<StoreType>({ return new Store<StoreType>({
defaults: { defaults: {
window: { window: {

View File

@@ -1,7 +1,7 @@
import { BrowserWindow, nativeImage, nativeTheme, ThumbarButton } from "electron"; import { BrowserWindow, nativeImage, nativeTheme, ThumbarButton } from "electron";
import { join } from "path"; import { join } from "path";
import { isWin } from "./utils"; import { isWin } from "../utils/config";
import log from "./logger"; import { thumbarLog } from "../logger";
enum ThumbarKeys { enum ThumbarKeys {
Play = "play", Play = "play",
@@ -17,6 +17,9 @@ export interface Thumbar {
updateThumbar(playing: boolean, clean?: boolean): void; updateThumbar(playing: boolean, clean?: boolean): void;
} }
// 缩略图单例
let thumbar: Thumbar | null = null;
// 工具栏图标 // 工具栏图标
const thumbarIcon = (filename: string) => { const thumbarIcon = (filename: string) => {
// 是否为暗色 // 是否为暗色
@@ -86,14 +89,26 @@ class createThumbar implements Thumbar {
} }
} }
/**
*
* @param win
* @returns
*/
export const initThumbar = (win: BrowserWindow) => { export const initThumbar = (win: BrowserWindow) => {
try { try {
// 若非 Win // 若非 Win
if (!isWin) return null; if (!isWin) return null;
log.info("🚀 ThumbarButtons Startup"); thumbarLog.info("🚀 ThumbarButtons Startup");
return new createThumbar(win); thumbar = new createThumbar(win);
return thumbar;
} catch (error) { } catch (error) {
log.error("❌ ThumbarButtons Error", error); thumbarLog.error("❌ ThumbarButtons Error", error);
throw error; throw error;
} }
}; };
/**
*
* @returns
*/
export const getThumbar = () => thumbar;

View File

@@ -7,9 +7,9 @@ import {
nativeImage, nativeImage,
nativeTheme, nativeTheme,
} from "electron"; } from "electron";
import { isWin, appName } from "./utils"; import { isWin, appName } from "../utils/config";
import { join } from "path"; import { join } from "path";
import log from "./logger"; import { trayLog } from "../logger";
// 播放模式 // 播放模式
type PlayMode = "repeat" | "repeat-once" | "shuffle"; type PlayMode = "repeat" | "repeat-once" | "shuffle";
@@ -34,6 +34,9 @@ export interface MainTray {
destroyTray(): void; destroyTray(): void;
} }
// 托盘单例
let mainTrayInstance: MainTray | null = null;
// 托盘图标 // 托盘图标
const trayIcon = (filename: string) => { const trayIcon = (filename: string) => {
// const rootPath = isDev // const rootPath = isDev
@@ -219,11 +222,19 @@ class CreateTray implements MainTray {
}); });
} }
// 设置标题 // 设置标题
/**
*
* @param title
*/
setTitle(title: string) { setTitle(title: string) {
this._win.setTitle(title);
this._tray.setTitle(title); this._tray.setTitle(title);
this._tray.setToolTip(title); this._tray.setToolTip(title);
} }
// 设置播放名称 /**
*
* @param name
*/
setPlayName(name: string) { setPlayName(name: string) {
// 超长处理 // 超长处理
if (name.length > 20) name = name.slice(0, 20) + "..."; if (name.length > 20) name = name.slice(0, 20) + "...";
@@ -231,48 +242,80 @@ class CreateTray implements MainTray {
// 更新菜单 // 更新菜单
this.initTrayMenu(); this.initTrayMenu();
} }
// 设置播放状态 /**
*
* @param state
*/
setPlayState(state: PlayState) { setPlayState(state: PlayState) {
playState = state; playState = state;
// 更新菜单 // 更新菜单
this.initTrayMenu(); this.initTrayMenu();
} }
// 设置播放模式 /**
*
* @param mode
*/
setPlayMode(mode: PlayMode) { setPlayMode(mode: PlayMode) {
playMode = mode; playMode = mode;
// 更新菜单 // 更新菜单
this.initTrayMenu(); this.initTrayMenu();
} }
// 设置喜欢状态 /**
*
* @param like
*/
setLikeState(like: boolean) { setLikeState(like: boolean) {
likeSong = like; likeSong = like;
// 更新菜单 // 更新菜单
this.initTrayMenu(); this.initTrayMenu();
} }
// 桌面歌词开关 /**
*
* @param show
*/
setDesktopLyricShow(show: boolean) { setDesktopLyricShow(show: boolean) {
desktopLyricShow = show; desktopLyricShow = show;
// 更新菜单 // 更新菜单
this.initTrayMenu(); this.initTrayMenu();
} }
// 锁定桌面歌词 /**
*
* @param lock
*/
setDesktopLyricLock(lock: boolean) { setDesktopLyricLock(lock: boolean) {
desktopLyricLock = lock; desktopLyricLock = lock;
// 更新菜单 // 更新菜单
this.initTrayMenu(); this.initTrayMenu();
} }
// 销毁托盘 /**
*
*/
destroyTray() { destroyTray() {
this._tray.destroy(); this._tray.destroy();
} }
} }
/**
*
* @param win
* @param lyricWin
* @returns
*/
export const initTray = (win: BrowserWindow, lyricWin: BrowserWindow) => { export const initTray = (win: BrowserWindow, lyricWin: BrowserWindow) => {
try { try {
log.info("🚀 Tray Process Startup"); trayLog.info("🚀 Tray Process Startup");
return new CreateTray(win, lyricWin); const tray = new CreateTray(win, lyricWin);
// 保存单例实例
mainTrayInstance = tray;
return tray;
} catch (error) { } catch (error) {
log.error("❌ Tray Process Error", error); trayLog.error("❌ Tray Process Error", error);
return null; return null;
} }
}; };
/**
*
* @returns
*/
export const getMainTray = (): MainTray | null => mainTrayInstance;

View File

@@ -1,10 +1,18 @@
import { type BrowserWindow } from "electron"; import { app, type BrowserWindow } from "electron";
import { updateLog } from "../logger";
import electronUpdater from "electron-updater"; import electronUpdater from "electron-updater";
import log from "./logger"; import { isDev } from "../utils/config";
// import // import
const { autoUpdater } = electronUpdater; const { autoUpdater } = electronUpdater;
// 开发环境启用
if (isDev) {
Object.defineProperty(app, "isPackaged", {
get: () => true,
});
}
// 更新源 // 更新源
autoUpdater.setFeedURL({ autoUpdater.setFeedURL({
provider: "github", provider: "github",
@@ -28,19 +36,19 @@ const initUpdaterListeners = (win: BrowserWindow) => {
// 当有新版本可用时 // 当有新版本可用时
autoUpdater.on("update-available", (info) => { autoUpdater.on("update-available", (info) => {
win.webContents.send("update-available", info); win.webContents.send("update-available", info);
log.info(`🚀 New version available: ${info.version}`); updateLog.info(`🚀 New version available: ${info.version}`);
}); });
// 更新下载进度 // 更新下载进度
autoUpdater.on("download-progress", (progress) => { autoUpdater.on("download-progress", (progress) => {
win.webContents.send("download-progress", progress); win.webContents.send("download-progress", progress);
log.info(`🚀 Downloading: ${progress.percent}%`); updateLog.info(`🚀 Downloading: ${progress.percent}%`);
}); });
// 当下载完成时 // 当下载完成时
autoUpdater.on("update-downloaded", (info) => { autoUpdater.on("update-downloaded", (info) => {
win.webContents.send("update-downloaded", info); win.webContents.send("update-downloaded", info);
log.info(`🚀 Update downloaded: ${info.version}`); updateLog.info(`🚀 Update downloaded: ${info.version}`);
// 安装更新 // 安装更新
autoUpdater.quitAndInstall(); autoUpdater.quitAndInstall();
}); });
@@ -48,13 +56,13 @@ const initUpdaterListeners = (win: BrowserWindow) => {
// 当没有新版本时 // 当没有新版本时
autoUpdater.on("update-not-available", (info) => { autoUpdater.on("update-not-available", (info) => {
if (isShowTip) win.webContents.send("update-not-available", info); if (isShowTip) win.webContents.send("update-not-available", info);
log.info(`✅ No new version available: ${info.version}`); updateLog.info(`✅ No new version available: ${info.version}`);
}); });
// 更新错误 // 更新错误
autoUpdater.on("error", (err) => { autoUpdater.on("error", (err) => {
win.webContents.send("update-error", err); win.webContents.send("update-error", err);
log.error(`❌ Update error: ${err.message}`); updateLog.error(`❌ Update error: ${err.message}`);
}); });
isInit = true; isInit = true;

View File

@@ -0,0 +1,51 @@
import { is } from "@electron-toolkit/utils";
import { app } from "electron";
/**
* 是否为开发环境
* @returns boolean
*/
export const isDev = is.dev;
/** 是否为 Windows 系统 */
export const isWin = process.platform === "win32";
/** 是否为 macOS 系统 */
export const isMac = process.platform === "darwin";
/** 是否为 Linux 系统 */
export const isLinux = process.platform === "linux";
/**
* 软件版本
* @returns string
*/
export const appVersion = app.getVersion();
/**
* 程序名称
* @returns string
*/
export const appName = app.getName() || "SPlayer";
/**
* 服务器端口
* @returns number
*/
export const port = Number(import.meta.env["VITE_SERVER_PORT"] || 25884);
/**
* 主窗口加载地址
* @returns string
*/
export const mainWinUrl =
isDev && process.env["ELECTRON_RENDERER_URL"]
? process.env["ELECTRON_RENDERER_URL"]
: `http://localhost:${port}`;
/**
* 加载窗口地址
* @returns string
*/
export const loadWinUrl =
isDev && process.env["ELECTRON_RENDERER_URL"]
? `${process.env["ELECTRON_RENDERER_URL"]}/web/loading/index.html`
: `http://localhost:${port}/web/loading/index.html`;

View File

@@ -1,21 +1,14 @@
import { app } from "electron"; import { createHash } from "crypto";
import { is } from "@electron-toolkit/utils"; import { readFile } from "fs/promises";
import fs from "fs/promises";
import crypto from "crypto";
// 系统判断 /**
export const isDev = is.dev; * ID
export const isWin = process.platform === "win32"; * @param filePath
export const isMac = process.platform === "darwin"; * @returns ID
export const isLinux = process.platform === "linux"; */
// 程序名称
export const appName = app.getName() || "SPlayer";
// 生成唯一ID
export const getFileID = (filePath: string): number => { export const getFileID = (filePath: string): number => {
// SHA-256 // SHA-256
const hash = crypto.createHash("sha256"); const hash = createHash("sha256");
hash.update(filePath); hash.update(filePath);
const digest = hash.digest("hex"); const digest = hash.digest("hex");
// 将哈希值的前 16 位转换为十进制数字 // 将哈希值的前 16 位转换为十进制数字
@@ -23,10 +16,14 @@ export const getFileID = (filePath: string): number => {
return Number(uniqueId.toString().padStart(16, "0")); return Number(uniqueId.toString().padStart(16, "0"));
}; };
// 生成文件 MD5 /**
* MD5
* @param path
* @returns MD5值
*/
export const getFileMD5 = async (path: string): Promise<string> => { export const getFileMD5 = async (path: string): Promise<string> => {
const data = await fs.readFile(path); const data = await readFile(path);
const hash = crypto.createHash("md5"); const hash = createHash("md5");
hash.update(data); hash.update(data);
return hash.digest("hex"); return hash.digest("hex");
}; };

View File

@@ -0,0 +1,25 @@
import { app } from "electron";
import { systemLog } from "../logger";
import mainWindow from "../windows/main-window";
/**
* 初始化单实例锁
* @returns 如果当前实例获得了锁,返回 true否则返回 false
*/
export const initSingleLock = (): boolean => {
const gotTheLock = app.requestSingleInstanceLock();
// 如果未获得锁,退出当前实例
if (!gotTheLock) {
app.quit();
systemLog.warn("❌ 已有一个实例正在运行");
return false;
}
// 当第二个实例启动时触发
else {
app.on("second-instance", () => {
systemLog.warn("❌ 第二个实例将要启动");
mainWindow.getWin()?.show();
});
}
return true;
};

View File

@@ -0,0 +1,45 @@
import { BrowserWindow, BrowserWindowConstructorOptions } from "electron";
import { windowsLog } from "../logger";
import { appName } from "../utils/config";
import { join } from "path";
import icon from "../../../public/icons/favicon.png?asset";
export const createWindow = (
options: BrowserWindowConstructorOptions = {},
): BrowserWindow | null => {
try {
const defaultOptions: BrowserWindowConstructorOptions = {
title: appName,
width: 1280,
height: 720,
frame: false, // 创建后是否显示窗口
center: true, // 窗口居中
icon, // 窗口图标
autoHideMenuBar: true, // 隐藏菜单栏
webPreferences: {
preload: join(__dirname, "../preload/index.mjs"),
// 禁用渲染器沙盒
sandbox: false,
// 禁用同源策略
webSecurity: false,
// 允许 HTTP
allowRunningInsecureContent: true,
// 禁用拼写检查
spellcheck: false,
// 启用 Node.js
nodeIntegration: true,
nodeIntegrationInWorker: true,
// 启用上下文隔离
contextIsolation: false,
},
};
// 合并参数
options = Object.assign(defaultOptions, options);
// 创建窗口
const win = new BrowserWindow(options);
return win;
} catch (error) {
windowsLog.error(error);
return null;
}
};

View File

@@ -0,0 +1,60 @@
import { BrowserWindow } from "electron";
import { createWindow } from "./index";
import { loadWinUrl } from "../utils/config";
class LoadWindow {
private win: BrowserWindow | null = null;
private winURL: string;
constructor() {
this.winURL = loadWinUrl;
}
/**
* 主窗口事件
* @returns void
*/
private event(): void {
if (!this.win) return;
// 准备好显示
this.win.on("ready-to-show", () => {
this.win?.show();
});
}
/**
* 创建窗口
* @returns BrowserWindow | null
*/
create(): BrowserWindow | null {
this.win = createWindow({
width: 800,
height: 560,
maxWidth: 800,
maxHeight: 560,
resizable: false,
alwaysOnTop: true,
// 不在任务栏显示
skipTaskbar: true,
// 窗口不能最小化
minimizable: false,
// 窗口不能最大化
maximizable: false,
// 窗口不能进入全屏状态
fullscreenable: false,
show: false,
});
if (!this.win) return null;
// 加载地址
this.win.loadURL(this.winURL);
// 窗口事件
this.event();
return this.win;
}
/**
* 获取窗口
* @returns BrowserWindow | null
*/
getWin(): BrowserWindow | null {
return this.win;
}
}
export default new LoadWindow();

View File

@@ -0,0 +1,100 @@
import { app, BrowserWindow, session } from "electron";
import { createWindow } from "./index";
import { join } from "path";
class LoginWindow {
private win: BrowserWindow | null = null;
private loginTimer: NodeJS.Timeout | null = null;
private loginSession: Electron.Session | null = null;
constructor() {}
private getLoginSession(): Electron.Session {
if (!this.loginSession) {
this.loginSession = session.fromPartition("persist:login");
}
return this.loginSession;
}
// 事件绑定
private event(mainWin: BrowserWindow): void {
if (!this.win) return;
// 阻止新窗口创建
this.win.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
// 加载完成后显示并开始轮询登录状态
this.win.webContents.once("did-finish-load", () => {
this.win?.show();
this.loginTimer = setInterval(() => this.checkLogin(mainWin), 1000);
this.win?.on("closed", () => {
if (this.loginTimer) clearInterval(this.loginTimer);
this.loginTimer = null;
});
});
}
// 检查是否已登录
private async checkLogin(mainWin: BrowserWindow) {
if (!this.win) return;
try {
this.win.webContents.executeJavaScript(
"document.title = '登录网易云音乐( 若遇到无响应请关闭后重试 '",
);
// 判断 MUSIC_U
const MUSIC_U = await this.getLoginSession().cookies.get({ name: "MUSIC_U" });
if (MUSIC_U && MUSIC_U.length > 0) {
if (this.loginTimer) clearInterval(this.loginTimer);
this.loginTimer = null;
const value = `MUSIC_U=${MUSIC_U[0].value};`;
// 发送回主进程
mainWin?.webContents.send("send-cookies", value);
this.win.destroy();
this.win = null;
}
} catch (error) {
console.error(error);
}
}
// 创建登录窗口
async create(mainWin: BrowserWindow): Promise<BrowserWindow | null> {
await app.whenReady();
const loginSession = this.getLoginSession();
// 清理登录会话存储
await loginSession.clearStorageData({
storages: ["cookies", "localstorage"],
});
this.win = createWindow({
parent: mainWin,
title: "登录网易云音乐( 若遇到无响应请关闭后重试 ",
width: 1280,
height: 800,
center: true,
autoHideMenuBar: true,
webPreferences: {
preload: join(__dirname, "../preload/index.mjs"),
sandbox: false,
webSecurity: false,
allowRunningInsecureContent: true,
spellcheck: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false,
session: loginSession,
},
});
if (!this.win) return null;
// 加载登录地址
this.win.loadURL("https://music.163.com/#/login/");
// 绑定事件
this.event(mainWin);
return this.win;
}
// 获取窗口
getWin(): BrowserWindow | null {
return this.win;
}
}
export default new LoginWindow();

View File

@@ -0,0 +1,73 @@
import { BrowserWindow } from "electron";
import { createWindow } from "./index";
import { useStore } from "../store";
import { join } from "path";
class LyricWindow {
private win: BrowserWindow | null = null;
constructor() {}
/**
* 主窗口事件
* @returns void
*/
private event(): void {
if (!this.win) return;
// 歌词窗口缩放
this.win?.on("resized", () => {
const store = useStore();
const bounds = this.win?.getBounds();
if (bounds) {
const { width, height } = bounds;
store.set("lyric", { ...store.get("lyric"), width, height });
}
});
}
/**
* 创建主窗口
* @returns BrowserWindow | null
*/
create(): BrowserWindow | null {
const store = useStore();
const { width, height, x, y } = store.get("lyric");
this.win = createWindow({
width: width || 800,
height: height || 180,
minWidth: 440,
minHeight: 120,
maxWidth: 1600,
maxHeight: 300,
// 窗口位置
x,
y,
transparent: true,
backgroundColor: "rgba(0, 0, 0, 0)",
alwaysOnTop: true,
resizable: true,
movable: true,
show: false,
// 不在任务栏显示
skipTaskbar: true,
// 窗口不能最小化
minimizable: false,
// 窗口不能最大化
maximizable: false,
// 窗口不能进入全屏状态
fullscreenable: false,
});
if (!this.win) return null;
// 加载地址
this.win.loadFile(join(__dirname, "../main/web/lyric.html"));
// 窗口事件
this.event();
return this.win;
}
/**
* 获取窗口
* @returns BrowserWindow | null
*/
getWin(): BrowserWindow | null {
return this.win;
}
}
export default new LyricWindow();

View File

@@ -0,0 +1,126 @@
import { BrowserWindow, shell } from "electron";
import { createWindow } from "./index";
import { mainWinUrl } from "../utils/config";
import { useStore } from "../store";
import { isLinux } from "../utils/config";
class MainWindow {
private win: BrowserWindow | null = null;
private winURL: string;
constructor() {
this.winURL = mainWinUrl;
}
/**
* 保存窗口大小和状态
*/
private saveBounds() {
if (this.win?.isFullScreen()) return;
const store = useStore();
const bounds = this.win?.getBounds();
if (bounds) {
const maximized = this.win?.isMaximized();
store.set("window", { ...bounds, maximized });
}
}
/**
* 主窗口事件
* @returns void
*/
private event(): void {
if (!this.win) return;
const store = useStore();
// 配置网络代理
if (store.get("proxy")) {
this.win.webContents.session.setProxy({ proxyRules: store.get("proxy") });
}
// 窗口打开处理程序
this.win.webContents.setWindowOpenHandler((details) => {
const { url } = details;
if (url.startsWith("https://") || url.startsWith("http://")) {
shell.openExternal(url);
}
return { action: "deny" };
});
// 窗口显示时
this.win?.on("show", () => {
// this.mainWindow?.webContents.send("lyricsScroll");
});
// 窗口获得焦点时
this.win?.on("focus", () => {
this.saveBounds();
});
// 窗口大小改变时
this.win?.on("resized", () => {
// 若处于全屏则不保存
if (this.win?.isFullScreen()) return;
this.saveBounds();
});
// 窗口位置改变时
this.win?.on("moved", () => {
this.saveBounds();
});
// 窗口最大化时
this.win?.on("maximize", () => {
this.saveBounds();
this.win?.webContents.send("win-state-change", true);
});
// 窗口取消最大化时
this.win?.on("unmaximize", () => {
this.saveBounds();
this.win?.webContents.send("win-state-change", false);
});
// Linux 无法使用 resized 和 moved
if (isLinux) {
this.win?.on("resize", () => {
// 若处于全屏则不保存
if (this.win?.isFullScreen()) return;
this.saveBounds();
});
this.win?.on("move", () => {
this.saveBounds();
});
}
}
/**
* 创建窗口
* @returns BrowserWindow | null
*/
create(): BrowserWindow | null {
const store = useStore();
const { width, height } = store.get("window");
this.win = createWindow({
// 菜单栏
titleBarStyle: "customButtonsOnHover",
width,
height,
minHeight: 600,
minWidth: 800,
show: false,
});
if (!this.win) return null;
// 加载地址
this.win.loadURL(this.winURL);
// 窗口事件
this.event();
return this.win;
}
/**
* 获取窗口
* @returns BrowserWindow | null
*/
getWin(): BrowserWindow | null {
return this.win;
}
/**
* 显示主窗口
*/
showWindow() {
if (this.win) {
this.win.show();
if (this.win.isMinimized()) this.win.restore();
this.win.focus();
}
}
}
export default new MainWindow();

View File

@@ -1,12 +1,12 @@
import { join } from "path"; import { join } from "path";
import { isDev } from "../main/utils"; import { isDev } from "../main/utils/config";
import { serverLog } from "../main/logger";
import initNcmAPI from "./netease"; import initNcmAPI from "./netease";
import initUnblockAPI from "./unblock"; import initUnblockAPI from "./unblock";
import fastifyCookie from "@fastify/cookie"; import fastifyCookie from "@fastify/cookie";
import fastifyMultipart from "@fastify/multipart"; import fastifyMultipart from "@fastify/multipart";
import fastifyStatic from "@fastify/static"; import fastifyStatic from "@fastify/static";
import fastify from "fastify"; import fastify from "fastify";
import log from "../main/logger";
const initAppServer = async () => { const initAppServer = async () => {
try { try {
@@ -21,7 +21,7 @@ const initAppServer = async () => {
server.register(fastifyMultipart); server.register(fastifyMultipart);
// 生产环境启用静态文件 // 生产环境启用静态文件
if (!isDev) { if (!isDev) {
log.info("📂 Serving static files from /renderer"); serverLog.info("📂 Serving static files from /renderer");
server.register(fastifyStatic, { server.register(fastifyStatic, {
root: join(__dirname, "../renderer"), root: join(__dirname, "../renderer"),
}); });
@@ -50,10 +50,10 @@ const initAppServer = async () => {
// 启动端口 // 启动端口
const port = Number(process.env["VITE_SERVER_PORT"] || 25884); const port = Number(process.env["VITE_SERVER_PORT"] || 25884);
await server.listen({ port }); await server.listen({ port });
log.info(`🌐 Starting AppServer on port ${port}`); serverLog.info(`🌐 Starting AppServer on port ${port}`);
return server; return server;
} catch (error) { } catch (error) {
log.error("🚫 AppServer failed to start"); serverLog.error("🚫 AppServer failed to start");
throw error; throw error;
} }
}; };

View File

@@ -1,7 +1,7 @@
import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify"; import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
import { pathCase } from "change-case"; import { pathCase } from "change-case";
import { serverLog } from "../../main/logger";
import NeteaseCloudMusicApi from "@neteasecloudmusicapienhanced/api"; import NeteaseCloudMusicApi from "@neteasecloudmusicapienhanced/api";
import log from "../../main/logger";
// 获取数据 // 获取数据
const getHandler = (name: string, neteaseApi: (params: any) => any) => { const getHandler = (name: string, neteaseApi: (params: any) => any) => {
@@ -9,7 +9,7 @@ const getHandler = (name: string, neteaseApi: (params: any) => any) => {
req: FastifyRequest<{ Querystring: { [key: string]: string } }>, req: FastifyRequest<{ Querystring: { [key: string]: string } }>,
reply: FastifyReply, reply: FastifyReply,
) => { ) => {
log.info("🌐 Request NcmAPI:", name); serverLog.log("🌐 Request NcmAPI:", name);
// 获取 NcmAPI 数据 // 获取 NcmAPI 数据
try { try {
const result = await neteaseApi({ const result = await neteaseApi({
@@ -19,7 +19,7 @@ const getHandler = (name: string, neteaseApi: (params: any) => any) => {
}); });
return reply.send(result.body); return reply.send(result.body);
} catch (error: any) { } catch (error: any) {
log.error("❌ NcmAPI Error:", error); serverLog.error("❌ NcmAPI Error:", error);
if ([400, 301].includes(error.status)) { if ([400, 301].includes(error.status)) {
return reply.status(error.status).send(error.body); return reply.status(error.status).send(error.body);
} }
@@ -60,7 +60,7 @@ const initNcmAPI = async (fastify: FastifyInstance) => {
} }
}); });
log.info("🌐 Register NcmAPI successfully"); serverLog.info("🌐 Register NcmAPI successfully");
}; };
export default initNcmAPI; export default initNcmAPI;

View File

@@ -1,7 +1,7 @@
import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify"; import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
import { SongUrlResult } from "./unblock"; import { SongUrlResult } from "./unblock";
import { serverLog } from "../../main/logger";
import getKuwoSongUrl from "./kuwo"; import getKuwoSongUrl from "./kuwo";
import log from "../../main/logger";
import axios from "axios"; import axios from "axios";
/** /**
@@ -17,10 +17,10 @@ const getNeteaseSongUrl = async (id: number | string): Promise<SongUrlResult> =>
params: { types: "url", id }, params: { types: "url", id },
}); });
const songUrl = result.data.url; const songUrl = result.data.url;
log.info("🔗 NeteaseSongUrl URL:", songUrl); serverLog.log("🔗 NeteaseSongUrl URL:", songUrl);
return { code: 200, url: songUrl }; return { code: 200, url: songUrl };
} catch (error) { } catch (error) {
log.error("❌ Get NeteaseSongUrl Error:", error); serverLog.error("❌ Get NeteaseSongUrl Error:", error);
return { code: 404, url: null }; return { code: 404, url: null };
} }
}; };
@@ -62,7 +62,7 @@ const UnblockAPI = async (fastify: FastifyInstance) => {
}, },
); );
log.info("🌐 Register UnblockAPI successfully"); serverLog.info("🌐 Register UnblockAPI successfully");
}; };
export default UnblockAPI; export default UnblockAPI;

View File

@@ -1,6 +1,6 @@
import { encryptQuery } from "./kwDES"; import { encryptQuery } from "./kwDES";
import { SongUrlResult } from "./unblock"; import { SongUrlResult } from "./unblock";
import log from "../../main/logger"; import { serverLog } from "../../main/logger";
import axios from "axios"; import axios from "axios";
// 获取酷我音乐歌曲 ID // 获取酷我音乐歌曲 ID
@@ -26,7 +26,7 @@ const getKuwoSongId = async (keyword: string): Promise<string | null> => {
if (songName && !songName?.includes(originalName[0])) return null; if (songName && !songName?.includes(originalName[0])) return null;
return songId.slice("MUSIC_".length); return songId.slice("MUSIC_".length);
} catch (error) { } catch (error) {
log.error("❌ Get KuwoSongId Error:", error); serverLog.error("❌ Get KuwoSongId Error:", error);
return null; return null;
} }
}; };
@@ -53,12 +53,12 @@ const getKuwoSongUrl = async (keyword: string): Promise<SongUrlResult> => {
}); });
if (result.data) { if (result.data) {
const urlMatch = result.data.match(/http[^\s$"]+/)[0]; const urlMatch = result.data.match(/http[^\s$"]+/)[0];
log.info("🔗 KuwoSong URL:", urlMatch); serverLog.log("🔗 KuwoSong URL:", urlMatch);
return { code: 200, url: urlMatch }; return { code: 200, url: urlMatch };
} }
return { code: 404, url: null }; return { code: 404, url: null };
} catch (error) { } catch (error) {
log.error("❌ Get KuwoSong URL Error:", error); serverLog.error("❌ Get KuwoSong URL Error:", error);
return { code: 404, url: null }; return { code: 404, url: null };
} }
}; };

View File

@@ -34,13 +34,13 @@
}, },
"dependencies": { "dependencies": {
"@applemusic-like-lyrics/core": "^0.1.3", "@applemusic-like-lyrics/core": "^0.1.3",
"@applemusic-like-lyrics/lyric": "^0.2.4", "@applemusic-like-lyrics/lyric": "^0.3.0",
"@applemusic-like-lyrics/vue": "^0.1.5", "@applemusic-like-lyrics/vue": "^0.1.5",
"@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"@imsyy/color-utils": "^1.0.2", "@imsyy/color-utils": "^1.0.2",
"@material/material-color-utilities": "^0.3.0", "@material/material-color-utilities": "^0.3.0",
"@neteasecloudmusicapienhanced/api": "^4.29.11", "@neteasecloudmusicapienhanced/api": "^4.29.12",
"@pixi/app": "^7.4.3", "@pixi/app": "^7.4.3",
"@pixi/core": "^7.4.3", "@pixi/core": "^7.4.3",
"@pixi/display": "^7.4.3", "@pixi/display": "^7.4.3",
@@ -53,7 +53,7 @@
"change-case": "^5.4.4", "change-case": "^5.4.4",
"dayjs": "^1.11.18", "dayjs": "^1.11.18",
"electron-dl": "^4.0.0", "electron-dl": "^4.0.0",
"electron-store": "^8.2.0", "electron-store": "^11.0.2",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"font-list": "^2.0.1", "font-list": "^2.0.1",

199
pnpm-lock.yaml generated
View File

@@ -16,8 +16,8 @@ importers:
specifier: ^0.1.3 specifier: ^0.1.3
version: 0.1.3(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0) version: 0.1.3(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)
'@applemusic-like-lyrics/lyric': '@applemusic-like-lyrics/lyric':
specifier: ^0.2.4 specifier: ^0.3.0
version: 0.2.4 version: 0.3.0
'@applemusic-like-lyrics/vue': '@applemusic-like-lyrics/vue':
specifier: ^0.1.5 specifier: ^0.1.5
version: 0.1.5(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)(vue@3.5.22(typescript@5.9.3)) version: 0.1.5(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)(vue@3.5.22(typescript@5.9.3))
@@ -34,8 +34,8 @@ importers:
specifier: ^0.3.0 specifier: ^0.3.0
version: 0.3.0 version: 0.3.0
'@neteasecloudmusicapienhanced/api': '@neteasecloudmusicapienhanced/api':
specifier: ^4.29.11 specifier: ^4.29.12
version: 4.29.11 version: 4.29.12
'@pixi/app': '@pixi/app':
specifier: ^7.4.3 specifier: ^7.4.3
version: 7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)) version: 7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))
@@ -73,8 +73,8 @@ importers:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.0 version: 4.0.0
electron-store: electron-store:
specifier: ^8.2.0 specifier: ^11.0.2
version: 8.2.0 version: 11.0.2
electron-updater: electron-updater:
specifier: ^6.6.2 specifier: ^6.6.2
version: 6.6.2 version: 6.6.2
@@ -260,8 +260,8 @@ packages:
jss: '*' jss: '*'
jss-preset-default: '*' jss-preset-default: '*'
'@applemusic-like-lyrics/lyric@0.2.4': '@applemusic-like-lyrics/lyric@0.3.0':
resolution: {integrity: sha512-B1N7dMEwEIlgtyqLGVt7EbXJBQLCbv5J/N0YjkgWHu5qM1vMhKSGOXzrBuMDusmr4Z6tfO5iLfOYQUtoSXZyeg==} resolution: {integrity: sha512-ZGyBheZZfqjQmGnEUciNKCARwkqIP39ONZirJE+NOQjQ47TYlZz4tlLBRH/uRfq5qviYyJ1S9Q+pZxlYoyHWVw==}
'@applemusic-like-lyrics/vue@0.1.5': '@applemusic-like-lyrics/vue@0.1.5':
resolution: {integrity: sha512-FE8XtfoScmSwg61XFdNjdYBBQ8RvT10V01hFv38sMsOPUxLQn7rVUafx3NCwUDwGbAYPrC4Azn5wUpeZPdKFOg==} resolution: {integrity: sha512-FE8XtfoScmSwg61XFdNjdYBBQ8RvT10V01hFv38sMsOPUxLQn7rVUafx3NCwUDwGbAYPrC4Azn5wUpeZPdKFOg==}
@@ -871,8 +871,8 @@ packages:
'@material/material-color-utilities@0.3.0': '@material/material-color-utilities@0.3.0':
resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==} resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==}
'@neteasecloudmusicapienhanced/api@4.29.11': '@neteasecloudmusicapienhanced/api@4.29.12':
resolution: {integrity: sha512-6yajC4cDH74+xpI5OadFXqIeA1TgmAi67QLRv3JbPet9JIWN6DXAagCz0QJUbo0bUPIdH1CimyU/P+eEyxP5nA==} resolution: {integrity: sha512-oIT+XgjMN/m0e1C7c+KUKee9AMjAziJY28aZ2m4yAAJyMkAwgcMHpNs9nQ91U8FRHGwX4TNXoWGX1Tczx484TA==}
engines: {node: '>=12'} engines: {node: '>=12'}
hasBin: true hasBin: true
@@ -1458,14 +1458,6 @@ packages:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'} engines: {node: '>=8'}
ajv-formats@2.1.1:
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
ajv-formats@3.0.1: ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies: peerDependencies:
@@ -1562,9 +1554,8 @@ packages:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
atomically@1.7.0: atomically@2.0.3:
resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==}
engines: {node: '>=10.12.0'}
avvio@9.1.0: avvio@9.1.0:
resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==}
@@ -1802,9 +1793,9 @@ packages:
concat-map@0.0.1: concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
conf@10.2.0: conf@15.0.2:
resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} resolution: {integrity: sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw==}
engines: {node: '>=12'} engines: {node: '>=20'}
confbox@0.1.8: confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
@@ -1909,9 +1900,9 @@ packages:
dayjs@1.11.18: dayjs@1.11.18:
resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
debounce-fn@4.0.0: debounce-fn@6.0.0:
resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==}
engines: {node: '>=10'} engines: {node: '>=18'}
debug@4.4.3: debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
@@ -2000,9 +1991,9 @@ packages:
os: [darwin] os: [darwin]
hasBin: true hasBin: true
dot-prop@6.0.1: dot-prop@10.1.0:
resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==}
engines: {node: '>=10'} engines: {node: '>=20'}
dotenv-expand@11.0.7: dotenv-expand@11.0.7:
resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
@@ -2012,6 +2003,10 @@ packages:
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'} engines: {node: '>=12'}
dotenv@17.2.3:
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
engines: {node: '>=12'}
dunder-proto@1.0.1: dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2052,8 +2047,9 @@ packages:
electron-publish@26.0.11: electron-publish@26.0.11:
resolution: {integrity: sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A==} resolution: {integrity: sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A==}
electron-store@8.2.0: electron-store@11.0.2:
resolution: {integrity: sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==} resolution: {integrity: sha512-4VkNRdN+BImL2KcCi41WvAYbh6zLX5AUTi4so68yPqiItjbgTjqpEnGAqasgnG+lB6GuAyUltKwVopp6Uv+gwQ==}
engines: {node: '>=20'}
electron-to-chromium@1.5.235: electron-to-chromium@1.5.235:
resolution: {integrity: sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==} resolution: {integrity: sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==}
@@ -2105,6 +2101,10 @@ packages:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'} engines: {node: '>=6'}
env-paths@3.0.0:
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
err-code@2.0.3: err-code@2.0.3:
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
@@ -2365,10 +2365,6 @@ packages:
resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==} resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==}
engines: {node: '>=20'} engines: {node: '>=20'}
find-up@3.0.0:
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
engines: {node: '>=6'}
find-up@4.1.0: find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2715,10 +2711,6 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
is-obj@2.0.0:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
is-plain-obj@1.1.0: is-plain-obj@1.1.0:
resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -2795,8 +2787,8 @@ packages:
json-schema-traverse@1.0.0: json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
json-schema-typed@7.0.3: json-schema-typed@8.0.1:
resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} resolution: {integrity: sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==}
json-stable-stringify-without-jsonify@1.0.1: json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -2891,10 +2883,6 @@ packages:
localforage@1.10.0: localforage@1.10.0:
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
locate-path@3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
engines: {node: '>=6'}
locate-path@5.0.0: locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -3014,9 +3002,9 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'} engines: {node: '>=6'}
mimic-fn@3.1.0: mimic-function@5.0.1:
resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=8'} engines: {node: '>=18'}
mimic-response@1.0.1: mimic-response@1.0.1:
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
@@ -3240,10 +3228,6 @@ packages:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
p-locate@3.0.0:
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
engines: {node: '>=6'}
p-locate@4.1.0: p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -3282,10 +3266,6 @@ packages:
path-browserify@1.0.1: path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
path-exists@3.0.0:
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
engines: {node: '>=4'}
path-exists@4.0.0: path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -3394,10 +3374,6 @@ packages:
pkg-types@2.3.0: pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
pkg-up@3.1.0:
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
engines: {node: '>=8'}
plist@3.1.0: plist@3.1.0:
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
engines: {node: '>=10.4.0'} engines: {node: '>=10.4.0'}
@@ -3849,6 +3825,9 @@ packages:
resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==}
engines: {node: '>=10'} engines: {node: '>=10'}
stubborn-fs@1.2.5:
resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==}
sumchecker@3.0.1: sumchecker@3.0.1:
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
engines: {node: '>= 8.0'} engines: {node: '>= 8.0'}
@@ -3869,6 +3848,10 @@ packages:
resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==} resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
tagged-tag@1.0.0:
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
engines: {node: '>=20'}
tar@6.2.1: tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -3955,9 +3938,9 @@ packages:
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
engines: {node: '>=10'} engines: {node: '>=10'}
type-fest@2.19.0: type-fest@5.1.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} resolution: {integrity: sha512-wQ531tuWvB6oK+pchHIu5lHe5f5wpSCqB8Kf4dWQRbOYc9HTge7JL0G4Qd44bh6QuJCccIzL3bugb8GI0MwHrg==}
engines: {node: '>=12.20'} engines: {node: '>=20'}
type-is@2.0.1: type-is@2.0.1:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
@@ -4202,6 +4185,9 @@ packages:
webpack-virtual-modules@0.6.2: webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
when-exit@2.1.4:
resolution: {integrity: sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==}
which-module@2.0.1: which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
@@ -4313,7 +4299,7 @@ snapshots:
jss: 10.10.0 jss: 10.10.0
jss-preset-default: 10.10.0 jss-preset-default: 10.10.0
'@applemusic-like-lyrics/lyric@0.2.4': {} '@applemusic-like-lyrics/lyric@0.3.0': {}
'@applemusic-like-lyrics/vue@0.1.5(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)(vue@3.5.22(typescript@5.9.3))': '@applemusic-like-lyrics/vue@0.1.5(@pixi/app@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3))(@pixi/filter-blur@7.4.3(@pixi/core@7.4.3))(@pixi/filter-bulge-pinch@5.1.1(@pixi/core@7.4.3))(@pixi/filter-color-matrix@7.4.3(@pixi/core@7.4.3))(@pixi/sprite@7.4.3(@pixi/core@7.4.3)(@pixi/display@7.4.3(@pixi/core@7.4.3)))(jss-preset-default@10.10.0)(jss@10.10.0)(vue@3.5.22(typescript@5.9.3))':
dependencies: dependencies:
@@ -4943,12 +4929,12 @@ snapshots:
'@material/material-color-utilities@0.3.0': {} '@material/material-color-utilities@0.3.0': {}
'@neteasecloudmusicapienhanced/api@4.29.11': '@neteasecloudmusicapienhanced/api@4.29.12':
dependencies: dependencies:
'@unblockneteasemusic/server': 0.28.0 '@unblockneteasemusic/server': 0.28.0
axios: 1.12.2 axios: 1.12.2
crypto-js: 4.2.0 crypto-js: 4.2.0
dotenv: 16.6.1 dotenv: 17.2.3
express: 5.1.0 express: 5.1.0
express-fileupload: 1.5.2 express-fileupload: 1.5.2
md5: 2.3.0 md5: 2.3.0
@@ -5542,10 +5528,6 @@ snapshots:
clean-stack: 2.2.0 clean-stack: 2.2.0
indent-string: 4.0.0 indent-string: 4.0.0
ajv-formats@2.1.1(ajv@8.17.1):
optionalDependencies:
ajv: 8.17.1
ajv-formats@3.0.1(ajv@8.17.1): ajv-formats@3.0.1(ajv@8.17.1):
optionalDependencies: optionalDependencies:
ajv: 8.17.1 ajv: 8.17.1
@@ -5663,7 +5645,10 @@ snapshots:
atomic-sleep@1.0.0: {} atomic-sleep@1.0.0: {}
atomically@1.7.0: {} atomically@2.0.3:
dependencies:
stubborn-fs: 1.2.5
when-exit: 2.1.4
avvio@9.1.0: avvio@9.1.0:
dependencies: dependencies:
@@ -5957,18 +5942,17 @@ snapshots:
concat-map@0.0.1: {} concat-map@0.0.1: {}
conf@10.2.0: conf@15.0.2:
dependencies: dependencies:
ajv: 8.17.1 ajv: 8.17.1
ajv-formats: 2.1.1(ajv@8.17.1) ajv-formats: 3.0.1(ajv@8.17.1)
atomically: 1.7.0 atomically: 2.0.3
debounce-fn: 4.0.0 debounce-fn: 6.0.0
dot-prop: 6.0.1 dot-prop: 10.1.0
env-paths: 2.2.1 env-paths: 3.0.0
json-schema-typed: 7.0.3 json-schema-typed: 8.0.1
onetime: 5.1.2
pkg-up: 3.1.0
semver: 7.7.3 semver: 7.7.3
uint8array-extras: 1.5.0
confbox@0.1.8: {} confbox@0.1.8: {}
@@ -6058,9 +6042,9 @@ snapshots:
dayjs@1.11.18: {} dayjs@1.11.18: {}
debounce-fn@4.0.0: debounce-fn@6.0.0:
dependencies: dependencies:
mimic-fn: 3.1.0 mimic-function: 5.0.1
debug@4.4.3: debug@4.4.3:
dependencies: dependencies:
@@ -6154,9 +6138,9 @@ snapshots:
verror: 1.10.1 verror: 1.10.1
optional: true optional: true
dot-prop@6.0.1: dot-prop@10.1.0:
dependencies: dependencies:
is-obj: 2.0.0 type-fest: 5.1.0
dotenv-expand@11.0.7: dotenv-expand@11.0.7:
dependencies: dependencies:
@@ -6164,6 +6148,8 @@ snapshots:
dotenv@16.6.1: {} dotenv@16.6.1: {}
dotenv@17.2.3: {}
dunder-proto@1.0.1: dunder-proto@1.0.1:
dependencies: dependencies:
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2
@@ -6235,10 +6221,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
electron-store@8.2.0: electron-store@11.0.2:
dependencies: dependencies:
conf: 10.2.0 conf: 15.0.2
type-fest: 2.19.0 type-fest: 5.1.0
electron-to-chromium@1.5.235: {} electron-to-chromium@1.5.235: {}
@@ -6306,6 +6292,8 @@ snapshots:
env-paths@2.2.1: {} env-paths@2.2.1: {}
env-paths@3.0.0: {}
err-code@2.0.3: {} err-code@2.0.3: {}
es-define-property@1.0.1: {} es-define-property@1.0.1: {}
@@ -6652,10 +6640,6 @@ snapshots:
fast-querystring: 1.1.2 fast-querystring: 1.1.2
safe-regex2: 5.0.0 safe-regex2: 5.0.0
find-up@3.0.0:
dependencies:
locate-path: 3.0.0
find-up@4.1.0: find-up@4.1.0:
dependencies: dependencies:
locate-path: 5.0.0 locate-path: 5.0.0
@@ -7016,8 +7000,6 @@ snapshots:
is-number@7.0.0: {} is-number@7.0.0: {}
is-obj@2.0.0: {}
is-plain-obj@1.1.0: {} is-plain-obj@1.1.0: {}
is-promise@4.0.0: {} is-promise@4.0.0: {}
@@ -7074,7 +7056,7 @@ snapshots:
json-schema-traverse@1.0.0: {} json-schema-traverse@1.0.0: {}
json-schema-typed@7.0.3: {} json-schema-typed@8.0.1: {}
json-stable-stringify-without-jsonify@1.0.1: {} json-stable-stringify-without-jsonify@1.0.1: {}
@@ -7224,11 +7206,6 @@ snapshots:
dependencies: dependencies:
lie: 3.1.1 lie: 3.1.1
locate-path@3.0.0:
dependencies:
p-locate: 3.0.0
path-exists: 3.0.0
locate-path@5.0.0: locate-path@5.0.0:
dependencies: dependencies:
p-locate: 4.1.0 p-locate: 4.1.0
@@ -7338,7 +7315,7 @@ snapshots:
mimic-fn@2.1.0: {} mimic-fn@2.1.0: {}
mimic-fn@3.1.0: {} mimic-function@5.0.1: {}
mimic-response@1.0.1: {} mimic-response@1.0.1: {}
@@ -7592,10 +7569,6 @@ snapshots:
dependencies: dependencies:
yocto-queue: 0.1.0 yocto-queue: 0.1.0
p-locate@3.0.0:
dependencies:
p-limit: 2.3.0
p-locate@4.1.0: p-locate@4.1.0:
dependencies: dependencies:
p-limit: 2.3.0 p-limit: 2.3.0
@@ -7638,8 +7611,6 @@ snapshots:
path-browserify@1.0.1: {} path-browserify@1.0.1: {}
path-exists@3.0.0: {}
path-exists@4.0.0: {} path-exists@4.0.0: {}
path-exists@5.0.0: {} path-exists@5.0.0: {}
@@ -7756,10 +7727,6 @@ snapshots:
exsolve: 1.0.7 exsolve: 1.0.7
pathe: 2.0.3 pathe: 2.0.3
pkg-up@3.1.0:
dependencies:
find-up: 3.0.0
plist@3.1.0: plist@3.1.0:
dependencies: dependencies:
'@xmldom/xmldom': 0.8.11 '@xmldom/xmldom': 0.8.11
@@ -8258,6 +8225,8 @@ snapshots:
'@tokenizer/token': 0.3.0 '@tokenizer/token': 0.3.0
peek-readable: 4.1.0 peek-readable: 4.1.0
stubborn-fs@1.2.5: {}
sumchecker@3.0.1: sumchecker@3.0.1:
dependencies: dependencies:
debug: 4.4.3 debug: 4.4.3
@@ -8278,6 +8247,8 @@ snapshots:
symbol-observable@1.2.0: {} symbol-observable@1.2.0: {}
tagged-tag@1.0.0: {}
tar@6.2.1: tar@6.2.1:
dependencies: dependencies:
chownr: 2.0.0 chownr: 2.0.0
@@ -8367,7 +8338,9 @@ snapshots:
type-fest@0.13.1: type-fest@0.13.1:
optional: true optional: true
type-fest@2.19.0: {} type-fest@5.1.0:
dependencies:
tagged-tag: 1.0.0
type-is@2.0.1: type-is@2.0.1:
dependencies: dependencies:
@@ -8594,6 +8567,8 @@ snapshots:
webpack-virtual-modules@0.6.2: {} webpack-virtual-modules@0.6.2: {}
when-exit@2.1.4: {}
which-module@2.0.1: {} which-module@2.0.1: {}
which@2.0.2: which@2.0.2:

View File

@@ -110,10 +110,8 @@ const min = () => window.electron.ipcRenderer.send("win-min");
// 最大化或还原 // 最大化或还原
const maxOrRes = () => { const maxOrRes = () => {
if (window.electron.ipcRenderer.sendSync("win-state")) { if (window.electron.ipcRenderer.sendSync("win-state")) {
isMax.value = false;
window.electron.ipcRenderer.send("win-restore"); window.electron.ipcRenderer.send("win-restore");
} else { } else {
isMax.value = true;
window.electron.ipcRenderer.send("win-max"); window.electron.ipcRenderer.send("win-max");
} }
}; };
@@ -198,9 +196,15 @@ const setSelect = (key: string) => {
}; };
onMounted(() => { onMounted(() => {
// 获取窗口状态 // 获取窗口状态并监听主进程的状态变更
if (isElectron) { if (isElectron) {
isMax.value = window.electron.ipcRenderer.sendSync("win-state"); isMax.value = window.electron.ipcRenderer.sendSync("win-state");
window.electron.ipcRenderer.on(
"win-state-change",
(_event, value: boolean) => {
isMax.value = value;
},
);
} }
}); });
</script> </script>

View File

@@ -290,7 +290,9 @@ class Player {
if (currentSessionId !== this.playSessionId) return; if (currentSessionId !== this.playSessionId) return;
if (!isElectron) window.document.title = "SPlayer"; if (!isElectron) window.document.title = "SPlayer";
// ipc // ipc
if (isElectron) window.electron.ipcRenderer.send("play-status-change", false); if (isElectron) {
window.electron.ipcRenderer.send("play-status-change", false);
}
console.log("⏸️ song pause:", playSongData); console.log("⏸️ song pause:", playSongData);
}); });
// 结束 // 结束

View File

@@ -4,8 +4,8 @@
"electron.vite.config.*", "electron.vite.config.*",
"electron/**/*", "electron/**/*",
"electron/main/index.ts", "electron/main/index.ts",
"electron/main/logger.ts", "electron/main/logger/index.ts",
"electron/main/store.ts", "electron/main/store/index.ts",
"electron/main/utils.ts", "electron/main/utils.ts",
"electron/main/index.d.ts", "electron/main/index.d.ts",
"electron/preload/index.d.ts", "electron/preload/index.d.ts",

View File

@@ -1,183 +0,0 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SPlayer</title>
<style>
:root {
--bg-color: #fff;
--font-coloe: #000;
--primary: #f55e55;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #161616;
--font-coloe: #fff;
}
}
* {
margin: 0;
padding: 0;
user-select: none;
box-sizing: border-box;
-webkit-user-drag: none;
}
body {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--bg-color);
}
main {
display: flex;
flex-direction: column;
align-items: center;
}
footer {
position: absolute;
bottom: 20px;
font-size: 14px;
color: var(--primary);
opacity: 0.6;
}
.logo {
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
width: 2.5em;
height: 2.5em;
animation-fill-mode: both;
animation: bblFadInOut 1.8s infinite ease-in-out;
}
.loader {
color: var(--primary);
font-size: 6px;
position: relative;
text-indent: -9999em;
transform: translateZ(0);
animation-delay: -0.16s;
margin: 40px 0;
}
.loader:before,
.loader:after {
content: "";
position: absolute;
top: 0;
}
.loader:before {
left: -3.5em;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
#startApp {
opacity: 0;
margin-top: 10px;
outline: none;
border: 2px solid var(--primary);
height: 36px;
width: 90px;
font-size: 14px;
border-radius: 12px;
color: var(--primary);
font-weight: bold;
background-color: transparent;
cursor: pointer;
transition: opacity 0.3s;
}
@keyframes bblFadInOut {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}
</style>
</head>
<body>
<main>
<div class="logo">
<svg
t="1663641871751"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="11550"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="200"
height="200"
>
<path
d="M511.764091 131.708086a446.145957 446.145957 0 1 0 446.145957 446.145957 446.145957 446.145957 0 0 0-446.145957-446.145957z m0 519.76004A71.829499 71.829499 0 1 1 583.59359 580.530919 72.275645 72.275645 0 0 1 511.764091 651.468126z"
fill="#F55E55"
p-id="11551"
></path>
<path
d="M802.205109 0.541175l-168.197026 37.030114a67.814185 67.814185 0 0 0-53.091369 66.029602V223.614153l3.569168 349.778431h114.213365V223.614153h108.859613a26.322611 26.322611 0 0 0 26.768758-26.322611V26.863786a26.768757 26.768757 0 0 0-32.122509-26.322611z"
fill="#F9BBB8"
p-id="11552"
></path>
<path
d="M511.764091 386.457428a186.935156 186.935156 0 1 0 186.935156 186.48901A186.935156 186.935156 0 0 0 511.764091 386.457428z m0 264.564552a71.383353 71.383353 0 1 1 71.383353-71.383353 71.383353 71.383353 0 0 1-71.383353 71.383353z"
fill="#F9BBB8"
p-id="11553"
></path>
</svg>
</div>
<div class="loader"></div>
<button id="startApp">直接启动</button>
</main>
<footer>
<span>SPlayer · Copyright &copy; <span id="year"></span> IMSYY</span>
</footer>
<script>
const startAppDom = document.getElementById("startApp");
// 更改年份
const currentYear = new Date().getFullYear();
document.getElementById("year").innerHTML = currentYear;
startAppDom.addEventListener("click", () => {
window.electron.ipcRenderer.send("win-loaded");
});
window.addEventListener("load", () => {
setTimeout(() => {
startAppDom.style.opacity = 1;
}, 3000);
});
</script>
</body>
</html>

54
web/loading/index.html Normal file
View File

@@ -0,0 +1,54 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SPlayer</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<main>
<div class="logo">
<svg t="1663641871751" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="11550" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path
d="M511.764091 131.708086a446.145957 446.145957 0 1 0 446.145957 446.145957 446.145957 446.145957 0 0 0-446.145957-446.145957z m0 519.76004A71.829499 71.829499 0 1 1 583.59359 580.530919 72.275645 72.275645 0 0 1 511.764091 651.468126z"
fill="#F55E55" p-id="11551"></path>
<path
d="M802.205109 0.541175l-168.197026 37.030114a67.814185 67.814185 0 0 0-53.091369 66.029602V223.614153l3.569168 349.778431h114.213365V223.614153h108.859613a26.322611 26.322611 0 0 0 26.768758-26.322611V26.863786a26.768757 26.768757 0 0 0-32.122509-26.322611z"
fill="#F9BBB8" p-id="11552"></path>
<path
d="M511.764091 386.457428a186.935156 186.935156 0 1 0 186.935156 186.48901A186.935156 186.935156 0 0 0 511.764091 386.457428z m0 264.564552a71.383353 71.383353 0 1 1 71.383353-71.383353 71.383353 71.383353 0 0 1-71.383353 71.383353z"
fill="#F9BBB8" p-id="11553"></path>
</svg>
</div>
<div class="loader"></div>
<button id="startApp">直接启动</button>
</main>
<footer>
<span>SPlayer · Copyright &copy; <span id="year"></span> IMSYY</span>
</footer>
<script>
const startAppDom = document.getElementById("startApp");
// 更改年份
const currentYear = new Date().getFullYear();
document.getElementById("year").innerHTML = currentYear;
startAppDom.addEventListener("click", () => {
window.electron.ipcRenderer.send("win-loaded");
});
window.addEventListener("load", () => {
setTimeout(() => {
startAppDom.style.opacity = 1;
}, 3000);
});
</script>
</body>
</html>

115
web/loading/style.css Normal file
View File

@@ -0,0 +1,115 @@
:root {
--bg-color: #fff;
--font-coloe: #000;
--primary: #f55e55;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #161616;
--font-coloe: #fff;
}
}
* {
margin: 0;
padding: 0;
user-select: none;
box-sizing: border-box;
-webkit-user-drag: none;
}
body {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--bg-color);
}
main {
display: flex;
flex-direction: column;
align-items: center;
}
footer {
position: absolute;
bottom: 20px;
font-size: 14px;
color: var(--primary);
opacity: 0.6;
}
.logo {
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
width: 2.5em;
height: 2.5em;
animation-fill-mode: both;
animation: bblFadInOut 1.8s infinite ease-in-out;
}
.loader {
color: var(--primary);
font-size: 6px;
position: relative;
text-indent: -9999em;
transform: translateZ(0);
animation-delay: -0.16s;
margin: 40px 0;
}
.loader:before,
.loader:after {
content: "";
position: absolute;
top: 0;
}
.loader:before {
left: -3.5em;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
#startApp {
opacity: 0;
margin-top: 10px;
outline: none;
border: 2px solid var(--primary);
height: 36px;
width: 90px;
font-size: 14px;
border-radius: 12px;
color: var(--primary);
font-weight: bold;
background-color: transparent;
cursor: pointer;
transition: opacity 0.3s;
}
@keyframes bblFadInOut {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}