74 lines
3.8 KiB
JavaScript
74 lines
3.8 KiB
JavaScript
// 离线 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();
|