netease-music-cli/tests/fixtures/mpv-ipc.mjs
2026-10-02 07:26:46 +08:00

74 lines
3.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 离线 mpv IPC:只读写测试目录内的 JSON;禁止真实 socket 与启动播放器。
import net from 'node:net';
import childProcess from 'node:child_process';
import { EventEmitter } from 'node:events';
import { appendFileSync, readFileSync, writeFileSync } from 'node:fs';
import { syncBuiltinESMExports } from 'node:module';
import { join } from 'node:path';
childProcess.spawn = () => { throw new Error('离线测试禁止启动真实进程/播放器'); };
net.connect = (socket) => {
const file = process.env.NETEASE_TEST_MPV_STATE;
if (!file || socket !== join(process.env.NETEASE_MPV_DIR, 'mpv.sock')) {
throw new Error('离线测试禁止连接真实 mpv socket');
}
const client = new EventEmitter();
client.destroy = () => {};
client.end = () => {};
client.write = (line) => {
const { command, request_id } = JSON.parse(line);
appendFileSync(`${file}.calls`, JSON.stringify(command) + '\n');
const state = JSON.parse(readFileSync(file, 'utf8'));
const [op, property, value] = command;
let data, error = 'success';
const current = () => state.entries[state.pos];
if (state.fail && command.slice(0, state.fail.length).every((v, i) => v === state.fail[i])) {
error = 'fixture rejected command';
} else if (op === 'get_property') {
const props = {
'playlist-pos': state.pos, 'playlist-count': state.entries.length, playlist: state.entries,
'idle-active': !current(), pause: state.pause, 'time-pos': current() ? state.elapsed : null,
duration: current() ? 180 : null, volume: 70,
'media-title': current()?.title || '', path: current()?.filename || '',
'loop-file': state.loopFile, 'loop-playlist': state.loopList, shuffle: state.shuffle,
};
if (property in props) data = props[property];
else error = `unknown fixture property ${property}`;
} else if (op === 'set_property') {
if (property === 'playlist-pos') {
if (!Number.isInteger(value) || value < 0 || value >= state.entries.length) error = 'invalid index';
else { state.pos = value; state.elapsed = 0; }
} else {
const key = { pause: 'pause', shuffle: 'shuffle', 'loop-file': 'loopFile', 'loop-playlist': 'loopList' }[property];
if (key) state[key] = value;
else error = `unknown fixture property ${property}`;
}
} else if (op === 'loadlist') {
const lines = readFileSync(property, 'utf8').trim().split('\n');
const entries = [];
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXTINF:')) entries.push({ title: lines[i].slice(lines[i].indexOf(',') + 1), filename: lines[++i] });
}
state.entries = value === 'append' ? [...state.entries, ...entries] : entries;
if (value !== 'append') { state.pos = entries.length ? 0 : -1; state.elapsed = 0; }
} else if (op === 'playlist-remove') {
if (!Number.isInteger(property) || property < 0 || property >= state.entries.length) error = 'invalid index';
else {
state.entries.splice(property, 1);
if (property < state.pos) state.pos--;
else if (property === state.pos && state.pos >= state.entries.length) state.pos = -1;
}
} else if (op === 'stop') { state.entries = []; state.pos = -1; }
else if (op === 'playlist-clear') {
// mpv 的 playlist-clear 自身会保留正在播放的条目;调用方必须先 stop。
state.entries = current() ? [current()] : [];
state.pos = state.entries.length ? 0 : -1;
} else error = `unknown fixture command ${op}`;
writeFileSync(file, JSON.stringify(state));
queueMicrotask(() => client.emit('data', Buffer.from(JSON.stringify({ request_id, error, data }) + '\n')));
};
queueMicrotask(() => client.emit('connect'));
return client;
};
syncBuiltinESMExports();