@torrent-tv/proxy 2.101.30 → 2.101.32
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
|
@@ -290,86 +290,30 @@ export class LocalObservations {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
/**
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
* the training observation, otherwise that observation is not comparable.
|
|
293
|
+
* Price the early wait from separate measured stage medians for the same
|
|
294
|
+
* output path. Numeric proximity of a past file, source rate, or link rate
|
|
295
|
+
* does not select a completed run. Each observation contributes once, using
|
|
296
|
+
* its latest snapshot that states the current cache and operation path.
|
|
298
297
|
*
|
|
299
298
|
* @param {object} features
|
|
300
299
|
* @returns {number | null}
|
|
301
300
|
*/
|
|
302
301
|
firstFrameSeconds(features) {
|
|
303
302
|
if (!features || typeof features !== "object") return null;
|
|
304
|
-
const candidates = (this.#entries.firstFrame[this.#firstFrameKey()] ?? [])
|
|
305
|
-
.filter(reading => reading.extension === (features.extension ?? "") && reading.stages);
|
|
306
|
-
if (candidates.length === 0) return null;
|
|
307
|
-
const dimensions = ["fileLength", "downloadSpeed", "linkMbps", "linkSampleAgeMs", "preparedOutputBytes",
|
|
308
|
-
"fileAvailable", "headerRemaining", "peers", "videoOperation", "audioOperation", "videoSpeedX", "audioSpeedX"];
|
|
309
|
-
const pointsOf = reading => Array.isArray(reading.featureSnapshots) && reading.featureSnapshots.length > 0
|
|
310
|
-
? reading.featureSnapshots : [reading];
|
|
311
|
-
const scales = new Map(dimensions.map(key => [key, median(candidates.flatMap(reading => pointsOf(reading)
|
|
312
|
-
.map(point => FIRST_FRAME_CATEGORICAL.has(key) ? null : nonNegative(point[key])))
|
|
313
|
-
.filter(value => value !== null && value > 0))]));
|
|
314
|
-
const distances = [];
|
|
315
|
-
for (const reading of candidates) {
|
|
316
|
-
let bestDistance = null;
|
|
317
|
-
for (const point of pointsOf(reading)) {
|
|
318
|
-
if ((features.preparedOutputBytes !== null && features.preparedOutputBytes !== undefined &&
|
|
319
|
-
point.preparedOutputBytes === null) ||
|
|
320
|
-
[...FIRST_FRAME_CATEGORICAL].some(key => {
|
|
321
|
-
const current = key === "fileAvailable" ? features[key] : operationOf(features[key]);
|
|
322
|
-
return current !== null && current !== undefined && point[key] !== current;
|
|
323
|
-
})) continue;
|
|
324
|
-
let distance = 0;
|
|
325
|
-
let used = 0;
|
|
326
|
-
let shared = 0;
|
|
327
|
-
for (const key of dimensions) {
|
|
328
|
-
const current = FIRST_FRAME_CATEGORICAL.has(key)
|
|
329
|
-
? key === "fileAvailable" ? (typeof features[key] === "boolean" ? features[key] : null)
|
|
330
|
-
: operationOf(features[key])
|
|
331
|
-
: nonNegative(features[key]);
|
|
332
|
-
const previous = FIRST_FRAME_CATEGORICAL.has(key)
|
|
333
|
-
? key === "fileAvailable" ? (typeof point[key] === "boolean" ? point[key] : null)
|
|
334
|
-
: operationOf(point[key])
|
|
335
|
-
: nonNegative(point[key]);
|
|
336
|
-
if (current === null && previous === null) continue;
|
|
337
|
-
if (current === null || previous === null) {
|
|
338
|
-
distance += 1;
|
|
339
|
-
used += 1;
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
shared += 1;
|
|
343
|
-
const scale = scales.get(key);
|
|
344
|
-
const difference = typeof current !== "number" && typeof previous !== "number"
|
|
345
|
-
? Number(current !== previous)
|
|
346
|
-
: scale === null
|
|
347
|
-
? Number(current !== previous)
|
|
348
|
-
: Math.log1p(current / scale) - Math.log1p(previous / scale);
|
|
349
|
-
distance += difference * difference;
|
|
350
|
-
used += 1;
|
|
351
|
-
}
|
|
352
|
-
if (used === 0 || shared === 0) continue;
|
|
353
|
-
distance /= used;
|
|
354
|
-
if (bestDistance === null || distance < bestDistance) bestDistance = distance;
|
|
355
|
-
}
|
|
356
|
-
if (bestDistance !== null) distances.push({ distance: bestDistance, reading });
|
|
357
|
-
}
|
|
358
|
-
if (distances.length === 0) return null;
|
|
359
|
-
const exact = distances.filter(candidate => candidate.distance === 0);
|
|
360
|
-
const weighted = exact.length > 0
|
|
361
|
-
? exact.map(candidate => ({ ...candidate, weight: 1 }))
|
|
362
|
-
: distances.map(candidate => ({ ...candidate, weight: 1 / candidate.distance }));
|
|
363
303
|
const stages = ["transportMs", "planMs", "prepareMs", "prebufferMs", "presentationMs"];
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
?
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
304
|
+
const candidates = [];
|
|
305
|
+
for (const reading of this.#entries.firstFrame[this.#firstFrameKey()] ?? []) {
|
|
306
|
+
if (reading.extension !== (features.extension ?? "") || !reading.stages) continue;
|
|
307
|
+
const points = Array.isArray(reading.featureSnapshots) && reading.featureSnapshots.length > 0
|
|
308
|
+
? reading.featureSnapshots : [reading];
|
|
309
|
+
const point = [...points].reverse().find(candidate => firstFramePathMatches(features, candidate));
|
|
310
|
+
if (point) candidates.push(reading.stages);
|
|
311
|
+
}
|
|
312
|
+
if (candidates.length === 0) return null;
|
|
313
|
+
const prices = stages.map(stage => median(candidates.map(reading => reading[stage])
|
|
314
|
+
.filter(Number.isFinite)));
|
|
315
|
+
if (prices.some(value => value === null)) return null;
|
|
316
|
+
return prices.reduce((sum, value) => sum + value, 0) / 1000;
|
|
373
317
|
}
|
|
374
318
|
|
|
375
319
|
#firstFrameKey() {
|
|
@@ -487,6 +431,20 @@ function operationOf(value) {
|
|
|
487
431
|
return value === "copy" || value === "encode" ? value : null;
|
|
488
432
|
}
|
|
489
433
|
|
|
434
|
+
/** @param {object} current @param {object} previous @returns {boolean} */
|
|
435
|
+
function firstFramePathMatches(current, previous) {
|
|
436
|
+
for (const key of FIRST_FRAME_CATEGORICAL) {
|
|
437
|
+
const expected = key === "fileAvailable" ? current[key] : operationOf(current[key]);
|
|
438
|
+
if (expected !== null && expected !== undefined && previous[key] !== expected) return false;
|
|
439
|
+
}
|
|
440
|
+
const preparedBytes = nonNegative(current.preparedOutputBytes);
|
|
441
|
+
if (preparedBytes !== null) {
|
|
442
|
+
const previousBytes = nonNegative(previous.preparedOutputBytes);
|
|
443
|
+
if (previousBytes === null || (previousBytes > 0) !== (preparedBytes > 0)) return false;
|
|
444
|
+
}
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
|
|
490
448
|
/** @param {unknown} value @returns {{ totalMs: number, transportMs: number, planMs: number, prepareMs: number, prebufferMs: number, presentationMs: number } | null} */
|
|
491
449
|
function stagesOf(value) {
|
|
492
450
|
if (!value || typeof value !== "object") return null;
|
|
@@ -132,7 +132,9 @@ export class ViewerRequests {
|
|
|
132
132
|
const changed = this.#recordEarlyWaitSnapshot(context, context.features);
|
|
133
133
|
if (changed) context.revision += 1;
|
|
134
134
|
this.#pruneEarlyWaits();
|
|
135
|
-
const totalSeconds =
|
|
135
|
+
const totalSeconds = Number.isFinite(context.activeTotalSeconds)
|
|
136
|
+
? context.activeTotalSeconds
|
|
137
|
+
: this.#host.firstFrameSecondsOf?.(context.features) ?? null;
|
|
136
138
|
const seconds = earlyWaitRemainingSeconds(totalSeconds, waitElapsedSince(context, waitElapsedMs));
|
|
137
139
|
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
138
140
|
return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
|
|
@@ -144,7 +146,9 @@ export class ViewerRequests {
|
|
|
144
146
|
const changed = this.#recordEarlyWaitSnapshot(context, context.features);
|
|
145
147
|
if (sameWait && changed) context.revision += 1;
|
|
146
148
|
this.#pruneEarlyWaits();
|
|
147
|
-
const totalSeconds =
|
|
149
|
+
const totalSeconds = Number.isFinite(context.activeTotalSeconds)
|
|
150
|
+
? context.activeTotalSeconds
|
|
151
|
+
: this.#host.firstFrameSecondsOf?.(context.features) ?? null;
|
|
148
152
|
const seconds = earlyWaitRemainingSeconds(totalSeconds, waitElapsedSince(context, waitElapsedMs));
|
|
149
153
|
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
150
154
|
return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
|
|
@@ -187,14 +191,22 @@ export class ViewerRequests {
|
|
|
187
191
|
const revision = sameWait ? current.revision : 0;
|
|
188
192
|
const context = this.#setEarlyWait({ consumerId, generation, fileIndex, waitId, features, revision, current,
|
|
189
193
|
sourceObserved: true, waitElapsedMs });
|
|
190
|
-
const
|
|
194
|
+
const featuresChanged = this.#recordEarlyWaitSnapshot(context, context.features);
|
|
195
|
+
const elapsedMs = waitElapsedSince(context, waitElapsedMs);
|
|
196
|
+
const measuredWorkSeconds = processing.firstRequiredSegmentsSeconds;
|
|
197
|
+
const hasActiveEstimate = Number.isFinite(measuredWorkSeconds) && measuredWorkSeconds >= 0;
|
|
198
|
+
const activeTotalSeconds = hasActiveEstimate
|
|
199
|
+
? elapsedMs / 1000 + measuredWorkSeconds
|
|
200
|
+
: context.activeTotalSeconds;
|
|
201
|
+
const activeEstimateChanged = hasActiveEstimate && activeTotalSeconds !== context.activeTotalSeconds;
|
|
202
|
+
if (hasActiveEstimate) context.activeTotalSeconds = activeTotalSeconds;
|
|
203
|
+
const changed = featuresChanged || activeEstimateChanged;
|
|
191
204
|
if (sameWait && changed) context.revision += 1;
|
|
192
205
|
this.#pruneEarlyWaits();
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
? waitElapsedSince(context, waitElapsedMs) / 1000 + measuredWorkSeconds
|
|
206
|
+
const totalSeconds = Number.isFinite(activeTotalSeconds)
|
|
207
|
+
? activeTotalSeconds
|
|
196
208
|
: this.#host.firstFrameSecondsOf?.(context.features) ?? null;
|
|
197
|
-
const seconds = earlyWaitRemainingSeconds(totalSeconds,
|
|
209
|
+
const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
|
|
198
210
|
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
199
211
|
return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
|
|
200
212
|
}
|