@torrent-tv/proxy 2.107.42 → 2.107.44
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
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
import { ENCODE_RUN_EVENT, ENCODE_RUN_STATE, INITIAL_RUN_STATE, nextState } from "./encode-run-state.js";
|
|
41
41
|
import { classifyEncodeExit, ENCODE_EXIT } from "./encode-exit.js";
|
|
42
|
-
import {
|
|
42
|
+
import { workDeltaBetween } from "./encoder-readings.js";
|
|
43
43
|
import { RunClock } from "./RunClock.js";
|
|
44
44
|
import { RunProgress } from "./RunProgress.js";
|
|
45
45
|
|
|
@@ -229,7 +229,7 @@ export class EncodeRun {
|
|
|
229
229
|
* @param {{ info: (line: string) => void, warn: (line: string) => void, error?: (line: string) => void }} params.logger
|
|
230
230
|
* @param {() => number} [params.now]
|
|
231
231
|
* @param {(ended: RunEnded) => void} [params.onEnded]
|
|
232
|
-
* @param {(reading: { speed: number, at: number }) => void} [params.onSpeedMeasured] -
|
|
232
|
+
* @param {(reading: { speed: number, at: number, workSeconds: number, workingSeconds: number }) => void} [params.onSpeedMeasured] -
|
|
233
233
|
* Told each time this run has measured its processing speed, once per
|
|
234
234
|
* reading.
|
|
235
235
|
* @param {(name: string, following: string | null) => string | null} [params.onClosed] - Called with the
|
|
@@ -353,10 +353,10 @@ export class EncodeRun {
|
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
/**
|
|
356
|
-
* The
|
|
357
|
-
* piece
|
|
356
|
+
* The processing speed, its produced media and active working time, with
|
|
357
|
+
* the measurement time; null before a piece supplies a measured interval.
|
|
358
358
|
*
|
|
359
|
-
* @returns {{ speed: number, at: number } | null}
|
|
359
|
+
* @returns {{ speed: number, at: number, workSeconds: number, workingSeconds: number } | null}
|
|
360
360
|
*/
|
|
361
361
|
get speedReading() {
|
|
362
362
|
return this.#speedReading === null ? null : { ...this.#speedReading };
|
|
@@ -764,9 +764,9 @@ export class EncodeRun {
|
|
|
764
764
|
if (sample === null) {
|
|
765
765
|
return;
|
|
766
766
|
}
|
|
767
|
-
const
|
|
768
|
-
if (
|
|
769
|
-
this.#speedReading = { speed, at: sample.at };
|
|
767
|
+
const work = workDeltaBetween(this.#pieceSample, sample);
|
|
768
|
+
if (work !== null) {
|
|
769
|
+
this.#speedReading = { speed: work.workSeconds / work.workingSeconds, at: sample.at, ...work };
|
|
770
770
|
this.#pieceSample = sample;
|
|
771
771
|
this.#speedMeasurementPending = false;
|
|
772
772
|
this.onSpeedMeasured(this.speedReading);
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
export class SpeedReadings {
|
|
17
|
-
/** @type {WeakMap<object, Array<{ speed: number, at: number }>>} */
|
|
17
|
+
/** @type {WeakMap<object, Array<{ speed: number, at: number, workSeconds?: number, workingSeconds?: number }>>} */
|
|
18
18
|
#byOutput = new WeakMap();
|
|
19
19
|
#keepMs;
|
|
20
20
|
|
|
@@ -25,7 +25,7 @@ export class SpeedReadings {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* @param {object} output
|
|
28
|
-
* @param {{ speed: number, at: number } | null} reading
|
|
28
|
+
* @param {{ speed: number, at: number, workSeconds?: number, workingSeconds?: number } | null} reading
|
|
29
29
|
*/
|
|
30
30
|
note(output, reading) {
|
|
31
31
|
if (!output || !(reading?.speed > 0) || !Number.isFinite(reading.at)) return;
|
|
@@ -33,7 +33,12 @@ export class SpeedReadings {
|
|
|
33
33
|
if (!readings) this.#byOutput.set(output, readings = []);
|
|
34
34
|
let place = readings.length;
|
|
35
35
|
while (place > 0 && readings[place - 1].at > reading.at) place -= 1;
|
|
36
|
-
|
|
36
|
+
const sample = { speed: reading.speed, at: reading.at };
|
|
37
|
+
if (Number.isFinite(reading.workSeconds) && reading.workSeconds > 0 &&
|
|
38
|
+
Number.isFinite(reading.workingSeconds) && reading.workingSeconds > 0) {
|
|
39
|
+
Object.assign(sample, { workSeconds: reading.workSeconds, workingSeconds: reading.workingSeconds });
|
|
40
|
+
}
|
|
41
|
+
readings.splice(place, 0, sample);
|
|
37
42
|
const cutoff = readings.at(-1).at - this.#keepMs;
|
|
38
43
|
// As the forecast's own trend keeps them: the newest one before the horizon stays.
|
|
39
44
|
while (readings.length > 1 && readings[1].at <= cutoff) readings.shift();
|
|
@@ -41,7 +46,7 @@ export class SpeedReadings {
|
|
|
41
46
|
|
|
42
47
|
/**
|
|
43
48
|
* @param {object} output
|
|
44
|
-
* @returns {Array<{ speed: number, at: number }>} The
|
|
49
|
+
* @returns {Array<{ speed: number, at: number, workSeconds?: number, workingSeconds?: number }>} The readings in measurement order.
|
|
45
50
|
*/
|
|
46
51
|
windowOf(output) {
|
|
47
52
|
return [...(this.#byOutput.get(output) ?? [])];
|
|
@@ -31,13 +31,21 @@
|
|
|
31
31
|
* @returns {number | null} Film seconds made per second of the run's own work.
|
|
32
32
|
*/
|
|
33
33
|
export function speedFromWork(previous, current) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
const work = workDeltaBetween(previous, current);
|
|
35
|
+
return work === null ? null : work.workSeconds / work.workingSeconds;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Produced media and active processing time between samples of one run.
|
|
40
|
+
* @param {WorkSample | null} previous
|
|
41
|
+
* @param {WorkSample | null} current
|
|
42
|
+
* @returns {{ workSeconds: number, workingSeconds: number } | null}
|
|
43
|
+
*/
|
|
44
|
+
export function workDeltaBetween(previous, current) {
|
|
45
|
+
if (!previous || !current) return null;
|
|
46
|
+
const workSeconds = current.producedSeconds - previous.producedSeconds;
|
|
38
47
|
const workingSeconds = (current.workingMs - previous.workingMs) / 1000;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return producedSeconds / workingSeconds;
|
|
48
|
+
return Number.isFinite(workSeconds) && workSeconds > 0 &&
|
|
49
|
+
Number.isFinite(workingSeconds) && workingSeconds > 0
|
|
50
|
+
? { workSeconds, workingSeconds } : null;
|
|
43
51
|
}
|
|
@@ -76,12 +76,13 @@ export function forecastRate(readings, now = Date.now(), horizonSeconds = 0, wei
|
|
|
76
76
|
* ordered by when each was taken, readings taken in the same millisecond kept
|
|
77
77
|
* apart. Null for no reading.
|
|
78
78
|
*
|
|
79
|
-
* @param {Array<{ at: number, speed?: number, value?: number }>} readings
|
|
79
|
+
* @param {Array<{ at: number, speed?: number, value?: number, workSeconds?: number, workingSeconds?: number }>} readings
|
|
80
80
|
* @returns {object | null}
|
|
81
81
|
*/
|
|
82
82
|
export function rateSummaryOf(readings) {
|
|
83
83
|
const ordered = (Array.isArray(readings) ? readings : [])
|
|
84
|
-
.map(reading => ({ at: reading?.at, value: reading?.value ?? reading?.speed
|
|
84
|
+
.map(reading => ({ at: reading?.at, value: reading?.value ?? reading?.speed,
|
|
85
|
+
workSeconds: reading?.workSeconds, workingSeconds: reading?.workingSeconds }))
|
|
85
86
|
.filter(reading => Number.isFinite(reading.at) && Number.isFinite(reading.value) && reading.value >= 0)
|
|
86
87
|
.sort((left, right) => left.at - right.at);
|
|
87
88
|
return ordered.length > 0 ? summarizeRate(ordered) : null;
|
|
@@ -278,9 +279,9 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
278
279
|
[{ start: at, end: add(at, measured ? nearest(fromSeconds(buffered), unit) :
|
|
279
280
|
rescale(fromSeconds(buffered), unit)), frame: zero }],
|
|
280
281
|
ready: new Set(Array.isArray(track.readySegmentIndices) ? track.readySegmentIndices : []),
|
|
281
|
-
// Film made over
|
|
282
|
-
//
|
|
283
|
-
//
|
|
282
|
+
// Film made over active processing time, summed over the readings retained.
|
|
283
|
+
// Point-only legacy readings retain their equal-work harmonic summary.
|
|
284
|
+
// The last reading alone is one piece's content and machine — a
|
|
284
285
|
// first piece of credits read 6.49x and started a cold AVI on 2.4 s of
|
|
285
286
|
// buffer that the next piece, at 0.57x, did not hold (torrent-tv/meta#178).
|
|
286
287
|
curve: rateCurve(track.readings, now, "piece"),
|
|
@@ -855,6 +856,7 @@ function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
|
|
|
855
856
|
|
|
856
857
|
let outputBits = 0;
|
|
857
858
|
let latestTrackFinish = 0;
|
|
859
|
+
let earliestTrackStart = Number.POSITIVE_INFINITY;
|
|
858
860
|
for (let trackIndex = 0; trackIndex < trackState.length; trackIndex += 1) {
|
|
859
861
|
const state = trackState[trackIndex];
|
|
860
862
|
const end = state.tailEndSeconds;
|
|
@@ -873,7 +875,9 @@ function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
|
|
|
873
875
|
const localStart = tailStart - state.clockSeconds;
|
|
874
876
|
const inRun = run.spans.some(span => localStart >= span.start && localStart < span.end);
|
|
875
877
|
const open = work > 0 && !inRun ? run.openSeconds : 0;
|
|
876
|
-
const
|
|
878
|
+
const productionStart = trackFinish[trackIndex] + open;
|
|
879
|
+
const finishedAt = work > 0 ? processing.finish(work, productionStart) : trackFinish[trackIndex];
|
|
880
|
+
if (work > 0) earliestTrackStart = Math.min(earliestTrackStart, productionStart);
|
|
877
881
|
latestTrackFinish = Math.max(latestTrackFinish, finishedAt);
|
|
878
882
|
const deliveryStart = Math.max(tailStart, position + buffered);
|
|
879
883
|
outputBits += Math.max(0, state.bitsPerMediaSecond) * Math.max(0, end - deliveryStart);
|
|
@@ -884,7 +888,13 @@ function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
|
|
|
884
888
|
|
|
885
889
|
if (outputBits > 0) {
|
|
886
890
|
const end = Math.max(...trackState.map(state => state.tailEndSeconds));
|
|
887
|
-
|
|
891
|
+
// Production and transfer are tandem services. Starting the link only
|
|
892
|
+
// after the last track finishes charges both complete workloads in series.
|
|
893
|
+
// The fluid suffix is bounded by the later service completion, with the
|
|
894
|
+
// link queue beginning no earlier than the first produced output.
|
|
895
|
+
const deliveryStart = Math.max(transferFinish, Number.isFinite(earliestTrackStart) ? earliestTrackStart : 0);
|
|
896
|
+
const deliveryAt = link.finish(outputBits, deliveryStart);
|
|
897
|
+
const completedAt = Math.max(latestTrackFinish, deliveryAt);
|
|
888
898
|
const ahead = Math.max(0, end - position);
|
|
889
899
|
constrain(completedAt - Math.max(0, ahead - reserve), "delivery", 0,
|
|
890
900
|
trackState[0]?.segments.at(-1)?.index ?? 0, completedAt, ahead, false, sourceArrivalAt, false);
|
|
@@ -1417,6 +1427,10 @@ function rateCurve(readings, now, weighting = "last") {
|
|
|
1417
1427
|
|
|
1418
1428
|
function summarizeRate(readings) {
|
|
1419
1429
|
const latest = readings.at(-1);
|
|
1430
|
+
const workKnown = readings.every(reading => Number.isFinite(reading.workSeconds) && reading.workSeconds > 0 &&
|
|
1431
|
+
Number.isFinite(reading.workingSeconds) && reading.workingSeconds > 0);
|
|
1432
|
+
const workSeconds = workKnown ? readings.reduce((sum, reading) => sum + reading.workSeconds, 0) : null;
|
|
1433
|
+
const workingSeconds = workKnown ? readings.reduce((sum, reading) => sum + reading.workingSeconds, 0) : null;
|
|
1420
1434
|
let weighted = 0;
|
|
1421
1435
|
for (let index = 0; index + 1 < readings.length; index += 1) {
|
|
1422
1436
|
weighted += readings[index].value * (readings[index + 1].at - readings[index].at);
|
|
@@ -1427,12 +1441,13 @@ function summarizeRate(readings) {
|
|
|
1427
1441
|
lastAt: latest.at,
|
|
1428
1442
|
lastValue: latest.value,
|
|
1429
1443
|
meanValue: readings.reduce((sum, reading) => sum + reading.value, 0) / readings.length,
|
|
1430
|
-
//
|
|
1444
|
+
// Equal-work summary for legacy readings without measured work quantities.
|
|
1431
1445
|
harmonicValue: readings.some(reading => !(reading.value > 0)) ? 0
|
|
1432
1446
|
: readings.length / readings.reduce((sum, reading) => sum + 1 / reading.value, 0),
|
|
1433
1447
|
// Each reading holding until the next: quantity over the span.
|
|
1434
1448
|
timeWeightedValue: spanMs > 0 ? weighted / spanMs : latest.value,
|
|
1435
|
-
spanSeconds: spanMs / 1000
|
|
1449
|
+
spanSeconds: spanMs / 1000,
|
|
1450
|
+
...(workKnown ? { workSeconds, workingSeconds, workRateValue: workSeconds / workingSeconds } : {})
|
|
1436
1451
|
};
|
|
1437
1452
|
}
|
|
1438
1453
|
|
|
@@ -1440,7 +1455,8 @@ function rateCurveFrom(summary, weighting = "last") {
|
|
|
1440
1455
|
// Conditional projection of the measured service. No exponential
|
|
1441
1456
|
// relaxation, smoothing time, slope extrapolation or guessed recovery.
|
|
1442
1457
|
// A new measurement changes the integral; polling does not change its rate.
|
|
1443
|
-
const value = weighting === "piece" && Number.isFinite(summary.
|
|
1458
|
+
const value = weighting === "piece" && Number.isFinite(summary.workRateValue) ? summary.workRateValue
|
|
1459
|
+
: weighting === "piece" && Number.isFinite(summary.harmonicValue) ? summary.harmonicValue
|
|
1444
1460
|
: weighting === "time" && Number.isFinite(summary.timeWeightedValue) ? summary.timeWeightedValue
|
|
1445
1461
|
: summary.lastValue;
|
|
1446
1462
|
const rate = Math.max(0, value);
|