@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
|
@@ -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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
#
|
|
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" :
|
|
289
|
-
const estimate =
|
|
290
|
-
|
|
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),
|