🦄 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: {
index: resolve(__dirname, "electron/main/index.ts"),
lyric: resolve(__dirname, "web/lyric.html"),
loading: resolve(__dirname, "web/loading.html"),
},
},
},
@@ -101,6 +100,7 @@ export default defineConfig(({ command, mode }) => {
rollupOptions: {
input: {
index: resolve(__dirname, "index.html"),
loading: resolve(__dirname, "web/loading/index.html"),
},
output: {
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 { join } from "path";
import { release, type } from "os";
import { isDev, isMac, appName, isLinux } from "./utils";
import { isMac } from "./utils/config";
import { unregisterShortcuts } from "./shortcut";
import { initTray, MainTray } from "./tray";
import { initThumbar, Thumbar } from "./thumbar";
import { type StoreType, initStore } from "./store";
import Store from "electron-store";
import { processLog } from "./logger";
import initAppServer from "../server";
import initIpcMain from "./ipcMain";
import log from "./logger";
// icon
import icon from "../../public/icons/favicon.png?asset";
import { initSingleLock } from "./utils/single-lock";
import loadWindow from "./windows/load-window";
import mainWindow from "./windows/main-window";
import lyricWindow from "./windows/lyric-window";
import initIpc from "./ipc";
// 屏蔽报错
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
// 模拟打包
Object.defineProperty(app, "isPackaged", {
get() {
return true;
},
});
// 主进程
class MainProcess {
// 窗口
mainWindow: BrowserWindow | null = null;
lyricWindow: BrowserWindow | null = null;
loadingWindow: BrowserWindow | null = null;
// store
store: Store<StoreType> | null = null;
loadWindow: BrowserWindow | null = null;
// 托盘
mainTray: MainTray | null = null;
// 工具栏
thumbar: Thumbar | null = null;
// 是否退出
isQuit: boolean = false;
constructor() {
log.info("🚀 Main process startup");
processLog.info("🚀 Main process startup");
// 程序单例锁
initSingleLock();
// 禁用 Windows 7 的 GPU 加速功能
if (release().startsWith("6.1") && type() == "Windows_NT") app.disableHardwareAcceleration();
// 单例锁
if (!app.requestSingleInstanceLock()) {
log.error("❌ There is already a program running and this process is terminated");
app.quit();
process.exit(0);
} else this.showWindow();
// 准备就绪
app.on("ready", async () => {
log.info("🚀 Application Process Startup");
// 监听应用事件
this.handleAppEvents();
// Electron 初始化完成后
// 某些API只有在此事件发生后才能使用
app.whenReady().then(async () => {
processLog.info("🚀 Application Process Startup");
// 设置应用程序名称
electronApp.setAppUserModelId("com.imsyy.splayer");
// 初始化 store
this.store = initStore();
// 启动主服务进程
await initAppServer();
// 启动进程
this.createLoadingWindow();
this.createMainWindow();
this.createLyricsWindow();
this.handleAppEvents();
this.handleWindowEvents();
// 启动窗口
this.loadWindow = loadWindow.create();
this.mainWindow = mainWindow.create();
this.lyricWindow = lyricWindow.create();
// 注册其他服务
this.mainTray = initTray(this.mainWindow!, this.lyricWindow!);
this.thumbar = initThumbar(this.mainWindow!);
// 注册主进程事件
initIpcMain(
this.mainWindow,
this.lyricWindow,
this.loadingWindow,
this.mainTray,
this.thumbar,
this.store,
);
// 注册 IPC 通信
initIpc();
});
}
// 创建窗口
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() {
// 窗口被关闭时
app.on("window-all-closed", () => {
if (!isMac) app.quit();
this.mainWindow = null;
this.loadingWindow = null;
this.loadWindow = null;
});
// 应用被激活
@@ -206,19 +65,12 @@ class MainProcess {
const allWindows = BrowserWindow.getAllWindows();
if (allWindows.length) {
allWindows[0].focus();
} else {
this.createMainWindow();
}
});
// 新增 session
app.on("second-instance", () => {
this.showWindow();
});
// 自定义协议
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;
});
}
// 窗口事件
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();

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 log from "../main/logger";
import { shortcutLog } from "../logger";
// 注册快捷键并检查
export const registerShortcut = (shortcut: string, callback: () => void): boolean => {
try {
const success = globalShortcut.register(shortcut, callback);
if (!success) {
log.error(`❌ Failed to register shortcut: ${shortcut}`);
shortcutLog.error(`❌ Failed to register shortcut: ${shortcut}`);
return false;
} else {
log.info(`✅ Shortcut registered: ${shortcut}`);
shortcutLog.info(`✅ Shortcut registered: ${shortcut}`);
return true;
}
} catch (error) {
log.error(` Error registering shortcut ${shortcut}:`, error);
shortcutLog.error(` Error registering shortcut ${shortcut}:`, error);
return false;
}
};
@@ -26,5 +26,5 @@ export const isShortcutRegistered = (shortcut: string): boolean => {
// 卸载所有快捷键
export const unregisterShortcuts = () => {
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 log from "./logger";
import { storeLog } from "../logger";
import Store from "electron-store";
log.info("🌱 Store init");
storeLog.info("🌱 Store init");
export interface StoreType {
window: {
@@ -25,8 +25,11 @@ export interface StoreType {
proxy: string;
}
// 初始化仓库
export const initStore = () => {
/**
* 使 Store
* @returns Store<StoreType>
*/
export const useStore = () => {
return new Store<StoreType>({
defaults: {
window: {

View File

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

View File

@@ -7,9 +7,9 @@ import {
nativeImage,
nativeTheme,
} from "electron";
import { isWin, appName } from "./utils";
import { isWin, appName } from "../utils/config";
import { join } from "path";
import log from "./logger";
import { trayLog } from "../logger";
// 播放模式
type PlayMode = "repeat" | "repeat-once" | "shuffle";
@@ -34,6 +34,9 @@ export interface MainTray {
destroyTray(): void;
}
// 托盘单例
let mainTrayInstance: MainTray | null = null;
// 托盘图标
const trayIcon = (filename: string) => {
// const rootPath = isDev
@@ -219,11 +222,19 @@ class CreateTray implements MainTray {
});
}
// 设置标题
/**
*
* @param title
*/
setTitle(title: string) {
this._win.setTitle(title);
this._tray.setTitle(title);
this._tray.setToolTip(title);
}
// 设置播放名称
/**
*
* @param name
*/
setPlayName(name: string) {
// 超长处理
if (name.length > 20) name = name.slice(0, 20) + "...";
@@ -231,48 +242,80 @@ class CreateTray implements MainTray {
// 更新菜单
this.initTrayMenu();
}
// 设置播放状态
/**
*
* @param state
*/
setPlayState(state: PlayState) {
playState = state;
// 更新菜单
this.initTrayMenu();
}
// 设置播放模式
/**
*
* @param mode
*/
setPlayMode(mode: PlayMode) {
playMode = mode;
// 更新菜单
this.initTrayMenu();
}
// 设置喜欢状态
/**
*
* @param like
*/
setLikeState(like: boolean) {
likeSong = like;
// 更新菜单
this.initTrayMenu();
}
// 桌面歌词开关
/**
*
* @param show
*/
setDesktopLyricShow(show: boolean) {
desktopLyricShow = show;
// 更新菜单
this.initTrayMenu();
}
// 锁定桌面歌词
/**
*
* @param lock
*/
setDesktopLyricLock(lock: boolean) {
desktopLyricLock = lock;
// 更新菜单
this.initTrayMenu();
}
// 销毁托盘
/**
*
*/
destroyTray() {
this._tray.destroy();
}
}
/**
*
* @param win
* @param lyricWin
* @returns
*/
export const initTray = (win: BrowserWindow, lyricWin: BrowserWindow) => {
try {
log.info("🚀 Tray Process Startup");
return new CreateTray(win, lyricWin);
trayLog.info("🚀 Tray Process Startup");
const tray = new CreateTray(win, lyricWin);
// 保存单例实例
mainTrayInstance = tray;
return tray;
} catch (error) {
log.error("❌ Tray Process Error", error);
trayLog.error("❌ Tray Process Error", error);
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 log from "./logger";
import { isDev } from "../utils/config";
// import
const { autoUpdater } = electronUpdater;
// 开发环境启用
if (isDev) {
Object.defineProperty(app, "isPackaged", {
get: () => true,
});
}
// 更新源
autoUpdater.setFeedURL({
provider: "github",
@@ -28,19 +36,19 @@ const initUpdaterListeners = (win: BrowserWindow) => {
// 当有新版本可用时
autoUpdater.on("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) => {
win.webContents.send("download-progress", progress);
log.info(`🚀 Downloading: ${progress.percent}%`);
updateLog.info(`🚀 Downloading: ${progress.percent}%`);
});
// 当下载完成时
autoUpdater.on("update-downloaded", (info) => {
win.webContents.send("update-downloaded", info);
log.info(`🚀 Update downloaded: ${info.version}`);
updateLog.info(`🚀 Update downloaded: ${info.version}`);
// 安装更新
autoUpdater.quitAndInstall();
});
@@ -48,13 +56,13 @@ const initUpdaterListeners = (win: BrowserWindow) => {
// 当没有新版本时
autoUpdater.on("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) => {
win.webContents.send("update-error", err);
log.error(`❌ Update error: ${err.message}`);
updateLog.error(`❌ Update error: ${err.message}`);
});
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 { is } from "@electron-toolkit/utils";
import fs from "fs/promises";
import crypto from "crypto";
import { createHash } from "crypto";
import { readFile } from "fs/promises";
// 系统判断
export const isDev = is.dev;
export const isWin = process.platform === "win32";
export const isMac = process.platform === "darwin";
export const isLinux = process.platform === "linux";
// 程序名称
export const appName = app.getName() || "SPlayer";
// 生成唯一ID
/**
* ID
* @param filePath
* @returns ID
*/
export const getFileID = (filePath: string): number => {
// SHA-256
const hash = crypto.createHash("sha256");
const hash = createHash("sha256");
hash.update(filePath);
const digest = hash.digest("hex");
// 将哈希值的前 16 位转换为十进制数字
@@ -23,10 +16,14 @@ export const getFileID = (filePath: string): number => {
return Number(uniqueId.toString().padStart(16, "0"));
};
// 生成文件 MD5
/**
* MD5
* @param path
* @returns MD5值
*/
export const getFileMD5 = async (path: string): Promise<string> => {
const data = await fs.readFile(path);
const hash = crypto.createHash("md5");
const data = await readFile(path);
const hash = createHash("md5");
hash.update(data);
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 { isDev } from "../main/utils";
import { isDev } from "../main/utils/config";
import { serverLog } from "../main/logger";
import initNcmAPI from "./netease";
import initUnblockAPI from "./unblock";
import fastifyCookie from "@fastify/cookie";
import fastifyMultipart from "@fastify/multipart";
import fastifyStatic from "@fastify/static";
import fastify from "fastify";
import log from "../main/logger";
const initAppServer = async () => {
try {
@@ -21,7 +21,7 @@ const initAppServer = async () => {
server.register(fastifyMultipart);
// 生产环境启用静态文件
if (!isDev) {
log.info("📂 Serving static files from /renderer");
serverLog.info("📂 Serving static files from /renderer");
server.register(fastifyStatic, {
root: join(__dirname, "../renderer"),
});
@@ -50,10 +50,10 @@ const initAppServer = async () => {
// 启动端口
const port = Number(process.env["VITE_SERVER_PORT"] || 25884);
await server.listen({ port });
log.info(`🌐 Starting AppServer on port ${port}`);
serverLog.info(`🌐 Starting AppServer on port ${port}`);
return server;
} catch (error) {
log.error("🚫 AppServer failed to start");
serverLog.error("🚫 AppServer failed to start");
throw error;
}
};

View File

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

View File

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

View File

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

View File

@@ -34,13 +34,13 @@
},
"dependencies": {
"@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",
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0",
"@imsyy/color-utils": "^1.0.2",
"@material/material-color-utilities": "^0.3.0",
"@neteasecloudmusicapienhanced/api": "^4.29.11",
"@neteasecloudmusicapienhanced/api": "^4.29.12",
"@pixi/app": "^7.4.3",
"@pixi/core": "^7.4.3",
"@pixi/display": "^7.4.3",
@@ -53,7 +53,7 @@
"change-case": "^5.4.4",
"dayjs": "^1.11.18",
"electron-dl": "^4.0.0",
"electron-store": "^8.2.0",
"electron-store": "^11.0.2",
"electron-updater": "^6.6.2",
"file-saver": "^2.0.5",
"font-list": "^2.0.1",

199
pnpm-lock.yaml generated
View File

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

View File

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

View File

@@ -290,7 +290,9 @@ class Player {
if (currentSessionId !== this.playSessionId) return;
if (!isElectron) window.document.title = "SPlayer";
// 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);
});
// 结束

View File

@@ -4,8 +4,8 @@
"electron.vite.config.*",
"electron/**/*",
"electron/main/index.ts",
"electron/main/logger.ts",
"electron/main/store.ts",
"electron/main/logger/index.ts",
"electron/main/store/index.ts",
"electron/main/utils.ts",
"electron/main/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;
}
}