@torrent-tv/proxy 2.107.44 → 2.107.45

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.44",
3
+ "version": "2.107.45",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -62,7 +62,6 @@ export const PROXY_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url))
62
62
  * figure follows the host as it changes.
63
63
  */
64
64
  const READINGS_KEPT = 20;
65
- const FIRST_FRAME_CATEGORICAL = new Set(["fileAvailable", "videoOperation", "audioOperation"]);
66
65
 
67
66
  /** The version of the file's shape; a file of another shape is not read. */
68
67
  const FILE_VERSION = 1;
@@ -292,50 +291,6 @@ export class LocalObservations {
292
291
  this.#save();
293
292
  }
294
293
 
295
- /**
296
- * Price the early wait from separate measured stage medians on this proxy
297
- * configuration. Before the container and track operations are known, use
298
- * measurements from every observed file type; once current cache and
299
- * operation facts are known, retain only observations that match those
300
- * facts. Numeric proximity to a past file, source rate, or link rate never
301
- * selects a completed run. Each observation contributes once, using its
302
- * latest snapshot that states the current cache and operation path.
303
- *
304
- * @param {object} features
305
- * @returns {number | null}
306
- */
307
- firstFrameSeconds(features) {
308
- return this.firstFrameEstimate(features)?.seconds ?? null;
309
- }
310
-
311
- /**
312
- * Return the current stage-median forecast and the measurements that formed it.
313
- *
314
- * @param {object} features
315
- * @returns {{ seconds: number, stagesMs: object, observations: number } | null}
316
- */
317
- firstFrameEstimate(features) {
318
- if (!features || typeof features !== "object") return null;
319
- const stages = ["transportMs", "planMs", "prepareMs", "prebufferMs", "presentationMs"];
320
- const candidates = [];
321
- for (const reading of this.#entries.firstFrame[this.#firstFrameKey()] ?? []) {
322
- if (!reading.stages) continue;
323
- const points = Array.isArray(reading.featureSnapshots) && reading.featureSnapshots.length > 0
324
- ? reading.featureSnapshots : [reading];
325
- const point = [...points].reverse().find(candidate => firstFramePathMatches(features, candidate));
326
- if (point) candidates.push(reading.stages);
327
- }
328
- if (candidates.length === 0) return null;
329
- const prices = stages.map(stage => median(candidates.map(reading => reading[stage])
330
- .filter(Number.isFinite)));
331
- if (prices.some(value => value === null)) return null;
332
- return {
333
- seconds: prices.reduce((sum, value) => sum + value, 0) / 1000,
334
- stagesMs: Object.fromEntries(stages.map((stage, index) => [stage, prices[index]])),
335
- observations: candidates.length
336
- };
337
- }
338
-
339
294
  #firstFrameKey() {
340
295
  return `firstFrame:${[...this.#configurations].sort().join("||")}`;
341
296
  }
@@ -451,20 +406,6 @@ function operationOf(value) {
451
406
  return value === "copy" || value === "encode" ? value : null;
452
407
  }
453
408
 
454
- /** @param {object} current @param {object} previous @returns {boolean} */
455
- function firstFramePathMatches(current, previous) {
456
- for (const key of FIRST_FRAME_CATEGORICAL) {
457
- const expected = key === "fileAvailable" ? current[key] : operationOf(current[key]);
458
- if (expected !== null && expected !== undefined && previous[key] !== expected) return false;
459
- }
460
- const preparedBytes = nonNegative(current.preparedOutputBytes);
461
- if (preparedBytes !== null) {
462
- const previousBytes = nonNegative(previous.preparedOutputBytes);
463
- if (previousBytes === null || (previousBytes > 0) !== (preparedBytes > 0)) return false;
464
- }
465
- return true;
466
- }
467
-
468
409
  /** @param {unknown} value @returns {{ totalMs: number, transportMs: number, planMs: number, prepareMs: number, prebufferMs: number, presentationMs: number } | null} */
469
410
  function stagesOf(value) {
470
411
  if (!value || typeof value !== "object") return null;
@@ -492,14 +433,6 @@ function firstFrameFeatures(features) {
492
433
  };
493
434
  }
494
435
 
