mirror of
https://github.com/lqtmcstudio/QZMusic_PC.git
synced 2026-06-22 00:19:54 +08:00
feat: 重构页面
This commit is contained in:
194
electron/main.ts
194
electron/main.ts
@@ -2,10 +2,8 @@ import { app, BrowserWindow, Menu, ipcMain } from 'electron'
|
||||
import { createRequire } from 'node:module'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
import net from 'node:net'
|
||||
import { spawn, type ChildProcess } from 'node:child_process'
|
||||
|
||||
// @ts-ignore
|
||||
const require = createRequire(import.meta.url)
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
@@ -19,162 +17,8 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT,
|
||||
|
||||
let win: BrowserWindow | null
|
||||
|
||||
|
||||
class MpvController {
|
||||
private process: ChildProcess | null = null;
|
||||
private socket: net.Socket | null = null;
|
||||
private socketPath: string;
|
||||
private buffer: string = '';
|
||||
|
||||
constructor() {
|
||||
const socketName = `mpv-socket-${Date.now()}`;
|
||||
this.socketPath = process.platform === 'win32'
|
||||
? `\\\\.\\pipe\\${socketName}`
|
||||
: path.join(os.tmpdir(), socketName);
|
||||
}
|
||||
|
||||
start(binaryPath: string) {
|
||||
console.log(`[MPV] Starting from: ${binaryPath}`);
|
||||
console.log(`[MPV] IPC Socket: ${this.socketPath}`);
|
||||
|
||||
// 启动 MPV 进程
|
||||
this.process = spawn(binaryPath, [
|
||||
'--idle', // 空闲时不退出
|
||||
'--no-video', // 纯音频模式
|
||||
'--keep-open=yes', // 播放结束不退出
|
||||
`--input-ipc-server=${this.socketPath}`
|
||||
]);
|
||||
|
||||
this.process.on('error', (err) => {
|
||||
console.error('[MPV] Process Error:', err);
|
||||
});
|
||||
|
||||
this.process.on('exit', (code) => {
|
||||
console.log(`[MPV] Process exited with code ${code}`);
|
||||
this.socket?.destroy();
|
||||
});
|
||||
|
||||
this.connectSocket();
|
||||
}
|
||||
|
||||
private connectSocket(retries = 10) {
|
||||
setTimeout(() => {
|
||||
const socket = net.createConnection(this.socketPath);
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('[MPV] IPC Connected!');
|
||||
this.socket = socket;
|
||||
this.setupObservers();
|
||||
});
|
||||
|
||||
socket.on('data', (data) => this.handleData(data));
|
||||
|
||||
socket.on('error', (err) => {
|
||||
if (retries > 0) {
|
||||
// MPV 还没准备好,继续重试
|
||||
this.connectSocket(retries - 1);
|
||||
} else {
|
||||
console.error('[MPV] Failed to connect to IPC socket:', err);
|
||||
}
|
||||
});
|
||||
}, 500); // 500ms 重试间隔
|
||||
}
|
||||
|
||||
// 处理接收到的数据
|
||||
private handleData(data: Buffer) {
|
||||
this.buffer += data.toString();
|
||||
|
||||
// MPV 的消息以 \n 分隔
|
||||
const lines = this.buffer.split('\n');
|
||||
// 最后一个元素可能是不完整的行,留到下一次处理
|
||||
this.buffer = lines.pop() || '';
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line.trim()) return;
|
||||
try {
|
||||
const message = JSON.parse(line);
|
||||
console.log(message);
|
||||
this.handleMessage(message);
|
||||
} catch (e) {
|
||||
console.error('[MPV] JSON Parse Error:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理解析后的 JSON 消息
|
||||
private handleMessage(msg: any) {
|
||||
if (!win) return;
|
||||
|
||||
// 处理属性变更事件
|
||||
if (msg.event === 'property-change') {
|
||||
switch (msg.name) {
|
||||
case 'time-pos':
|
||||
win.webContents.send('mpv-time-update', msg.data);
|
||||
break;
|
||||
case 'duration':
|
||||
win.webContents.send('mpv-duration', msg.data);
|
||||
break;
|
||||
case 'pause':
|
||||
win.webContents.send('mpv-play-state', !msg.data); // data=true 意味着暂停
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理播放结束事件
|
||||
if (msg.event === 'end-file') {
|
||||
win.webContents.send('mpv-ended');
|
||||
win.webContents.send('mpv-play-state', false);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化监听属性
|
||||
private setupObservers() {
|
||||
this.send(['observe_property', 1, 'time-pos']);
|
||||
this.send(['observe_property', 2, 'duration']);
|
||||
this.send(['observe_property', 3, 'pause']);
|
||||
}
|
||||
|
||||
// 发送命令给 MPV
|
||||
send(command: any[]) {
|
||||
if (!this.socket || this.socket.destroyed) return;
|
||||
|
||||
const payload = JSON.stringify({ command });
|
||||
this.socket.write(payload + '\n');
|
||||
}
|
||||
|
||||
// === 业务方法 ===
|
||||
|
||||
load(url: string, autoPlay: boolean) {
|
||||
if (autoPlay) {
|
||||
// 确保取消暂停 (防止之前是暂停状态)
|
||||
this.send(['set_property', 'pause', false]);
|
||||
// 加载文件
|
||||
this.send(['loadfile', url, 'replace']);
|
||||
} else {
|
||||
// 先设置为暂停 (这样后续加载的文件会继承这个暂停状态)
|
||||
this.send(['set_property', 'pause', true]);
|
||||
// 加载文件
|
||||
this.send(['loadfile', url, 'replace']);
|
||||
}
|
||||
}
|
||||
|
||||
play() { this.send(['set_property', 'pause', false]); }
|
||||
|
||||
pause() { this.send(['set_property', 'pause', true]); }
|
||||
|
||||
seek(time: number) { this.send(['seek', time, 'absolute']); }
|
||||
|
||||
setVolume(volume: number) { this.send(['set_property', 'volume', volume]); }
|
||||
}
|
||||
|
||||
const mpv = new MpvController();
|
||||
|
||||
// === Electron 窗口逻辑 ===
|
||||
|
||||
const mpvExecutablePath = app.isPackaged
|
||||
? path.join(process.resourcesPath, 'core', 'mpv.exe')
|
||||
: path.join(process.env.APP_ROOT, 'core', 'mpv.exe');
|
||||
|
||||
function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
frame: false,
|
||||
@@ -199,6 +43,8 @@ function createWindow() {
|
||||
} else {
|
||||
win.loadFile(path.join(RENDERER_DIST, 'index.html'))
|
||||
}
|
||||
|
||||
registerZoomShortcuts(win)
|
||||
}
|
||||
|
||||
// === IPC 监听 ===
|
||||
@@ -208,14 +54,6 @@ ipcMain.on('window-maximize', () => win?.isMaximized() ? win.unmaximize() : win?
|
||||
ipcMain.on('window-close', () => win?.close())
|
||||
ipcMain.handle('window-is-maximized', () => win?.isMaximized() || false)
|
||||
|
||||
// MPV 控制指令
|
||||
ipcMain.on('mpv-load', (_, url, autoPlay = true) => mpv.load(url, autoPlay))
|
||||
ipcMain.on('mpv-play', () => mpv.play())
|
||||
ipcMain.on('mpv-pause', () => mpv.pause())
|
||||
ipcMain.on('mpv-seek', (_, time) => mpv.seek(time))
|
||||
ipcMain.on('mpv-volume', (_, volume) => mpv.setVolume(volume))
|
||||
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
@@ -223,6 +61,28 @@ app.on('window-all-closed', () => {
|
||||
}
|
||||
})
|
||||
|
||||
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()
|
||||
@@ -231,7 +91,5 @@ app.on('activate', () => {
|
||||
|
||||
app.whenReady().then(() => {
|
||||
Menu.setApplicationMenu(null)
|
||||
// 启动 MPV
|
||||
mpv.start(mpvExecutablePath);
|
||||
createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user