@torrent-tv/proxy 2.101.10 → 2.101.11
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
|
@@ -156,10 +156,19 @@ export class EncodeOrchestrator {
|
|
|
156
156
|
* Whether the run a viewer needs first is short of the processor, from the
|
|
157
157
|
* last time who has the processor was decided (`processorShortfall`).
|
|
158
158
|
*
|
|
159
|
-
* @type {{ address: string, speed: { middle: number, scatter: number }, need: number, seconds: number
|
|
159
|
+
* @type {{ address: string, speed: { middle: number, scatter: number }, need: number, seconds: number,
|
|
160
|
+
* waitedFor?: number } | null}
|
|
160
161
|
*/
|
|
161
162
|
#shortfall = null;
|
|
162
163
|
|
|
164
|
+
/**
|
|
165
|
+
* The piece each waiting viewer's start is held by the production of, as
|
|
166
|
+
* the start forecast last said it (`noteViewerWaiting`), by viewer.
|
|
167
|
+
*
|
|
168
|
+
* @type {Map<string, { address: string, index: number }>}
|
|
169
|
+
*/
|
|
170
|
+
#viewersWaiting = new Map();
|
|
171
|
+
|
|
163
172
|
/**
|
|
164
173
|
* What starting and stopping an encoder was measured to cost on this host,
|
|
165
174
|
* before any viewer existed. Told once; real runs replace it as they end.
|
|
@@ -455,6 +464,7 @@ export class EncodeOrchestrator {
|
|
|
455
464
|
this.#lastError.delete(address);
|
|
456
465
|
this.#lastProgress.delete(address);
|
|
457
466
|
this.#failedStarts.delete(address);
|
|
467
|
+
this.#forgetWaitsOn(address);
|
|
458
468
|
}
|
|
459
469
|
|
|
460
470
|
/** @param {string} address @returns {{ at: number, count: number, blocked: boolean, limit: number }} */
|
|
@@ -610,6 +620,37 @@ export class EncodeOrchestrator {
|
|
|
610
620
|
// right whatever the last one met.
|
|
611
621
|
if (!Array.isArray(zones) || zones.length === 0) {
|
|
612
622
|
this.#forgetInputState(address);
|
|
623
|
+
this.#forgetWaitsOn(address);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* What a viewer's start waits for, as the start forecast says each time it
|
|
629
|
+
* is asked: the production of piece `index` of `address`, or nothing an
|
|
630
|
+
* encoder makes (null). The map's deadlines can be met while the forecast
|
|
631
|
+
* still holds the viewer for the stock it wants after the piece they stand
|
|
632
|
+
* in: on Home Assistant (r87, torrent-tv/meta#180) the download was let go
|
|
633
|
+
* 5.3 s before a seek's viewer started, while the forecast held them for
|
|
634
|
+
* the production of the next piece. Who has the processor is decided again
|
|
635
|
+
* when it changes.
|
|
636
|
+
*
|
|
637
|
+
* @param {string} viewer
|
|
638
|
+
* @param {{ address: string, index: number } | null} wait
|
|
639
|
+
*/
|
|
640
|
+
noteViewerWaiting(viewer, wait) {
|
|
641
|
+
const said = this.#viewersWaiting.get(viewer) ?? null;
|
|
642
|
+
const next = typeof wait?.address === "string" && Number.isInteger(wait.index)
|
|
643
|
+
? { address: wait.address, index: wait.index } : null;
|
|
644
|
+
if (said?.address === next?.address && said?.index === next?.index) return;
|
|
645
|
+
if (next) this.#viewersWaiting.set(viewer, next);
|
|
646
|
+
else this.#viewersWaiting.delete(viewer);
|
|
647
|
+
this.shareTheMachine();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** @param {string} address */
|
|
651
|
+
#forgetWaitsOn(address) {
|
|
652
|
+
for (const [viewer, wait] of this.#viewersWaiting) {
|
|
653
|
+
if (wait.address === address) this.#viewersWaiting.delete(viewer);
|
|
613
654
|
}
|
|
614
655
|
}
|
|
615
656
|
|
|
@@ -739,9 +780,14 @@ export class EncodeOrchestrator {
|
|
|
739
780
|
const now = this.now();
|
|
740
781
|
const running = this.#runningNow();
|
|
741
782
|
const need = this.#needOf(leading);
|
|
742
|
-
// Only a run in a band a viewer is in can be short for a viewer
|
|
743
|
-
|
|
744
|
-
|
|
783
|
+
// Only a run in a band a viewer is in can be short for a viewer, and a run
|
|
784
|
+
// making a piece the start forecast holds a viewer for needs all it can
|
|
785
|
+
// make, whatever the map's deadlines say.
|
|
786
|
+
const waited = this.#runAViewerWaitsFor();
|
|
787
|
+
const judged = waited ?? (leading.urgent === true ? leading : null);
|
|
788
|
+
const short = judged
|
|
789
|
+
? this.#pauseRule.shortfall({ leader: judged.run, need: waited ? Number.POSITIVE_INFINITY : need }) : null;
|
|
790
|
+
this.#noteShortfall(short ? { address: judged.address, ...short, ...(waited ? { waitedFor: waited.index } : {}) } : null);
|
|
745
791
|
const free = this.#processorReading("freeCores");
|
|
746
792
|
const others = this.#processorReading("otherCores");
|
|
747
793
|
for (const [address, runs] of this.#runs) {
|
|
@@ -768,20 +814,41 @@ export class EncodeOrchestrator {
|
|
|
768
814
|
this.#placeHeldBack(leading);
|
|
769
815
|
}
|
|
770
816
|
|
|
817
|
+
/**
|
|
818
|
+
* The live run making a piece the start forecast holds a viewer for, the
|
|
819
|
+
* most urgent if there are several; null where none is.
|
|
820
|
+
*
|
|
821
|
+
* @returns {{ address: string, index: number, run: object, band: { urgent: boolean, priority: number } } | null}
|
|
822
|
+
*/
|
|
823
|
+
#runAViewerWaitsFor() {
|
|
824
|
+
let found = null;
|
|
825
|
+
for (const { address, index } of this.#viewersWaiting.values()) {
|
|
826
|
+
for (const run of this.#runs.get(address) ?? []) {
|
|
827
|
+
if (!run.isAlive || run.isStopping || run.isSuspended === true || index < run.head || index > run.to) continue;
|
|
828
|
+
const band = this.#bandAt(address, run.head);
|
|
829
|
+
if (!found || compareUrgency(band, found.band) < 0) found = { address, index, run, band };
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return found;
|
|
833
|
+
}
|
|
834
|
+
|
|
771
835
|
/**
|
|
772
836
|
* Whether the run a viewer needs first is short of the processor, and by
|
|
773
837
|
* what: its output, the speed it made over its seconds kept (median and
|
|
774
838
|
* scatter) and the speed its deadlines need, infinite where a viewer waits
|
|
775
|
-
* for it (`PauseRule.shortfall`, torrent-tv/meta#180).
|
|
776
|
-
*
|
|
839
|
+
* for it (`PauseRule.shortfall`, torrent-tv/meta#180). The run the start
|
|
840
|
+
* forecast holds a viewer for is judged before the map's leader, and names
|
|
841
|
+
* the piece it is waited for by (`waitedFor`). Null where it is not short,
|
|
842
|
+
* or nothing measured it.
|
|
777
843
|
*
|
|
778
|
-
* @returns {{ address: string, speed: { middle: number, scatter: number }, need: number, seconds: number
|
|
844
|
+
* @returns {{ address: string, speed: { middle: number, scatter: number }, need: number, seconds: number,
|
|
845
|
+
* waitedFor?: number } | null}
|
|
779
846
|
*/
|
|
780
847
|
processorShortfall() {
|
|
781
848
|
return this.#shortfall;
|
|
782
849
|
}
|
|
783
850
|
|
|
784
|
-
/** @param {{ address: string, speed: { middle: number, scatter: number }, need: number, seconds: number } | null} short */
|
|
851
|
+
/** @param {{ address: string, speed: { middle: number, scatter: number }, need: number, seconds: number, waitedFor?: number } | null} short */
|
|
785
852
|
#noteShortfall(short) {
|
|
786
853
|
this.#shortfall = short;
|
|
787
854
|
this.shortfallNoted();
|
|
@@ -790,6 +790,11 @@ export class ViewerRequests {
|
|
|
790
790
|
})}`);
|
|
791
791
|
}
|
|
792
792
|
this.#sayHolding(session, consumerId, measurement, forecast, input, now);
|
|
793
|
+
// The encoder this viewer waits for, told to whoever shares the processor
|
|
794
|
+
// (torrent-tv/meta#180): the forecast knows it before the map's deadlines do.
|
|
795
|
+
const holding = forecast.holdingBy;
|
|
796
|
+
this.#host.noteViewerWaiting?.(consumerId || session.id,
|
|
797
|
+
holding?.kind === "production" ? { address: holding.track, index: holding.segment } : null);
|
|
793
798
|
return forecast;
|
|
794
799
|
}
|
|
795
800
|
|
|
@@ -631,6 +631,7 @@ export function wireOutputs({
|
|
|
631
631
|
disposeSession: (...args) => parts.lifecycle.disposeSession(...args),
|
|
632
632
|
planEncodersSoon: (...args) => parts.encodeRuns.planEncodersSoon(...args),
|
|
633
633
|
waitUntilReady: (...args) => parts.serving.waitUntilReady(...args),
|
|
634
|
+
noteViewerWaiting: (viewer, wait) => parts.encodeOrchestrator?.noteViewerWaiting(viewer, wait),
|
|
634
635
|
get encodeRuns() { return parts.encodeRuns; },
|
|
635
636
|
// When this viewer's playback can start and run to the end: the viewer
|
|
636
637
|
// component's forecast, which models the viewer's browser.
|
|
@@ -781,7 +782,8 @@ export function wireOutputs({
|
|
|
781
782
|
const swarm = parts.hostLoad.swarmRate();
|
|
782
783
|
const made = `the encoder a viewer needs first made ${short.speed.middle.toFixed(2)}x ` +
|
|
783
784
|
`(scatter ${short.speed.scatter.toFixed(2)}) over ${short.seconds}s` +
|
|
784
|
-
(Number.isFinite(short.need) ? `, needing ${short.need.toFixed(2)}x` : " with a viewer waiting")
|
|
785
|
+
(Number.isFinite(short.need) ? `, needing ${short.need.toFixed(2)}x` : " with a viewer waiting") +
|
|
786
|
+
(Number.isInteger(short.waitedFor) ? ` for its #${short.waitedFor} by the start forecast` : "");
|
|
785
787
|
if (!Number.isFinite(needs) || needs <= 0) {
|
|
786
788
|
because = `${made}; its film's bytes per second are unknown`;
|
|
787
789
|
} else if (swarm === null) {
|