@torrent-tv/proxy 2.9.91 → 2.9.92
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 +5 -0
- package/package.json +1 -1
- package/services/hls-session-manager.js +30 -61
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## 2.9.92
|
|
2
|
+
|
|
3
|
+
- **Fix**: A seek acts on what the viewer asked for, instead of waiting out guards built for a signal that no longer exists. Three delays sat in front of every seek, all of them there because a far segment REQUEST used to steer the encoder and the player's playlist scan produced dozens of them. Requests stopped steering anything when the position became explicit, so what arrives now is only ever a position the viewer stated. The settle window drops from 1.2 s to 300 ms (the browser already collapses a drag into one report at 300 ms — this was a second debounce on an already-debounced signal, and it cost 1.2 s of every measured seek). The floor between restarts drops from 4 s to 500 ms, now a guard against a client spamming the endpoint rather than a policy about noise. And a run in progress is no longer protected for up to 30 s while it reaches its first segment: finishing a segment for a position the viewer has left is work nobody wants, and the hold could delay a genuine second seek by the whole grace. Measured cost of the old behaviour, 2026-08-04: two seeks 1.3 s apart produced two restarts 4.4 s apart, the first encoding 119.5 s of content before the second killed it.
|
|
4
|
+
- **Chore**: Removed `ENCODER_STALL_MS`, declared with a paragraph describing a watchdog that was never wired to anything.
|
|
5
|
+
|
|
1
6
|
## 2.9.91
|
|
2
7
|
|
|
3
8
|
- **Fix**: The encoder no longer runs away from the viewer. Nothing bounded how far ahead it produced: measured 2026-08-04, three minutes after a film was opened the encode had reached 00:39:24 of a 01:26:51 source at 12.8x while the viewer was still at the start, and the torrent had pulled 80% of 4.7 GB to feed it — the pool owner's bandwidth and disk spent on a viewer who may watch two minutes, the pieces being read evicted from memory by pieces forty minutes ahead, and the swarm busy with anything but the segment being waited for. An encoder more than two minutes of content ahead of the last segment its viewer asked for is now **suspended**, and released once the viewer is within a minute of it — or at once when a segment is requested. Suspended rather than killed on purpose: restarting costs about nine seconds on this hardware, so a viewer reaching the end of the produced range would stall every time, while suspending keeps the process, its input and its position. POSIX only; where `SIGSTOP` does not exist the attempt fails once, is logged, and that session keeps the old behaviour. Every path that terminates an encoder now releases it first — a suspended process does not act on `SIGTERM` until it is continued, which would have hung the wait a seek performs before starting its replacement.
|
package/package.json
CHANGED
|
@@ -43,26 +43,15 @@ const DEFAULT_SEGMENT_DURATION_SEC = 4;
|
|
|
43
43
|
// is allowed to be before we restart ffmpeg at that position (server-side seek).
|
|
44
44
|
// Requests within the window are served by waiting for the running encode.
|
|
45
45
|
const MAX_LOOKAHEAD_SEGMENTS = 8;
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
// run cannot block seeking forever. See #fireSettledSeek.
|
|
56
|
-
const RUN_FIRST_SEGMENT_GRACE_MS = 30_000;
|
|
57
|
-
// Encoder stall watchdog. A running ffmpeg emits `-progress` output on stdout
|
|
58
|
-
// continuously while it encodes; when it hangs mid-file (alive, but producing
|
|
59
|
-
// no output and no stderr — a deadlock, e.g. a stalled input read), that output
|
|
60
|
-
// stops and `progress.updatedAt` freezes. If a segment INSIDE the look-ahead
|
|
61
|
-
// window is being demanded but progress has not advanced for this long, the
|
|
62
|
-
// encoder is wedged (observed: the segment 503s forever). Treat it like a seek
|
|
63
|
-
// and restart ffmpeg at the demanded segment. Conservative — a slow-but-moving
|
|
64
|
-
// encode keeps advancing `updatedAt`, so this only fires on a true freeze.
|
|
65
|
-
const ENCODER_STALL_MS = 12_000;
|
|
46
|
+
// Floor between actual restarts. It used to be 4 s, from when a far segment
|
|
47
|
+
// REQUEST could steer the encoder and a playlist scan produced a burst of them.
|
|
48
|
+
// Requests no longer steer anything (see #ensureEncodingFor) — every restart
|
|
49
|
+
// now comes from a position the viewer stated — so this is no longer a policy
|
|
50
|
+
// about noise, only a guard against a client that spams the seek endpoint.
|
|
51
|
+
// Measured cost of the old value 2026-08-04: two seeks 1.3 s apart produced two
|
|
52
|
+
// restarts 4.4 s apart, the first encoding 119.5 s of content nobody wanted
|
|
53
|
+
// before the second killed it.
|
|
54
|
+
const RESTART_COOLDOWN_MS = 500;
|
|
66
55
|
// How far ahead of the viewer the encoder may run before it is stopped, and how
|
|
67
56
|
// far it must fall back to before it is let go again.
|
|
68
57
|
//
|
|
@@ -101,10 +90,17 @@ const LOOKAHEAD_RESUME_SECONDS = 60;
|
|
|
101
90
|
// encoding 125 s of content before reaching the viewer's position. Field
|
|
102
91
|
// 2026-08-02: a seek took 56 s, of which ~50 s was this backoff.
|
|
103
92
|
const SEEK_BACKOFF_SEGMENTS = 1;
|
|
104
|
-
|
|
105
|
-
//
|
|
93
|
+
// How long to wait for a scrub to stop moving before acting on it. Small,
|
|
94
|
+
// because the browser already collapses a drag into ONE report
|
|
95
|
+
// (`SEEK_REPORT_DEBOUNCE_MS`, 300 ms) and only reports where it settled — this
|
|
96
|
+
// is a second debounce on an already-debounced signal, and every millisecond of
|
|
97
|
+
// it is dead time in front of the viewer. It was 1.2 s when the encoder was
|
|
98
|
+
// also steered by segment requests, which arrive in bursts of dozens; measured
|
|
99
|
+
// 2026-08-04, that cost 1.2 s of every seek.
|
|
100
|
+
const SEEK_SETTLE_MS = 300;
|
|
101
|
+
// Hard cap on the total settle wait, measured from the first request of a
|
|
106
102
|
// burst, so a still-moving scrubber cannot delay a genuine seek forever.
|
|
107
|
-
const SEEK_SETTLE_MAX_MS =
|
|
103
|
+
const SEEK_SETTLE_MAX_MS = 1_000;
|
|
108
104
|
// Grace period to wait for the PREVIOUS ffmpeg process to exit (per signal
|
|
109
105
|
// escalation step: SIGTERM, then SIGKILL) before spawning its replacement into
|
|
110
106
|
// the same session directory. See #startEncodeRun.
|
|
@@ -2464,48 +2460,21 @@ export class HlsSessionManager {
|
|
|
2464
2460
|
session.seekSettleTimer.unref?.();
|
|
2465
2461
|
return;
|
|
2466
2462
|
}
|
|
2467
|
-
//
|
|
2468
|
-
//
|
|
2469
|
-
//
|
|
2470
|
-
//
|
|
2471
|
-
//
|
|
2472
|
-
//
|
|
2473
|
-
//
|
|
2474
|
-
//
|
|
2475
|
-
//
|
|
2476
|
-
//
|
|
2477
|
-
// synthetic VOD playlist, so from its point of view they all exist), and
|
|
2478
|
-
// each far-enough probe looked like a fresh seek to us. Waiting for the
|
|
2479
|
-
// first segment makes the scan harmless — it can no longer steer the
|
|
2480
|
-
// encoder — and one genuine seek now reliably completes.
|
|
2481
|
-
//
|
|
2482
|
-
// Bounded by RUN_FIRST_SEGMENT_GRACE_MS so a wedged run cannot block seeks
|
|
2483
|
-
// forever; the encoder-stall watchdog and the exit handler cover a run that
|
|
2484
|
-
// dies outright.
|
|
2463
|
+
// A run in progress is NOT protected any more. It used to be: a restart was
|
|
2464
|
+
// held for up to 30 s while the current run reached its first segment,
|
|
2465
|
+
// because a far segment REQUEST could steer the encoder and the player's
|
|
2466
|
+
// playlist scan produced dozens of them — restarts at #617 → #717 → #732 →
|
|
2467
|
+
// #732 every 5-7 s, none producing anything (field 2026-08-02). Requests
|
|
2468
|
+
// stopped steering anything when the position became explicit, so the only
|
|
2469
|
+
// thing that can arrive here is a position the viewer has stated, and
|
|
2470
|
+
// finishing a segment for where they no longer are is work nobody wants.
|
|
2471
|
+
// Holding it was also expensive in the other direction: a genuine second
|
|
2472
|
+
// seek could be delayed by the whole grace.
|
|
2485
2473
|
const producedThisRun = this.#producedSecondsThisRun(session);
|
|
2486
2474
|
const runIsAlive = session.ffmpeg != null && !hasChildExited(session.ffmpeg);
|
|
2487
|
-
if (
|
|
2488
|
-
runIsAlive &&
|
|
2489
|
-
producedThisRun < this.segmentDurationSec &&
|
|
2490
|
-
sinceLastRestart < RUN_FIRST_SEGMENT_GRACE_MS
|
|
2491
|
-
) {
|
|
2492
|
-
logger.info(
|
|
2493
|
-
`transcode ${session.id} seek #${target} HELD — current run has produced ` +
|
|
2494
|
-
`${producedThisRun.toFixed(1)}s of the ${this.segmentDurationSec}s first segment ` +
|
|
2495
|
-
`(${(sinceLastRestart / 1000).toFixed(1)}s into a ${RUN_FIRST_SEGMENT_GRACE_MS / 1000}s grace)`
|
|
2496
|
-
);
|
|
2497
|
-
session.seekSettleTimer = setTimeout(() => this.#fireSettledSeek(session), SEEK_SETTLE_MS);
|
|
2498
|
-
session.seekSettleTimer.unref?.();
|
|
2499
|
-
return;
|
|
2500
|
-
}
|
|
2501
|
-
// Why the restart was allowed — the counterpart of the HELD line above.
|
|
2502
|
-
// Without it a restart is indistinguishable from the runaway ping-pong this
|
|
2503
|
-
// guard exists to stop, and diagnosing a field report becomes guesswork.
|
|
2504
2475
|
const allowedBecause = !runIsAlive
|
|
2505
2476
|
? "run is dead"
|
|
2506
|
-
:
|
|
2507
|
-
? `run produced ${producedThisRun.toFixed(1)}s (first segment done)`
|
|
2508
|
-
: `grace of ${RUN_FIRST_SEGMENT_GRACE_MS / 1000}s expired`;
|
|
2477
|
+
: `viewer moved; run had produced ${producedThisRun.toFixed(1)}s`;
|
|
2509
2478
|
// The start is exactly what requestSeek computed — one segment before the
|
|
2510
2479
|
// viewer's position — and nothing else may move it.
|
|
2511
2480
|
//
|