@torrent-tv/proxy 2.98.11 → 2.98.13

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.98.11",
3
+ "version": "2.98.13",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -362,6 +362,26 @@ export function planEncoders({
362
362
  const decided = new Map();
363
363
  const room = Math.max(0, maxRuns - live.length);
364
364
  const refetchPerSegment = segmentSeconds * refetchSecPerFilmSecond;
365
+ // WHAT "NEVER" IS, ONE FIGURE FOR EVERY ARRANGEMENT THIS PASS COMPARES: later
366
+ // than any encoder here can reach the last wanted number — the film's length,
367
+ // a start and a stop, the whole wanted stretch at the slowest rate any
368
+ // arrangement can run at, and every piece fetched again. Capped at the film's
369
+ // length instead, a slow encoder's arrival scored as never; and worked out
370
+ // from each arrangement's own encoders, the arrangement without an encoder
371
+ // had a smaller never than the one with it. Either way an encoder making the
372
+ // film behind the viewer at 0.57x scored no better than none, and the plan
373
+ // stopped it before its first piece, then started it again when the speed
374
+ // read 1.9x — thirteen times in three minutes on Home Assistant, each start
375
+ // copying 108-122 MB of input (r37, torrent-tv/meta#178).
376
+ const neverSec = (() => {
377
+ const first = Math.min(...wanted.map((span) => span.from));
378
+ const last = Math.max(...wanted.map((span) => span.to));
379
+ const film = (last + 1) * segmentSeconds;
380
+ const slowest = rate / contentionPenaltyFor(Math.max(maxRuns, live.length));
381
+ if (!(slowest > 0) || !Number.isFinite(film)) return film;
382
+ return film + firstByteWaitSec + killCostSec + (last - first + 2) / slowest +
383
+ (last - first + 1) * refetchPerSegment + segmentSeconds;
384
+ })();
365
385
 
366
386
  let arrangements = [{ fill: [], used: new Set(), fresh: 0 }];
367
387
  for (let index = 0; index < positions.length; index += 1) {
@@ -441,7 +461,7 @@ export function planEncoders({
441
461
  : (piece) => finishesItsPieceIn(run, piece)
442
462
  });
443
463
  }
444
- const scored = latenessOf(priced(encoders), coverage, wanted, untilNeeded, rate / contentionPenaltyFor(Math.max(0, encoders.length - 1)), refetchPerSegment, segmentSeconds);
464
+ const scored = latenessOf(priced(encoders), coverage, wanted, untilNeeded, rate / contentionPenaltyFor(Math.max(0, encoders.length - 1)), refetchPerSegment, segmentSeconds, neverSec);
445
465
  if (bestScore === null || cheaperThan(scored, bestScore)) {
446
466
  bestScore = scored;
447
467
  best = arrangement;
@@ -492,7 +512,7 @@ export function planEncoders({
492
512
  const scoreOf = (override) => {
493
513
  const encoders = encodersOf(override);
494
514
  return latenessOf(encoders, coverage, wanted, untilNeeded,
495
- rate / contentionPenaltyFor(Math.max(0, encoders.length - 1)), refetchPerSegment, segmentSeconds);
515
+ rate / contentionPenaltyFor(Math.max(0, encoders.length - 1)), refetchPerSegment, segmentSeconds, neverSec);
496
516
  };
497
517
  for (const run of live) {
498
518
  if (placement.has(run)) {
@@ -519,7 +539,7 @@ export function planEncoders({
519
539
  const kept = encodersOf(new Map());
520
540
  const without = encodersOf(new Map([[run, null]]));
521
541
  const scoreOf_ = (encoders) => latenessOf(encoders, coverage, wanted, untilNeeded,
522
- rate / contentionPenaltyFor(Math.max(0, encoders.length - 1)), refetchPerSegment, segmentSeconds);
542
+ rate / contentionPenaltyFor(Math.max(0, encoders.length - 1)), refetchPerSegment, segmentSeconds, neverSec);
523
543
  return cheaperThan(scoreOf_(kept), scoreOf_(without));
524
544
  };
525
545
 
@@ -586,7 +606,7 @@ export function planEncoders({
586
606
  continue;
587
607
  }
588
608
  const without = latenessOf(encodersOf(new Map([[run, null]])), coverage, wanted, untilNeeded,
589
- rate / contentionPenaltyFor(Math.max(0, live.length - 2)), refetchPerSegment, segmentSeconds);
609
+ rate / contentionPenaltyFor(Math.max(0, live.length - 2)), refetchPerSegment, segmentSeconds, neverSec);
590
610
  if (cheapestScore === null || cheaperThan(without, cheapestScore)) {
591
611
  cheapestScore = without;
592
612
  cheapest = run;
@@ -751,13 +771,14 @@ function ranksOf(wanted, first, last) {
751
771
  return rankAt;
752
772
  }
753
773
 
754
- function latenessOf(encoders, coverage, wanted, untilNeeded, rate, refetchSecPerSegment, segmentSeconds) {
774
+ function latenessOf(encoders, coverage, wanted, untilNeeded, rate, refetchSecPerSegment, segmentSeconds, neverSec = null) {
755
775
  const first = Math.min(...wanted.map((span) => span.from));
756
776
  const last = Math.max(...wanted.map((span) => span.to));
757
- // What a number nobody reaches at all counts as. The film's own length is the
758
- // honest bound — nothing can be later than never — and a finite figure is what
759
- // lets two hopeless arrangements still be told apart by the rest of the sum.
760
- const never = (last + 1) * segmentSeconds;
777
+ // What a number nobody reaches at all counts as: one figure for every
778
+ // arrangement the plan compares, later than any arrival (`neverSec` in
779
+ // `planEncoders`), and finite, so two hopeless arrangements are still told
780
+ // apart by the rest of the sum. Without it, the film's own length.
781
+ const never = Number.isFinite(neverSec) ? neverSec : (last + 1) * segmentSeconds;
761
782
 
762
783
  // WHICH SIDE OF THE VIEWERS a piece is on. The map states it; nothing here
763
784
  // works it out from positions, and nothing here knows where a viewer stands.
@@ -876,7 +897,7 @@ function latenessOf(encoders, coverage, wanted, untilNeeded, rate, refetchSecPer
876
897
  // gives. It is a definition rather than a guard: it also makes the score
877
898
  // total on a host whose startup measured nothing at all, where every arrival
878
899
  // is beyond reckoning and every arrangement is therefore equally hopeless.
879
- const when = byWhom === null ? never : Math.min(soonest, never);
900
+ const when = byWhom === null || !Number.isFinite(soonest) ? never : soonest;
880
901
  const rank = rankAt(index);
881
902
  doneAt.set(rank, Math.max(doneAt.get(rank) ?? 0, when));
882
903
  const deadline = untilNeeded(index);
@@ -13,6 +13,7 @@ import { contentionPenalty } from "./contention.js";
13
13
  import { LOOKAHEAD_PAUSE_SECONDS } from "./CushionReport.js";
14
14
  import { SpeedReadings } from "./SpeedReadings.js";
15
15
  import { nextInputAtFirstPiece } from "./next-input.js";
16
+ import { runOpenOf } from "./run-open.js";
16
17
  import { ENCODE_RUN_STATE, liveRunsOf, runStateOf, wireState } from "./encode-run-state.js";
17
18
  import { ENCODE_EXIT } from "./encode-exit.js";
18
19
  import { EncodeRun, endOfRun } from "./EncodeRun.js";
@@ -1330,11 +1331,14 @@ export class EncodeRuns {
1330
1331
  const grid = session.timeline?.published ?? session.timeline?.boundaries;
1331
1332
  const pieceSeconds = Number(grid?.[run.from + 1]) - Number(grid?.[run.from]);
1332
1333
  const speed = run.speedX > 0 ? run.speedX : this.#host.encodeOrchestrator.speedOn?.(session.outputKey) ?? 0;
1333
- if (!Number.isFinite(ended.firstNamedMs) || !(pieceSeconds > 0) || !(speed > 0)) return;
1334
1334
  // FFmpeg's reads may have waited for the stretch being copied; that wait
1335
- // is the copy's, and the stretch is sized from the open (torrent-tv/meta#178).
1336
- const waitedMs = Number.isFinite(ended.firstNamedWallMs) ? run.originalInput?.waitedBefore?.(ended.firstNamedWallMs) ?? 0 : 0;
1337
- const openSeconds = Math.max(0, (ended.firstNamedMs - waitedMs) / 1000 - pieceSeconds / speed);
1335
+ // is the copy's, and the stretch is sized from the open (`run-open.js`).
1336
+ const open = runOpenOf({
1337
+ firstNamedMs: ended.firstNamedMs, firstNamedWallMs: ended.firstNamedWallMs, startedAt: run.startedAt,
1338
+ waitedBefore: run.originalInput?.waitedBefore, pieceSeconds, speed
1339
+ });
1340
+ if (!open) return;
1341
+ const { openSeconds, waitedMs } = open;
1338
1342
  this.#host.encodeOrchestrator.noteOpen?.(session.outputKey ?? "", openSeconds);
1339
1343
  this.#host.logger.info(`encode input of run #${run.from}..#${run.to} on ${session.outputKey}: opened in ` +
1340
1344
  `${openSeconds.toFixed(2)}s (first piece named at ${ended.firstNamedMs}ms, ${pieceSeconds.toFixed(3)}s of film at ${speed.toFixed(2)}x` +
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @file How long a run took to open its original input (torrent-tv/meta#178).
3
+ *
4
+ * Measured from the run's spawn to the name of its first closed piece, less
5
+ * what encoding that piece costs at the run's speed, and less the time
6
+ * FFmpeg's reads waited for the stretch being copied: that wait is the copy's,
7
+ * and the stretch is sized from the open. The input states those waits on the
8
+ * wall clock, so they are taken up to the wall-clock moment the first piece
9
+ * was named — the run's start plus its time to that name, not that time alone,
10
+ * which as a moment lay before every wait and counted none of them.
11
+ */
12
+
13
+ /**
14
+ * @param {object} params
15
+ * @param {number | null} params.firstNamedMs - Spawn to the first piece's name, pauses left out.
16
+ * @param {number | null} params.firstNamedWallMs - The same on the wall clock, pauses included.
17
+ * @param {number} params.startedAt - The run's spawn on the wall clock, ms.
18
+ * @param {((until: number) => number) | null | undefined} params.waitedBefore - The input's wall-clock waits for its copy before `until`, ms.
19
+ * @param {number} params.pieceSeconds - The first piece's film.
20
+ * @param {number} params.speed - Film seconds per second; 0 when not known.
21
+ * @returns {{ openSeconds: number, waitedMs: number } | null} Null where any term is unknown.
22
+ */
23
+ export function runOpenOf({ firstNamedMs, firstNamedWallMs, startedAt, waitedBefore, pieceSeconds, speed }) {
24
+ if (!Number.isFinite(firstNamedMs) || !(pieceSeconds > 0) || !(speed > 0)) return null;
25
+ const waitedMs = Number.isFinite(firstNamedWallMs) && startedAt > 0 && typeof waitedBefore === "function"
26
+ ? waitedBefore(startedAt + firstNamedWallMs)
27
+ : 0;
28
+ return { waitedMs, openSeconds: Math.max(0, (firstNamedMs - waitedMs) / 1000 - pieceSeconds / speed) };
29
+ }