@torrent-tv/proxy 2.64.7 → 2.64.8
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 +87 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 2.64.8
|
|
2
|
+
|
|
3
|
+
- **New**: A backward restart says what it costs. Nothing already written is lost — every run keeps its own directory and the session serves the union of all of them — so the price of moving the encoder back is not the files; it is work about to be done twice, because the new run walks forward through segments the old one had already finished and ffmpeg cannot know they exist, and it is the viewer in front, who has nothing produced ahead of them until the run gets back to where it already was. Neither had ever been counted. The line now says how far back it went, how many of the segments it will walk through are already on disk, and the running totals for the session. This is the reading roadmap item 64 needs before a session is allowed more than one concurrent run: if a viewer is behind the head twice a week, that design does not earn its complexity.
|
|
4
|
+
|
|
1
5
|
## 2.64.7
|
|
2
6
|
|
|
3
7
|
- **Fix**: A file opened at a position starts its SOUND at that position. The audio rendition's start is worked out from the picture's read head less the buffer the viewer reports holding — sound, because a read head is the furthest request of any viewer and the picture sits behind it by however deep that buffer is. At a cold open there is no report yet, and the fallback subtracted the WHOLE 120 s look-ahead from a buffer that does not exist: field 2026-08-31, a page opened at 588 s started its sound at 460 s, 131 seconds of film nobody would hear, and the segment the viewer needed took 38.8 s to appear against the picture's 8.4 s — the audio encoder healthy at 2.3-3.1x throughout, simply given a running start it did not need. The reading now says WHICH of its three sources answered (`viewerPositionSource`): a seek and a served segment are request edges and keep the subtraction, while the opening position is not an edge — nothing has been asked for since the session was made, and a browser that has just opened holds nothing by construction (`research/cold-open-audio-start-2026-08-31.md`).
|
package/package.json
CHANGED
|
@@ -468,6 +468,10 @@ const BEHIND_HEAD_REPAIR_MS = 400;
|
|
|
468
468
|
// Generous against that measurement, and far short of the hundreds of segments
|
|
469
469
|
// a scan reaches.
|
|
470
470
|
const BEHIND_HEAD_REPAIR_MAX_SEGMENTS = 60;
|
|
471
|
+
// How far the accounting of a backward restart looks for work about to be done
|
|
472
|
+
// twice. It runs on the restart path and a session an hour in has thousands of
|
|
473
|
+
// segments; the figure is for a comparison, not an inventory.
|
|
474
|
+
const BACKWARD_RESTART_SCAN_SEGMENTS = 300;
|
|
471
475
|
// Hard cap on the total settle wait, measured from the first request of a
|
|
472
476
|
// burst, so a still-moving scrubber cannot delay a genuine seek forever.
|
|
473
477
|
const SEEK_SETTLE_MAX_MS = 1_000;
|
|
@@ -4699,6 +4703,10 @@ export class HlsSessionManager {
|
|
|
4699
4703
|
// 0.54-1.47 s — does not account for it. Before rebuilding the hottest path
|
|
4700
4704
|
// in the proxy on a guess, make each stage state its own cost.
|
|
4701
4705
|
const restartEnteredAt = Date.now();
|
|
4706
|
+
// Reads where the old run began BEFORE the new one overwrites it, and does
|
|
4707
|
+
// not await: everything below is the restart path, which is measured in
|
|
4708
|
+
// milliseconds and has been worked on twice to keep it that way.
|
|
4709
|
+
this.#accountBackwardRestart(session, startIndex);
|
|
4702
4710
|
// One directory per run. Two runs writing the same segment name at once
|
|
4703
4711
|
// produce a file that is neither, which is the only reason a restart ever
|
|
4704
4712
|
// had to wait for its predecessor to die.
|
|
@@ -9330,6 +9338,85 @@ export class HlsSessionManager {
|
|
|
9330
9338
|
);
|
|
9331
9339
|
}
|
|
9332
9340
|
|
|
9341
|
+
/**
|
|
9342
|
+
* What moving the encoder BACKWARDS costs, said out loud when it happens.
|
|
9343
|
+
*
|
|
9344
|
+
* Nothing already written is lost — every run keeps its own directory and
|
|
9345
|
+
* {@link HlsSessionManager##findProducedFile} serves the union of all of them
|
|
9346
|
+
* — so the price of a restart is not the files. It is two other things, and
|
|
9347
|
+
* neither was ever counted:
|
|
9348
|
+
*
|
|
9349
|
+
* - **work done twice.** The new run begins at the target and encodes
|
|
9350
|
+
* forward through segments the old run had already finished. ffmpeg cannot
|
|
9351
|
+
* know they exist, so it makes them again.
|
|
9352
|
+
* - **the viewer in front.** While the run walks back up to where it already
|
|
9353
|
+
* was, nothing new is being made ahead of them, and their cushion drains.
|
|
9354
|
+
*
|
|
9355
|
+
* Both are what decides whether a session should be allowed a SECOND
|
|
9356
|
+
* concurrent run instead — roadmap item 64. That question cannot be answered
|
|
9357
|
+
* from taste, and this is the reading it needs: how often it happens at all,
|
|
9358
|
+
* how far back, and how much of the walk is a repeat.
|
|
9359
|
+
*
|
|
9360
|
+
* Nothing here is awaited by the caller. Everything below the call site is
|
|
9361
|
+
* the restart path, which is measured in milliseconds and has been worked on
|
|
9362
|
+
* twice to keep it that way; a reading that delays the thing it is reading
|
|
9363
|
+
* about is not a reading. The figures that MUST be taken before the new run
|
|
9364
|
+
* exists are taken synchronously, and only the file counting is left to run
|
|
9365
|
+
* on its own — against the directories that existed at this instant, so what
|
|
9366
|
+
* the new run is about to write cannot be counted as already there.
|
|
9367
|
+
*
|
|
9368
|
+
* @param {HlsSession} session
|
|
9369
|
+
* @param {number} startIndex - Where the new run will begin.
|
|
9370
|
+
* @returns {void}
|
|
9371
|
+
*/
|
|
9372
|
+
#accountBackwardRestart(session, startIndex) {
|
|
9373
|
+
const previousStart = session.encodeStartIndex;
|
|
9374
|
+
if (!Number.isInteger(previousStart) || !Number.isInteger(startIndex) || startIndex >= previousStart) {
|
|
9375
|
+
// A first run, or one moving forward. Neither costs anything here: a
|
|
9376
|
+
// forward restart skips material it never made.
|
|
9377
|
+
return;
|
|
9378
|
+
}
|
|
9379
|
+
const processed = Number(session.progress?.processedSeconds);
|
|
9380
|
+
const head = Number.isFinite(processed)
|
|
9381
|
+
? Math.max(previousStart, this.#segmentIndexForTime(session, processed))
|
|
9382
|
+
: previousStart;
|
|
9383
|
+
// Bounded: a session an hour in has thousands of segments, and the count is
|
|
9384
|
+
// for a comparison, not an inventory.
|
|
9385
|
+
const last = Math.min(head, startIndex + BACKWARD_RESTART_SCAN_SEGMENTS);
|
|
9386
|
+
const dirsBefore = this.#runDirs(session);
|
|
9387
|
+
const accounting = session.backwardRestarts ?? { count: 0, segmentsBack: 0, worstBack: 0, remade: 0 };
|
|
9388
|
+
accounting.count += 1;
|
|
9389
|
+
accounting.segmentsBack += previousStart - startIndex;
|
|
9390
|
+
accounting.worstBack = Math.max(accounting.worstBack, previousStart - startIndex);
|
|
9391
|
+
session.backwardRestarts = accounting;
|
|
9392
|
+
|
|
9393
|
+
void (async () => {
|
|
9394
|
+
let alreadyOnDisk = 0;
|
|
9395
|
+
for (let index = startIndex; index <= last; index += 1) {
|
|
9396
|
+
const fileName = session.segmentFormat.segmentFileName(index);
|
|
9397
|
+
for (const dir of dirsBefore) {
|
|
9398
|
+
try {
|
|
9399
|
+
await access(path.join(dir, fileName));
|
|
9400
|
+
alreadyOnDisk += 1;
|
|
9401
|
+
break;
|
|
9402
|
+
} catch {
|
|
9403
|
+
// Not this run's; try an older one.
|
|
9404
|
+
}
|
|
9405
|
+
}
|
|
9406
|
+
}
|
|
9407
|
+
accounting.remade += alreadyOnDisk;
|
|
9408
|
+
logger.info(
|
|
9409
|
+
`transcode ${session.id} moving the encoder BACK from #${previousStart} to #${startIndex} ` +
|
|
9410
|
+
`(head was #${head}): ${alreadyOnDisk} of the ${last - startIndex + 1} segment(s) it will walk through ` +
|
|
9411
|
+
`are already on disk and will be made again, and nothing is produced ahead of #${head} until it gets ` +
|
|
9412
|
+
`back there — ${accounting.count} backward restart(s) this session, worst ${accounting.worstBack} ` +
|
|
9413
|
+
`segment(s) back, ${accounting.remade} segment(s) remade in total (roadmap 64)`
|
|
9414
|
+
);
|
|
9415
|
+
})().catch(() => {
|
|
9416
|
+
// silent-ok: a reading that fails is not worth ending a restart over.
|
|
9417
|
+
});
|
|
9418
|
+
}
|
|
9419
|
+
|
|
9333
9420
|
/**
|
|
9334
9421
|
* The directories runs have written into, newest first.
|
|
9335
9422
|
*
|