diff --git a/core/qzplayer.exe b/core/qzplayer.exe
index 75dd78e..dc1a675 100644
Binary files a/core/qzplayer.exe and b/core/qzplayer.exe differ
diff --git a/dist-electron/main.js b/dist-electron/main.js
deleted file mode 100644
index bbdfa91..0000000
--- a/dist-electron/main.js
+++ /dev/null
@@ -1,1162 +0,0 @@
-var __defProp = Object.defineProperty;
-var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
-var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
-import { app, ipcMain, BrowserWindow, Menu } from "electron";
-import { createRequire } from "node:module";
-import { fileURLToPath } from "node:url";
-import path$1 from "node:path";
-import fs$1 from "node:fs";
-import { spawn } from "child_process";
-import { Socket } from "net";
-import { EventEmitter } from "events";
-import path from "path";
-import http from "http";
-import { Readable } from "stream";
-import fs from "fs";
-class QzpController extends EventEmitter {
- constructor(ipcPath) {
- super();
- __publicField(this, "process", null);
- __publicField(this, "socket", null);
- __publicField(this, "ipcPath");
- __publicField(this, "messageBuffer", "");
- this.ipcPath = ipcPath || this.getIpcPath();
- }
- getIpcPath() {
- if (process.platform === "win32") {
- return "\\\\.\\pipe\\qzplayer";
- }
- return "/tmp/qzmusic_mpv_socket";
- }
- getCorePath() {
- const appRoot = process.env.APP_ROOT || process.cwd();
- if (process.platform === "win32") {
- return path.join(appRoot, "core", "qzplayer.exe");
- }
- return "qzplayer";
- }
- start() {
- const playerPath = this.getCorePath();
- console.log("Starting QZPlayer from:", playerPath);
- this.process = spawn(playerPath);
- this.process.on("error", (err) => {
- console.error("Failed to start QZPlayer:", err);
- this.emit("error", err);
- });
- this.process.on("exit", (code, signal) => {
- var _a;
- console.log(`QZPlayer exited with code ${code} and signal ${signal}`);
- this.emit("exit", { code, signal });
- (_a = this.socket) == null ? void 0 : _a.destroy();
- });
- this.tryConnect();
- }
- tryConnect(retries = 10) {
- if (retries <= 0) {
- console.error("Could not connect to QZPlayer socket after multiple attempts.");
- return;
- }
- setTimeout(() => {
- this.socket = new Socket();
- this.socket.on("connect", () => {
- console.log("Connected to QZPlayer IPC socket");
- this.emit("ready");
- this.send(["observe_property", 1, "pause"]);
- this.send(["observe_property", 2, "time-pos"]);
- this.send(["observe_property", 3, "duration"]);
- this.send(["observe_property", 4, "idle-active"]);
- this.send(["observe_property", 5, "eof-reached"]);
- this.send(["set_property", "volume", 50]);
- });
- this.socket.on("data", (data) => {
- this.handleData(data);
- });
- this.socket.on("error", (_) => {
- var _a;
- (_a = this.socket) == null ? void 0 : _a.destroy();
- this.tryConnect(retries - 1);
- });
- this.socket.connect(this.ipcPath);
- }, 500);
- }
- handleData(data) {
- const raw = data.toString();
- this.messageBuffer += raw;
- const messages = this.messageBuffer.split("\n");
- this.messageBuffer = messages.pop() || "";
- for (const msg of messages) {
- if (!msg.trim()) continue;
- console.log("[IPC]", msg);
- try {
- const json = JSON.parse(msg);
- this.emit("message", json);
- if (json.event) {
- this.emit("event", json);
- }
- } catch (e) {
- console.error("Failed to parse QZPlayer message:", msg);
- }
- }
- }
- async send(command) {
- if (!this.socket || this.socket.destroyed) {
- console.warn("QZPlayer socket not connected");
- return;
- }
- const payload = JSON.stringify({ command });
- console.log("[QZPlayer TX]", payload);
- this.socket.write(payload + "\n");
- }
- // Convenience methods
- async load(url) {
- return this.send(["loadfile", url]);
- }
- async play() {
- return this.send(["set_property", "pause", false]);
- }
- async pause() {
- return this.send(["set_property", "pause", true]);
- }
- async togglePause() {
- return this.send(["cycle", "pause"]);
- }
- async stop() {
- return this.send(["stop"]);
- }
- async setVolume(vol) {
- return this.send(["set_property", "volume", vol]);
- }
- async seek(seconds) {
- return this.send(["seek", seconds, "absolute"]);
- }
- destroy() {
- if (this.process) {
- console.log("Killing QZPlayer process...");
- this.process.kill();
- this.process = null;
- }
- if (this.socket) {
- this.socket.destroy();
- this.socket = null;
- }
- }
-}
-const require$2 = createRequire(import.meta.url);
-class PluginSystem {
- constructor(pluginId) {
- __publicField(this, "pluginId");
- __publicField(this, "plugin", null);
- this.pluginId = pluginId;
- this.loadPlugin();
- }
- async search(query, page, limit) {
- var _a, _b;
- if (!((_b = (_a = this.plugin) == null ? void 0 : _a.musicSearch) == null ? void 0 : _b.search)) {
- return {
- list: [],
- total: 0,
- allPage: 0,
- error: "Search not implemented"
- };
- }
- try {
- return await this.plugin.musicSearch.search(query, page, limit);
- } catch (e) {
- return {
- list: [],
- total: 0,
- allPage: 0,
- error: e.message || "Plugin search error"
- };
- }
- }
- loadPlugin() {
- try {
- const pluginPath = path.join(
- app.getPath("userData"),
- "plugins",
- this.pluginId,
- "index.js"
- );
- if (!fs.existsSync(pluginPath)) {
- throw new Error(`Plugin ${this.pluginId} not found`);
- }
- delete require$2.cache[require$2.resolve(pluginPath)];
- this.plugin = require$2(pluginPath);
- } catch (e) {
- console.error(`[PluginSystem] load failed:`, e);
- this.plugin = null;
- }
- }
- async getUrl(id, quality) {
- var _a;
- if (!((_a = this.plugin) == null ? void 0 : _a.getUrl)) {
- return {
- success: false,
- error: "getUrl not implemented"
- };
- }
- try {
- const url = await this.plugin.getUrl(id, quality);
- if (typeof url !== "string" || !url.startsWith("http")) {
- return {
- success: false,
- error: "Invalid URL scheme"
- };
- }
- return {
- success: true,
- url
- };
- } catch (e) {
- return {
- success: false,
- error: e.message || "plugin error"
- };
- }
- }
-}
-const PORT = 5266;
-let CACHE_DIR = "";
-function ensureCacheDir() {
- if (!CACHE_DIR) {
- CACHE_DIR = path.join(app.getPath("userData"), "music", "cache");
- }
- if (!fs.existsSync(CACHE_DIR)) {
- try {
- fs.mkdirSync(CACHE_DIR, { recursive: true });
- console.log(`[Proxy] Created cache directory: ${CACHE_DIR}`);
- } catch (e) {
- console.error("[Proxy] Failed to create cache directory:", e);
- }
- }
- return CACHE_DIR;
-}
-const urlCache = /* @__PURE__ */ new Map();
-const downloadTasks = /* @__PURE__ */ new Map();
-function getCachePath(source, id, quality) {
- const dir = ensureCacheDir();
- const safeId = id.replace(/[^a-z0-9]/gi, "_");
- return path.join(dir, `${source}-${safeId}-${quality}`);
-}
-function getMetadataPath(cachePath) {
- return cachePath + ".meta";
-}
-function getTempPath(cachePath) {
- return cachePath + ".tmp";
-}
-function readMetadata(cachePath) {
- const metaPath = getMetadataPath(cachePath);
- try {
- if (fs.existsSync(metaPath)) {
- return JSON.parse(fs.readFileSync(metaPath, "utf-8"));
- }
- } catch (e) {
- console.error("[Proxy] Failed to read metadata:", e);
- }
- return null;
-}
-function writeMetadata(cachePath, metadata) {
- const metaPath = getMetadataPath(cachePath);
- try {
- fs.writeFileSync(metaPath, JSON.stringify(metadata));
- } catch (e) {
- console.error("[Proxy] Failed to write metadata:", e);
- }
-}
-async function fetchFreshUrl(source, id, quality) {
- try {
- const plugin = new PluginSystem(source);
- const result = await plugin.getUrl(id, quality);
- if (result.success && result.url) {
- urlCache.set(`${source}:${id}:${quality}`, {
- url: result.url,
- expiresAt: Date.now() + 3600 * 1e3
- });
- return result.url;
- }
- console.error(`[Proxy] Failed to fetch URL for ${source}:${id}`, result.error);
- return null;
- } catch (e) {
- console.error(`[Proxy] Error fetching URL:`, e);
- return null;
- }
-}
-function isValidCacheFile(filePath) {
- try {
- const metadata = readMetadata(filePath);
- if (!metadata || !metadata.complete) return false;
- if (!fs.existsSync(filePath)) return false;
- const stat = fs.statSync(filePath);
- return stat.size === metadata.totalSize && stat.size > 1024;
- } catch {
- return false;
- }
-}
-function parseRangeHeader(range, fileSize) {
- if (!range) return null;
- const match = range.match(/bytes=(\d*)-(\d*)/);
- if (!match) return null;
- let start = match[1] ? parseInt(match[1], 10) : 0;
- let end = match[2] ? parseInt(match[2], 10) : fileSize - 1;
- if (start >= fileSize) return null;
- end = Math.min(end, fileSize - 1);
- return { start, end };
-}
-function serveFromCache(req, res, filePath) {
- try {
- const metadata = readMetadata(filePath);
- if (!metadata) {
- console.warn("[Proxy] No metadata for cache file");
- return false;
- }
- const stat = fs.statSync(filePath);
- const fileSize = stat.size;
- const range = parseRangeHeader(req.headers.range, fileSize);
- if (range) {
- const { start, end } = range;
- const chunksize = end - start + 1;
- res.writeHead(206, {
- "Content-Range": `bytes ${start}-${end}/${fileSize}`,
- "Accept-Ranges": "bytes",
- "Content-Length": chunksize,
- "Content-Type": metadata.contentType || "audio/mpeg"
- });
- const file = fs.createReadStream(filePath, { start, end });
- file.pipe(res);
- file.on("error", (err) => {
- console.error("[Proxy] Error reading cache file:", err);
- if (!res.headersSent) {
- res.writeHead(500);
- res.end("Cache read error");
- }
- });
- } else {
- res.writeHead(200, {
- "Content-Length": fileSize,
- "Content-Type": metadata.contentType || "audio/mpeg",
- "Accept-Ranges": "bytes"
- });
- const stream = fs.createReadStream(filePath);
- stream.pipe(res);
- stream.on("error", (err) => {
- console.error("[Proxy] Error reading cache file:", err);
- });
- }
- return true;
- } catch (e) {
- console.error("[Proxy] Error serving from cache:", e);
- return false;
- }
-}
-async function proxyRangeDirect(req, res, targetUrl, totalSize, contentType) {
- const headers = {
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
- };
- if (req.headers.range) {
- headers["Range"] = req.headers.range;
- }
- const response = await fetch(targetUrl, {
- method: "GET",
- headers
- });
- if (!response.ok && response.status !== 206) {
- throw { statusCode: response.status, statusText: response.statusText };
- }
- const responseContentType = response.headers.get("content-type") || contentType || "audio/mpeg";
- const contentLength = response.headers.get("content-length");
- const contentRange = response.headers.get("content-range");
- if (response.status === 206 && contentRange) {
- res.writeHead(206, {
- "Content-Type": responseContentType,
- "Content-Length": contentLength || "",
- "Content-Range": contentRange,
- "Accept-Ranges": "bytes"
- });
- } else {
- res.writeHead(200, {
- "Content-Type": responseContentType,
- "Content-Length": contentLength || "",
- "Accept-Ranges": "bytes"
- });
- }
- if (!response.body) {
- res.end();
- return;
- }
- const nodeStream = Readable.fromWeb(response.body);
- nodeStream.pipe(res);
- return new Promise((resolve, reject) => {
- nodeStream.on("end", resolve);
- nodeStream.on("error", reject);
- res.on("close", () => {
- nodeStream.destroy();
- resolve();
- });
- });
-}
-function serveFromPartialCache(req, res, filePath, task) {
- try {
- const tempPath = getTempPath(filePath);
- if (!fs.existsSync(tempPath)) return false;
- const range = parseRangeHeader(req.headers.range, task.totalSize);
- if (!range) return false;
- const { start, end } = range;
- let actualSize;
- try {
- actualSize = fs.statSync(tempPath).size;
- } catch {
- return false;
- }
- if (end >= actualSize) {
- return false;
- }
- const chunksize = end - start + 1;
- res.writeHead(206, {
- "Content-Range": `bytes ${start}-${end}/${task.totalSize}`,
- "Accept-Ranges": "bytes",
- "Content-Length": chunksize,
- "Content-Type": task.contentType || "audio/mpeg"
- });
- const file = fs.createReadStream(tempPath, { start, end });
- file.pipe(res);
- file.on("error", (err) => {
- console.error("[Proxy] Error reading partial cache:", err);
- });
- return true;
- } catch (e) {
- console.error("[Proxy] Error serving from partial cache:", e);
- return false;
- }
-}
-function startBackgroundDownload(targetUrl, cacheFilePath, cacheKey) {
- const existingTask = downloadTasks.get(cacheFilePath);
- if (existingTask && existingTask.promise) {
- console.log(`[Proxy] Reusing existing download task for ${cacheFilePath}`);
- return existingTask;
- }
- const abortController = new AbortController();
- const task = {
- currentSize: 0,
- totalSize: 0,
- contentType: "audio/mpeg",
- abortController,
- promise: null,
- waiters: []
- };
- downloadTasks.set(cacheFilePath, task);
- task.promise = (async () => {
- const tempPath = getTempPath(cacheFilePath);
- try {
- try {
- if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
- } catch {
- }
- const response = await fetch(targetUrl, {
- method: "GET",
- headers: {
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
- },
- signal: abortController.signal
- });
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}`);
- }
- const contentLength = parseInt(response.headers.get("content-length") || "0", 10);
- const contentType = response.headers.get("content-type") || "audio/mpeg";
- if (!contentLength) {
- console.warn("[Proxy] Background download: No content-length, skipping cache");
- downloadTasks.delete(cacheFilePath);
- return;
- }
- task.totalSize = contentLength;
- task.contentType = contentType;
- console.log(`[Proxy] Background download started: ${cacheFilePath} (${contentLength} bytes)`);
- const fileStream = fs.createWriteStream(tempPath);
- if (!response.body) {
- downloadTasks.delete(cacheFilePath);
- return;
- }
- const nodeStream = Readable.fromWeb(response.body);
- await new Promise((resolve, reject) => {
- nodeStream.on("data", (chunk) => {
- task.currentSize += chunk.length;
- notifyWaiters(task);
- if (task.currentSize % Math.floor(contentLength / 10) < chunk.length) {
- const percent = Math.floor(task.currentSize / contentLength * 100);
- console.log(`[Proxy] Download progress: ${percent}% (${task.currentSize}/${contentLength})`);
- }
- });
- nodeStream.pipe(fileStream);
- fileStream.on("finish", () => {
- try {
- const stat = fs.statSync(tempPath);
- if (stat.size === contentLength) {
- fs.renameSync(tempPath, cacheFilePath);
- writeMetadata(cacheFilePath, {
- totalSize: contentLength,
- contentType,
- complete: true,
- createdAt: Date.now()
- });
- console.log(`[Proxy] Background download complete: ${cacheFilePath}`);
- } else {
- console.warn(`[Proxy] Incomplete download: ${stat.size}/${contentLength}`);
- try {
- fs.unlinkSync(tempPath);
- } catch {
- }
- }
- } catch (e) {
- console.error("[Proxy] Failed to finalize cache:", e);
- try {
- fs.unlinkSync(tempPath);
- } catch {
- }
- }
- resolve();
- });
- fileStream.on("error", (err) => {
- console.error("[Proxy] Background download write error:", err);
- try {
- fs.unlinkSync(tempPath);
- } catch {
- }
- reject(err);
- });
- nodeStream.on("error", (err) => {
- console.error("[Proxy] Background download stream error:", err);
- fileStream.destroy();
- try {
- fs.unlinkSync(tempPath);
- } catch {
- }
- reject(err);
- });
- });
- } catch (e) {
- if (e.name !== "AbortError") {
- console.error("[Proxy] Background download failed:", e);
- }
- try {
- fs.unlinkSync(tempPath);
- } catch {
- }
- for (const waiter of task.waiters) {
- waiter.reject(new Error("Download failed"));
- }
- task.waiters = [];
- } finally {
- downloadTasks.delete(cacheFilePath);
- }
- })();
- return task;
-}
-function notifyWaiters(task) {
- const resolvedWaiters = [];
- for (let i = 0; i < task.waiters.length; i++) {
- const waiter = task.waiters[i];
- if (task.currentSize > waiter.end) {
- waiter.resolve();
- resolvedWaiters.push(i);
- }
- }
- for (let i = resolvedWaiters.length - 1; i >= 0; i--) {
- task.waiters.splice(resolvedWaiters[i], 1);
- }
-}
-async function proxyAndCache(req, res, targetUrl, cacheFilePath, cacheKey) {
- const requestedRange = req.headers.range;
- const isSeekRequest = requestedRange && !requestedRange.startsWith("bytes=0-");
- let task = downloadTasks.get(cacheFilePath);
- if (task) {
- console.log(`[Proxy] Download in progress: ${task.currentSize}/${task.totalSize}`);
- if (requestedRange && task.totalSize > 0) {
- const range = parseRangeHeader(requestedRange, task.totalSize);
- if (range) {
- const tempPath = getTempPath(cacheFilePath);
- let actualSize = 0;
- try {
- if (fs.existsSync(tempPath)) {
- actualSize = fs.statSync(tempPath).size;
- }
- } catch {
- }
- if (range.end < actualSize) {
- console.log(`[Proxy] Serving range ${range.start}-${range.end} from partial cache (downloaded: ${actualSize})`);
- if (serveFromPartialCache(req, res, cacheFilePath, task)) {
- return;
- }
- }
- console.log(`[Proxy] Range ${range.start}-${range.end} not cached yet (downloaded: ${actualSize}), proxying directly`);
- await proxyRangeDirect(req, res, targetUrl, task.totalSize, task.contentType);
- return;
- }
- }
- await proxyRangeDirect(req, res, targetUrl, task.totalSize, task.contentType);
- return;
- }
- if (isSeekRequest) {
- console.log(`[Proxy] Seek request: ${requestedRange}, starting background download`);
- startBackgroundDownload(targetUrl, cacheFilePath);
- const headResponse = await fetch(targetUrl, {
- method: "HEAD",
- headers: {
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
- }
- });
- const totalSize = parseInt(headResponse.headers.get("content-length") || "0", 10);
- const contentType = headResponse.headers.get("content-type") || "audio/mpeg";
- await proxyRangeDirect(req, res, targetUrl, totalSize, contentType);
- return;
- }
- await streamAndCache(req, res, targetUrl, cacheFilePath);
-}
-async function streamAndCache(req, res, targetUrl, cacheFilePath, cacheKey) {
- const headers = {
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
- };
- const response = await fetch(targetUrl, {
- method: "GET",
- headers
- });
- if (!response.ok) {
- throw { statusCode: response.status, statusText: response.statusText };
- }
- const contentLength = parseInt(response.headers.get("content-length") || "0", 10);
- const contentType = response.headers.get("content-type") || "audio/mpeg";
- if (!contentLength) {
- console.warn("[Proxy] No content-length, proxying without cache");
- res.writeHead(200, {
- "Content-Type": contentType,
- "Accept-Ranges": "bytes"
- });
- if (response.body) {
- const nodeStream2 = Readable.fromWeb(response.body);
- nodeStream2.pipe(res);
- } else {
- res.end();
- }
- return;
- }
- const tempPath = getTempPath(cacheFilePath);
- try {
- if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
- if (fs.existsSync(cacheFilePath)) fs.unlinkSync(cacheFilePath);
- } catch {
- }
- const fileStream = fs.createWriteStream(tempPath);
- const task = {
- currentSize: 0,
- totalSize: contentLength,
- contentType,
- abortController: null,
- promise: null,
- waiters: []
- };
- downloadTasks.set(cacheFilePath, task);
- console.log(`[Proxy] Starting stream download: ${cacheFilePath} (${contentLength} bytes)`);
- res.writeHead(200, {
- "Content-Length": contentLength,
- "Content-Type": contentType,
- "Accept-Ranges": "bytes"
- });
- if (!response.body) {
- res.end();
- downloadTasks.delete(cacheFilePath);
- return;
- }
- const nodeStream = Readable.fromWeb(response.body);
- let clientConnected = true;
- res.on("close", () => {
- clientConnected = false;
- console.log("[Proxy] Client disconnected");
- });
- return new Promise((resolve, reject) => {
- nodeStream.on("data", (chunk) => {
- task.currentSize += chunk.length;
- fileStream.write(chunk);
- if (clientConnected && !res.writableEnded) {
- res.write(chunk);
- }
- notifyWaiters(task);
- });
- nodeStream.on("end", () => {
- fileStream.end();
- if (clientConnected && !res.writableEnded) {
- res.end();
- }
- });
- nodeStream.on("error", (err) => {
- console.error("[Proxy] Stream error:", err);
- fileStream.destroy();
- if (clientConnected && !res.headersSent) {
- res.writeHead(502);
- res.end("Proxy stream error");
- }
- downloadTasks.delete(cacheFilePath);
- try {
- fs.unlinkSync(tempPath);
- } catch {
- }
- reject(err);
- });
- fileStream.on("finish", () => {
- downloadTasks.delete(cacheFilePath);
- try {
- const stat = fs.statSync(tempPath);
- if (stat.size === contentLength) {
- fs.renameSync(tempPath, cacheFilePath);
- writeMetadata(cacheFilePath, {
- totalSize: contentLength,
- contentType,
- complete: true,
- createdAt: Date.now()
- });
- console.log(`[Proxy] Cache complete: ${cacheFilePath} (${contentLength} bytes)`);
- } else {
- console.warn(`[Proxy] Incomplete download: ${stat.size}/${contentLength}`);
- try {
- fs.unlinkSync(tempPath);
- } catch {
- }
- }
- } catch (e) {
- console.error("[Proxy] Failed to finalize cache:", e);
- try {
- fs.unlinkSync(tempPath);
- } catch {
- }
- }
- resolve();
- });
- fileStream.on("error", (err) => {
- console.error("[Proxy] File write error:", err);
- downloadTasks.delete(cacheFilePath);
- try {
- fs.unlinkSync(tempPath);
- } catch {
- }
- reject(err);
- });
- });
-}
-let persistCacheEnabled = true;
-function getCacheDir() {
- return ensureCacheDir();
-}
-function getCacheSize() {
- const dir = ensureCacheDir();
- if (!fs.existsSync(dir)) return "0 B";
- let totalSize = 0;
- try {
- const files = fs.readdirSync(dir);
- for (const file of files) {
- const filePath = path.join(dir, file);
- try {
- const stat = fs.statSync(filePath);
- if (stat.isFile()) {
- totalSize += stat.size;
- }
- } catch {
- }
- }
- } catch (e) {
- console.error("[Proxy] Error calculating cache size:", e);
- }
- if (totalSize < 1024) return `${totalSize} B`;
- if (totalSize < 1024 * 1024) return `${(totalSize / 1024).toFixed(1)} KB`;
- if (totalSize < 1024 * 1024 * 1024) return `${(totalSize / (1024 * 1024)).toFixed(1)} MB`;
- return `${(totalSize / (1024 * 1024 * 1024)).toFixed(2)} GB`;
-}
-function setPersistCache(persist) {
- persistCacheEnabled = persist;
- console.log(`[Proxy] Cache persistence set to: ${persist}`);
-}
-function clearCacheNow() {
- const dir = ensureCacheDir();
- if (fs.existsSync(dir)) {
- console.log(`[Proxy] Clearing cache directory: ${dir}`);
- for (const [, task] of downloadTasks) {
- if (task.abortController) {
- task.abortController.abort();
- }
- }
- downloadTasks.clear();
- try {
- fs.rmSync(dir, { recursive: true, force: true });
- fs.mkdirSync(dir, { recursive: true });
- console.log("[Proxy] Cache cleared");
- } catch (e) {
- console.error("[Proxy] Failed to clear cache:", e);
- }
- }
-}
-function cleanupCache() {
- if (!persistCacheEnabled && CACHE_DIR && fs.existsSync(CACHE_DIR)) {
- console.log(`[Proxy] Cleaning up cache directory: ${CACHE_DIR}`);
- for (const [, task] of downloadTasks) {
- if (task.abortController) {
- task.abortController.abort();
- }
- }
- downloadTasks.clear();
- try {
- fs.rmSync(CACHE_DIR, { recursive: true, force: true });
- console.log("[Proxy] Cache cleanup complete");
- } catch (e) {
- console.error("[Proxy] Failed to cleanup cache:", e);
- }
- }
-}
-function cleanupTempFiles() {
- const dir = ensureCacheDir();
- try {
- const files = fs.readdirSync(dir);
- const now = Date.now();
- for (const file of files) {
- if (file.endsWith(".tmp")) {
- const filePath = path.join(dir, file);
- const cacheFilePath = filePath.replace(".tmp", "");
- if (!downloadTasks.has(cacheFilePath)) {
- try {
- const stat = fs.statSync(filePath);
- if (now - stat.mtimeMs > 3600 * 1e3) {
- fs.unlinkSync(filePath);
- console.log(`[Proxy] Cleaned up stale temp file: ${file}`);
- }
- } catch {
- }
- }
- }
- }
- } catch (e) {
- console.error("[Proxy] Error cleaning up temp files:", e);
- }
-}
-function startProxyServer(persistCache = true) {
- persistCacheEnabled = persistCache;
- console.log(`[Proxy] Persist cache: ${persistCache}`);
- setInterval(cleanupTempFiles, 30 * 60 * 1e3);
- const server = http.createServer(async (req, res) => {
- res.setHeader("Access-Control-Allow-Origin", "*");
- res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
- res.setHeader("Access-Control-Allow-Headers", "Range");
- if (req.method === "OPTIONS") {
- res.statusCode = 204;
- res.end();
- return;
- }
- if (req.method === "HEAD") {
- res.setHeader("Accept-Ranges", "bytes");
- res.statusCode = 200;
- res.end();
- return;
- }
- try {
- const parsedUrl = new URL(req.url || "", `http://localhost:${PORT}`);
- if (parsedUrl.pathname !== "/music") {
- res.writeHead(404);
- res.end("Not Found");
- return;
- }
- const id = parsedUrl.searchParams.get("id");
- const source = parsedUrl.searchParams.get("source");
- const quality = parsedUrl.searchParams.get("quality") || "standard";
- if (!id || !source) {
- res.writeHead(400);
- res.end("Missing id or source");
- return;
- }
- const cacheKey = `${source}:${id}:${quality}`;
- const cacheFilePath = getCachePath(source, id, quality);
- if (isValidCacheFile(cacheFilePath)) {
- console.log(`[Proxy] Serving from complete cache: ${cacheFilePath}`);
- const success = serveFromCache(req, res, cacheFilePath);
- if (success) return;
- console.warn("[Proxy] Cache file invalid, cleaning up...");
- try {
- fs.unlinkSync(cacheFilePath);
- fs.unlinkSync(getMetadataPath(cacheFilePath));
- } catch {
- }
- }
- let playUrl = null;
- const memCached = urlCache.get(cacheKey);
- if (memCached && Date.now() < memCached.expiresAt) {
- playUrl = memCached.url;
- }
- if (!playUrl) {
- console.log(`[Proxy] Resolving URL for ${cacheKey}...`);
- playUrl = await fetchFreshUrl(source, id, quality);
- }
- if (!playUrl) {
- res.writeHead(500);
- res.end("Failed to Obtain URL");
- return;
- }
- const maxAttempts = 3;
- let currentAttempt = 0;
- let lastError = null;
- while (currentAttempt < maxAttempts) {
- try {
- await proxyAndCache(req, res, playUrl, cacheFilePath, cacheKey);
- break;
- } catch (e) {
- lastError = e;
- currentAttempt++;
- console.warn(`[Proxy] Proxy error (Attempt ${currentAttempt}/${maxAttempts}):`, e);
- if (currentAttempt < maxAttempts) {
- console.log("[Proxy] Error occurred, refreshing URL from plugin...");
- urlCache.delete(cacheKey);
- const newUrl = await fetchFreshUrl(source, id, quality);
- if (newUrl) {
- playUrl = newUrl;
- continue;
- } else {
- console.error("[Proxy] Failed to refresh URL");
- }
- }
- if (!res.headersSent) {
- res.writeHead((lastError == null ? void 0 : lastError.statusCode) || 502);
- res.end("Proxy Error");
- }
- break;
- }
- }
- } catch (err) {
- console.error("[Proxy] Internal Error:", err);
- if (!res.headersSent) {
- res.writeHead(500);
- res.end("Internal Server Error");
- }
- }
- });
- server.listen(PORT, "127.0.0.1", () => {
- ensureCacheDir();
- cleanupTempFiles();
- console.log(`[Proxy] Server running at http://127.0.0.1:${PORT}/music`);
- console.log(`[Proxy] Cache dir: ${CACHE_DIR}`);
- });
- return server;
-}
-const DEFAULT_SETTINGS = {
- persistCache: true,
- theme: "dark",
- accentColor: "#ec4141"
- // Default red
-};
-let settingsCache = null;
-function getSettingsPath() {
- return path.join(app.getPath("userData"), "settings.json");
-}
-function loadSettings() {
- if (settingsCache) return settingsCache;
- const settingsPath = getSettingsPath();
- try {
- if (fs.existsSync(settingsPath)) {
- const data = fs.readFileSync(settingsPath, "utf-8");
- settingsCache = { ...DEFAULT_SETTINGS, ...JSON.parse(data) };
- console.log("[Settings] Loaded from disk:", settingsCache);
- return settingsCache;
- }
- } catch (e) {
- console.error("[Settings] Failed to load settings:", e);
- }
- settingsCache = { ...DEFAULT_SETTINGS };
- return settingsCache;
-}
-function saveSettings(settings) {
- settingsCache = { ...loadSettings(), ...settings };
- const settingsPath = getSettingsPath();
- try {
- fs.writeFileSync(settingsPath, JSON.stringify(settingsCache, null, 2));
- console.log("[Settings] Saved to disk:", settingsCache);
- } catch (e) {
- console.error("[Settings] Failed to save settings:", e);
- }
- return settingsCache;
-}
-function getSetting(key) {
- return loadSettings()[key];
-}
-const require$1 = createRequire(import.meta.url);
-const __dirname$1 = path$1.dirname(fileURLToPath(import.meta.url));
-process.env.APP_ROOT = path$1.join(__dirname$1, "..");
-const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"];
-const MAIN_DIST = path$1.join(process.env.APP_ROOT, "dist-electron");
-const RENDERER_DIST = path$1.join(process.env.APP_ROOT, "dist");
-process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path$1.join(process.env.APP_ROOT, "public") : RENDERER_DIST;
-let win;
-let qzplayer;
-function createWindow() {
- win = new BrowserWindow({
- frame: false,
- minWidth: 950,
- minHeight: 800,
- width: 1e3,
- height: 800,
- icon: path$1.join(process.env.VITE_PUBLIC, "electron-vite.svg"),
- webPreferences: {
- preload: path$1.join(__dirname$1, "preload.mjs")
- // nodeIntegration: false,
- // contextIsolation: true,
- }
- });
- win.webContents.on("did-finish-load", () => {
- win == null ? void 0 : win.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString());
- });
- if (VITE_DEV_SERVER_URL) {
- win.loadURL(VITE_DEV_SERVER_URL);
- } else {
- win.loadFile(path$1.join(RENDERER_DIST, "index.html"));
- }
- win.webContents.openDevTools();
- registerZoomShortcuts(win);
-}
-ipcMain.on("window-minimize", (event) => {
- var _a;
- return (_a = BrowserWindow.fromWebContents(event.sender)) == null ? void 0 : _a.minimize();
-});
-ipcMain.on("window-maximize", () => (win == null ? void 0 : win.isMaximized()) ? win.unmaximize() : win == null ? void 0 : win.maximize());
-ipcMain.on("window-close", () => win == null ? void 0 : win.close());
-ipcMain.handle("window-is-maximized", () => (win == null ? void 0 : win.isMaximized()) || false);
-ipcMain.handle("qzplayer-command", async (_, command) => {
- if (qzplayer) {
- qzplayer.send(command);
- }
-});
-ipcMain.handle("qzplayer-load", (_, url) => qzplayer == null ? void 0 : qzplayer.load(url));
-ipcMain.handle("qzplayer-play", () => qzplayer == null ? void 0 : qzplayer.play());
-ipcMain.handle("qzplayer-pause", () => qzplayer == null ? void 0 : qzplayer.pause());
-ipcMain.handle("qzplayer-toggle-pause", () => qzplayer == null ? void 0 : qzplayer.togglePause());
-ipcMain.handle("qzplayer-stop", () => qzplayer == null ? void 0 : qzplayer.stop());
-ipcMain.handle("qzplayer-set-volume", (_, vol) => qzplayer == null ? void 0 : qzplayer.setVolume(vol));
-ipcMain.handle("qzplayer-seek", (_, time) => qzplayer == null ? void 0 : qzplayer.seek(time));
-ipcMain.handle(
- "plugin:call",
- async (_evenv, pluginId, method, args) => {
- const plugin = new PluginSystem(pluginId);
- if (typeof plugin[method] !== "function") {
- return {
- success: false,
- error: `Method ${method} not found`
- };
- }
- return await plugin[method](...args);
- }
-);
-ipcMain.handle("cache:getInfo", () => {
- const settings = loadSettings();
- return {
- path: getCacheDir(),
- size: getCacheSize(),
- persistCache: settings.persistCache
- };
-});
-ipcMain.handle("cache:setPersist", (_, persist) => {
- setPersistCache(persist);
- saveSettings({ persistCache: persist });
-});
-ipcMain.handle("cache:openFolder", () => {
- const dir = getCacheDir();
- require$1("electron").shell.openPath(dir);
-});
-ipcMain.handle("cache:clear", () => {
- clearCacheNow();
-});
-ipcMain.handle("settings:getAll", () => {
- return loadSettings();
-});
-ipcMain.handle("settings:set", (_, settings) => {
- return saveSettings(settings);
-});
-ipcMain.handle("settings:getTheme", () => {
- return getSetting("theme");
-});
-ipcMain.handle("settings:setTheme", (_, theme) => {
- saveSettings({ theme });
-});
-ipcMain.handle("settings:getAccentColor", () => {
- return getSetting("accentColor");
-});
-ipcMain.handle("settings:setAccentColor", (_, color) => {
- saveSettings({ accentColor: color });
-});
-app.on("window-all-closed", () => {
- if (process.platform !== "darwin") {
- app.quit();
- win = null;
- }
-});
-app.on("will-quit", () => {
- cleanupCache();
- if (qzplayer) {
- qzplayer.destroy();
- }
-});
-function registerZoomShortcuts(win2) {
- win2.webContents.on("before-input-event", (event, input) => {
- if (input.control || input.meta) {
- if (input.key.toLowerCase() === "=" || input.key === "+") {
- let currentZoom = win2.webContents.getZoomFactor();
- win2.webContents.setZoomFactor(currentZoom + 0.1);
- event.preventDefault();
- } else if (input.key === "-" || input.key === "_") {
- let currentZoom = win2.webContents.getZoomFactor();
- if (currentZoom > 0.5) {
- win2.webContents.setZoomFactor(currentZoom - 0.1);
- }
- event.preventDefault();
- } else if (input.key === "0") {
- win2.webContents.setZoomFactor(1);
- event.preventDefault();
- }
- }
- });
-}
-app.on("activate", () => {
- if (BrowserWindow.getAllWindows().length === 0) {
- createWindow();
- }
-});
-app.whenReady().then(() => {
- const pluginsPath = path$1.join(app.getPath("userData"), "plugins");
- if (!fs$1.existsSync(pluginsPath)) {
- fs$1.mkdirSync(pluginsPath, { recursive: true });
- }
- const wyPluginPath = path$1.join(pluginsPath, "wy");
- const wyPluginIndex = path$1.join(wyPluginPath, "index.js");
- if (!fs$1.existsSync(wyPluginIndex)) {
- if (!fs$1.existsSync(wyPluginPath)) fs$1.mkdirSync(wyPluginPath, { recursive: true });
- fs$1.writeFileSync(wyPluginIndex, `
-module.exports = {
- async getUrl(id, quality) {
- const url = \`https://api.qz.shiqianjiang.cn/music/url?source=wy&songId=\${id}&quality=\${quality}&key=testkey\`;
- try {
- const response = await fetch(url);
- const data = await response.json();
- return data;
- } catch (e) {
- return { success: false, error: e.message };
- }
- }
-}
- `.trim());
- }
- Menu.setApplicationMenu(null);
- createWindow();
- startProxyServer();
- qzplayer = new QzpController();
- qzplayer.start();
- qzplayer.on("event", (data) => {
- if (win && !win.isDestroyed()) {
- win.webContents.send("qzplayer-event", data);
- }
- });
-});
-export {
- MAIN_DIST,
- RENDERER_DIST,
- VITE_DEV_SERVER_URL
-};
diff --git a/dist-electron/preload.mjs b/dist-electron/preload.mjs
deleted file mode 100644
index b548e37..0000000
--- a/dist-electron/preload.mjs
+++ /dev/null
@@ -1,39 +0,0 @@
-"use strict";
-const electron = require("electron");
-electron.contextBridge.exposeInMainWorld("electronAPI", {
- // 窗口控制
- minimizeWindow: () => electron.ipcRenderer.send("window-minimize"),
- maximizeWindow: () => electron.ipcRenderer.send("window-maximize"),
- closeWindow: () => electron.ipcRenderer.send("window-close"),
- isMaximized: () => electron.ipcRenderer.invoke("window-is-maximized"),
- // qzplayer Control
- qzplayer: {
- load: (url) => electron.ipcRenderer.invoke("qzplayer-load", url),
- play: () => electron.ipcRenderer.invoke("qzplayer-play"),
- pause: () => electron.ipcRenderer.invoke("qzplayer-pause"),
- togglePause: () => electron.ipcRenderer.invoke("qzplayer-toggle-pause"),
- stop: () => electron.ipcRenderer.invoke("qzplayer-stop"),
- setVolume: (vol) => electron.ipcRenderer.invoke("qzplayer-set-volume", vol),
- seek: (time) => electron.ipcRenderer.invoke("qzplayer-seek", time),
- onEvent: (callback) => electron.ipcRenderer.on("qzplayer-event", callback)
- },
- // Plugin System
- plugin: {
- call: (pluginId, method, args) => electron.ipcRenderer.invoke("plugin:call", pluginId, method, args),
- search: (pluginId, query, page, limit) => electron.ipcRenderer.invoke("plugin:call", pluginId, "search", [query, page, limit])
- },
- // Cache Control
- getCacheInfo: () => electron.ipcRenderer.invoke("cache:getInfo"),
- setCachePersist: (persist) => electron.ipcRenderer.invoke("cache:setPersist", persist),
- openCacheFolder: () => electron.ipcRenderer.invoke("cache:openFolder"),
- clearCache: () => electron.ipcRenderer.invoke("cache:clear"),
- // Settings
- settings: {
- getAll: () => electron.ipcRenderer.invoke("settings:getAll"),
- set: (settings) => electron.ipcRenderer.invoke("settings:set", settings),
- getTheme: () => electron.ipcRenderer.invoke("settings:getTheme"),
- setTheme: (theme) => electron.ipcRenderer.invoke("settings:setTheme", theme),
- getAccentColor: () => electron.ipcRenderer.invoke("settings:getAccentColor"),
- setAccentColor: (color) => electron.ipcRenderer.invoke("settings:setAccentColor", color)
- }
-});
diff --git a/electron-builder.json5 b/electron-builder.json5
deleted file mode 100644
index d8e386b..0000000
--- a/electron-builder.json5
+++ /dev/null
@@ -1,43 +0,0 @@
-// @see - https://www.electron.build/configuration/configuration
-{
- "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
- "appId": "YourAppID",
- "asar": true,
- "productName": "YourAppName",
- "directories": {
- "output": "release/${version}"
- },
- "files": [
- "dist",
- "dist-electron"
- ],
- "mac": {
- "target": [
- "dmg"
- ],
- "artifactName": "${productName}-Mac-${version}-Installer.${ext}"
- },
- "win": {
- "target": [
- {
- "target": "nsis",
- "arch": [
- "x64"
- ]
- }
- ],
- "artifactName": "${productName}-Windows-${version}-Setup.${ext}"
- },
- "nsis": {
- "oneClick": false,
- "perMachine": false,
- "allowToChangeInstallationDirectory": true,
- "deleteAppDataOnUninstall": false
- },
- "linux": {
- "target": [
- "AppImage"
- ],
- "artifactName": "${productName}-Linux-${version}.${ext}"
- }
-}
diff --git a/electron.vite.config.ts b/electron.vite.config.ts
new file mode 100644
index 0000000..dc162b2
--- /dev/null
+++ b/electron.vite.config.ts
@@ -0,0 +1,29 @@
+import { resolve } from 'path'
+import { defineConfig } from 'electron-vite'
+import vue from '@vitejs/plugin-vue'
+import vueJsx from '@vitejs/plugin-vue-jsx'
+import wasm from 'vite-plugin-wasm'
+
+export default defineConfig({
+ main: {
+ },
+ preload: {
+ },
+ renderer: {
+ resolve: {
+ alias: {
+ '@renderer': resolve('src/renderer/src'),
+ '@assets': resolve('src/renderer/assets')
+ }
+ },
+ plugins: [vue(), vueJsx(), wasm()],
+ build: {
+ rollupOptions: {
+ input: resolve(__dirname, 'src/renderer/index.html')
+ }
+ },
+ server: {
+ host: "0.0.0.0"
+ }
+ }
+})
diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts
deleted file mode 100644
index f3b4f4a..0000000
--- a/electron/electron-env.d.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-///
-
-declare namespace NodeJS {
- interface ProcessEnv {
- /**
- * The built directory structure
- *
- * ```tree
- * ├─┬─┬ dist
- * │ │ └── index.html
- * │ │
- * │ ├─┬ dist-electron
- * │ │ ├── main.js
- * │ │ └── preload.js
- * │
- * ```
- */
- APP_ROOT: string
- /** /dist/ or /public/ */
- VITE_PUBLIC: string
- }
-}
-
-// Used in Renderer process, expose in `preload.ts`
-interface Window {
- ipcRenderer: import('electron').IpcRenderer
-}
diff --git a/electron/main.ts b/electron/main.ts
deleted file mode 100644
index d6a4c93..0000000
--- a/electron/main.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-import { app, BrowserWindow, Menu, ipcMain } from 'electron'
-import { createRequire } from 'node:module'
-import { fileURLToPath } from 'node:url'
-import path from 'node:path'
-import fs from 'node:fs'
-import { QzpController } from './qzpController.ts'
-import { startProxyServer, cleanupCache, getCacheDir, getCacheSize, setPersistCache, clearCacheNow } from './proxyServer'
-import { PluginSystem } from '../src/main/pluginSystem.ts'
-import { loadSettings, saveSettings, getSetting, AppSettings } from './settingsStore'
-// @ts-ignore
-const require = createRequire(import.meta.url)
-const __dirname = path.dirname(fileURLToPath(import.meta.url))
-
-process.env.APP_ROOT = path.join(__dirname, '..')
-
-export const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL']
-export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron')
-export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist')
-
-process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, 'public') : RENDERER_DIST
-
-let win: BrowserWindow | null
-let qzplayer: QzpController | null
-
-// === Electron 窗口逻辑 ===
-
-function createWindow() {
- win = new BrowserWindow({
- frame: false,
- minWidth: 950,
- minHeight: 800,
- width: 1000,
- height: 800,
- icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'),
- webPreferences: {
- preload: path.join(__dirname, 'preload.mjs'),
- // nodeIntegration: false,
- // contextIsolation: true,
- },
- })
-
- win.webContents.on('did-finish-load', () => {
- win?.webContents.send('main-process-message', new Date().toLocaleString())
- })
-
- if (VITE_DEV_SERVER_URL) {
- win.loadURL(VITE_DEV_SERVER_URL)
- } else {
- win.loadFile(path.join(RENDERER_DIST, 'index.html'))
- }
- win.webContents.openDevTools();
- registerZoomShortcuts(win)
-}
-
-// === IPC 监听 ===
-
-ipcMain.on('window-minimize', (event) => BrowserWindow.fromWebContents(event.sender)?.minimize())
-ipcMain.on('window-maximize', () => win?.isMaximized() ? win.unmaximize() : win?.maximize())
-ipcMain.on('window-close', () => win?.close())
-ipcMain.handle('window-is-maximized', () => win?.isMaximized() || false)
-
-// --- qzplayer IPC Handlers ---
-ipcMain.handle('qzplayer-command', async (_, command: any[]) => {
- if (qzplayer) {
- qzplayer.send(command)
- }
-})
-
-// Quick Helpers
-ipcMain.handle('qzplayer-load', (_, url) => qzplayer?.load(url))
-ipcMain.handle('qzplayer-play', () => qzplayer?.play())
-ipcMain.handle('qzplayer-pause', () => qzplayer?.pause())
-ipcMain.handle('qzplayer-toggle-pause', () => qzplayer?.togglePause())
-ipcMain.handle('qzplayer-stop', () => qzplayer?.stop())
-ipcMain.handle('qzplayer-set-volume', (_, vol) => qzplayer?.setVolume(vol))
-ipcMain.handle('qzplayer-seek', (_, time) => qzplayer?.seek(time))
-
-// PluginSystem
-ipcMain.handle(
- 'plugin:call',
- async (_evenv, pluginId: string, method: string, args: any[]) => {
- const plugin = new PluginSystem(pluginId)
-
- if (typeof (plugin as any)[method] !== 'function') {
- return {
- success: false,
- error: `Method ${method} not found`
- }
- }
-
- return await (plugin as any)[method](...args)
- }
-)
-
-
-
-// Cache IPC Handlers
-ipcMain.handle('cache:getInfo', () => {
- const settings = loadSettings();
- return {
- path: getCacheDir(),
- size: getCacheSize(),
- persistCache: settings.persistCache
- }
-})
-
-ipcMain.handle('cache:setPersist', (_, persist: boolean) => {
- setPersistCache(persist)
- saveSettings({ persistCache: persist })
-})
-
-ipcMain.handle('cache:openFolder', () => {
- const dir = getCacheDir()
- require('electron').shell.openPath(dir)
-})
-
-ipcMain.handle('cache:clear', () => {
- clearCacheNow()
-})
-
-// Settings IPC Handlers
-ipcMain.handle('settings:getAll', () => {
- return loadSettings()
-})
-
-ipcMain.handle('settings:set', (_, settings: Partial) => {
- return saveSettings(settings)
-})
-
-ipcMain.handle('settings:getTheme', () => {
- return getSetting('theme')
-})
-
-ipcMain.handle('settings:setTheme', (_, theme: 'dark' | 'light') => {
- saveSettings({ theme })
-})
-
-ipcMain.handle('settings:getAccentColor', () => {
- return getSetting('accentColor')
-})
-
-ipcMain.handle('settings:setAccentColor', (_, color: string) => {
- saveSettings({ accentColor: color })
-})
-
-app.on('window-all-closed', () => {
- if (process.platform !== 'darwin') {
- app.quit()
- win = null
- }
-})
-
-app.on('will-quit', () => {
- cleanupCache()
- if (qzplayer) {
- qzplayer.destroy()
- }
-})
-
-function registerZoomShortcuts(win: BrowserWindow) {
- win.webContents.on('before-input-event', (event, input) => {
- if (input.control || input.meta) {
- if (input.key.toLowerCase() === '=' || input.key === '+') {
- let currentZoom = win.webContents.getZoomFactor();
- win.webContents.setZoomFactor(currentZoom + 0.1);
- event.preventDefault();
- } else if (input.key === '-' || input.key === '_') {
- let currentZoom = win.webContents.getZoomFactor();
- // Limit minimum zoom to avoid making it too small to see
- if (currentZoom > 0.5) {
- win.webContents.setZoomFactor(currentZoom - 0.1);
- }
- event.preventDefault();
- } else if (input.key === '0') {
- win.webContents.setZoomFactor(1);
- event.preventDefault();
- }
- }
- });
-}
-
-app.on('activate', () => {
- if (BrowserWindow.getAllWindows().length === 0) {
- createWindow()
- }
-})
-
-// Test
-app.whenReady().then(() => {
- // Ensure plugins directory exists
- const pluginsPath = path.join(app.getPath('userData'), 'plugins')
- if (!fs.existsSync(pluginsPath)) {
- fs.mkdirSync(pluginsPath, { recursive: true })
- }
-
- // --- Ensure Sample 'wy' Plugin Exists ---
- const wyPluginPath = path.join(pluginsPath, 'wy')
- const wyPluginIndex = path.join(wyPluginPath, 'index.js')
- if (!fs.existsSync(wyPluginIndex)) {
- if (!fs.existsSync(wyPluginPath)) fs.mkdirSync(wyPluginPath, { recursive: true })
- fs.writeFileSync(wyPluginIndex, `
-module.exports = {
- async getUrl(id, quality) {
- const url = \`https://api.qz.shiqianjiang.cn/music/url?source=wy&songId=\${id}&quality=\${quality}&key=testkey\`;
- try {
- const response = await fetch(url);
- const data = await response.json();
- return data;
- } catch (e) {
- return { success: false, error: e.message };
- }
- }
-}
- `.trim())
- }
- // ----------------------------------------
-
- Menu.setApplicationMenu(null)
- createWindow()
-
- // Start Proxy Server
- startProxyServer()
-
- // Start qzplayer
- qzplayer = new QzpController()
- qzplayer.start()
-
- qzplayer.on('event', (data) => {
- // Forward qzplayer events to Render Process
- if (win && !win.isDestroyed()) {
- win.webContents.send('qzplayer-event', data)
- }
- })
-})
diff --git a/electron/preload.ts b/electron/preload.ts
deleted file mode 100644
index 20eb66d..0000000
--- a/electron/preload.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { contextBridge, ipcRenderer } from 'electron'
-
-contextBridge.exposeInMainWorld('electronAPI', {
- // 窗口控制
- minimizeWindow: () => ipcRenderer.send('window-minimize'),
- maximizeWindow: () => ipcRenderer.send('window-maximize'),
- closeWindow: () => ipcRenderer.send('window-close'),
- isMaximized: () => ipcRenderer.invoke('window-is-maximized'),
-
- // qzplayer Control
- qzplayer: {
- load: (url: string) => ipcRenderer.invoke('qzplayer-load', url),
- play: () => ipcRenderer.invoke('qzplayer-play'),
- pause: () => ipcRenderer.invoke('qzplayer-pause'),
- togglePause: () => ipcRenderer.invoke('qzplayer-toggle-pause'),
- stop: () => ipcRenderer.invoke('qzplayer-stop'),
- setVolume: (vol: number) => ipcRenderer.invoke('qzplayer-set-volume', vol),
- seek: (time: number) => ipcRenderer.invoke('qzplayer-seek', time),
- onEvent: (callback: (event: any, data: any) => void) => ipcRenderer.on('qzplayer-event', callback)
- },
-
- // Plugin System
- plugin: {
- call: (pluginId: string, method: string, args: any[]) => ipcRenderer.invoke('plugin:call', pluginId, method, args),
- search: (pluginId: string, query: string, page: number, limit: number) => ipcRenderer.invoke('plugin:call', pluginId, 'search', [query, page, limit]),
- },
-
- // Cache Control
- getCacheInfo: () => ipcRenderer.invoke('cache:getInfo'),
- setCachePersist: (persist: boolean) => ipcRenderer.invoke('cache:setPersist', persist),
- openCacheFolder: () => ipcRenderer.invoke('cache:openFolder'),
- clearCache: () => ipcRenderer.invoke('cache:clear'),
-
- // Settings
- settings: {
- getAll: () => ipcRenderer.invoke('settings:getAll'),
- set: (settings: any) => ipcRenderer.invoke('settings:set', settings),
- getTheme: () => ipcRenderer.invoke('settings:getTheme'),
- setTheme: (theme: 'dark' | 'light') => ipcRenderer.invoke('settings:setTheme', theme),
- getAccentColor: () => ipcRenderer.invoke('settings:getAccentColor'),
- setAccentColor: (color: string) => ipcRenderer.invoke('settings:setAccentColor', color)
- }
-})
\ No newline at end of file
diff --git a/index.html b/index.html
deleted file mode 100644
index ba7d41f..0000000
--- a/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
- Vite + Vue + TS
-
-
-
-
-
-
diff --git a/package-lock.json b/package-lock.json
index 4c8e093..f628e09 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"@applemusic-like-lyrics/core": "^0.2.0",
"@applemusic-like-lyrics/lyric": "^0.3.0",
"@applemusic-like-lyrics/vue": "^0.2.0",
+ "@electron-toolkit/tsconfig": "^2.0.0",
"@iconify/vue": "^5.0.0",
"@pixi/app": "^7.4.3",
"@pixi/core": "^7.4.3",
@@ -26,7 +27,6 @@
"pinia": "^3.0.4",
"tdesign-vue-next": "^1.17.7",
"url": "^0.11.4",
- "vite-plugin-electron-renderer": "^0.14.6",
"vite-plugin-wasm": "^3.5.0",
"vue": "^3.4.21",
"vue-router": "^4.6.4"
@@ -35,10 +35,10 @@
"@vitejs/plugin-vue": "^5.0.4",
"electron": "^30.0.1",
"electron-builder": "^24.13.3",
+ "electron-vite": "^5.0.0",
"sass-embedded": "^1.97.1",
"typescript": "^5.2.2",
"vite": "^5.1.6",
- "vite-plugin-electron": "^0.28.6",
"vite-plugin-node-polyfills": "^0.25.0",
"vue-tsc": "^2.0.26"
}
@@ -406,6 +406,22 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-transform-arrow-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
+ "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/plugin-transform-typescript": {
"version": "7.28.6",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
@@ -513,6 +529,15 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/@electron-toolkit/tsconfig": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/@electron-toolkit/tsconfig/-/tsconfig-2.0.0.tgz",
+ "integrity": "sha512-AdPsP770WhW7b260h13SHMdmjEEHJL6xFtgi3jwgdsSQbJOkJLeNnnpZW9qxTPCvmRI6vmdzWz5K3gibFS6SNg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@electron/asar": {
"version": "3.4.1",
"resolved": "https://registry.npmmirror.com/@electron/asar/-/asar-3.4.1.tgz",
@@ -1067,6 +1092,23 @@
"node": ">=12"
}
},
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
@@ -1083,6 +1125,23 @@
"node": ">=12"
}
},
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
@@ -1099,6 +1158,23 @@
"node": ">=12"
}
},
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/sunos-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
@@ -2393,7 +2469,6 @@
"version": "20.19.27",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.27.tgz",
"integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -3694,6 +3769,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
@@ -4815,6 +4900,469 @@
"integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
"license": "ISC"
},
+ "node_modules/electron-vite": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/electron-vite/-/electron-vite-5.0.0.tgz",
+ "integrity": "sha512-OHp/vjdlubNlhNkPkL/+3JD34ii5ov7M0GpuXEVdQeqdQ3ulvVR7Dg/rNBLfS5XPIFwgoBLDf9sjjrL+CuDyRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.4",
+ "@babel/plugin-transform-arrow-functions": "^7.27.1",
+ "cac": "^6.7.14",
+ "esbuild": "^0.25.11",
+ "magic-string": "^0.30.19",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "electron-vite": "bin/electron-vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@swc/core": "^1.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/electron-vite/node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
"node_modules/element-plus": {
"version": "2.13.0",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.0.tgz",
@@ -8764,7 +9312,6 @@
"version": "6.21.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/universalify": {
@@ -8955,27 +9502,6 @@
}
}
},
- "node_modules/vite-plugin-electron": {
- "version": "0.28.8",
- "resolved": "https://registry.npmmirror.com/vite-plugin-electron/-/vite-plugin-electron-0.28.8.tgz",
- "integrity": "sha512-ir+B21oSGK9j23OEvt4EXyco9xDCaF6OGFe0V/8Zc0yL2+HMyQ6mmNQEIhXsEsZCSfIowBpwQBeHH4wVsfraeg==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "vite-plugin-electron-renderer": "*"
- },
- "peerDependenciesMeta": {
- "vite-plugin-electron-renderer": {
- "optional": true
- }
- }
- },
- "node_modules/vite-plugin-electron-renderer": {
- "version": "0.14.6",
- "resolved": "https://registry.npmmirror.com/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz",
- "integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==",
- "license": "MIT"
- },
"node_modules/vite-plugin-node-polyfills": {
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.25.0.tgz",
diff --git a/package.json b/package.json
index 73c55e9..951b231 100644
--- a/package.json
+++ b/package.json
@@ -2,16 +2,16 @@
"name": "qzmusic",
"private": true,
"version": "0.0.0",
- "type": "module",
"scripts": {
- "dev": "vite",
- "build": "vue-tsc && vite build && electron-builder",
- "preview": "vite preview"
+ "dev": "electron-vite dev",
+ "build": "electron-vite build",
+ "preview": "electron-vite preview"
},
"dependencies": {
"@applemusic-like-lyrics/core": "^0.2.0",
"@applemusic-like-lyrics/lyric": "^0.3.0",
"@applemusic-like-lyrics/vue": "^0.2.0",
+ "@electron-toolkit/tsconfig": "^2.0.0",
"@iconify/vue": "^5.0.0",
"@pixi/app": "^7.4.3",
"@pixi/core": "^7.4.3",
@@ -27,7 +27,6 @@
"pinia": "^3.0.4",
"tdesign-vue-next": "^1.17.7",
"url": "^0.11.4",
- "vite-plugin-electron-renderer": "^0.14.6",
"vite-plugin-wasm": "^3.5.0",
"vue": "^3.4.21",
"vue-router": "^4.6.4"
@@ -36,12 +35,12 @@
"@vitejs/plugin-vue": "^5.0.4",
"electron": "^30.0.1",
"electron-builder": "^24.13.3",
+ "electron-vite": "^5.0.0",
"sass-embedded": "^1.97.1",
"typescript": "^5.2.2",
"vite": "^5.1.6",
- "vite-plugin-electron": "^0.28.6",
"vite-plugin-node-polyfills": "^0.25.0",
"vue-tsc": "^2.0.26"
},
- "main": "dist-electron/main.js"
+ "main": "out/main/index.js"
}
diff --git a/src/main/pluginSystem.ts b/src/main/pluginSystem.ts
index 47684ba..3c1e314 100644
--- a/src/main/pluginSystem.ts
+++ b/src/main/pluginSystem.ts
@@ -2,6 +2,7 @@ import { app } from 'electron'
import path from 'path'
import fs from 'fs'
import { createRequire } from 'node:module'
+import { MessagePlugin } from "tdesign-vue-next";
const require = createRequire(import.meta.url)
@@ -15,7 +16,8 @@ type PluginModule = {
getUrl?: (id: string, quality: string) => Promise | string,
musicSearch?: {
search: (query: string, page: number, limit: number) => Promise | any
- }
+ },
+ getLyric?: (id: string) => Promise | object
}
export class PluginSystem {
@@ -26,27 +28,6 @@ export class PluginSystem {
this.loadPlugin()
}
- async search(query: string, page: number, limit: number): Promise {
- if (!this.plugin?.musicSearch?.search) {
- return {
- list: [],
- total: 0,
- allPage: 0,
- error: 'Search not implemented'
- }
- }
- try {
- return await this.plugin.musicSearch.search(query, page, limit)
- } catch (e: any) {
- return {
- list: [],
- total: 0,
- allPage: 0,
- error: e.message || 'Plugin search error'
- }
- }
- }
-
private loadPlugin() {
try {
const pluginPath = path.join(
@@ -100,4 +81,42 @@ export class PluginSystem {
}
}
}
+
+ async search(query: string, page: number, limit: number): Promise {
+ if (!this.plugin?.musicSearch?.search) {
+ return {
+ list: [],
+ total: 0,
+ allPage: 0,
+ error: 'Search not implemented'
+ }
+ }
+ try {
+ return await this.plugin.musicSearch.search(query, page, limit)
+ } catch (e: any) {
+ return {
+ list: [],
+ total: 0,
+ allPage: 0,
+ error: e.message || 'Plugin search error'
+ }
+ }
+ }
+
+ async getLyric(id: string): Promise {
+ console.log("getLyric not implemented");
+ if (!this.plugin?.getLyric) {
+ console.log("getLyric not implemented2");
+ return
+ }
+ try {
+ const result = await this.plugin.getLyric(id)
+ console.log(result)
+ return result
+ } catch (e: any) {
+ console.log(e)
+ MessagePlugin.error(e).then()
+ return {}
+ }
+ }
}
\ No newline at end of file
diff --git a/electron/proxyServer.ts b/src/main/proxyServer.ts
similarity index 99%
rename from electron/proxyServer.ts
rename to src/main/proxyServer.ts
index bee3e3e..c92c183 100644
--- a/electron/proxyServer.ts
+++ b/src/main/proxyServer.ts
@@ -4,7 +4,7 @@ import fs from 'fs';
import path from 'path';
import { app } from 'electron';
// @ts-ignore
-import { PluginSystem } from '../src/main/pluginSystem.ts';
+import { PluginSystem } from './pluginSystem.ts';
const PORT = 5266;
let CACHE_DIR = '';
diff --git a/electron/qzpController.ts b/src/main/qzpController.ts
similarity index 98%
rename from electron/qzpController.ts
rename to src/main/qzpController.ts
index 9ef4578..7ddb15d 100644
--- a/electron/qzpController.ts
+++ b/src/main/qzpController.ts
@@ -97,7 +97,7 @@ export class QzpController extends EventEmitter {
for (const msg of messages) {
if (!msg.trim()) continue;
- console.log('[IPC]', msg); // User requested raw communication
+ //console.log('[IPC]', msg); // User requested raw communication
try {
const json = JSON.parse(msg);
this.emit('message', json);
diff --git a/electron/settingsStore.ts b/src/main/settingsStore.ts
similarity index 100%
rename from electron/settingsStore.ts
rename to src/main/settingsStore.ts
diff --git a/src/renderer/index.html b/src/renderer/index.html
new file mode 100644
index 0000000..ce4e985
--- /dev/null
+++ b/src/renderer/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ Vite + Vue + TS
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/renderer/App.vue b/src/renderer/src/App.vue
similarity index 96%
rename from src/renderer/App.vue
rename to src/renderer/src/App.vue
index c6ad75e..33f18a6 100644
--- a/src/renderer/App.vue
+++ b/src/renderer/src/App.vue
@@ -26,5 +26,5 @@ onMounted(async () => {
\ No newline at end of file
diff --git a/src/renderer/components/FullScreenPlayer.vue b/src/renderer/src/components/FullScreenPlayer.vue
similarity index 98%
rename from src/renderer/components/FullScreenPlayer.vue
rename to src/renderer/src/components/FullScreenPlayer.vue
index 9ed6a85..cd55ccf 100644
--- a/src/renderer/components/FullScreenPlayer.vue
+++ b/src/renderer/src/components/FullScreenPlayer.vue
@@ -234,6 +234,7 @@ const drawSpectrum = () => {
// Adjust canvas size
const dpr = window.devicePixelRatio || 1;
+
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
@@ -241,7 +242,7 @@ const drawSpectrum = () => {
const width = rect.width;
const height = rect.height;
-
+
ctx.clearRect(0, 0, width, height);
const targetData = playerStore.spectrum;
@@ -251,8 +252,8 @@ const drawSpectrum = () => {
// Adjust speed (0.1 - 0.3) for desired smoothness
const lerpSpeed = 0.15;
for (let i = 0; i < 32; i++) {
- const target = targetData[i] || 0;
- currentData[i] = currentData[i] + (target - currentData[i]) * lerpSpeed;
+ const target = targetData[i] || 0;
+ currentData[i] = currentData[i] + (target - currentData[i]) * lerpSpeed;
}
// 2. Draw Smooth Wave
@@ -260,7 +261,7 @@ const drawSpectrum = () => {
ctx.moveTo(0, height);
const pointCount = currentData.length;
- // Use a subset if desired, but 32 is fine.
+ // Use a subset if desired, but 32 is fine.
// We want the wave to be centered or span the width.
const step = width / (pointCount - 1);
@@ -272,14 +273,14 @@ const drawSpectrum = () => {
for (let i = 0; i < pointCount - 1; i++) {
const xCurr = i * step;
const yCurr = height - (currentData[i] * height * 0.5);
-
+
const xNext = (i + 1) * step;
const yNext = height - (currentData[i + 1] * height * 0.5);
-
+
// Control point for quadratic curve (midpoint)
const xMid = (xCurr + xNext) / 2;
const yMid = (yCurr + yNext) / 2;
-
+
ctx.quadraticCurveTo(xCurr, yCurr, xMid, yMid);
}
@@ -296,13 +297,13 @@ const drawSpectrum = () => {
const gradient = ctx.createLinearGradient(0, height - 200, 0, height);
gradient.addColorStop(0, 'rgba(255, 255, 255, 0.4)');
gradient.addColorStop(1, 'rgba(255, 255, 255, 0.05)');
-
+
ctx.fillStyle = gradient;
-
+
// Add blur effect
ctx.shadowBlur = 20;
ctx.shadowColor = 'rgba(255, 255, 255, 0.5)';
-
+
ctx.fill();
// Reset shadow for next frame (performance)
diff --git a/src/renderer/components/PlayerBar.vue b/src/renderer/src/components/PlayerBar.vue
similarity index 99%
rename from src/renderer/components/PlayerBar.vue
rename to src/renderer/src/components/PlayerBar.vue
index 13d2223..af7cee6 100644
--- a/src/renderer/components/PlayerBar.vue
+++ b/src/renderer/src/components/PlayerBar.vue
@@ -179,7 +179,8 @@ const handleBarClick = (e: MouseEvent) => {
};
// Utils
-const formatTime = (seconds: number) => {
+const formatTime = (seconds2: number) => {
+ const seconds = Math.floor(seconds2 / 1000);
if (!seconds || isNaN(seconds)) return '00:00';
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60);
diff --git a/src/renderer/components/Settings.vue b/src/renderer/src/components/Settings.vue
similarity index 100%
rename from src/renderer/components/Settings.vue
rename to src/renderer/src/components/Settings.vue
diff --git a/src/renderer/components/Sidebar.vue b/src/renderer/src/components/Sidebar.vue
similarity index 100%
rename from src/renderer/components/Sidebar.vue
rename to src/renderer/src/components/Sidebar.vue
diff --git a/src/renderer/components/TopBar.vue b/src/renderer/src/components/TopBar.vue
similarity index 100%
rename from src/renderer/components/TopBar.vue
rename to src/renderer/src/components/TopBar.vue
diff --git a/src/renderer/layout/MainLayout.vue b/src/renderer/src/layout/MainLayout.vue
similarity index 93%
rename from src/renderer/layout/MainLayout.vue
rename to src/renderer/src/layout/MainLayout.vue
index 8810b06..c7088ce 100644
--- a/src/renderer/layout/MainLayout.vue
+++ b/src/renderer/src/layout/MainLayout.vue
@@ -27,7 +27,6 @@ const hasSongs = computed(() => playerStore.playlist.length > 0);