@torrent-tv/proxy 2.107.26 → 2.107.27

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.26",
3
+ "version": "2.107.27",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -194,9 +194,10 @@ export class EncodeAdmission {
194
194
  }
195
195
 
196
196
  /**
197
- * Whether a not-yet-opened format can take a place on the whole machine.
198
- * The output choice calls this for each candidate rung; the final claim uses
199
- * `admitsWatching` with the same arithmetic after registering the output.
197
+ * Whether an output candidate can take a place on the whole machine. A
198
+ * partial output already in the catalogue uses its measured cost; a
199
+ * not-yet-opened output uses the same candidate estimate as before. The final
200
+ * claim uses `admitsWatching` after registering the output.
200
201
  *
201
202
  * @param {object} spec
202
203
  * @param {object} file
@@ -207,13 +208,22 @@ export class EncodeAdmission {
207
208
  if (!address) {
208
209
  return { admitted: false, reason: "the output has no address", speedX: null };
209
210
  }
211
+ if (this.#host.finished?.(address) === true) {
212
+ return { admitted: true, reason: "everything it serves is already made", speedX: null };
213
+ }
210
214
  if ((this.#host.liveRunsByAddress().get(address) ?? 0) > 0) {
211
215
  return { admitted: true, reason: "an encoder already runs there", speedX: null };
212
216
  }
213
217
  if (this.#heldWithoutRun().has(address)) {
214
218
  return { admitted: true, reason: "it already holds a place for another viewer", speedX: null };
215
219
  }
216
- const load = this.#host.loadForCandidate?.(spec, file) ?? null;
220
+ // A partial output already in the catalogue has its measured rate. Use it
221
+ // before the candidate estimate; completed outputs took the return above.
222
+ const existing = this.#host.loadOf(address);
223
+ const hasMeasuredExistingCost = Number.isFinite(existing?.costSec) && existing.costSec > 0;
224
+ const load = hasMeasuredExistingCost
225
+ ? existing
226
+ : this.#host.loadForCandidate?.(spec, file) ?? existing;
217
227
  return this.#assessCandidate(address, load);
218
228
  }
219
229
 
@@ -228,6 +228,20 @@ export function decideOutputFormat({
228
228
  rateControl: limit === null ? null : softwareRateControlFor({ width: w, height: h, fps, source, capKbps: limit })
229
229
  }));
230
230
  };
231
+ let machineRefusal = null;
232
+ const machineAdmissionFor = (spec) => typeof machineAdmission === "function" ? machineAdmission(spec) : null;
233
+ const machineCanContinue = (spec) => machineAdmissionFor(spec)?.admitted !== false;
234
+ const admittedByMachine = (spec) => {
235
+ if (!machineCan(spec.video?.encode?.width ?? 0, spec.video?.encode?.height ?? 0)) {
236
+ return false;
237
+ }
238
+ const answer = machineAdmissionFor(spec);
239
+ if (answer?.admitted === false) {
240
+ machineRefusal ??= answer;
241
+ return false;
242
+ }
243
+ return true;
244
+ };
231
245
  const wantedFormats = formatsAt(width, height);
232
246
  const wantedSpec = wantedFormats[0];
233
247
  const wantedKey = wantedSpec.toKey();
@@ -247,6 +261,7 @@ export function decideOutputFormat({
247
261
  wanted: { width, height },
248
262
  nextLowerArea: nextRungAreaBelow(buildResolutionLadder(ceiling.w, ceiling.h), height),
249
263
  atRisk: measured && !canSustainOutput({ benchmark, ...priced, outputPixelsPerSec: width * height * fps, frame: { width, height } }).sustainable,
264
+ canProduce: (candidate) => machineCanContinue(candidate.spec),
250
265
  judge: (candidate) => judge(candidate.spec),
251
266
  candidates: servingCandidates({
252
267
  wanted: wantedSpec,
@@ -262,18 +277,6 @@ export function decideOutputFormat({
262
277
  }
263
278
 
264
279
  let anySizeHeld = false;
265
- let machineRefusal = null;
266
- const admittedByMachine = (spec) => {
267
- if (!machineCan(spec.video?.encode?.width ?? 0, spec.video?.encode?.height ?? 0)) {
268
- return false;
269
- }
270
- const answer = typeof machineAdmission === "function" ? machineAdmission(spec) : null;
271
- if (answer?.admitted === false) {
272
- machineRefusal ??= answer;
273
- return false;
274
- }
275
- return true;
276
- };
277
280
  if (machineCan(width, height)) {
278
281
  for (const spec of wantedFormats) {
279
282
  if (!admittedByMachine(spec)) {
@@ -48,15 +48,17 @@ import { admissionRank } from "./link-budget.js";
48
48
  * on this viewer's own ladder; zero when there is none.
49
49
  * @param {boolean} params.atRisk - Producing `wanted` here is measured not to
50
50
  * keep up with what the file needs. False where nothing measured it.
51
+ * @param {(candidate: Candidate) => boolean} [params.canProduce] - Whether
52
+ * this machine can keep producing an output that is not finished.
51
53
  * @param {(candidate: Candidate) => { admitted: boolean, load: object }} params.judge -
52
54
  * What this viewer's link does with the candidate's whole load.
53
55
  * @param {Candidate[]} params.candidates
54
56
  * @returns {{ key: string, answer: object } | null} The output to serve, or
55
57
  * null to produce `wanted`.
56
58
  */
57
- export function chooseServingOutput({ mode, wanted, nextLowerArea, atRisk, judge, candidates }) {
59
+ export function chooseServingOutput({ mode, wanted, nextLowerArea, atRisk, canProduce = () => true, judge, candidates }) {
58
60
  const admitted = (candidates ?? [])
59
- .filter((candidate) => candidate.readyHere === true)
61
+ .filter((candidate) => candidate.readyHere === true && canProduce(candidate))
60
62
  .map((candidate) => ({ candidate, answer: judge(candidate) }))
61
63
  .filter((one) => one.answer.admitted);
62
64
  const areaOf = (one) => one.candidate.width * one.candidate.height;