@torrent-tv/proxy 2.102.1 → 2.102.2
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
|
@@ -305,6 +305,16 @@ export class LocalObservations {
|
|
|
305
305
|
* @returns {number | null}
|
|
306
306
|
*/
|
|
307
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) {
|
|
308
318
|
if (!features || typeof features !== "object") return null;
|
|
309
319
|
const stages = ["transportMs", "planMs", "prepareMs", "prebufferMs", "presentationMs"];
|
|
310
320
|
const candidates = [];
|
|
@@ -319,7 +329,11 @@ export class LocalObservations {
|
|
|
319
329
|
const prices = stages.map(stage => median(candidates.map(reading => reading[stage])
|
|
320
330
|
.filter(Number.isFinite)));
|
|
321
331
|
if (prices.some(value => value === null)) return null;
|
|
322
|
-
return
|
|
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
|
+
};
|
|
323
337
|
}
|
|
324
338
|
|
|
325
339
|
#firstFrameKey() {
|
|
@@ -125,11 +125,13 @@ export class ViewerRequests {
|
|
|
125
125
|
const changed = this.#recordEarlyWaitSnapshot(context, context.features);
|
|
126
126
|
if (changed) context.revision += 1;
|
|
127
127
|
this.#pruneEarlyWaits();
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const seconds = earlyWaitRemainingSeconds(totalSeconds,
|
|
128
|
+
const elapsedMs = waitElapsedSince(context, waitElapsedMs);
|
|
129
|
+
const historical = Number.isFinite(context.activeTotalSeconds) ? null : this.#historicalFirstFrame(context.features);
|
|
130
|
+
const totalSeconds = Number.isFinite(context.activeTotalSeconds) ? context.activeTotalSeconds : historical?.seconds ?? null;
|
|
131
|
+
const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
|
|
132
132
|
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
133
|
+
this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical,
|
|
134
|
+
firstRequiredSegmentsSeconds: context.activeFirstRequiredSegmentsSeconds });
|
|
133
135
|
return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
|
|
134
136
|
}
|
|
135
137
|
const sameWait = current?.waitId === waitId;
|
|
@@ -139,11 +141,13 @@ export class ViewerRequests {
|
|
|
139
141
|
const changed = this.#recordEarlyWaitSnapshot(context, context.features);
|
|
140
142
|
if (sameWait && changed) context.revision += 1;
|
|
141
143
|
this.#pruneEarlyWaits();
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const seconds = earlyWaitRemainingSeconds(totalSeconds,
|
|
144
|
+
const elapsedMs = waitElapsedSince(context, waitElapsedMs);
|
|
145
|
+
const historical = Number.isFinite(context.activeTotalSeconds) ? null : this.#historicalFirstFrame(context.features);
|
|
146
|
+
const totalSeconds = Number.isFinite(context.activeTotalSeconds) ? context.activeTotalSeconds : historical?.seconds ?? null;
|
|
147
|
+
const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
|
|
146
148
|
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
149
|
+
this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical,
|
|
150
|
+
firstRequiredSegmentsSeconds: context.activeFirstRequiredSegmentsSeconds });
|
|
147
151
|
return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
|
|
148
152
|
}
|
|
149
153
|
|
|
@@ -192,20 +196,44 @@ export class ViewerRequests {
|
|
|
192
196
|
? elapsedMs / 1000 + measuredWorkSeconds
|
|
193
197
|
: context.activeTotalSeconds;
|
|
194
198
|
const activeEstimateChanged = hasActiveEstimate && activeTotalSeconds !== context.activeTotalSeconds;
|
|
195
|
-
if (hasActiveEstimate)
|
|
199
|
+
if (hasActiveEstimate) {
|
|
200
|
+
context.activeTotalSeconds = activeTotalSeconds;
|
|
201
|
+
context.activeFirstRequiredSegmentsSeconds = measuredWorkSeconds;
|
|
202
|
+
}
|
|
196
203
|
const changed = featuresChanged || activeEstimateChanged;
|
|
197
204
|
if (sameWait && changed) context.revision += 1;
|
|
198
205
|
this.#pruneEarlyWaits();
|
|
199
206
|
const progressHasComponentForecast = Object.hasOwn(processing, "firstRequiredSegmentsSeconds");
|
|
200
207
|
let totalSeconds = activeTotalSeconds;
|
|
208
|
+
let historical = null;
|
|
201
209
|
if (!Number.isFinite(totalSeconds) && !progressHasComponentForecast) {
|
|
202
|
-
|
|
210
|
+
historical = this.#historicalFirstFrame(context.features);
|
|
211
|
+
totalSeconds = historical?.seconds ?? null;
|
|
203
212
|
}
|
|
204
213
|
const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
|
|
205
214
|
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
215
|
+
this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical,
|
|
216
|
+
firstRequiredSegmentsSeconds: hasActiveEstimate ? measuredWorkSeconds : context.activeFirstRequiredSegmentsSeconds });
|
|
206
217
|
return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
|
|
207
218
|
}
|
|
208
219
|
|
|
220
|
+
#historicalFirstFrame(features) {
|
|
221
|
+
const estimate = this.#host.firstFrameSecondsOf?.(features) ?? null;
|
|
222
|
+
return Number.isFinite(estimate) ? { seconds: estimate, stagesMs: null, observations: null } : estimate;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical, firstRequiredSegmentsSeconds }) {
|
|
226
|
+
if (context.loggedForecastRevision === context.revision) return;
|
|
227
|
+
const basis = Number.isFinite(firstRequiredSegmentsSeconds) ? "first-required" : "historical-stages";
|
|
228
|
+
const estimate = basis === "first-required"
|
|
229
|
+
? { firstRequiredSegmentsSeconds }
|
|
230
|
+
: { stagesMs: historical?.stagesMs ?? null, observations: historical?.observations ?? null };
|
|
231
|
+
logger.info(`early wait forecast ${JSON.stringify({ waitId: context.waitId, consumer: context.consumerId,
|
|
232
|
+
generation: context.generation, features: context.features, elapsedMs, revision: context.revision,
|
|
233
|
+
predicted: { remainingSeconds: seconds, totalSeconds, basis, ...estimate } })}`);
|
|
234
|
+
context.loggedForecastRevision = context.revision;
|
|
235
|
+
}
|
|
236
|
+
|
|
209
237
|
/**
|
|
210
238
|
* Learn from the browser's presented-frame measurement for the current
|
|
211
239
|
* viewing. The saved source and link readings are the ones available at
|
|
@@ -651,7 +651,7 @@ export function wireOutputs({
|
|
|
651
651
|
const predicted = Number(qualityStateOf(output).predictedSpeedWhenOffered);
|
|
652
652
|
return Number.isFinite(predicted) && predicted > 0 ? predicted : null;
|
|
653
653
|
},
|
|
654
|
-
firstFrameSecondsOf: (features) => parts.localObservations?.
|
|
654
|
+
firstFrameSecondsOf: (features) => parts.localObservations?.firstFrameEstimate(features) ?? null,
|
|
655
655
|
noteFirstFrame: (observation) => parts.localObservations?.noteFirstFrame(observation),
|
|
656
656
|
audioDescriptionOf,
|
|
657
657
|
openSecondsOf: (output) => runStartSecondsOf(output),
|