@torrent-tv/proxy 2.100.14 → 2.101.0

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/bin/cli.js CHANGED
@@ -90,6 +90,7 @@ program
90
90
  .option("--no-port-mapping", "Disable automatic UPnP/NAT-PMP port mapping")
91
91
  .option("--delivery-sink", "Serve /api/delivery-sink, a torrent-free byte stream for delivery testing")
92
92
  .option("--pin-preset <name>", "Encode every software picture at this libx264 preset only, for runs that must be compared at one quality (off by default)")
93
+ .option("--input-stretch-bytes <bytes>", "Size every original-input stretch to this many bytes instead of copy rate times open, for runs that compare input sizes; memory, downloaded bytes and the plan still bound it (off by default)")
93
94
  .option("--heap-snapshots", "Write a heap snapshot of a thread whose memory reaches a new high-water; each one stops that thread while it is written (off by default)")
94
95
  .option("--profile-start <seconds>", "Profile the CPU of the main thread and the torrent thread for this many seconds from the first source opened, written beside the state (off by default)")
95
96
  .option(
@@ -399,6 +400,7 @@ try {
399
400
  profileStartSeconds: Number(options.profileStart) || 0,
400
401
  heapSnapshots: options.heapSnapshots === true,
401
402
  pinnedPreset: typeof options.pinPreset === "string" && options.pinPreset.trim() ? options.pinPreset.trim() : null,
403
+ inputStretchBytes: Number(options.inputStretchBytes) > 0 ? Math.floor(Number(options.inputStretchBytes)) : null,
402
404
  // Late-bound the same way `webRtcManager` is below: the torrent pool is
403
405
  // built inside `startProxyServer`, before `dataChannelHandler` — the
404
406
  // thing that actually owns a channel to push down — exists.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.100.14",
3
+ "version": "2.101.0",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
package/server.js CHANGED
@@ -134,6 +134,9 @@ export async function startProxyServer({
134
134
  // The one software preset every picture is encoded at, for runs compared at
135
135
  // one quality; null leaves the choice to the machine.
136
136
  pinnedPreset = null,
137
+ // The size every original-input stretch is given in place of copy rate
138
+ // times open, for runs that compare input sizes; null keeps the rule.
139
+ inputStretchBytes = null,
137
140
  // What viewers' connections carry beyond the film, per byte of film; the
138
141
  // transport counts it and is built after this server, so it is late-bound.
139
142
  serviceShare = () => null,
@@ -854,6 +857,7 @@ export async function startProxyServer({
854
857
  let sourcePreparation;
855
858
  const outputParts = wireOutputs({
856
859
  pinnedPreset,
860
+ inputStretchBytes,
857
861
  indexMemory,
858
862
  serviceShare,
859
863
  readMetadataActivity: () => containerOrchestrator.activity(),
@@ -62,10 +62,20 @@ export class EncodeInputs {
62
62
  * @type {(output: object, from: number, to: number, input: object) => void}
63
63
  */
64
64
  #stretchCopied;
65
+ /**
66
+ * A stretch size the operator named for comparison runs
67
+ * (`--input-stretch-bytes`), in place of copy rate times open; null keeps
68
+ * the measured rule. Only the target changes: memory, downloaded bytes and
69
+ * the plan's bound still end a stretch (torrent-tv/meta#182).
70
+ *
71
+ * @type {number | null}
72
+ */
73
+ #fixedStretchBytes;
65
74
 
66
75
  constructor({ resolve, readRanges, heldRanges = null, reviseBudget, changed, failed, capacity = () => null, urgent = () => true,
67
- rankAt = () => null, openOf = () => null, log = () => {}, now = Date.now, stretchCopied = () => {} }) {
76
+ rankAt = () => null, openOf = () => null, log = () => {}, now = Date.now, stretchCopied = () => {}, stretchBytes = null }) {
68
77
  this.#stretchCopied = stretchCopied;
78
+ this.#fixedStretchBytes = Number.isSafeInteger(stretchBytes) && stretchBytes > 0 ? stretchBytes : null;
69
79
  this.#openOf = openOf;
70
80
  this.#resolve = resolve;
71
81
  this.#read = readRanges;
@@ -337,9 +347,19 @@ export class EncodeInputs {
337
347
  * @returns {{ bytes: number, openMeasured: boolean } | null}
338
348
  */
339
349
  stretchFor(output) {
350
+ const sizing = this.#sizingFor(output);
351
+ return sizing ? { bytes: sizing.bytes, openMeasured: sizing.openMeasured } : null;
352
+ }
353
+
354
+ #sizingFor(output) {
340
355
  const rate = middleOf(this.#copyRates);
341
356
  const open = this.#openOf(output);
342
- return rate === null || !open ? null : { bytes: Math.floor(rate * open.seconds * 1000), openMeasured: open.measured };
357
+ if (this.#fixedStretchBytes !== null) {
358
+ return { bytes: this.#fixedStretchBytes, openMeasured: true, openSeconds: open?.seconds ?? null,
359
+ copyBytesPerMs: rate, fixed: true };
360
+ }
361
+ return rate === null || !open ? null : { bytes: Math.floor(rate * open.seconds * 1000),
362
+ openMeasured: open.measured, openSeconds: open.seconds, copyBytesPerMs: rate };
343
363
  }
344
364
 
345
365
  /**
@@ -493,6 +513,9 @@ export class EncodeInputs {
493
513
  */
494
514
  async #admitStretch(request, firstSources) {
495
515
  const { output, from } = request;
516
+ // Record existing decisions only: diagnostics must not resolve another
517
+ // interval or ask storage for an additional availability reading.
518
+ const probes = [], reservations = [];
496
519
  let minimum = originalInputBytes(firstSources);
497
520
  const stretches = new Map([[from, { sources: firstSources, bytes: minimum }]]);
498
521
  const held = new Map();
@@ -508,7 +531,11 @@ export class EncodeInputs {
508
531
  };
509
532
  const fits = async (end, maxBytes) => {
510
533
  const stretch = await at(end);
511
- return Boolean(stretch) && stretch.bytes <= maxBytes && await this.#present(stretch.sources, held, request.timing);
534
+ const withinLimit = Boolean(stretch) && stretch.bytes <= maxBytes;
535
+ const result = !stretch ? "unresolved" : !withinLimit ? "size"
536
+ : await this.#present(stretch.sources, held, request.timing) ? "fits" : "missing-bytes";
537
+ probes.push({ end, limitBytes: maxBytes, bytes: stretch?.bytes ?? null, result });
538
+ return result === "fits";
512
539
  };
513
540
  // Grows by doubling and then halves back: a handful of resolutions find
514
541
  // the end whether it is a piece away or the rest of the film.
@@ -535,10 +562,11 @@ export class EncodeInputs {
535
562
  }
536
563
  return good;
537
564
  };
538
- const sizing = this.stretchFor(output);
565
+ const sizing = this.#sizingFor(output);
539
566
  const target = sizing?.bytes ?? null;
540
567
  if (sizing && !sizing.openMeasured) request.timing.openApproximate = true;
541
568
  let end = target === null || this.#heldRanges === null ? floor : await longest(target);
569
+ const targetEnd = end;
542
570
  if (this.#requests.get(request.key) !== request) return undefined;
543
571
  this.#wanted.set(request.key, { wanted: stretches.get(end).bytes, required: minimum });
544
572
  await this.#spent(request.timing, "budgetMs", () => this.#revise());
@@ -576,7 +604,13 @@ export class EncodeInputs {
576
604
  if (this.#requests.get(request.key) !== request) return undefined;
577
605
  this.#wanted.set(request.key, { wanted: chosen.bytes, required: chosen.bytes });
578
606
  result = await admitOriginalInput({ sources: chosen.sources, readRanges: this.#read, now: this.#now,
579
- reserve: async bytes => this.#hold(request, bytes) });
607
+ reserve: async bytes => {
608
+ const reservation = { bytes, allowed: this.#allowed, held: this.#held, capacity,
609
+ room: roomNow(), end };
610
+ const release = this.#hold(request, bytes);
611
+ reservations.push({ ...reservation, admitted: typeof release === "function" });
612
+ return release;
613
+ } });
580
614
  // Another output's copy can take the room this one was sized for while
581
615
  // it was being chosen. A shorter stretch is tried at once rather than
582
616
  // waiting for a memory change that may never come.
@@ -585,6 +619,12 @@ export class EncodeInputs {
585
619
  }
586
620
  if (result.kind === "needs-memory") this.#wanted.set(request.key, { wanted: minimum, required: minimum });
587
621
  if (result.kind === "result") {
622
+ this.#log(`encode input selection of ${addressOf(output)}: ${JSON.stringify({
623
+ from, requestedTo: request.to, serves, floor, targetEnd, chosenEnd: end,
624
+ targetBytes: target, targetFixed: sizing?.fixed === true, minimumBytes: minimum, inputBytes: result.bytes,
625
+ copyBytesPerMs: sizing?.copyBytesPerMs ?? null, openSeconds: sizing?.openSeconds ?? null,
626
+ openMeasured: sizing?.openMeasured ?? null, reservations, probes
627
+ })}`);
588
628
  Object.assign(result, { from, to: end });
589
629
  // The stretch is copied after the answer, while the run opens; the rate
590
630
  // is the whole copy's, learned once it is done.
@@ -195,6 +195,9 @@ export function wireOutputs({
195
195
  // A software preset every picture is encoded at, for runs compared at one
196
196
  // quality (`--pin-preset`); null leaves the choice to the machine.
197
197
  pinnedPreset = null,
198
+ // A size every original-input stretch is given in place of copy rate
199
+ // times open (`--input-stretch-bytes`); null keeps the rule.
200
+ inputStretchBytes = null,
198
201
  // What every encoder this host may use was qualified and measured to do at
199
202
  // startup, by kind (`encode/calibration.js`). Takes the place of the one
200
203
  // array above, which a test may still hand in on its own.
@@ -1097,6 +1100,7 @@ export function wireOutputs({
1097
1100
  readRanges: readEncodeRanges,
1098
1101
  heldRanges: readEncodeHeldRanges,
1099
1102
  stretchCopied,
1103
+ stretchBytes: inputStretchBytes,
1100
1104
  reviseBudget: () => parts.machineBudget.revise("memory", "admitted encode inputs"),
1101
1105
  capacity: () => parts.machineBudget.capacityOf("memory"),
1102
1106
  // How long this output's input takes to open, from the one owner of start
@@ -1120,6 +1124,9 @@ export function wireOutputs({
1120
1124
  parts.serving.invalidateWaits(output);
1121
1125
  }
1122
1126
  }) : null);
1127
+ if (parts.encodeInputs && inputStretchBytes) {
1128
+ logger.info(`encode input: every original-input stretch sized to ${inputStretchBytes} bytes (--input-stretch-bytes)`);
1129
+ }
1123
1130
  // One owner of the disk, and the list of what takes it lives with the owner.
1124
1131
  parts.machineBudget = wireMachineBudget({
1125
1132
  segmentStore: parts.segmentStore,