From d6e1d11a52587ab79878c002947121aee8f7db03 Mon Sep 17 00:00:00 2001 From: lqtmcstudio Date: Fri, 6 Feb 2026 14:50:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=81=E7=A7=BB=E6=9E=B6=E6=9E=84&?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=8A=9F=E8=83=BD=20-=20=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E5=8C=96=E9=9F=B3=E9=87=8F=E8=AE=BE=E7=BD=AE=20-=20=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E9=A1=B5=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E6=AF=8F=E9=A1=B5=E6=98=BE=E7=A4=BA=E6=95=B0=E9=87=8F(?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96)=20-=20vite-electron-plugin?= =?UTF-8?q?=E8=BF=81=E7=A7=BBelectron-vite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/qzplayer.exe | Bin 3187872 -> 3187872 bytes dist-electron/main.js | 1162 ----------------- dist-electron/preload.mjs | 39 - electron-builder.json5 | 43 - electron.vite.config.ts | 29 + electron/electron-env.d.ts | 27 - electron/main.ts | 234 ---- electron/preload.ts | 43 - index.html | 13 - package-lock.json | 576 +++++++- package.json | 13 +- src/main/pluginSystem.ts | 63 +- {electron => src/main}/proxyServer.ts | 2 +- {electron => src/main}/qzpController.ts | 2 +- {electron => src/main}/settingsStore.ts | 0 src/renderer/index.html | 16 + src/renderer/{ => src}/App.vue | 2 +- .../{ => src}/components/FullScreenPlayer.vue | 21 +- .../{ => src}/components/PlayerBar.vue | 3 +- .../{ => src}/components/Settings.vue | 0 src/renderer/{ => src}/components/Sidebar.vue | 0 src/renderer/{ => src}/components/TopBar.vue | 0 src/renderer/{ => src}/layout/MainLayout.vue | 4 +- src/renderer/{ => src}/main.ts | 2 - src/renderer/{ => src}/stores/player.ts | 77 +- src/renderer/{ => src}/styles/main.css | 1 - src/renderer/{ => src}/styles/variables.css | 0 src/renderer/{ => src}/types/electron.d.ts | 1 + src/renderer/{ => src}/types/song.ts | 0 src/renderer/{ => src}/utils/songUtils.ts | 0 src/renderer/{ => src}/views/Home.vue | 0 src/renderer/{ => src}/views/LocalMusic.vue | 0 src/renderer/{ => src}/views/Playlist.vue | 0 src/renderer/{ => src}/views/Search.vue | 150 ++- tsconfig.json | 24 +- tsconfig.node.json | 11 +- vite.config.ts | 41 - 37 files changed, 867 insertions(+), 1732 deletions(-) delete mode 100644 dist-electron/main.js delete mode 100644 dist-electron/preload.mjs delete mode 100644 electron-builder.json5 create mode 100644 electron.vite.config.ts delete mode 100644 electron/electron-env.d.ts delete mode 100644 electron/main.ts delete mode 100644 electron/preload.ts delete mode 100644 index.html rename {electron => src/main}/proxyServer.ts (99%) rename {electron => src/main}/qzpController.ts (98%) rename {electron => src/main}/settingsStore.ts (100%) create mode 100644 src/renderer/index.html rename src/renderer/{ => src}/App.vue (96%) rename src/renderer/{ => src}/components/FullScreenPlayer.vue (98%) rename src/renderer/{ => src}/components/PlayerBar.vue (99%) rename src/renderer/{ => src}/components/Settings.vue (100%) rename src/renderer/{ => src}/components/Sidebar.vue (100%) rename src/renderer/{ => src}/components/TopBar.vue (100%) rename src/renderer/{ => src}/layout/MainLayout.vue (93%) rename src/renderer/{ => src}/main.ts (97%) rename src/renderer/{ => src}/stores/player.ts (80%) rename src/renderer/{ => src}/styles/main.css (97%) rename src/renderer/{ => src}/styles/variables.css (100%) rename src/renderer/{ => src}/types/electron.d.ts (95%) rename src/renderer/{ => src}/types/song.ts (100%) rename src/renderer/{ => src}/utils/songUtils.ts (100%) rename src/renderer/{ => src}/views/Home.vue (100%) rename src/renderer/{ => src}/views/LocalMusic.vue (100%) rename src/renderer/{ => src}/views/Playlist.vue (100%) rename src/renderer/{ => src}/views/Search.vue (78%) delete mode 100644 vite.config.ts diff --git a/core/qzplayer.exe b/core/qzplayer.exe index 75dd78e0cb70911448c12629d380dbc089325df9..dc1a6754154e265960cba5684dde2d383af87545 100644 GIT binary patch delta 38875 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 delta 38866 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

}#M;a-7*qTAuui@%zB>QMf6z}mVH?tw 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 PHihV5&ZzC!Zu9>Dhyfh5 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);