495
- /** @param {number[]} values @returns {number | null} */
496
- function median(values) {
497
- if (values.length === 0) return null;
498
- const sorted = [...values].sort((left, right) => left - right);
499
- const middle = Math.floor(sorted.length / 2);
500
- return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle];
501
- }
502
-
503
436
  /**
504
437
  * What an observation of an encode is filed as having been made FROM.
505
438
  *
@@ -164,11 +164,10 @@ export class ViewerRequests {
164
164
  if (changed) context.revision += 1;
165
165
  this.#pruneEarlyWaits();
166
166
  const elapsedMs = waitElapsedSince(context, waitElapsedMs);
167
- const historical = Number.isFinite(context.activeTotalSeconds) ? null : this.#historicalFirstFrame(context.features);
168
- const totalSeconds = Number.isFinite(context.activeTotalSeconds) ? context.activeTotalSeconds : historical?.seconds ?? null;
167
+ const totalSeconds = context.activeTotalSeconds;
169
168
  const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
170
169
  if (!Number.isFinite(seconds) || seconds < 0) return null;
171
- this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical,
170
+ this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds,
172
171
  firstRequiredSegmentsSeconds: context.activeFirstRequiredSegmentsSeconds });
173
172
  return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
174
173
  }
@@ -180,11 +179,10 @@ export class ViewerRequests {
180
179
  if (sameWait && changed) context.revision += 1;
181
180
  this.#pruneEarlyWaits();
182
181
  const elapsedMs = waitElapsedSince(context, waitElapsedMs);
183
- const historical = Number.isFinite(context.activeTotalSeconds) ? null : this.#historicalFirstFrame(context.features);
184
- const totalSeconds = Number.isFinite(context.activeTotalSeconds) ? context.activeTotalSeconds : historical?.seconds ?? null;
182
+ const totalSeconds = context.activeTotalSeconds;
185
183
  const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
186
184
  if (!Number.isFinite(seconds) || seconds < 0) return null;
187
- this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical,
185
+ this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds,
188
186
  firstRequiredSegmentsSeconds: context.activeFirstRequiredSegmentsSeconds,
189
187
  firstRequiredStages: context.activeFirstRequiredStages });
190
188
  return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
@@ -259,37 +257,24 @@ export class ViewerRequests {
259
257
  const changed = featuresChanged || activeEstimateChanged;
260
258
  if (sameWait && changed) context.revision += 1;
261
259
  this.#pruneEarlyWaits();
262
- const progressHasComponentForecast = Object.hasOwn(processing, "firstRequiredSegmentsSeconds");
263
- let totalSeconds = activeTotalSeconds;
264
- let historical = null;
265
- if (!Number.isFinite(totalSeconds) && !progressHasComponentForecast) {
266
- historical = this.#historicalFirstFrame(context.features);
267
- totalSeconds = historical?.seconds ?? null;
268
- }
260
+ const totalSeconds = activeTotalSeconds;
269
261
  const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
270
262
  if (!Number.isFinite(seconds) || seconds < 0) return null;
271
- this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical,
263
+ this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds,
272
264
  firstRequiredSegmentsSeconds: hasActiveEstimate ? firstRequiredSeconds ?? null : context.activeFirstRequiredSegmentsSeconds,
273
265
  startGateSeconds: hasActiveEstimate && Number.isFinite(startGateSeconds) ? startGateSeconds : null,
274
266
  firstRequiredStages: context.activeFirstRequiredStages });
275
267
  return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
276
268
  }
277
269
 
278
- #historicalFirstFrame(features) {
279
- const estimate = this.#host.firstFrameSecondsOf?.(features) ?? null;
280
- return Number.isFinite(estimate) ? { seconds: estimate, stagesMs: null, observations: null } : estimate;
281
- }
282
-
283
- #logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical, firstRequiredSegmentsSeconds,
270
+ #logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, firstRequiredSegmentsSeconds,
284
271
  startGateSeconds = null, firstRequiredStages }) {
285
272
  if (context.loggedForecastRevision === context.revision) return;
286
273
  const gated = Number.isFinite(startGateSeconds) &&
287
274
  !(Number.isFinite(firstRequiredSegmentsSeconds) && firstRequiredSegmentsSeconds >= startGateSeconds);
288
- const basis = gated ? "start-gate" : Number.isFinite(firstRequiredSegmentsSeconds) ? "first-required" : "historical-stages";
289
- const estimate = basis !== "historical-stages"
290
- ? { firstRequiredSegmentsSeconds, ...(Number.isFinite(startGateSeconds) ? { startGateSeconds } : {}),
291
- ...(Array.isArray(firstRequiredStages) ? { firstRequiredStages } : {}) }
292
- : { stagesMs: historical?.stagesMs ?? null, observations: historical?.observations ?? null };
275
+ const basis = gated ? "start-gate" : "first-required";
276
+ const estimate = { firstRequiredSegmentsSeconds, ...(Number.isFinite(startGateSeconds) ? { startGateSeconds } : {}),
277
+ ...(Array.isArray(firstRequiredStages) ? { firstRequiredStages } : {}) };
293
278
  logger.info(`early wait forecast ${JSON.stringify({ waitId: context.waitId, consumer: context.consumerId,
294
279
  generation: context.generation, features: context.features, elapsedMs, revision: context.revision,
295
280
  predicted: { remainingSeconds: seconds, totalSeconds, basis, ...estimate } })}`);
@@ -652,7 +652,6 @@ export function wireOutputs({
652
652
  const predicted = Number(qualityStateOf(output).predictedSpeedWhenOffered);
653
653
  return Number.isFinite(predicted) && predicted > 0 ? predicted : null;
654
654
  },
655
- firstFrameSecondsOf: (features) => parts.localObservations?.firstFrameEstimate(features) ?? null,
656
655
  noteFirstFrame: (observation) => parts.localObservations?.noteFirstFrame(observation),
657
656
  audioDescriptionOf,
658
657
  openSecondsOf: (output) => runStartSecondsOf(output),