ziplayer 0.3.5 → 0.3.7
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/dist/plugins/index.d.ts +6 -15
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +214 -219
- package/dist/plugins/index.js.map +1 -1
- package/dist/structures/FilterManager.d.ts +2 -0
- package/dist/structures/FilterManager.d.ts.map +1 -1
- package/dist/structures/FilterManager.js +123 -58
- package/dist/structures/FilterManager.js.map +1 -1
- package/dist/structures/Player.d.ts +7 -1
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +172 -80
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/Queue.d.ts.map +1 -1
- package/dist/structures/Queue.js +4 -0
- package/dist/structures/Queue.js.map +1 -1
- package/dist/structures/StreamManager.d.ts +7 -0
- package/dist/structures/StreamManager.d.ts.map +1 -1
- package/dist/structures/StreamManager.js +23 -0
- package/dist/structures/StreamManager.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/src/plugins/index.ts +252 -262
- package/src/structures/FilterManager.ts +139 -62
- package/src/structures/Player.ts +192 -87
- package/src/structures/Queue.ts +5 -0
- package/src/structures/StreamManager.ts +583 -563
- package/src/types/index.ts +2 -0
|
@@ -4,6 +4,8 @@ import type { Player } from "./Player";
|
|
|
4
4
|
import type { PlayerManager } from "./PlayerManager";
|
|
5
5
|
import prism, { FFmpeg } from "prism-media";
|
|
6
6
|
import type { Readable } from "stream";
|
|
7
|
+
import { spawn, type ChildProcess } from "child_process";
|
|
8
|
+
import ffmpegPath from "ffmpeg-static";
|
|
7
9
|
|
|
8
10
|
type DebugFn = (message?: any, ...optionalParams: any[]) => void;
|
|
9
11
|
|
|
@@ -14,6 +16,7 @@ export class FilterManager {
|
|
|
14
16
|
private ffmpeg: FFmpeg | null = null;
|
|
15
17
|
private currentInputStream: Readable | null = null;
|
|
16
18
|
public StreamType: "webm/opus" | "ogg/opus" | "mp3" | "arbitrary" = "mp3";
|
|
19
|
+
private ffmpegProcess: ChildProcess | null = null;
|
|
17
20
|
|
|
18
21
|
constructor(player: Player, manager: PlayerManager) {
|
|
19
22
|
this.player = player as Player;
|
|
@@ -35,15 +38,18 @@ export class FilterManager {
|
|
|
35
38
|
destroy(): void {
|
|
36
39
|
this.activeFilters = [];
|
|
37
40
|
|
|
38
|
-
// Destroy FFmpeg process
|
|
39
41
|
if (this.ffmpeg) {
|
|
40
42
|
try {
|
|
41
43
|
this.ffmpeg.destroy();
|
|
42
44
|
} catch {}
|
|
43
45
|
this.ffmpeg = null;
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
if (this.ffmpegProcess) {
|
|
48
|
+
try {
|
|
49
|
+
this.ffmpegProcess.kill("SIGKILL");
|
|
50
|
+
} catch {}
|
|
51
|
+
this.ffmpegProcess = null;
|
|
52
|
+
}
|
|
47
53
|
if (this.currentInputStream && typeof (this.currentInputStream as any).destroy === "function") {
|
|
48
54
|
try {
|
|
49
55
|
(this.currentInputStream as any).destroy();
|
|
@@ -221,83 +227,154 @@ export class FilterManager {
|
|
|
221
227
|
*/
|
|
222
228
|
public async applyFiltersAndSeek(stream: Readable, position: number = -1): Promise<Readable> {
|
|
223
229
|
const filterString = this.getFilterString();
|
|
224
|
-
this.debug(`
|
|
225
|
-
try {
|
|
226
|
-
const args = ["-analyzeduration", "0", "-loglevel", "0"];
|
|
230
|
+
this.debug(`Applying filters and seek — filters: ${filterString || "none"}, seek: ${position}ms`);
|
|
227
231
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
232
|
+
// Tear down any previous FFmpeg instances.
|
|
233
|
+
try {
|
|
234
|
+
if (this.ffmpeg) {
|
|
235
|
+
this.ffmpeg.destroy();
|
|
236
|
+
this.ffmpeg = null;
|
|
231
237
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
238
|
+
if (this.ffmpegProcess) {
|
|
239
|
+
this.ffmpegProcess.kill("SIGKILL");
|
|
240
|
+
this.ffmpegProcess = null;
|
|
241
|
+
}
|
|
242
|
+
if (
|
|
243
|
+
this.currentInputStream &&
|
|
244
|
+
typeof (this.currentInputStream as any).destroy === "function" &&
|
|
245
|
+
!(this.currentInputStream as any).destroyed
|
|
246
|
+
) {
|
|
247
|
+
try {
|
|
248
|
+
(this.currentInputStream as any).destroy();
|
|
249
|
+
} catch {}
|
|
236
250
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.StreamType === "webm/opus" ? "webm/opus"
|
|
240
|
-
: this.StreamType === "ogg/opus" ? "ogg/opus"
|
|
241
|
-
: "mp3",
|
|
242
|
-
);
|
|
243
|
-
args.push("-ar", "48000", "-ac", "2");
|
|
251
|
+
this.currentInputStream = null;
|
|
252
|
+
} catch {}
|
|
244
253
|
|
|
254
|
+
this.currentInputStream = stream;
|
|
255
|
+
|
|
256
|
+
// ── INPUT-SIDE SEEKING ─────────────────────────────────────────────────────
|
|
257
|
+
// When a seek position is requested, place -ss BEFORE -i so FFmpeg seeks
|
|
258
|
+
// in the compressed domain (keyframe-level) rather than decoding every
|
|
259
|
+
// frame up to the target. This is dramatically faster for large positions.
|
|
260
|
+
//
|
|
261
|
+
// Output-side (slow): ffmpeg -i pipe:0 -ss 109 ...
|
|
262
|
+
// → reads and decodes all 109 s before outputting anything
|
|
263
|
+
//
|
|
264
|
+
// Input-side (fast): ffmpeg -ss 109 -i pipe:0 ...
|
|
265
|
+
// → seeks to nearest keyframe < 109 s, outputs from there
|
|
266
|
+
//
|
|
267
|
+
// prism.FFmpeg always places user args after -i, so we spawn directly.
|
|
268
|
+
if (position >= 0 && ffmpegPath) {
|
|
269
|
+
return this.spawnFFmpegInputSeek(stream, position, filterString);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── FILTER-ONLY (no seek) — use prism.FFmpeg as before ────────────────────
|
|
273
|
+
const args = ["-analyzeduration", "0", "-loglevel", "0"];
|
|
274
|
+
if (filterString) {
|
|
275
|
+
args.push("-af", filterString);
|
|
276
|
+
}
|
|
277
|
+
args.push(
|
|
278
|
+
"-f",
|
|
279
|
+
this.StreamType === "webm/opus" ? "webm/opus"
|
|
280
|
+
: this.StreamType === "ogg/opus" ? "ogg/opus"
|
|
281
|
+
: "mp3",
|
|
282
|
+
);
|
|
283
|
+
args.push("-ar", "48000", "-ac", "2");
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
this.ffmpeg = stream.pipe(new prism.FFmpeg({ args }));
|
|
287
|
+
} catch (spawnError) {
|
|
288
|
+
this.debug(`FFmpeg spawn error:`, spawnError);
|
|
289
|
+
this.currentInputStream = null;
|
|
290
|
+
throw spawnError;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.ffmpeg.on("close", () => {
|
|
294
|
+
this.debug(`FFmpeg processing completed`);
|
|
295
|
+
try {
|
|
296
|
+
if (this.ffmpeg) {
|
|
297
|
+
this.ffmpeg.destroy();
|
|
298
|
+
this.ffmpeg = null;
|
|
299
|
+
}
|
|
300
|
+
} catch {}
|
|
301
|
+
});
|
|
302
|
+
this.ffmpeg.on("error", (err: Error) => {
|
|
303
|
+
this.debug(`FFmpeg error:`, err);
|
|
245
304
|
try {
|
|
246
305
|
if (this.ffmpeg) {
|
|
247
306
|
this.ffmpeg.destroy();
|
|
248
307
|
this.ffmpeg = null;
|
|
249
308
|
}
|
|
250
|
-
|
|
251
|
-
if (this.currentInputStream && typeof (this.currentInputStream as any).destroy === "function") {
|
|
309
|
+
if (this.currentInputStream && !(this.currentInputStream as any).destroyed) {
|
|
252
310
|
try {
|
|
253
311
|
(this.currentInputStream as any).destroy();
|
|
254
312
|
} catch {}
|
|
255
313
|
}
|
|
256
|
-
this.currentInputStream = null;
|
|
257
314
|
} catch {}
|
|
315
|
+
this.currentInputStream = null;
|
|
316
|
+
});
|
|
258
317
|
|
|
259
|
-
|
|
260
|
-
|
|
318
|
+
return this.ffmpeg;
|
|
319
|
+
}
|
|
261
320
|
|
|
262
|
-
|
|
321
|
+
private spawnFFmpegInputSeek(stream: Readable, position: number, filterString: string): Readable {
|
|
322
|
+
const seekSeconds = (position / 1000).toFixed(3);
|
|
263
323
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
324
|
+
const args: string[] = [
|
|
325
|
+
// INPUT-SIDE SEEK: placed before -i
|
|
326
|
+
"-ss",
|
|
327
|
+
seekSeconds,
|
|
328
|
+
"-i",
|
|
329
|
+
"pipe:0",
|
|
330
|
+
// Output options
|
|
331
|
+
"-analyzeduration",
|
|
332
|
+
"0",
|
|
333
|
+
"-loglevel",
|
|
334
|
+
"0",
|
|
335
|
+
];
|
|
273
336
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
if (this.ffmpeg) {
|
|
278
|
-
this.ffmpeg.destroy();
|
|
279
|
-
this.ffmpeg = null;
|
|
280
|
-
}
|
|
281
|
-
// Also destroy input stream on error
|
|
282
|
-
if (this.currentInputStream && typeof (this.currentInputStream as any).destroy === "function") {
|
|
283
|
-
(this.currentInputStream as any).destroy();
|
|
284
|
-
}
|
|
285
|
-
} catch {}
|
|
286
|
-
this.currentInputStream = null;
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
return this.ffmpeg;
|
|
290
|
-
} catch (error) {
|
|
291
|
-
this.debug(`[FilterManager] Error creating FFmpeg instance:`, error);
|
|
292
|
-
// Destroy input stream if FFmpeg fails
|
|
293
|
-
if (this.currentInputStream && typeof (this.currentInputStream as any).destroy === "function") {
|
|
294
|
-
try {
|
|
295
|
-
(this.currentInputStream as any).destroy();
|
|
296
|
-
} catch {}
|
|
297
|
-
}
|
|
298
|
-
this.currentInputStream = null;
|
|
299
|
-
// Fallback to original stream if FFmpeg fails
|
|
300
|
-
throw error;
|
|
337
|
+
if (filterString) {
|
|
338
|
+
args.push("-af", filterString);
|
|
301
339
|
}
|
|
340
|
+
|
|
341
|
+
const outFormat =
|
|
342
|
+
this.StreamType === "webm/opus" ? "webm"
|
|
343
|
+
: this.StreamType === "ogg/opus" ? "ogg"
|
|
344
|
+
: "mp3";
|
|
345
|
+
|
|
346
|
+
args.push("-f", outFormat, "-ar", "48000", "-ac", "2", "pipe:1");
|
|
347
|
+
|
|
348
|
+
const proc = spawn(ffmpegPath!, args, {
|
|
349
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
350
|
+
});
|
|
351
|
+
this.ffmpegProcess = proc;
|
|
352
|
+
|
|
353
|
+
// Pipe source → ffmpeg stdin
|
|
354
|
+
stream.pipe(proc.stdin!);
|
|
355
|
+
|
|
356
|
+
// Suppress EPIPE on stdin when the process exits early
|
|
357
|
+
proc.stdin!.on("error", (err: Error) => {
|
|
358
|
+
if ((err as any).code !== "EPIPE") {
|
|
359
|
+
this.debug(`FFmpeg stdin error: ${err.message}`);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
proc.stdout!.on("error", (err: Error) => {
|
|
364
|
+
this.debug(`FFmpeg stdout error: ${err.message}`);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
proc.on("close", (code) => {
|
|
368
|
+
this.debug(`FFmpeg process exited (code: ${code})`);
|
|
369
|
+
this.ffmpegProcess = null;
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
proc.on("error", (err: Error) => {
|
|
373
|
+
this.debug(`FFmpeg process error: ${err.message}`);
|
|
374
|
+
this.ffmpegProcess = null;
|
|
375
|
+
throw err;
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return proc.stdout as Readable;
|
|
302
379
|
}
|
|
303
380
|
}
|