@torrent-tv/proxy 2.96.10 → 2.96.12
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
package/server.js
CHANGED
|
@@ -55,6 +55,7 @@ import { createPlaybackPlanner } from "./services/media/playback-planner.js";
|
|
|
55
55
|
import { KeyframeTables } from "./services/media/KeyframeTables.js";
|
|
56
56
|
import { contentsOf } from "./services/torrent/Contents.js";
|
|
57
57
|
import { joinedWithinPieces } from "./services/torrent/demand/pieces.js";
|
|
58
|
+
import { withoutRepeatedZones } from "./services/torrent/map-zones.js";
|
|
58
59
|
import { SubtitleOrchestrator } from "./services/media/SubtitleOrchestrator.js";
|
|
59
60
|
import { containerOrchestrator, CONTAINER_HEAD_BYTES, describeWorkTags } from "./services/media/ContainerOrchestrator.js";
|
|
60
61
|
import { readPlaybackDeclarations } from "./services/media/read-playback-declarations.js";
|
|
@@ -533,7 +534,9 @@ export async function startProxyServer({
|
|
|
533
534
|
segmentSourceRanges.set(rangesKey, input.ranges);
|
|
534
535
|
convert(input.ranges);
|
|
535
536
|
}
|
|
536
|
-
|
|
537
|
+
// The ranges a picture reads at open are named again by every segment;
|
|
538
|
+
// only the zone the swarm would rank first is sent (torrent-tv/meta#166).
|
|
539
|
+
return withoutRepeatedZones(converted);
|
|
537
540
|
}
|
|
538
541
|
});
|
|
539
542
|
const mediaReads = new MediaReadRequests({
|
|
@@ -33,6 +33,24 @@ import { contentionPenalty } from "./contention.js";
|
|
|
33
33
|
import { SegmentDemand } from "./SegmentDemand.js";
|
|
34
34
|
import { runStateOf } from "./encode-run-state.js";
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Which of two bands of the map is wanted sooner: what is needed before
|
|
38
|
+
* playback can continue first, then the higher priority.
|
|
39
|
+
*
|
|
40
|
+
* Everything needed before playback can continue is ONE band, whatever its
|
|
41
|
+
* priority: two viewers each short of their own minimum share the machine, and
|
|
42
|
+
* neither is stopped for the other. Priority orders only what can wait.
|
|
43
|
+
*
|
|
44
|
+
* @param {{ urgent: boolean, priority: number }} left
|
|
45
|
+
* @param {{ urgent: boolean, priority: number }} right
|
|
46
|
+
* @returns {number} Negative when `left` is more urgent, zero when equal.
|
|
47
|
+
*/
|
|
48
|
+
export function compareUrgency(left, right) {
|
|
49
|
+
const urgent = Number(right.urgent === true) - Number(left.urgent === true);
|
|
50
|
+
if (urgent !== 0 || left.urgent === true) return urgent;
|
|
51
|
+
return right.priority - left.priority;
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
/**
|
|
37
55
|
* @typedef {Object} SegmentCoverage
|
|
38
56
|
* @property {(address: string) => number[]} provenNumbers
|
|
@@ -105,6 +123,14 @@ export class EncodeOrchestrator {
|
|
|
105
123
|
/** The last state said out loud, so an unchanged state is not repeated. */
|
|
106
124
|
#lastDescribed = "";
|
|
107
125
|
|
|
126
|
+
/**
|
|
127
|
+
* The most urgent band any live encoder stands in, over every output, as the
|
|
128
|
+
* pass that is deciding now found it. Null when no encoder is alive.
|
|
129
|
+
*
|
|
130
|
+
* @type {{ urgent: boolean, priority: number, address: string, index: number } | null}
|
|
131
|
+
*/
|
|
132
|
+
#leading = null;
|
|
133
|
+
|
|
108
134
|
/** What a stop and a start have cost on this host. */
|
|
109
135
|
#costs = new RunCosts();
|
|
110
136
|
|
|
@@ -575,6 +601,9 @@ export class EncodeOrchestrator {
|
|
|
575
601
|
for (const run of this.runsOn(address)) {
|
|
576
602
|
run.noteProduced(index);
|
|
577
603
|
}
|
|
604
|
+
// A closed piece moves its encoder forward, perhaps out of the band that
|
|
605
|
+
// led: who has the processor is decided again now, not at the next pass.
|
|
606
|
+
this.shareTheMachine();
|
|
578
607
|
}
|
|
579
608
|
|
|
580
609
|
/**
|
|
@@ -586,9 +615,11 @@ export class EncodeOrchestrator {
|
|
|
586
615
|
*/
|
|
587
616
|
reconcile() {
|
|
588
617
|
const addresses = new Set([...this.#demand.addresses(), ...this.#runs.keys()]);
|
|
618
|
+
this.#leading = this.#leadingBand();
|
|
589
619
|
for (const address of addresses) {
|
|
590
620
|
this.#reconcileOne(address);
|
|
591
621
|
}
|
|
622
|
+
this.shareTheMachine();
|
|
592
623
|
// WHAT THIS CLASS BELIEVES, said by this class. `describe()` was written
|
|
593
624
|
// and called from nowhere, so on 2026-09-05 the question "why did the plan
|
|
594
625
|
// not see the gap the viewer was stopped at" had to be answered by
|
|
@@ -604,6 +635,82 @@ export class EncodeOrchestrator {
|
|
|
604
635
|
}
|
|
605
636
|
}
|
|
606
637
|
|
|
638
|
+
/**
|
|
639
|
+
* How urgently the map wants one segment of one output: whether it is needed
|
|
640
|
+
* before playback can continue, and the map's priority for it. A number in
|
|
641
|
+
* nobody's zone is wanted least of all.
|
|
642
|
+
*
|
|
643
|
+
* The priority is a step of distance from a viewer on one scale for every
|
|
644
|
+
* output, so the picture and the soundtrack a viewer plays are compared on it
|
|
645
|
+
* directly.
|
|
646
|
+
*
|
|
647
|
+
* @param {string} address
|
|
648
|
+
* @param {number} index
|
|
649
|
+
* @returns {{ urgent: boolean, priority: number }}
|
|
650
|
+
*/
|
|
651
|
+
#bandAt(address, index) {
|
|
652
|
+
let band = { urgent: false, priority: 0 };
|
|
653
|
+
for (const zone of this.#demand.mapOn(address)) {
|
|
654
|
+
if (index < zone.from || index > zone.to) continue;
|
|
655
|
+
const candidate = { urgent: zone.urgent === true, priority: Number(zone.priority) || 0 };
|
|
656
|
+
if (compareUrgency(candidate, band) < 0) band = candidate;
|
|
657
|
+
}
|
|
658
|
+
return band;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* The most urgent band a live encoder stands in, over every output.
|
|
663
|
+
*
|
|
664
|
+
* @returns {{ urgent: boolean, priority: number, address: string, index: number } | null}
|
|
665
|
+
*/
|
|
666
|
+
#leadingBand() {
|
|
667
|
+
let leading = null;
|
|
668
|
+
for (const [address, runs] of this.#runs) {
|
|
669
|
+
for (const run of runs) {
|
|
670
|
+
if (!run.isAlive || run.isStopping) continue;
|
|
671
|
+
const band = this.#bandAt(address, run.head);
|
|
672
|
+
if (!leading || compareUrgency(band, leading) < 0) leading = { ...band, address, index: run.head };
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return leading;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* WHO GETS THE PROCESSOR FIRST, over every output at once.
|
|
680
|
+
*
|
|
681
|
+
* Each output is planned on its own, and a file is encoded whole in the order
|
|
682
|
+
* its map gives (the rule of 2026-09-05), so an output that is cheap to make
|
|
683
|
+
* runs far ahead of one that is dear: field 2026-10-10, a copied soundtrack
|
|
684
|
+
* at 38 minutes while its picture, re-encoded at 0.65–1.31x, was at five and
|
|
685
|
+
* the viewer stalled. The machine is one, so the order is one: an encoder
|
|
686
|
+
* making something less urgent than what another encoder is making somewhere
|
|
687
|
+
* stands still where it is — paused, not stopped, so it keeps its place, its
|
|
688
|
+
* input and its claim — and goes on the moment nothing more urgent is being
|
|
689
|
+
* made. The encoder in the most urgent band is never paused, so something is
|
|
690
|
+
* always being made.
|
|
691
|
+
*
|
|
692
|
+
* Paused rather than lowered in priority: a lowered priority cannot be raised
|
|
693
|
+
* again without `CAP_SYS_NICE`, which the add-on's container does not have
|
|
694
|
+
* (Home Assistant 2026-10-10: `renice 0` after `renice 19` left 19).
|
|
695
|
+
*/
|
|
696
|
+
shareTheMachine() {
|
|
697
|
+
const leading = this.#leadingBand();
|
|
698
|
+
this.#leading = leading;
|
|
699
|
+
if (!leading) return;
|
|
700
|
+
for (const [address, runs] of this.#runs) {
|
|
701
|
+
for (const run of runs) {
|
|
702
|
+
if (!run.isAlive || run.isStopping || typeof run.pause !== "function") continue;
|
|
703
|
+
const behind = compareUrgency(this.#bandAt(address, run.head), leading) > 0;
|
|
704
|
+
if (behind && run.isSuspended !== true) {
|
|
705
|
+
run.pause(`#${run.head} of ${address} is wanted less urgently than #${leading.index} of ${leading.address}, ` +
|
|
706
|
+
`which is being made`);
|
|
707
|
+
} else if (!behind && run.isSuspended === true) {
|
|
708
|
+
run.resume(`nothing more urgent than #${run.head} of ${address} is being made`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
607
714
|
/**
|
|
608
715
|
* @param {string} address
|
|
609
716
|
*/
|
|
@@ -739,7 +846,16 @@ export class EncodeOrchestrator {
|
|
|
739
846
|
`live=${JSON.stringify(live.map((run) => ({ from: run.from, to: run.to, head: run.head, speedX: run.speedX })))}`
|
|
740
847
|
);
|
|
741
848
|
}
|
|
849
|
+
// A START BEHIND WHAT IS BEING MADE ELSEWHERE IS NOT MADE: the encoder would
|
|
850
|
+
// be paused the moment it exists (`shareTheMachine`), holding a process and
|
|
851
|
+
// its input for nothing. It is placed when nothing more urgent is in hand.
|
|
852
|
+
let heldBack = null;
|
|
742
853
|
for (const action of actions) {
|
|
854
|
+
if (action.type === "start" && this.#leading &&
|
|
855
|
+
compareUrgency(this.#bandAt(address, action.from), this.#leading) > 0) {
|
|
856
|
+
heldBack = action;
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
743
859
|
if (action.type === "stop") {
|
|
744
860
|
this.#stop(action.run, action.because);
|
|
745
861
|
continue;
|
|
@@ -795,6 +911,15 @@ export class EncodeOrchestrator {
|
|
|
795
911
|
const stillRunning = this.runsOn(address).filter((run) => run.isAlive);
|
|
796
912
|
if (wanting === null || stillRunning.length > 0) {
|
|
797
913
|
this.#lastUnmet.delete(address);
|
|
914
|
+
} else if (heldBack) {
|
|
915
|
+
const said = `held:${heldBack.from}:${this.#leading?.address}:${this.#leading?.index}`;
|
|
916
|
+
if (this.#lastUnmet.get(address) !== said) {
|
|
917
|
+
this.#lastUnmet.set(address, said);
|
|
918
|
+
this.logger.info(
|
|
919
|
+
`encode: #${heldBack.from} of ${address} waits — #${this.#leading?.index} of ` +
|
|
920
|
+
`${this.#leading?.address} is wanted more urgently and is being made`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
798
923
|
} else if (this.#lastUnmet.get(address) !== wanting) {
|
|
799
924
|
this.#lastUnmet.set(address, wanting);
|
|
800
925
|
const held = this.#segmentCoverage ? this.#segmentCoverage.filesHeld(address) : -1;
|
|
@@ -216,6 +216,8 @@ export class EncodeRun {
|
|
|
216
216
|
* encoder itself names it on its own channel, and answers with the name that
|
|
217
217
|
* piece is served under — because making it servable is a rename, and only
|
|
218
218
|
* whoever owns the disk can perform one.
|
|
219
|
+
* @param {(index: number) => void} [params.onProduced] - Called with every
|
|
220
|
+
* segment number this run has closed, as it closes it.
|
|
219
221
|
* @param {(progress: object) => void} [params.onProgress] - Called with this
|
|
220
222
|
* run's progress on the source timeline after every `-progress` report.
|
|
221
223
|
* @param {() => number | null} [params.lastSegmentIndex] - The film's last
|
|
@@ -251,6 +253,7 @@ export class EncodeRun {
|
|
|
251
253
|
onSpeedMeasured,
|
|
252
254
|
onClosed,
|
|
253
255
|
onProgress,
|
|
256
|
+
onProduced,
|
|
254
257
|
lastSegmentIndex,
|
|
255
258
|
inputUnavailable,
|
|
256
259
|
argsDescribed = "",
|
|
@@ -274,6 +277,7 @@ export class EncodeRun {
|
|
|
274
277
|
this.clock = new RunClock({ now: this.now });
|
|
275
278
|
this.onEnded = typeof onEnded === "function" ? onEnded : () => {};
|
|
276
279
|
this.onProgress = typeof onProgress === "function" ? onProgress : () => {};
|
|
280
|
+
this.onProduced = typeof onProduced === "function" ? onProduced : () => {};
|
|
277
281
|
this.onSpeedMeasured = typeof onSpeedMeasured === "function" ? onSpeedMeasured : () => {};
|
|
278
282
|
// Told the NAME of every piece the encoder has closed. The name is the
|
|
279
283
|
// proof it is whole; nothing else here can prove that.
|
|
@@ -569,6 +573,7 @@ export class EncodeRun {
|
|
|
569
573
|
if (this.#state === ENCODE_RUN_STATE.STARTING) {
|
|
570
574
|
this.#transition(ENCODE_RUN_EVENT.FIRST_SEGMENT);
|
|
571
575
|
}
|
|
576
|
+
this.onProduced(index);
|
|
572
577
|
}
|
|
573
578
|
}
|
|
574
579
|
|
|
@@ -999,6 +999,9 @@ export class EncodeRuns {
|
|
|
999
999
|
run.to,
|
|
1000
1000
|
inputUnavailable: (message) => isInputUnavailable(message),
|
|
1001
1001
|
onProgress: (report) => this.#noteRunProgress(session, run, report),
|
|
1002
|
+
// A closed piece moves this encoder forward, perhaps out of the band that
|
|
1003
|
+
// led; who has the processor is decided again at once.
|
|
1004
|
+
onProduced: () => this.#host.encodeOrchestrator?.shareTheMachine?.(),
|
|
1002
1005
|
indexOfName: (name) => session.segmentFormat.segmentIndexFromName(
|
|
1003
1006
|
session.segmentFormat.servedNameOf?.(name) ?? name),
|
|
1004
1007
|
// Why this encoder exists, recorded with its argument list. It used to be
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file What one zone of the priority map tells the torrent: the level of
|
|
3
|
+
* urgency it is stated at, and the band the swarm ranks it by.
|
|
4
|
+
*
|
|
5
|
+
* Pure arithmetic over the zone's own fields, so the main thread can ask the
|
|
6
|
+
* same question before a map is sent and the torrent thread when it is applied.
|
|
7
|
+
*/
|
|
8
|
+
import { isAtAWatchingViewer, isBehindEverybody, isNobodyComingNow } from "../viewer/PriorityMap.js";
|
|
9
|
+
import { Urgency } from "./demand/Urgency.js";
|
|
10
|
+
import { compareBands } from "./download/bands.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Which level of urgency one zone is stated at, from the map's own number
|
|
14
|
+
* and nothing else. The map's numbers are a scale as long as the film needs
|
|
15
|
+
* — ten bands on a fifty-minute film — while the register has five levels,
|
|
16
|
+
* and the fit is by meaning:
|
|
17
|
+
*
|
|
18
|
+
* - the top of the scale is where a viewer is standing, so it is the
|
|
19
|
+
* cushion being built: {@link Urgency.NEAR}. Never BLOCKED — that level
|
|
20
|
+
* means a reader is stopped on those bytes right now, which only a read
|
|
21
|
+
* can say;
|
|
22
|
+
* - the bottom is what nobody is approaching: behind a viewer moving
|
|
23
|
+
* forward, and the whole film of a viewer who has stopped the picture.
|
|
24
|
+
* Wanted only if somebody seeks back, which is {@link Urgency.BEHIND};
|
|
25
|
+
* - one above the bottom is the far tail — wanted for certain if the
|
|
26
|
+
* viewer watches on, and not before: {@link Urgency.TAIL};
|
|
27
|
+
* - everything between is the lead being built: {@link Urgency.AHEAD}.
|
|
28
|
+
*
|
|
29
|
+
* Read from the scale's own ends rather than from the highest and lowest
|
|
30
|
+
* number in THIS file's map. The two speculative levels are withheld across
|
|
31
|
+
* every torrent at once while anything urgent is missing anywhere, so a
|
|
32
|
+
* paused viewer's film has to compare as wanted-last against another film
|
|
33
|
+
* somebody is watching — and relative to itself alone it would compare as
|
|
34
|
+
* the most urgent thing there is.
|
|
35
|
+
*
|
|
36
|
+
* @param {{ priority?: number, behind?: boolean, deferred?: boolean, urgent?: boolean }} zone
|
|
37
|
+
* @returns {number}
|
|
38
|
+
*/
|
|
39
|
+
export function levelOfMapZone(zone) {
|
|
40
|
+
// Read through the map's own words rather than by comparing its numbers.
|
|
41
|
+
// The scale is that layer's, and the numbers inside a band mean nothing
|
|
42
|
+
// but their order.
|
|
43
|
+
const priority = zone.priority ?? 0;
|
|
44
|
+
if (zone.behind === true || (zone.behind === undefined && isBehindEverybody(priority))) {
|
|
45
|
+
return Urgency.BEHIND;
|
|
46
|
+
}
|
|
47
|
+
if (zone.deferred === true || (zone.deferred === undefined && isNobodyComingNow(priority))) {
|
|
48
|
+
// In front of somebody who has stopped the picture, and of nobody who is
|
|
49
|
+
// watching. Wanted, and wanted after everyone who is on their way.
|
|
50
|
+
return Urgency.TAIL;
|
|
51
|
+
}
|
|
52
|
+
return (zone.urgent === true || (zone.urgent === undefined && isAtAWatchingViewer(priority))) ? Urgency.NEAR : Urgency.AHEAD;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The priority a zone is stated at, within the register's scale. */
|
|
56
|
+
export function priorityOfMapZone(zone) {
|
|
57
|
+
return Number.isFinite(zone.priority) ? Math.max(1, Math.min(100, zone.priority)) : 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** When a zone's bytes are needed, as an instant; `now` reads `withinSeconds`. */
|
|
61
|
+
export function deadlineOfMapZone(zone, now = Date.now()) {
|
|
62
|
+
return Number.isFinite(zone.deadlineAt) ? zone.deadlineAt :
|
|
63
|
+
Number.isFinite(zone.withinSeconds) ? now + Math.max(0, zone.withinSeconds) * 1000 : Number.POSITIVE_INFINITY;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The zones with every repeat of the same bytes left out but the one the swarm
|
|
68
|
+
* would rank first.
|
|
69
|
+
*
|
|
70
|
+
* Each piece is fetched in the band of the best zone that holds it
|
|
71
|
+
* ({@link compareBands}), so a zone naming exactly the bytes of a better one
|
|
72
|
+
* changes nothing the swarm is told. The ranges an AVI's picture reads at open
|
|
73
|
+
* — its declarations and its indexes — are named again in the zone of every
|
|
74
|
+
* segment: field 2026-10-10 (torrent-tv/meta#166), maps of 9700 zones, for a
|
|
75
|
+
* film of 2035 segments, cost half a second each to deliver.
|
|
76
|
+
*
|
|
77
|
+
* @template {{ byteStart: number, byteEnd: number }} Zone
|
|
78
|
+
* @param {Zone[]} zones
|
|
79
|
+
* @param {number} [now]
|
|
80
|
+
* @returns {Zone[]} In their order, without the repeats.
|
|
81
|
+
*/
|
|
82
|
+
export function withoutRepeatedZones(zones, now = Date.now()) {
|
|
83
|
+
const bandOf = zone => ({ urgency: levelOfMapZone(zone), priority: priorityOfMapZone(zone), deadlineAt: deadlineOfMapZone(zone, now) });
|
|
84
|
+
const best = new Map();
|
|
85
|
+
zones.forEach((zone, index) => {
|
|
86
|
+
const key = `${zone.byteStart}:${zone.byteEnd}`;
|
|
87
|
+
const kept = best.get(key);
|
|
88
|
+
if (!kept || compareBands(bandOf(zone), kept.band) < 0) best.set(key, { index, band: bandOf(zone) });
|
|
89
|
+
});
|
|
90
|
+
return zones.filter((zone, index) => best.get(`${zone.byteStart}:${zone.byteEnd}`).index === index);
|
|
91
|
+
}
|
|
@@ -20,7 +20,7 @@ import { demandProgress } from "./demand-progress.js";
|
|
|
20
20
|
import { Urgency, urgencyName } from "./demand/index.js";
|
|
21
21
|
import { demandFor, firstClassStatement, forgetTorrent, reconcileAll, hasUnmetDemand } from "./download/registry.js";
|
|
22
22
|
import { withdrawClaim } from "./download/withdraw-claim.js";
|
|
23
|
-
import {
|
|
23
|
+
import { deadlineOfMapZone, levelOfMapZone, priorityOfMapZone } from "./map-zones.js";
|
|
24
24
|
import { deriveSourceKey } from "../../utils/torrent-source-key.js";
|
|
25
25
|
import { noteTorrentDestroyed } from "./destroyed-torrents.js";
|
|
26
26
|
|
|
@@ -1290,48 +1290,9 @@ export class TorrentPool {
|
|
|
1290
1290
|
.filter((zone) => Number.isSafeInteger(zone?.byteStart) && Number.isSafeInteger(zone?.byteEnd) &&
|
|
1291
1291
|
zone.byteStart >= 0 && zone.byteEnd >= zone.byteStart && zone.byteStart < length)
|
|
1292
1292
|
.sort((left, right) => (right.priority ?? 0) - (left.priority ?? 0));
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
* — ten bands on a fifty-minute film — while the register has five levels,
|
|
1297
|
-
* and the fit is by meaning:
|
|
1298
|
-
*
|
|
1299
|
-
* - the top of the scale is where a viewer is standing, so it is the
|
|
1300
|
-
* cushion being built: {@link Urgency.NEAR}. Never BLOCKED — that level
|
|
1301
|
-
* means a reader is stopped on those bytes right now, which only a read
|
|
1302
|
-
* can say;
|
|
1303
|
-
* - the bottom is what nobody is approaching: behind a viewer moving
|
|
1304
|
-
* forward, and the whole film of a viewer who has stopped the picture.
|
|
1305
|
-
* Wanted only if somebody seeks back, which is {@link Urgency.BEHIND};
|
|
1306
|
-
* - one above the bottom is the far tail — wanted for certain if the
|
|
1307
|
-
* viewer watches on, and not before: {@link Urgency.TAIL};
|
|
1308
|
-
* - everything between is the lead being built: {@link Urgency.AHEAD}.
|
|
1309
|
-
*
|
|
1310
|
-
* Read from the scale's own ends rather than from the highest and lowest
|
|
1311
|
-
* number in THIS file's map. The two speculative levels are withheld across
|
|
1312
|
-
* every torrent at once while anything urgent is missing anywhere, so a
|
|
1313
|
-
* paused viewer's film has to compare as wanted-last against another film
|
|
1314
|
-
* somebody is watching — and relative to itself alone it would compare as
|
|
1315
|
-
* the most urgent thing there is.
|
|
1316
|
-
*
|
|
1317
|
-
* @param {{ priority?: number }} zone
|
|
1318
|
-
* @returns {number}
|
|
1319
|
-
*/
|
|
1320
|
-
const levelOf = (zone) => {
|
|
1321
|
-
// Read through the map's own words rather than by comparing its numbers.
|
|
1322
|
-
// The scale is that layer's, and the numbers inside a band mean nothing
|
|
1323
|
-
// but their order.
|
|
1324
|
-
const priority = zone.priority ?? 0;
|
|
1325
|
-
if (zone.behind === true || (zone.behind === undefined && isBehindEverybody(priority))) {
|
|
1326
|
-
return Urgency.BEHIND;
|
|
1327
|
-
}
|
|
1328
|
-
if (zone.deferred === true || (zone.deferred === undefined && isNobodyComingNow(priority))) {
|
|
1329
|
-
// In front of somebody who has stopped the picture, and of nobody who is
|
|
1330
|
-
// watching. Wanted, and wanted after everyone who is on their way.
|
|
1331
|
-
return Urgency.TAIL;
|
|
1332
|
-
}
|
|
1333
|
-
return (zone.urgent === true || (zone.urgent === undefined && isAtAWatchingViewer(priority))) ? Urgency.NEAR : Urgency.AHEAD;
|
|
1334
|
-
};
|
|
1293
|
+
// Which level each zone is stated at: `map-zones.js`, where the main thread
|
|
1294
|
+
// reads the same rule before it sends a map.
|
|
1295
|
+
const levelOf = levelOfMapZone;
|
|
1335
1296
|
ordered.forEach((zone, index) => {
|
|
1336
1297
|
// Only ranges declared by the container index or metadata read are wanted.
|
|
1337
1298
|
const byteStart = zone.byteStart;
|
|
@@ -1345,10 +1306,9 @@ export class TorrentPool {
|
|
|
1345
1306
|
byteStart,
|
|
1346
1307
|
byteEnd,
|
|
1347
1308
|
urgency: levelOf(zone),
|
|
1348
|
-
priority:
|
|
1309
|
+
priority: priorityOfMapZone(zone),
|
|
1349
1310
|
order: Number.isSafeInteger(zone.order) && zone.order >= 0 ? zone.order : 0,
|
|
1350
|
-
deadlineAt:
|
|
1351
|
-
Number.isFinite(zone.withinSeconds) ? Date.now() + Math.max(0, zone.withinSeconds) * 1000 : Number.POSITIVE_INFINITY,
|
|
1311
|
+
deadlineAt: deadlineOfMapZone(zone),
|
|
1352
1312
|
requestId: zone.requestId ?? `${torrent.infoHash}:${fileIndex}:map`
|
|
1353
1313
|
});
|
|
1354
1314
|
});
|