@ciphore/radiocli 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -2
- package/CONTRIBUTING.md +10 -0
- package/README.md +108 -253
- package/SECURITY.md +23 -1
- package/dist/agent/alarm-service.js +10 -7
- package/dist/agent/cli.js +16 -1
- package/dist/agent/command-schema.js +47 -0
- package/dist/agent/headless-host.js +226 -81
- package/dist/agent/launcher.js +2 -1
- package/dist/agent/mcp-server.js +10 -5
- package/dist/agent/service.js +75 -40
- package/dist/agent/session.js +214 -24
- package/dist/agent/tui-session.js +213 -0
- package/dist/alarms/cli.js +19 -3
- package/dist/alarms/occurrence-cache.js +25 -0
- package/dist/alarms/runner.js +87 -23
- package/dist/alarms/schedule.js +29 -1
- package/dist/alarms/scheduler.js +11 -3
- package/dist/alarms/system-volume-linux.js +160 -0
- package/dist/alarms/system-volume-ownership.js +76 -8
- package/dist/alarms/system-volume.js +44 -23
- package/dist/cli/arguments.js +212 -0
- package/dist/cli/completion.js +44 -0
- package/dist/cli/doctor.js +156 -0
- package/dist/cli/interactive.js +79 -0
- package/dist/cli/listener.js +227 -0
- package/dist/cli/output.js +121 -0
- package/dist/cli/render-tui.js +34 -0
- package/dist/cli.js +178 -199
- package/dist/platform/capabilities.js +8 -1
- package/dist/platform/desktop-media.js +61 -0
- package/dist/platform/diagnostics.js +273 -0
- package/dist/platform/json-worker.js +111 -0
- package/dist/platform/launch-command.js +9 -6
- package/dist/platform/mpris.js +356 -0
- package/dist/platform/network.js +69 -1
- package/dist/platform/omarchy.js +147 -0
- package/dist/platform/packages.js +7 -1
- package/dist/platform/runtime.js +1 -1
- package/dist/platform/terminals.js +24 -4
- package/dist/player/airplay-discovery.js +144 -61
- package/dist/player/airplay-policy.js +15 -0
- package/dist/player/airplay-process.js +63 -0
- package/dist/player/airplay-readiness.js +42 -0
- package/dist/player/airplay-sender-health.js +49 -49
- package/dist/player/airplay-sender-package.js +5 -0
- package/dist/player/airplay-sender-patch.js +102 -4
- package/dist/player/airplay-setup.js +51 -0
- package/dist/player/airplay-worker.js +97 -41
- package/dist/player/backend-install.js +9 -6
- package/dist/player/ffmpeg-input.js +37 -0
- package/dist/player/mpv-desktop-media.js +194 -0
- package/dist/player/mpv-ipc-client.js +51 -2
- package/dist/player/player-controller.js +390 -73
- package/dist/player/radio-engine.js +282 -0
- package/dist/playlists/playlist.js +18 -16
- package/dist/providers/cache.js +291 -75
- package/dist/providers/discovery-session.js +111 -0
- package/dist/providers/provider-manager.js +106 -28
- package/dist/providers/radio-browser.js +272 -41
- package/dist/providers/radio-garden.js +4 -2
- package/dist/setup-playback.js +152 -0
- package/dist/setup.js +88 -21
- package/dist/storage/file-lock.js +113 -0
- package/dist/storage/read-settings.js +33 -0
- package/dist/storage/replace-file.js +20 -0
- package/dist/storage/repository.js +247 -0
- package/dist/storage/station-identity.js +21 -0
- package/dist/storage/station-schema.js +27 -0
- package/dist/storage/store.js +249 -76
- package/dist/ui/AdaptiveContent.js +18 -10
- package/dist/ui/App.js +708 -454
- package/dist/ui/AppContent.js +5 -5
- package/dist/ui/airplay-settings.js +12 -5
- package/dist/ui/bidi-ranges.js +45 -0
- package/dist/ui/components/AdaptiveMarquee.js +16 -9
- package/dist/ui/format.js +37 -4
- package/dist/ui/help-content.js +5 -2
- package/dist/ui/rtl-output.js +137 -0
- package/dist/ui/screen-items.js +10 -4
- package/dist/ui/screens/AirPlaySettingsScreen.js +3 -3
- package/dist/ui/screens/AlarmsScreen.js +5 -2
- package/dist/ui/screens/HelpScreen.js +3 -3
- package/dist/ui/screens/SettingsScreen.js +16 -16
- package/dist/ui/screens/StatsScreen.js +1 -1
- package/dist/ui/text-direction.js +9 -0
- package/dist/ui/use-alarm-tui.js +24 -24
- package/dist/ui/use-app-input.js +42 -8
- package/dist/ui/use-command-executor.js +21 -14
- package/dist/ui/visualizers/gallop.js +1 -1
- package/dist/ui/visualizers/receiver-style-registry.js +2 -1
- package/dist/update-check.js +2 -1
- package/docs/THIRD_PARTY_NOTICES.md +6 -4
- package/package.json +34 -11
- package/scripts/build-airplay-native.mjs +22 -0
- package/vendor/node-airtunes2/LICENSE +208 -0
- package/vendor/node-airtunes2/RADIOCLI_PATCH.md +29 -0
- package/vendor/node-airtunes2/README.md +225 -0
- package/vendor/node-airtunes2/UPSTREAM.json +50 -0
- package/vendor/node-airtunes2/binding.gyp +41 -0
- package/vendor/node-airtunes2/lib/ap2_test.js +4 -0
- package/vendor/node-airtunes2/lib/atv.js +307 -0
- package/vendor/node-airtunes2/lib/atvAuthenticator.js +136 -0
- package/vendor/node-airtunes2/lib/audio_out.js +69 -0
- package/vendor/node-airtunes2/lib/circular_buffer.js +133 -0
- package/vendor/node-airtunes2/lib/config.js +26 -0
- package/vendor/node-airtunes2/lib/device_airtunes.js +532 -0
- package/vendor/node-airtunes2/lib/devices.js +266 -0
- package/vendor/node-airtunes2/lib/ed25519.js +26 -0
- package/vendor/node-airtunes2/lib/homekit/credentials.js +86 -0
- package/vendor/node-airtunes2/lib/homekit/encryption.js +69 -0
- package/vendor/node-airtunes2/lib/homekit/number.js +44 -0
- package/vendor/node-airtunes2/lib/homekit/tlv.js +95 -0
- package/vendor/node-airtunes2/lib/http.js +189 -0
- package/vendor/node-airtunes2/lib/index.d.ts +141 -0
- package/vendor/node-airtunes2/lib/index.js +127 -0
- package/vendor/node-airtunes2/lib/ntp.js +37 -0
- package/vendor/node-airtunes2/lib/num_util.js +21 -0
- package/vendor/node-airtunes2/lib/packet_pool.js +43 -0
- package/vendor/node-airtunes2/lib/rtsp.js +1807 -0
- package/vendor/node-airtunes2/lib/srp.js +158 -0
- package/vendor/node-airtunes2/lib/udp_servers.js +214 -0
- package/vendor/node-airtunes2/lib/util.js +12 -0
- package/vendor/node-airtunes2/package.json +31 -0
- package/vendor/node-airtunes2/src/CADebugMacros.h +257 -0
- package/vendor/node-airtunes2/src/CAHostTimeBase.cpp +105 -0
- package/vendor/node-airtunes2/src/CAHostTimeBase.h +224 -0
- package/vendor/node-airtunes2/src/aes_utils.c +864 -0
- package/vendor/node-airtunes2/src/aes_utils.h +24 -0
- package/vendor/node-airtunes2/src/base64.c +136 -0
- package/vendor/node-airtunes2/src/base64.h +42 -0
- package/vendor/node-airtunes2/src/bindings.cc +29 -0
- package/vendor/node-airtunes2/src/codec.cc +170 -0
- package/vendor/node-airtunes2/src/coreaudio.cc +332 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, openSync, opendirSync, readSync, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { posix } from 'node:path';
|
|
3
|
+
const maxConfigBytes = 256 * 1024;
|
|
4
|
+
const maxScripts = 256;
|
|
5
|
+
const unsupported = (reason) => ({ status: 'unsupported', args: [], reason });
|
|
6
|
+
const compatible = (args, reason) => ({ status: 'compatible', args, reason });
|
|
7
|
+
/**
|
|
8
|
+
* mpv-mpris 1.3 has no per-process opt-out. Replace directory autoload with its
|
|
9
|
+
* exact file list, omitting only the packaged plugin. mpv still reads its normal
|
|
10
|
+
* configuration and loads every unrelated script. Nothing is written to disk.
|
|
11
|
+
*
|
|
12
|
+
* Source contracts: mpv player/scripting.c and options/path.c; mpv-mpris Makefile.
|
|
13
|
+
* Configurations whose script-loading semantics cannot be preserved are left
|
|
14
|
+
* completely alone; the application media service must then remain disabled.
|
|
15
|
+
*/
|
|
16
|
+
export function planMpvDesktopMedia(options = {}) {
|
|
17
|
+
if ((options.platform ?? process.platform) !== 'linux')
|
|
18
|
+
return unsupported('Desktop mpv media integration is available on Linux.');
|
|
19
|
+
const env = options.env ?? process.env;
|
|
20
|
+
const executable = options.executable ?? env.RADIOCLI_MPV_PATH ?? '/usr/bin/mpv';
|
|
21
|
+
if (!options.configDirectories && !['/usr/bin/mpv', '/bin/mpv'].includes(executable)) {
|
|
22
|
+
return unsupported('Desktop media controls require the packaged mpv configuration layout; the custom mpv executable was left unchanged.');
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const directories = options.configDirectories ?? configDirectories(env);
|
|
26
|
+
let stockMprisPath = options.stockMprisPath ?? '/usr/lib/mpv-mpris/mpris.so';
|
|
27
|
+
try {
|
|
28
|
+
stockMprisPath = realpathSync(stockMprisPath);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error.code !== 'ENOENT')
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
let autoload = true;
|
|
35
|
+
let ambiguous = false;
|
|
36
|
+
// mpv loads encoding profiles first, then config/mpv.conf in each search
|
|
37
|
+
// directory. A profile with script options is deliberately not reinterpreted.
|
|
38
|
+
const files = [
|
|
39
|
+
...directories.map(directory => posix.join(directory, 'encoding-profiles.conf')),
|
|
40
|
+
...directories.flatMap(directory => ['config', 'mpv.conf'].map(file => posix.join(directory, file)))
|
|
41
|
+
];
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const contents = readConfig(file);
|
|
44
|
+
if (contents === null)
|
|
45
|
+
continue;
|
|
46
|
+
let profile = false;
|
|
47
|
+
for (const raw of contents.split(/\r?\n/)) {
|
|
48
|
+
const line = raw.trim();
|
|
49
|
+
if (!line || line.startsWith('#'))
|
|
50
|
+
continue;
|
|
51
|
+
if (line.startsWith('[')) {
|
|
52
|
+
profile = !/^\[default\]\s*(?:#.*)?$/.test(line);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Includes and script lists can change startup script order or load
|
|
56
|
+
// another media plugin. Ordinary profiles remain intact; script options
|
|
57
|
+
// inside any profile are rejected even when that profile is inactive.
|
|
58
|
+
if (/^(?:--)?(?:include|config(?:-dir)?|use-filedir-conf|scripts?(?:-(?:add|append|clr|del|pre|set|toggle|remove))?)\s*(?:=|$)/i.test(line)) {
|
|
59
|
+
ambiguous = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (/^(?:--)?(?:no-)?load-scripts\b/i.test(line)) {
|
|
63
|
+
const setting = /^(?:--)?load-scripts\s*=\s*(yes|no)\s*(?:#.*)?$/i.exec(line);
|
|
64
|
+
if (profile || !setting)
|
|
65
|
+
ambiguous = true;
|
|
66
|
+
else
|
|
67
|
+
autoload = setting[1].toLowerCase() === 'yes';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (ambiguous)
|
|
72
|
+
return unsupported('Custom mpv script, include, or profile directives require native media handling; mpv configuration and scripts were left unchanged.');
|
|
73
|
+
if (!autoload)
|
|
74
|
+
return compatible([], 'mpv directory scripts are disabled by the existing configuration.');
|
|
75
|
+
const scripts = [];
|
|
76
|
+
let omitted = 0;
|
|
77
|
+
for (const directory of directories) {
|
|
78
|
+
const entries = scriptFiles(posix.join(directory, 'scripts'));
|
|
79
|
+
for (const file of entries) {
|
|
80
|
+
if (scripts.length + omitted >= maxScripts)
|
|
81
|
+
throw new Error('Too many mpv scripts to preserve within the bounded desktop media check.');
|
|
82
|
+
const actual = realpathSync(file);
|
|
83
|
+
if (actual === stockMprisPath) {
|
|
84
|
+
omitted++;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (/^(?:mpv[-_])?mpris(?:\.(?:so|lua|js))?$/i.test(posix.basename(file)) || /^(?:mpv[-_])?mpris(?:\.(?:so|lua|js))?$/i.test(posix.basename(actual))) {
|
|
88
|
+
return unsupported('A custom mpv MPRIS plugin needs native media handling; mpv configuration and scripts were left unchanged.');
|
|
89
|
+
}
|
|
90
|
+
scripts.push(file);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!omitted)
|
|
94
|
+
return compatible([], 'No stock mpv media plugin needs suppression.');
|
|
95
|
+
return compatible(['--load-scripts=no', ...scripts.map(file => `--script=${file}`)], 'The stock mpv media plugin is omitted for this process; other scripts and configuration are preserved.');
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return unsupported('mpv script configuration could not be safely inspected; native mpv behavior is preserved.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function configDirectories(env) {
|
|
102
|
+
if (env.MPV_HOME !== undefined) {
|
|
103
|
+
if (!env.MPV_HOME)
|
|
104
|
+
return [];
|
|
105
|
+
return [absolute(env.MPV_HOME)];
|
|
106
|
+
}
|
|
107
|
+
const result = ['/etc/mpv'];
|
|
108
|
+
const home = env.HOME ? absolute(env.HOME) : null;
|
|
109
|
+
const modern = env.XDG_CONFIG_HOME?.startsWith('/') ? posix.join(absolute(env.XDG_CONFIG_HOME), 'mpv') : home ? posix.join(home, '.config', 'mpv') : null;
|
|
110
|
+
const legacy = home ? posix.join(home, '.mpv') : null;
|
|
111
|
+
if (legacy && isDirectory(legacy))
|
|
112
|
+
result.push(legacy);
|
|
113
|
+
if (modern && (modern !== legacy) && (!legacy || !isDirectory(legacy) || isDirectory(modern)))
|
|
114
|
+
result.push(modern);
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
function absolute(value) {
|
|
118
|
+
if (!posix.isAbsolute(value) || /[\0\r\n]/.test(value))
|
|
119
|
+
throw new Error('Ambiguous mpv configuration path.');
|
|
120
|
+
return posix.normalize(value);
|
|
121
|
+
}
|
|
122
|
+
function isDirectory(path) {
|
|
123
|
+
try {
|
|
124
|
+
return statSync(path).isDirectory();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error.code === 'ENOENT')
|
|
128
|
+
return false;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function readConfig(path) {
|
|
133
|
+
let descriptor;
|
|
134
|
+
try {
|
|
135
|
+
descriptor = openSync(path, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
136
|
+
const info = fstatSync(descriptor);
|
|
137
|
+
if (!info.isFile() || info.size > maxConfigBytes)
|
|
138
|
+
throw new Error('Unbounded mpv configuration.');
|
|
139
|
+
const buffer = Buffer.alloc(maxConfigBytes + 1);
|
|
140
|
+
const size = readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
141
|
+
if (size > maxConfigBytes)
|
|
142
|
+
throw new Error('Unbounded mpv configuration.');
|
|
143
|
+
return buffer.toString('utf8', 0, size);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error.code === 'ENOENT')
|
|
147
|
+
return null;
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
if (descriptor !== undefined)
|
|
152
|
+
closeSync(descriptor);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function scriptFiles(directory) {
|
|
156
|
+
let handle;
|
|
157
|
+
try {
|
|
158
|
+
handle = opendirSync(directory);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (error.code === 'ENOENT')
|
|
162
|
+
return [];
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
const files = [];
|
|
166
|
+
let inspected = 0;
|
|
167
|
+
try {
|
|
168
|
+
for (let entry = handle.readSync(); entry; entry = handle.readSync()) {
|
|
169
|
+
if (++inspected > 1024 || files.length >= maxScripts)
|
|
170
|
+
throw new Error('Unbounded mpv script directory.');
|
|
171
|
+
if (entry.name.startsWith('.'))
|
|
172
|
+
continue;
|
|
173
|
+
const path = posix.join(directory, entry.name);
|
|
174
|
+
let info;
|
|
175
|
+
try {
|
|
176
|
+
info = statSync(path);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (error.code === 'ENOENT')
|
|
180
|
+
continue;
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
if (info.isFile() || info.isDirectory())
|
|
184
|
+
files.push(path);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
handle.closeSync();
|
|
189
|
+
}
|
|
190
|
+
// Use stable ASCII ordering for explicit scripts, without locale collation.
|
|
191
|
+
// Native mpv autoload order is not a reliable script dependency contract.
|
|
192
|
+
const fold = (value) => Buffer.from(value.replace(/[A-Z]/g, letter => letter.toLowerCase()));
|
|
193
|
+
return files.sort((left, right) => Buffer.compare(fold(left), fold(right)));
|
|
194
|
+
}
|
|
@@ -14,12 +14,25 @@ export class MpvIpcClient {
|
|
|
14
14
|
connecting = null;
|
|
15
15
|
pending = new Map();
|
|
16
16
|
buffer = '';
|
|
17
|
+
bufferBytes = 0;
|
|
18
|
+
bufferScanFrom = 0;
|
|
17
19
|
nextRequestId = 1;
|
|
18
20
|
closed = false;
|
|
21
|
+
connectedOnce = false;
|
|
22
|
+
observers = new Map();
|
|
23
|
+
eventListeners = new Set();
|
|
19
24
|
constructor(path, timeoutMs = 1000) {
|
|
20
25
|
this.path = path;
|
|
21
26
|
this.timeoutMs = timeoutMs;
|
|
22
27
|
}
|
|
28
|
+
onEvent(listener) {
|
|
29
|
+
this.eventListeners.add(listener);
|
|
30
|
+
return () => this.eventListeners.delete(listener);
|
|
31
|
+
}
|
|
32
|
+
async observe(id, property) {
|
|
33
|
+
this.observers.set(id, property);
|
|
34
|
+
await this.query({ command: ['observe_property', id, property] });
|
|
35
|
+
}
|
|
23
36
|
async query(payload) {
|
|
24
37
|
if (this.closed) {
|
|
25
38
|
throw new Error('mpv IPC client is closed.');
|
|
@@ -54,6 +67,8 @@ export class MpvIpcClient {
|
|
|
54
67
|
return;
|
|
55
68
|
}
|
|
56
69
|
this.closed = true;
|
|
70
|
+
this.eventListeners.clear();
|
|
71
|
+
this.observers.clear();
|
|
57
72
|
this.rejectPending(reason);
|
|
58
73
|
this.socket?.destroy();
|
|
59
74
|
this.connectingSocket?.destroy();
|
|
@@ -61,6 +76,8 @@ export class MpvIpcClient {
|
|
|
61
76
|
this.connectingSocket = null;
|
|
62
77
|
this.connecting = null;
|
|
63
78
|
this.buffer = '';
|
|
79
|
+
this.bufferBytes = 0;
|
|
80
|
+
this.bufferScanFrom = 0;
|
|
64
81
|
}
|
|
65
82
|
connect() {
|
|
66
83
|
if (this.socket && !this.socket.destroyed) {
|
|
@@ -102,11 +119,19 @@ export class MpvIpcClient {
|
|
|
102
119
|
this.socket = socket;
|
|
103
120
|
this.connectingSocket = null;
|
|
104
121
|
this.buffer = '';
|
|
122
|
+
this.bufferBytes = 0;
|
|
123
|
+
this.bufferScanFrom = 0;
|
|
105
124
|
socket.setEncoding('utf8');
|
|
106
|
-
socket.on('data', chunk => this.
|
|
125
|
+
socket.on('data', chunk => { if (this.socket === socket)
|
|
126
|
+
this.consume(String(chunk)); });
|
|
107
127
|
socket.on('error', error => this.dropSocket(socket, error));
|
|
108
128
|
socket.on('close', () => this.dropSocket(socket, new Error('mpv IPC connection closed.')));
|
|
109
129
|
resolve(socket);
|
|
130
|
+
if (this.connectedOnce) {
|
|
131
|
+
for (const [id, property] of this.observers)
|
|
132
|
+
void this.query({ command: ['observe_property', id, property] }).catch(() => undefined);
|
|
133
|
+
}
|
|
134
|
+
this.connectedOnce = true;
|
|
110
135
|
};
|
|
111
136
|
socket.once('error', failConnection);
|
|
112
137
|
socket.once('close', closeBeforeConnection);
|
|
@@ -127,10 +152,20 @@ export class MpvIpcClient {
|
|
|
127
152
|
}
|
|
128
153
|
consume(chunk) {
|
|
129
154
|
this.buffer += chunk;
|
|
130
|
-
|
|
155
|
+
this.bufferBytes += Buffer.byteLength(chunk);
|
|
156
|
+
if (this.bufferBytes > 4 * 1024 * 1024) {
|
|
157
|
+
const socket = this.socket;
|
|
158
|
+
if (socket) {
|
|
159
|
+
this.dropSocket(socket, new Error('mpv IPC message exceeds 4 MiB.'));
|
|
160
|
+
socket.destroy();
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
let newlineIndex = this.buffer.indexOf('\n', this.bufferScanFrom);
|
|
131
165
|
while (newlineIndex !== -1) {
|
|
132
166
|
const line = this.buffer.slice(0, newlineIndex);
|
|
133
167
|
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
168
|
+
this.bufferBytes -= Buffer.byteLength(line) + 1;
|
|
134
169
|
newlineIndex = this.buffer.indexOf('\n');
|
|
135
170
|
if (!line.trim()) {
|
|
136
171
|
continue;
|
|
@@ -142,7 +177,18 @@ export class MpvIpcClient {
|
|
|
142
177
|
catch {
|
|
143
178
|
continue;
|
|
144
179
|
}
|
|
180
|
+
if (!response || typeof response !== 'object' || Array.isArray(response))
|
|
181
|
+
continue;
|
|
145
182
|
if (typeof response.request_id !== 'number' || !this.pending.has(response.request_id)) {
|
|
183
|
+
const event = response;
|
|
184
|
+
if (typeof event.event === 'string') {
|
|
185
|
+
for (const listener of this.eventListeners) {
|
|
186
|
+
try {
|
|
187
|
+
listener(event);
|
|
188
|
+
}
|
|
189
|
+
catch { /* Isolate event consumers. */ }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
146
192
|
continue;
|
|
147
193
|
}
|
|
148
194
|
if (response.error && response.error !== 'success') {
|
|
@@ -152,6 +198,7 @@ export class MpvIpcClient {
|
|
|
152
198
|
this.settleRequest(response.request_id, pending => pending.resolve(response.data));
|
|
153
199
|
}
|
|
154
200
|
}
|
|
201
|
+
this.bufferScanFrom = this.buffer.length;
|
|
155
202
|
}
|
|
156
203
|
dropSocket(socket, error) {
|
|
157
204
|
if (this.socket !== socket) {
|
|
@@ -159,6 +206,8 @@ export class MpvIpcClient {
|
|
|
159
206
|
}
|
|
160
207
|
this.socket = null;
|
|
161
208
|
this.buffer = '';
|
|
209
|
+
this.bufferBytes = 0;
|
|
210
|
+
this.bufferScanFrom = 0;
|
|
162
211
|
this.rejectPending(error);
|
|
163
212
|
}
|
|
164
213
|
rejectPending(error) {
|