@torrent-tv/proxy 2.107.4 → 2.107.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.107.4",
3
+ "version": "2.107.6",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -70,8 +70,30 @@
70
70
  * (`EncodeRun.speedX`), with input waits and stops taken out. Zero or less
71
71
  * means nothing has measured it yet, and then no comparison involving its
72
72
  * speed can be made.
73
+ * @property {boolean} [fixedEnd] - Whether `to` is where its process ends. An
74
+ * original input is one copied stretch and its run makes nothing past it; a
75
+ * packet input's run grows past `to` one piece at a time, so its `to` bounds
76
+ * nothing yet (`EncodeRuns`).
73
77
  */
74
78
 
79
+ /**
80
+ * The last number a live run can make where it stands: its `to` when that is
81
+ * where its process ends, otherwise no bound.
82
+ *
83
+ * Counted as reaching every number after it instead, a run whose stretch ended
84
+ * at #204 was the plan's answer for #205: the run starting on #205 was stopped
85
+ * as "the film is no worse off without it", none was placed there again, and
86
+ * the viewer on the last piece of Drifters 04 waited for minutes (field
87
+ * 2026-10-05, torrent-tv/meta#117).
88
+ *
89
+ * @param {LiveRun} run
90
+ * @returns {number}
91
+ */
92
+ function roadEndOf(run) {
93
+ const to = Number(run.to);
94
+ return run.fixedEnd === true && to >= Number(run.from) ? to : Number.POSITIVE_INFINITY;
95
+ }
96
+
75
97
  /**
76
98
  * What a viewer is waiting for. Which viewer is deliberately absent.
77
99
  *
@@ -267,7 +289,8 @@ export function planEncoders({
267
289
  // outside.
268
290
  const priced = (encoders) => {
269
291
  const perPiece = pieceAt(encoders.length);
270
- return encoders.map((encoder) => ({ at: encoder.at, delaySec: encoder.owes(perPiece) }));
292
+ return encoders.map((encoder) => ({ at: encoder.at, delaySec: encoder.owes(perPiece),
293
+ through: encoder.through ?? Number.POSITIVE_INFINITY }));
271
294
  };
272
295
 
273
296
 
@@ -442,7 +465,8 @@ export function planEncoders({
442
465
  at: positions[index],
443
466
  owes: head === positions[index]
444
467
  ? (piece) => finishesItsPieceIn(filler, piece)
445
- : (piece) => moveSec + piece
468
+ : (piece) => moveSec + piece,
469
+ through: head === positions[index] ? roadEndOf(filler) : Number.POSITIVE_INFINITY
446
470
  });
447
471
  }
448
472
  // Bodies nobody was given a position for go on working where they stand,
@@ -465,7 +489,8 @@ export function planEncoders({
465
489
  at: head,
466
490
  owes: cutInFront
467
491
  ? (piece) => moveSec + piece
468
- : (piece) => finishesItsPieceIn(run, piece)
492
+ : (piece) => finishesItsPieceIn(run, piece),
493
+ through: roadEndOf(run)
469
494
  });
470
495
  }
471
496
  const scored = latenessOf(priced(encoders), coverage, wanted, untilNeeded, rate / contentionPenaltyFor(Math.max(0, encoders.length - 1)), refetchPerSegment, segmentSeconds, neverSec);
@@ -506,7 +531,8 @@ export function planEncoders({
506
531
  at,
507
532
  owes: at === head
508
533
  ? (piece) => finishesItsPieceIn(run, piece)
509
- : (piece) => moveSec + piece
534
+ : (piece) => moveSec + piece,
535
+ through: at === head ? roadEndOf(run) : Number.POSITIVE_INFINITY
510
536
  });
511
537
  }
512
538
  for (let index = 0; index < positions.length; index += 1) {
@@ -867,7 +893,8 @@ function latenessOf(encoders, coverage, wanted, untilNeeded, rate, refetchSecPer
867
893
  let byWhom = null;
868
894
  for (let at = 0; at < encoders.length; at += 1) {
869
895
  const encoder = encoders[at];
870
- if (encoder.at > index) {
896
+ // Behind it, or standing on a road that ends before it.
897
+ if (encoder.at > index || index > encoder.through) {
871
898
  continue;
872
899
  }
873
900
  // WHEN THIS BODY REACHES THIS NUMBER. `delaySec` is when it finishes the
@@ -1160,15 +1187,8 @@ function gapFinderFor(coverage, surviving, rate, refetchSecPerSegment = 0) {
1160
1187
  const head = Number(/** @type {{ head?: number }} */ (run).head);
