nixamp 0.7.37 → 0.7.38
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/channels.d.ts +22 -0
- package/dist/channels.js +61 -2
- package/package.json +1 -1
- package/src/channels.ts +62 -2
- package/web/dist/sw.js +1 -1
package/dist/channels.d.ts
CHANGED
|
@@ -40,6 +40,13 @@ export declare const GIVE_UP = 5;
|
|
|
40
40
|
* channel that is quiet for half a minute is not being quiet, it is dead.
|
|
41
41
|
*/
|
|
42
42
|
export declare const STALL = 30000;
|
|
43
|
+
/**
|
|
44
|
+
* How much of the recent stream a newcomer is handed. About six seconds of
|
|
45
|
+
* 720p television, and a couple of seconds of 192k MP3: enough to play
|
|
46
|
+
* through a hiccup, not enough to put a viewer noticeably behind the room.
|
|
47
|
+
*/
|
|
48
|
+
export declare const BACKLOG_VIDEO: number;
|
|
49
|
+
export declare const BACKLOG_AUDIO: number;
|
|
43
50
|
/** A name that can sit in a URL and be read back in a list. */
|
|
44
51
|
export declare function cleanId(value: unknown, fallback?: string): string;
|
|
45
52
|
export interface ChannelOptions {
|
|
@@ -70,6 +77,19 @@ export declare class Channel {
|
|
|
70
77
|
private watchdog;
|
|
71
78
|
private stall;
|
|
72
79
|
private stderr;
|
|
80
|
+
/**
|
|
81
|
+
* The last few seconds, for whoever joins next.
|
|
82
|
+
*
|
|
83
|
+
* A listener handed only what comes after they arrive starts exactly on the
|
|
84
|
+
* live edge, with nothing buffered ahead: every hiccup in the source or the
|
|
85
|
+
* network is a stall, and CNN in a browser was play, wait, play, wait, for
|
|
86
|
+
* ever. A few seconds of recent fragments, written before the live bytes,
|
|
87
|
+
* is the cushion every other live player has. For a picture the backlog
|
|
88
|
+
* starts at a fragment boundary, because a fragment is the unit a decoder
|
|
89
|
+
* can begin at; for MP3 any point will do, a frame announces itself.
|
|
90
|
+
*/
|
|
91
|
+
private recent;
|
|
92
|
+
private recentBytes;
|
|
73
93
|
constructor(info: ChannelInfo, options: ChannelOptions, onGone: (id: string) => void);
|
|
74
94
|
start(format: string): void;
|
|
75
95
|
/**
|
|
@@ -128,6 +148,8 @@ export declare class Channel {
|
|
|
128
148
|
* make sense.
|
|
129
149
|
*/
|
|
130
150
|
private emit;
|
|
151
|
+
/** Keep this for the next arrival, and let the oldest go once it is too much. */
|
|
152
|
+
private remember;
|
|
131
153
|
/** Feed the source. */
|
|
132
154
|
write(chunk: Buffer): boolean;
|
|
133
155
|
pump(body: Readable): Promise<void>;
|
package/dist/channels.js
CHANGED
|
@@ -18,7 +18,7 @@ import { spawn } from "node:child_process";
|
|
|
18
18
|
import { randomBytes } from "node:crypto";
|
|
19
19
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
20
|
import { join } from "node:path";
|
|
21
|
-
import { Fragments } from "./fragments.js";
|
|
21
|
+
import { Fragments, isOpening } from "./fragments.js";
|
|
22
22
|
/** How long to wait before dialling a dropped source again. */
|
|
23
23
|
export const REDIAL = 2000;
|
|
24
24
|
/** How many times in a row a source may fail without ever sending anything. */
|
|
@@ -33,6 +33,17 @@ export const GIVE_UP = 5;
|
|
|
33
33
|
export const STALL = 30_000;
|
|
34
34
|
/** How much of what ffmpeg said to keep, for the last line when it dies. */
|
|
35
35
|
const TAIL = 2000;
|
|
36
|
+
/**
|
|
37
|
+
* How much of the recent stream a newcomer is handed. About six seconds of
|
|
38
|
+
* 720p television, and a couple of seconds of 192k MP3: enough to play
|
|
39
|
+
* through a hiccup, not enough to put a viewer noticeably behind the room.
|
|
40
|
+
*/
|
|
41
|
+
export const BACKLOG_VIDEO = 4 * 1024 * 1024;
|
|
42
|
+
export const BACKLOG_AUDIO = 64 * 1024;
|
|
43
|
+
/** The four-letter name in a box header, or "" for something too short. */
|
|
44
|
+
function boxType(box) {
|
|
45
|
+
return box.length >= 8 ? box.toString("latin1", 4, 8) : "";
|
|
46
|
+
}
|
|
36
47
|
/**
|
|
37
48
|
* Read everything a child says on stderr, keeping only the end of it.
|
|
38
49
|
*
|
|
@@ -87,6 +98,19 @@ export class Channel {
|
|
|
87
98
|
watchdog = null;
|
|
88
99
|
stall = STALL;
|
|
89
100
|
stderr = "";
|
|
101
|
+
/**
|
|
102
|
+
* The last few seconds, for whoever joins next.
|
|
103
|
+
*
|
|
104
|
+
* A listener handed only what comes after they arrive starts exactly on the
|
|
105
|
+
* live edge, with nothing buffered ahead: every hiccup in the source or the
|
|
106
|
+
* network is a stall, and CNN in a browser was play, wait, play, wait, for
|
|
107
|
+
* ever. A few seconds of recent fragments, written before the live bytes,
|
|
108
|
+
* is the cushion every other live player has. For a picture the backlog
|
|
109
|
+
* starts at a fragment boundary, because a fragment is the unit a decoder
|
|
110
|
+
* can begin at; for MP3 any point will do, a frame announces itself.
|
|
111
|
+
*/
|
|
112
|
+
recent = [];
|
|
113
|
+
recentBytes = 0;
|
|
90
114
|
constructor(info, options, onGone) {
|
|
91
115
|
this.info = info;
|
|
92
116
|
this.options = options;
|
|
@@ -234,6 +258,8 @@ export class Channel {
|
|
|
234
258
|
startOver() {
|
|
235
259
|
if (this.info.kind === "video")
|
|
236
260
|
this.fragments = new Fragments();
|
|
261
|
+
this.recent = [];
|
|
262
|
+
this.recentBytes = 0;
|
|
237
263
|
this.hangUp();
|
|
238
264
|
}
|
|
239
265
|
/** Expect output within STALL, or treat the source as gone and dial again. */
|
|
@@ -304,11 +330,32 @@ export class Channel {
|
|
|
304
330
|
*/
|
|
305
331
|
emit(chunk) {
|
|
306
332
|
if (!this.fragments) {
|
|
333
|
+
this.remember(chunk, BACKLOG_AUDIO, false);
|
|
307
334
|
this.send(chunk);
|
|
308
335
|
return;
|
|
309
336
|
}
|
|
310
|
-
for (const box of this.fragments.push(chunk))
|
|
337
|
+
for (const box of this.fragments.push(chunk)) {
|
|
338
|
+
if (!isOpening(boxType(box)))
|
|
339
|
+
this.remember(box, BACKLOG_VIDEO, true);
|
|
311
340
|
this.send(box);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/** Keep this for the next arrival, and let the oldest go once it is too much. */
|
|
344
|
+
remember(piece, cap, aligned) {
|
|
345
|
+
this.recent.push(piece);
|
|
346
|
+
this.recentBytes += piece.byteLength;
|
|
347
|
+
while (this.recent.length > 0 && this.recentBytes > cap) {
|
|
348
|
+
const gone = this.recent.shift();
|
|
349
|
+
this.recentBytes -= gone.byteLength;
|
|
350
|
+
}
|
|
351
|
+
// A picture's backlog must begin at a `moof`: an `mdat` on its own is
|
|
352
|
+
// samples nobody has been told the layout of.
|
|
353
|
+
if (aligned) {
|
|
354
|
+
while (this.recent.length > 0 && boxType(this.recent[0]) !== "moof") {
|
|
355
|
+
const gone = this.recent.shift();
|
|
356
|
+
this.recentBytes -= gone.byteLength;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
312
359
|
}
|
|
313
360
|
/** Feed the source. */
|
|
314
361
|
write(chunk) {
|
|
@@ -357,6 +404,18 @@ export class Channel {
|
|
|
357
404
|
// Gone before it began; the detach below still tidies up.
|
|
358
405
|
}
|
|
359
406
|
}
|
|
407
|
+
// Then the last few seconds, so there is something to play while the
|
|
408
|
+
// live bytes catch up, rather than a picture that stalls on every hiccup.
|
|
409
|
+
if (!this.fragments || this.fragments.ready) {
|
|
410
|
+
for (const piece of this.recent) {
|
|
411
|
+
try {
|
|
412
|
+
listener.write(piece);
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
360
419
|
this.listeners.add(listener);
|
|
361
420
|
this.info.listeners = this.listeners.size;
|
|
362
421
|
return () => {
|
package/package.json
CHANGED
package/src/channels.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { randomBytes } from "node:crypto";
|
|
|
19
19
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import type { Readable } from "node:stream";
|
|
22
|
-
import { Fragments } from "./fragments.ts";
|
|
22
|
+
import { Fragments, isOpening } from "./fragments.ts";
|
|
23
23
|
|
|
24
24
|
/** Somewhere for a channel's audio to go. A response, in practice. */
|
|
25
25
|
export interface Listener {
|
|
@@ -66,6 +66,18 @@ export const GIVE_UP = 5;
|
|
|
66
66
|
export const STALL = 30_000;
|
|
67
67
|
/** How much of what ffmpeg said to keep, for the last line when it dies. */
|
|
68
68
|
const TAIL = 2000;
|
|
69
|
+
/**
|
|
70
|
+
* How much of the recent stream a newcomer is handed. About six seconds of
|
|
71
|
+
* 720p television, and a couple of seconds of 192k MP3: enough to play
|
|
72
|
+
* through a hiccup, not enough to put a viewer noticeably behind the room.
|
|
73
|
+
*/
|
|
74
|
+
export const BACKLOG_VIDEO = 4 * 1024 * 1024;
|
|
75
|
+
export const BACKLOG_AUDIO = 64 * 1024;
|
|
76
|
+
|
|
77
|
+
/** The four-letter name in a box header, or "" for something too short. */
|
|
78
|
+
function boxType(box: Buffer): string {
|
|
79
|
+
return box.length >= 8 ? box.toString("latin1", 4, 8) : "";
|
|
80
|
+
}
|
|
69
81
|
|
|
70
82
|
/**
|
|
71
83
|
* Read everything a child says on stderr, keeping only the end of it.
|
|
@@ -126,6 +138,19 @@ export class Channel {
|
|
|
126
138
|
private watchdog: ReturnType<typeof setTimeout> | null = null;
|
|
127
139
|
private stall = STALL;
|
|
128
140
|
private stderr = "";
|
|
141
|
+
/**
|
|
142
|
+
* The last few seconds, for whoever joins next.
|
|
143
|
+
*
|
|
144
|
+
* A listener handed only what comes after they arrive starts exactly on the
|
|
145
|
+
* live edge, with nothing buffered ahead: every hiccup in the source or the
|
|
146
|
+
* network is a stall, and CNN in a browser was play, wait, play, wait, for
|
|
147
|
+
* ever. A few seconds of recent fragments, written before the live bytes,
|
|
148
|
+
* is the cushion every other live player has. For a picture the backlog
|
|
149
|
+
* starts at a fragment boundary, because a fragment is the unit a decoder
|
|
150
|
+
* can begin at; for MP3 any point will do, a frame announces itself.
|
|
151
|
+
*/
|
|
152
|
+
private recent: Buffer[] = [];
|
|
153
|
+
private recentBytes = 0;
|
|
129
154
|
|
|
130
155
|
constructor(
|
|
131
156
|
readonly info: ChannelInfo,
|
|
@@ -282,6 +307,8 @@ export class Channel {
|
|
|
282
307
|
*/
|
|
283
308
|
private startOver(): void {
|
|
284
309
|
if (this.info.kind === "video") this.fragments = new Fragments();
|
|
310
|
+
this.recent = [];
|
|
311
|
+
this.recentBytes = 0;
|
|
285
312
|
this.hangUp();
|
|
286
313
|
}
|
|
287
314
|
|
|
@@ -350,10 +377,32 @@ export class Channel {
|
|
|
350
377
|
*/
|
|
351
378
|
private emit(chunk: Buffer): void {
|
|
352
379
|
if (!this.fragments) {
|
|
380
|
+
this.remember(chunk, BACKLOG_AUDIO, false);
|
|
353
381
|
this.send(chunk);
|
|
354
382
|
return;
|
|
355
383
|
}
|
|
356
|
-
for (const box of this.fragments.push(chunk))
|
|
384
|
+
for (const box of this.fragments.push(chunk)) {
|
|
385
|
+
if (!isOpening(boxType(box))) this.remember(box, BACKLOG_VIDEO, true);
|
|
386
|
+
this.send(box);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Keep this for the next arrival, and let the oldest go once it is too much. */
|
|
391
|
+
private remember(piece: Buffer, cap: number, aligned: boolean): void {
|
|
392
|
+
this.recent.push(piece);
|
|
393
|
+
this.recentBytes += piece.byteLength;
|
|
394
|
+
while (this.recent.length > 0 && this.recentBytes > cap) {
|
|
395
|
+
const gone = this.recent.shift() as Buffer;
|
|
396
|
+
this.recentBytes -= gone.byteLength;
|
|
397
|
+
}
|
|
398
|
+
// A picture's backlog must begin at a `moof`: an `mdat` on its own is
|
|
399
|
+
// samples nobody has been told the layout of.
|
|
400
|
+
if (aligned) {
|
|
401
|
+
while (this.recent.length > 0 && boxType(this.recent[0] as Buffer) !== "moof") {
|
|
402
|
+
const gone = this.recent.shift() as Buffer;
|
|
403
|
+
this.recentBytes -= gone.byteLength;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
357
406
|
}
|
|
358
407
|
|
|
359
408
|
/** Feed the source. */
|
|
@@ -404,6 +453,17 @@ export class Channel {
|
|
|
404
453
|
// Gone before it began; the detach below still tidies up.
|
|
405
454
|
}
|
|
406
455
|
}
|
|
456
|
+
// Then the last few seconds, so there is something to play while the
|
|
457
|
+
// live bytes catch up, rather than a picture that stalls on every hiccup.
|
|
458
|
+
if (!this.fragments || this.fragments.ready) {
|
|
459
|
+
for (const piece of this.recent) {
|
|
460
|
+
try {
|
|
461
|
+
listener.write(piece);
|
|
462
|
+
} catch {
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
407
467
|
this.listeners.add(listener);
|
|
408
468
|
this.info.listeners = this.listeners.size;
|
|
409
469
|
return () => {
|
package/web/dist/sw.js
CHANGED