@torrent-tv/proxy 2.37.0 → 2.37.1

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,7 @@
1
+ ## 2.37.1
2
+
3
+ - **Fix**: The cost of a seek no longer counts against the quality offer. `requiredSpeed` — the speed a step must sustain to survive a swarm — is built from the reader's interruptions, and the wait on the first piece after a JUMP is not one of them: those pieces have not been asked for yet and the encoder is restarting, so it measures the move, not the supply. Measured 2026-08-18: `proxy now offers 720p` landed 131 ms after a seek, collapsing a five-rung menu to one while the player was already hunting for a fragment, and another session churned `640p` → `640p 540p` → `640p 240p`. The wait is still reported, saying plainly that it belongs to the jump and is not counted, so a gap in the history cannot be mistaken for a swarm that never made the reader wait.
4
+
1
5
  ## 2.37.0
2
6
 
3
7
  - **Fix**: The segment the viewer seeks TO is no longer refused as stale. A seek bumps the wait epoch so requests made for the position being LEFT stop being held, and the epoch alone cannot tell those apart from the request for the position just arrived at — hls.js asks for it within milliseconds of the seek, and it raced the bump. Measured 2026-08-18: a seek to 1061.0 s, `segment-00101` answered 503 twice within 80 ms, the player never asked for it again, and instead re-fetched `a/0/segment-00103` and `a/0/segment-00104` **737 and 736 times over 149 seconds** — about half a gigabyte of the same two segments — while the picture stood at `t=1061.0s readyState=1` until the session ended. A held request is now released only when its segment lies behind where the viewer now is, or so far ahead that the running encode will not reach it; anything between is what the viewer is waiting for and is held.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.37.0",
3
+ "version": "2.37.1",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -484,6 +484,20 @@ export async function* readFragments({
484
484
  // process.
485
485
  const readerId = `read-${(readerSequence += 1)}`;
486
486
 
487
+ /**
488
+ * Set when the window JUMPS, cleared by the first wait after it.
489
+ *
490
+ * The wait that follows a jump is the cost of the jump: the pieces at the new
491
+ * position have not been asked for yet, and the encoder is restarting. It is
492
+ * not evidence about how well this swarm SUSTAINS a read, which is the only
493
+ * thing `requiredSpeed` is about — and letting it in is what collapsed the
494
+ * quality offer 131 ms after the seek measured on 2026-08-18, refusing every
495
+ * re-encoded rung on the strength of one jump.
496
+ *
497
+ * @type {boolean}
498
+ */
499
+ let waitBelongsToJump = false;
500
+
487
501
  const moveWindowTo = (pieceIndex) => {
488
502
  const next = readWindowFor({ pieceIndex, lastPiece, windowPieces });
489
503
  if (window && window.from === next.from && window.to === next.to) {
@@ -501,6 +515,7 @@ export async function* readFragments({
501
515
  // fills the store while the encoder runs ahead of the viewer.
502
516
  store.protectRange?.(readerId, next.from, next.to);
503
517
  if (isJump) {
518
+ waitBelongsToJump = true;
504
519
  // A jump — a seek, not the window sliding along — can land on pieces that
505
520
  // are already downloaded but have been spilled to disk. Bring the whole
506
521
  // window back at once instead of one disk round trip per piece as the
@@ -603,7 +618,18 @@ export async function* readFragments({
603
618
  // short, an immediate hit means it is longer than it needs to be. Applied
604
619
  // before the logging below so the line reports the window the next piece
605
620
  // will actually use.
606
- noteSupplyWait(`${torrent?.infoHash ?? "?"}/${file?.name ?? "?"}`, file?.name ?? "", waitedMs);
621
+ if (waitBelongsToJump) {
622
+ // Recorded nowhere: see `waitBelongsToJump`. Said out loud, because a
623
+ // gap in the supply history is otherwise indistinguishable from a swarm
624
+ // that never made the reader wait.
625
+ logger.info(
626
+ `piece-reader: ${waitedMs}ms on the first piece after a jump — the cost of moving, ` +
627
+ `not of this swarm's supply, so it is not counted against the quality offer`
628
+ );
629
+ waitBelongsToJump = false;
630
+ } else {
631
+ noteSupplyWait(`${torrent?.infoHash ?? "?"}/${file?.name ?? "?"}`, file?.name ?? "", waitedMs);
632
+ }
607
633
  const widened = nextWindowPieces({
608
634
  current: windowPieces,
609
635
  base: basePieces,