1161
1188
  placed.push({
1162
1189
  at: Number.isFinite(head) ? head : Number(/** @type {{ from: number }} */ (run).from),
1163
- // A live run's road, so that placing inside it can be priced. A run given
1164
- // no end drives to the end of the film, which is what makes the price real.
1165
- // A run given no end drives to the end of the film, which is what makes
1166
- // the price of cutting in front of it real. Written out rather than
1167
- // imported: this file depends on nothing, and that is what lets it be
1168
- // exercised with plain values alone.
1169
- to: Number(/** @type {{ to: number }} */ (run).to) < Number(/** @type {{ from: number }} */ (run).from)
1170
- ? Number.POSITIVE_INFINITY
1171
- : Number(/** @type {{ to: number }} */ (run).to)
1190
+ // A live run's road: nothing past its end is reached by it.
1191
+ to: roadEndOf(/** @type {LiveRun} */ (run))
1172
1192
  });
1173
1193
  }
1174
1194
  return (at, bound, deadlineAt, alsoPlaced) => {
@@ -1192,6 +1212,7 @@ function gapFinderFor(coverage, surviving, rate, refetchSecPerSegment = 0) {
1192
1212
  // millions of steps, and the plan held the proxy's thread for most of every
1193
1213
  // second (Home Assistant 2026-10-08, torrent-tv/meta#151).
1194
1214
  const standing = [...placed.map((live) => live.at), ...(alsoPlaced ?? [])];
1215
+ const roadEnds = [...placed.map((live) => live.to), ...(alsoPlaced ?? []).map(() => Number.POSITIVE_INFINITY)];
1195
1216
  const passed = standing.map((a) => coverage.madeBetween(a, start - 1));
1196
1217
  for (let index = start; index <= last; index += 1) {
1197
1218
  const readyHere = coverage.isReady(index);
@@ -1217,8 +1238,9 @@ function gapFinderFor(coverage, surviving, rate, refetchSecPerSegment = 0) {
1217
1238
  let soonest = Number.POSITIVE_INFINITY;
1218
1239
  for (let which = 0; which < standing.length; which += 1) {
1219
1240
  const a = standing[which];
1220
- if (a > index) {
1221
- // Standing past it. Encoders only move forward, so it never will.
1241
+ if (a > index || index > roadEnds[which]) {
1242
+ // Standing past it, or on a road that ends before it. Encoders only
1243
+ // move forward, so it never will.
1222
1244
  continue;
1223
1245
  }
1224
1246
  if (a === index) {
@@ -774,6 +774,14 @@ export class EncodeRun {
774
774
  if (this.#process === null || this.#ended) {
775
775
  return;
776
776
  }
777
+ // AN EXITED PROCESS IS NOT STOPPED. Its exit is being answered, and the
778
+ // answer can publish its last piece and ask the plan again, which then
779
+ // finds nothing left ahead of it. Taken as a stop, its ending took an edge
780
+ // the table does not have and the stop's age was kept as what a kill costs
781
+ // (Home Assistant 2026-10-23, torrent-tv/meta#117).
782
+ if (this.#processExited) {
783
+ return;
784
+ }
777
785
  // A cut closed before our stop stays complete; the file flushed by the
778
786
  // subsequent signal must never be published as that same proof. It is
779
787
  // waited for all the same: it says where the next keyframe's leading
@@ -1312,6 +1312,9 @@ export class EncodeRuns {
1312
1312
  from: run.from, to: run.to });
1313
1313
  if (admittedInput.original) {
1314
1314
  run.originalInput = admittedInput;
1315
+ // Its stretch is all it can make: the plan counts it reaching nothing
1316
+ // past `to` (torrent-tv/meta#117).
1317
+ run.fixedEnd = true;
1315
1318
  // Every number of the stretch answers to its one key, so a failure
1316
1319
  // anywhere inside it is recorded against the stretch that was commanded.
1317
1320
  run.admittedInputKeys = new Map(Array.from({ length: inputEnd - safeIndex + 1 }, (_, offset) => [safeIndex + offset, inputKey]));