@torrent-tv/proxy 2.61.0 → 2.62.0
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 +4 -0
- package/package.json +1 -1
- package/services/hls-session-manager.js +83 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 2.62.0
|
|
2
|
+
|
|
3
|
+
- **New**: Each session says what its cushion actually is, every half minute: how many seconds of film are ready ahead of the EARLIEST viewer's picture, roughly how many megabytes that is off the swarm, and how deep a buffer the browsers say they are holding. The three were never printed together and could not be reconstructed afterwards from anything that was — so whether deepening the browser's buffer (2.61.0) protected anybody, and what it cost the person hosting the proxy, had no answer. Every term is measured: the produced range from the segments on disk, the picture from the viewers' own reports, the byte rate from the file's length over its duration. The read window sits on top of the megabytes figure, so it is a floor.
|
|
4
|
+
|
|
1
5
|
## 2.61.0
|
|
2
6
|
|
|
3
7
|
- **New**: The proxy states how far ahead of the viewer it lets an encoder run — `lookaheadSeconds` on the session-create response. The browser sizes its forward buffer from it, so the two sides agree by construction instead of each carrying a figure of its own: the browser held 30 s, with a ceiling of 60, while this proxy keeps 120 s produced and suspends the encoder there. Three quarters of the protection against every interruption was left on the disk.
|
package/package.json
CHANGED
|
@@ -400,6 +400,10 @@ const READ_WINDOW_SECONDS = 30;
|
|
|
400
400
|
const READ_WINDOW_MIN_BYTES = 16 * 1024 * 1024;
|
|
401
401
|
const READ_WINDOW_MAX_BYTES = 96 * 1024 * 1024;
|
|
402
402
|
const LOOKAHEAD_PAUSE_SECONDS = 120;
|
|
403
|
+
// How often each session says what its cushion is. Half a minute: the link
|
|
404
|
+
// reports that feed it arrive every ten seconds, and a line per session per
|
|
405
|
+
// ten seconds would drown the log on a host serving several.
|
|
406
|
+
const CUSHION_REPORT_MS = 30_000;
|
|
403
407
|
// How old a viewer's link report may be and still describe where they are. It
|
|
404
408
|
// is sent every 10 s, and a seek in between moves them somewhere this cannot
|
|
405
409
|
// predict — so anything older is treated as no report at all.
|
|
@@ -2214,6 +2218,8 @@ export class HlsSessionManager {
|
|
|
2214
2218
|
// buffer from another viewer's read head.
|
|
2215
2219
|
/** @type {Map<string, { linkMbps: number, bufferedAheadSec: number, positionSeconds: number | null, at: number }>} */
|
|
2216
2220
|
netReports: new Map(),
|
|
2221
|
+
// When this session last said what its cushion is (see #sayCushion).
|
|
2222
|
+
cushionSaidAt: 0,
|
|
2217
2223
|
linkSlowSince: 0,
|
|
2218
2224
|
sourceWidth,
|
|
2219
2225
|
sourceHeight,
|
|
@@ -3339,6 +3345,7 @@ export class HlsSessionManager {
|
|
|
3339
3345
|
// reached. Reported on its EDGES, because it is a state and not a stream.
|
|
3340
3346
|
const claimed = Number(session.progress?.processedSeconds);
|
|
3341
3347
|
const encodedTo = this.#segmentStartTime(session, viewerSegment) + aheadSeconds;
|
|
3348
|
+
this.#sayCushion(session, encodedTo);
|
|
3342
3349
|
const disagrees =
|
|
3343
3350
|
Number.isFinite(claimed) && Math.abs(claimed - encodedTo) > LOOKAHEAD_PAUSE_SECONDS;
|
|
3344
3351
|
if (disagrees && !session.lookAheadDisagreementSince) {
|
|
@@ -3377,6 +3384,57 @@ export class HlsSessionManager {
|
|
|
3377
3384
|
}
|
|
3378
3385
|
}
|
|
3379
3386
|
|
|
3387
|
+
/**
|
|
3388
|
+
* What the cushion actually is, said once every half minute per session.
|
|
3389
|
+
*
|
|
3390
|
+
* Three quantities that were never printed together, and could not be
|
|
3391
|
+
* reconstructed afterwards from anything that was:
|
|
3392
|
+
*
|
|
3393
|
+
* - how far the produced range runs ahead of the EARLIEST viewer's picture,
|
|
3394
|
+
* which is the protection an interruption would have to exhaust before
|
|
3395
|
+
* anybody saw it;
|
|
3396
|
+
* - what that costs the person hosting this proxy, in megabytes of film
|
|
3397
|
+
* pulled off the swarm ahead of the picture — the read window sits on top
|
|
3398
|
+
* of it, so this is a floor;
|
|
3399
|
+
* - what the browsers say they are holding, so the depth asked for on that
|
|
3400
|
+
* side can be checked against the depth that arrived.
|
|
3401
|
+
*
|
|
3402
|
+
* Every term is measured: the produced range comes from the segments on disk,
|
|
3403
|
+
* the picture from the viewers' own reports, and the byte rate from the
|
|
3404
|
+
* file's length over its duration. Roadmap item 4.
|
|
3405
|
+
*
|
|
3406
|
+
* @param {HlsSession} session
|
|
3407
|
+
* @param {number} encodedTo - Seconds of film produced, contiguously, from
|
|
3408
|
+
* where the leading viewer is.
|
|
3409
|
+
* @returns {void}
|
|
3410
|
+
*/
|
|
3411
|
+
#sayCushion(session, encodedTo) {
|
|
3412
|
+
const now = Date.now();
|
|
3413
|
+
if (now - (session.cushionSaidAt ?? 0) < CUSHION_REPORT_MS) {
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
const { earliestPosition, deepestBuffer, viewers } = this.#reportedPictureOf(session, now);
|
|
3417
|
+
// Nobody has said where they are, so there is no picture to measure
|
|
3418
|
+
// against and the line would be about nothing.
|
|
3419
|
+
if (earliestPosition === null) {
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
session.cushionSaidAt = now;
|
|
3423
|
+
const aheadOfPicture = Math.max(0, encodedTo - earliestPosition);
|
|
3424
|
+
const fileLength = this.#fileLengthByKey.get(`${session.sourceKey}:${session.fileIndex}`);
|
|
3425
|
+
const duration = Number(session.totalDurationSeconds) || Number(session.durationSeconds) || 0;
|
|
3426
|
+
const megabytes =
|
|
3427
|
+
Number.isFinite(fileLength) && fileLength > 0 && duration > 0
|
|
3428
|
+
? ((aheadOfPicture * fileLength) / duration / 1e6).toFixed(0)
|
|
3429
|
+
: "?";
|
|
3430
|
+
logger.info(
|
|
3431
|
+
`transcode ${session.id.slice(0, 8)} cushion: ${Math.round(aheadOfPicture)}s of film ready ` +
|
|
3432
|
+
`ahead of the picture at ${Math.round(earliestPosition)}s (~${megabytes}MB pulled ahead), ` +
|
|
3433
|
+
`${viewers} viewer(s) holding up to ` +
|
|
3434
|
+
`${deepestBuffer === null ? "?" : deepestBuffer.toFixed(1)}s`
|
|
3435
|
+
);
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3380
3438
|
/**
|
|
3381
3439
|
* Seconds of playback ready without a gap, starting at the segment the viewer
|
|
3382
3440
|
* is on.
|
|
@@ -7714,21 +7772,29 @@ export class HlsSessionManager {
|
|
|
7714
7772
|
* @param {HlsSession} base
|
|
7715
7773
|
* @returns {number}
|
|
7716
7774
|
*/
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7775
|
+
/**
|
|
7776
|
+
* Where the earliest viewer's picture is, and the deepest cushion any of them
|
|
7777
|
+
* reports holding — both read from the link reports, both null when nobody
|
|
7778
|
+
* has said recently.
|
|
7779
|
+
*
|
|
7780
|
+
* @param {HlsSession} session
|
|
7781
|
+
* @param {number} now
|
|
7782
|
+
* @returns {{ earliestPosition: number | null, deepestBuffer: number | null, viewers: number }}
|
|
7783
|
+
*/
|
|
7784
|
+
#reportedPictureOf(session, now) {
|
|
7785
|
+
let earliestPosition = null;
|
|
7722
7786
|
let deepestBuffer = null;
|
|
7723
|
-
|
|
7787
|
+
let viewers = 0;
|
|
7788
|
+
for (const report of session.netReports.values()) {
|
|
7724
7789
|
if (now - report.at > NET_REPORT_FRESH_MS) {
|
|
7725
7790
|
continue;
|
|
7726
7791
|
}
|
|
7792
|
+
viewers += 1;
|
|
7727
7793
|
if (Number.isFinite(report.positionSeconds)) {
|
|
7728
|
-
|
|
7729
|
-
|
|
7794
|
+
earliestPosition =
|
|
7795
|
+
earliestPosition === null
|
|
7730
7796
|
? report.positionSeconds
|
|
7731
|
-
: Math.min(
|
|
7797
|
+
: Math.min(earliestPosition, report.positionSeconds);
|
|
7732
7798
|
}
|
|
7733
7799
|
if (Number.isFinite(report.bufferedAheadSec)) {
|
|
7734
7800
|
deepestBuffer =
|
|
@@ -7737,6 +7803,14 @@ export class HlsSessionManager {
|
|
|
7737
7803
|
: Math.max(deepestBuffer, report.bufferedAheadSec);
|
|
7738
7804
|
}
|
|
7739
7805
|
}
|
|
7806
|
+
return { earliestPosition, deepestBuffer, viewers };
|
|
7807
|
+
}
|
|
7808
|
+
|
|
7809
|
+
#audioStartSecondsFor(base) {
|
|
7810
|
+
const watching = this.#activeVariant(base);
|
|
7811
|
+
const readHead = this.#viewerPositionOf(watching);
|
|
7812
|
+
const now = Date.now();
|
|
7813
|
+
const { earliestPosition: earliestStated, deepestBuffer } = this.#reportedPictureOf(watching, now);
|
|
7740
7814
|
if (earliestStated !== null) {
|
|
7741
7815
|
// Never ahead of the read head: a position claiming to be past what has
|
|
7742
7816
|
// been asked for is a report that arrived out of order, and acting on it
|