@torrent-tv/proxy 2.9.66 → 2.9.68

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 CHANGED
@@ -1,3 +1,11 @@
1
+ ## 2.9.68
2
+
3
+ - **Fix**: A seek could be dragged back to the position the viewer had just left. The encoder start was pulled down to the lowest segment the player had outstanding — a stand-in from when the distance to the preceding keyframe was unknown — but at seek time those requests still describe where the player was PLAYING, not where it is going. Field 2026-08-02: a seek to 23:34 (#135) correctly resolved to a start of #134, then got pulled to **#82** (14:15, the position just left) and crawled forward from there. Removed: since boundaries became real keyframes (2.9.65), exactly one segment back always suffices, so the pull has nothing left to correct for.
4
+
5
+ ## 2.9.67
6
+
7
+ - **Fix**: A seek waited far longer than it needed to — 56 s measured in the field, of which roughly 50 s was self-inflicted. `SEEK_BACKOFF_SEGMENTS` (how far before the requested segment the encoder starts) was **12**, chosen when segments were an invented 4 s apart and the distance to a usable keyframe was unknown. Since 2.9.65 every boundary IS a real keyframe read from the container index, so the single preceding segment is guaranteed to start on one — and with real 10.43 s segments the old value meant encoding **125 s of content** before reaching the viewer position. Lowered to **1**. Observed: the encoder started at #332 for a seek to #344 and the requested segment only arrived 56 s later, while every segment after it was served in ~100 ms.
8
+
1
9
  ## 2.9.66
2
10
 
3
11
  - **New**: The container keyframe index now covers **MP4/MOV and AVI** as well as Matroska. MP4 reads the sync-sample and time-to-sample tables from `moov` — found by stepping over top-level box headers, so it works whether `moov` sits at the file start or the end, without scanning the gigabytes of `mdat` between them (verified on a 2 GB field file: **1145 keyframes in 625 ms**). AVI reads the trailing `idx1` table, still worth having because older releases are largely XviD-in-AVI and are exactly the files served by copying rather than re-encoding. Formats left out are documented in the module with the reason: MPEG-TS/M2TS carry no index anywhere by design, fragmented MP4 spreads timing across fragments instead of one table, and FLV/ASF have tables but effectively never appear in these releases.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.9.66",
3
+ "version": "2.9.68",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -72,26 +72,18 @@ const ENCODER_STALL_MS = 12_000;
72
72
  // producing nothing.
73
73
  // How many segments BEFORE the requested position the encoder starts.
74
74
  //
75
- // Required by how HLS players seek, per Apple's HLS authoring guidance: given a
76
- // position, the player locates the nearest IDR (keyframe) *preceding* it,
77
- // decodes from there, and only then presents from the requested point. So it
78
- // always fetches segments BELOW the target — measured 2026-08-02 on iOS: a seek
79
- // to #1082 fetched from #1074 (8 back), one to #1358 fetched from #1301 (57
80
- // back), and in the latter case the player asked for NOTHING at or above the
81
- // target, so an encoder starting exactly on it produced only files nobody was
82
- // waiting for and playback hung indefinitely.
75
+ // A player given a position decodes from the nearest keyframe PRECEDING it
76
+ // (Apple HLS authoring guidance), so it fetches segments below the target and
77
+ // an encoder starting exactly on it produces nothing anyone waits for.
83
78
  //
84
- // The observed backoff is not constant, so this is a floor, not the whole
85
- // answer: #fireSettledSeek also pulls the start down to the lowest segment the
86
- // player is actually waiting on when that is lower still. Costs a few seconds
87
- // of extra encoding per seek.
88
- const SEEK_BACKOFF_SEGMENTS = 12;
89
- // Hard limit on how far below the requested segment the start may be pulled by
90
- // `lowestAwaitedIndex`. Without it a stale request from earlier playback drags
91
- // the encoder across the whole file: field 2026-08-02, a seek to #1354 was
92
- // pulled to #123 — the start of the previous watch — because requests from
93
- // before the seek were still counted. Anything deeper than this is not the
94
- // preceding keyframe, it is a leftover.
79
+ // ONE segment is now enough. Since 2.9.65 every boundary IS a real keyframe
80
+ // (read from the container index), so the segment before the target is
81
+ // guaranteed to start on one. The old value of 12 dates from the invented 4 s
82
+ // grid, where the distance to a usable keyframe was unknown — and it became
83
+ // actively harmful once boundaries turned real: with 10.43 s segments it meant
84
+ // encoding 125 s of content before reaching the viewer's position. Field
85
+ // 2026-08-02: a seek took 56 s, of which ~50 s was this backoff.
86
+ const SEEK_BACKOFF_SEGMENTS = 1;
95
87
  const SEEK_PULL_LIMIT_SEGMENTS = 120;
96
88
  const SEEK_SETTLE_MS = 1_200;
97
89
  // Hard cap on the total settle wait, measured from the first far request of a
@@ -2252,18 +2244,15 @@ export class HlsSessionManager {
2252
2244
  : producedThisRun >= this.segmentDurationSec
2253
2245
  ? `run produced ${producedThisRun.toFixed(1)}s (first segment done)`
2254
2246
  : `grace of ${RUN_FIRST_SEGMENT_GRACE_MS / 1000}s expired`;
2255
- // The player may be waiting on something below our fixed backoff — its own
2256
- // requests say exactly how far back it needs the keyframe, so honour that
2257
- // rather than a guess. Only ever pulls the start EARLIER, never later.
2258
- const awaited = session.lowestAwaitedIndex;
2259
- const pullFloor = Math.max(0, target - SEEK_PULL_LIMIT_SEGMENTS);
2260
- const effectiveTarget = awaited >= pullFloor && awaited < target ? awaited : target;
2261
- if (effectiveTarget !== target) {
2262
- logger.info(
2263
- `transcode ${session.id} pulling encode start #${target} → #${effectiveTarget} ` +
2264
- `(lowest segment the player is waiting on)`
2265
- );
2266
- }
2247
+ // Deliberately NOT pulled towards the lowest segment the player is waiting
2248
+ // on. That was a stand-in for not knowing how far back the preceding
2249
+ // keyframe lay, and it is now both unnecessary and harmful: boundaries are
2250
+ // real keyframes (2.9.65), so exactly one segment back always suffices,
2251
+ // while the player's outstanding requests at seek time still describe where
2252
+ // it was PLAYING, not where it is going. Field 2026-08-02: a seek to #135
2253
+ // was dragged back to #82 — the position the viewer had just left — because
2254
+ // the player was still fetching around it.
2255
+ const effectiveTarget = target;
2267
2256
  session.seekTarget = null;
2268
2257
  session.seekFirstFarAt = 0;
2269
2258
  session.lowestAwaitedIndex = -1;