@torrent-tv/proxy 2.97.30 → 2.97.31
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 +1 -1
- package/server.js +11 -5
- package/services/torrent/map-zones.js +91 -26
- package/services/viewer/target-input-expansion.js +12 -4
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -543,7 +543,7 @@ export async function startProxyServer({
|
|
|
543
543
|
// ranges closer than a piece hold the same pieces as one.
|
|
544
544
|
const pieceLength = Number(torrentPool.knownTorrent(map.sourceKey)?.pieceLength);
|
|
545
545
|
const passStartedAt = performance.now();
|
|
546
|
-
let
|
|
546
|
+
let rangeCount = 0;
|
|
547
547
|
const converted = [];
|
|
548
548
|
const demands = [];
|
|
549
549
|
// What each output's next run would copy, asked of the swarm as one group
|
|
@@ -629,12 +629,18 @@ export async function startProxyServer({
|
|
|
629
629
|
const { tracks: _choices, owner: _owner, sourceInterval: _sourceInterval, ...demand } = zone;
|
|
630
630
|
const convert = ranges => {
|
|
631
631
|
const joined = joinedRangesOf(ranges, pieceLength);
|
|
632
|
-
|
|
632
|
+
rangeCount += joined.length;
|
|
633
633
|
// What the segment reads is kept for the forecast in a table of its
|
|
634
634
|
// own, whatever form the torrent is told it in.
|
|
635
635
|
segmentInputs.set(map.sourceKey, map.fileIndex, zone.outputKey, zone.index, joined);
|
|
636
|
-
|
|
637
|
-
|
|
636
|
+
// ONE ZONE FOR THE SEGMENT, its demand stated once for all the
|
|
637
|
+
// ranges it reads. The demand — level, priority, deadline, the map
|
|
638
|
+
// zone it belongs to — is this pass's; the ranges are the list kept
|
|
639
|
+
// for the segment, unchanged from pass to pass. A zone for each
|
|
640
|
+
// range copied the demand into every one: ten thousand objects a
|
|
641
|
+
// pass over a two-hour AVI, made again 21-61 times a minute on Home
|
|
642
|
+
// Assistant (r26, torrent-tv/meta#178).
|
|
643
|
+
converted.push({ ...demand, downloadInterval: { from: zone.owner.from, to: zone.owner.to }, ranges: joined });
|
|
638
644
|
};
|
|
639
645
|
// The bytes one segment needs are a fact of the file and the output, so
|
|
640
646
|
// they are worked out once. A whole film is thousands of segments and
|
|
@@ -694,7 +700,7 @@ export async function startProxyServer({
|
|
|
694
700
|
convert(input.ranges);
|
|
695
701
|
}
|
|
696
702
|
const result = expanded();
|
|
697
|
-
notePass({ complete, segments
|
|
703
|
+
notePass({ complete, segments: converted.length, ranges: rangeCount, zones: result.length, ms: performance.now() - passStartedAt });
|
|
698
704
|
if (complete) resolvedPasses.set(fileKey, { ...pass, result });
|
|
699
705
|
else resolvedPasses.delete(fileKey);
|
|
700
706
|
return result;
|
|
@@ -63,6 +63,18 @@ export function deadlineOfMapZone(zone, now = Date.now()) {
|
|
|
63
63
|
Number.isFinite(zone.withinSeconds) ? now + Math.max(0, zone.withinSeconds) * 1000 : Number.POSITIVE_INFINITY;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* The byte ranges a zone names: a segment's zone names all the ranges its
|
|
68
|
+
* segment reads, and states its demand once for them (`ranges`); any other
|
|
69
|
+
* zone names one (`byteStart`-`byteEnd`). Inclusive.
|
|
70
|
+
*
|
|
71
|
+
* @param {{ ranges?: Array<[number, number]>, byteStart?: number, byteEnd?: number }} zone
|
|
72
|
+
* @returns {Array<[number, number]>}
|
|
73
|
+
*/
|
|
74
|
+
export function rangesOfZone(zone) {
|
|
75
|
+
return Array.isArray(zone.ranges) ? zone.ranges : [[zone.byteStart, zone.byteEnd]];
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
/**
|
|
67
79
|
* The zones with every repeat of the same bytes left out but the one the swarm
|
|
68
80
|
* would rank first.
|
|
@@ -80,14 +92,9 @@ export function deadlineOfMapZone(zone, now = Date.now()) {
|
|
|
80
92
|
* @returns {Zone[]} In their order, without the repeats.
|
|
81
93
|
*/
|
|
82
94
|
export function withoutRepeatedZones(zones, now = Date.now()) {
|
|
83
|
-
const
|
|
84
|
-
const best =
|
|
85
|
-
zones.
|
|
86
|
-
const kept = best.get(keys[index]);
|
|
87
|
-
const band = { urgency: levelOfMapZone(zone), priority: priorityOfMapZone(zone), deadlineAt: deadlineOfMapZone(zone, now) };
|
|
88
|
-
if (!kept || compareBands(band, kept.band) < 0) best.set(keys[index], { index, band });
|
|
89
|
-
});
|
|
90
|
-
return zones.filter((_zone, index) => best.get(keys[index]).index === index);
|
|
95
|
+
const bands = zones.map(zone => bandOf(zone, now));
|
|
96
|
+
const best = bestOfRepeats(zones.length, index => zones[index].byteStart, index => zones[index].byteEnd, index => bands[index]);
|
|
97
|
+
return zones.filter((_zone, index) => best.get(zones[index].byteStart).get(zones[index].byteEnd) === index);
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
/**
|
|
@@ -105,45 +112,103 @@ export function withoutRepeatedZones(zones, now = Date.now()) {
|
|
|
105
112
|
* consecutive segments are consecutive bytes. A zone a metadata read states
|
|
106
113
|
* keeps its own request and order, and is left as it is.
|
|
107
114
|
*
|
|
108
|
-
*
|
|
109
|
-
* @
|
|
115
|
+
* A segment's zone states its demand once for all its ranges
|
|
116
|
+
* ({@link rangesOfZone}); each range is a part of its own here, so a repeat is
|
|
117
|
+
* found range by range, and the band and deadline every range is packed with
|
|
118
|
+
* are the zone's current ones.
|
|
119
|
+
*
|
|
120
|
+
* @param {Array<{ byteStart?: number, byteEnd?: number, ranges?: Array<[number, number]> }>} zones
|
|
110
121
|
* @param {number} [now]
|
|
111
122
|
* @returns {object[]}
|
|
112
123
|
*/
|
|
113
124
|
export function coalescedZones(zones, now = Date.now()) {
|
|
125
|
+
// Every range of every zone is a part, kept in numbers rather than objects:
|
|
126
|
+
// a film's map is ten thousand of them (r26, torrent-tv/meta#178).
|
|
127
|
+
let count = 0;
|
|
128
|
+
for (const zone of zones) count += Array.isArray(zone.ranges) ? zone.ranges.length : 1;
|
|
129
|
+
const starts = new Float64Array(count);
|
|
130
|
+
const ends = new Float64Array(count);
|
|
131
|
+
const owners = new Int32Array(count);
|
|
132
|
+
const bands = zones.map(zone => bandOf(zone, now));
|
|
133
|
+
let at = 0;
|
|
134
|
+
zones.forEach((zone, index) => {
|
|
135
|
+
for (const [start, end] of rangesOfZone(zone)) {
|
|
136
|
+
starts[at] = start;
|
|
137
|
+
ends[at] = end;
|
|
138
|
+
owners[at] = index;
|
|
139
|
+
at += 1;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
const best = bestOfRepeats(count, part => starts[part], part => ends[part], part => bands[owners[part]]);
|
|
114
143
|
const kept = [];
|
|
144
|
+
/** Band key → the band and its parts, in the order the bands first appear. */
|
|
115
145
|
const groups = new Map();
|
|
116
|
-
for (
|
|
146
|
+
for (let part = 0; part < count; part++) {
|
|
147
|
+
if (best.get(starts[part]).get(ends[part]) !== part) continue;
|
|
148
|
+
const zone = zones[owners[part]];
|
|
117
149
|
if (zone.requestId !== undefined || zone.downloadOnly || (Number.isSafeInteger(zone.order) && zone.order > 0)) {
|
|
118
150
|
kept.push(zone);
|
|
119
151
|
continue;
|
|
120
152
|
}
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const key = `${urgency}:${priority}`;
|
|
153
|
+
const band = bands[owners[part]];
|
|
154
|
+
const key = band.urgency * 1000 + band.priority;
|
|
124
155
|
let group = groups.get(key);
|
|
125
|
-
if (!group) groups.set(key, group = {
|
|
126
|
-
group.parts.push(
|
|
156
|
+
if (!group) groups.set(key, group = { band, parts: [] });
|
|
157
|
+
group.parts.push(part);
|
|
127
158
|
}
|
|
128
|
-
for (const { urgency, priority, parts } of groups.values()) {
|
|
129
|
-
parts.sort((left, right) => left
|
|
130
|
-
let
|
|
159
|
+
for (const { band: { urgency, priority }, parts } of groups.values()) {
|
|
160
|
+
parts.sort((left, right) => starts[left] - starts[right]);
|
|
161
|
+
let windowStart = 0;
|
|
162
|
+
let windowEnd = -1;
|
|
163
|
+
let windowDeadline = Number.POSITIVE_INFINITY;
|
|
164
|
+
let open = false;
|
|
131
165
|
const close = () => {
|
|
132
|
-
if (!
|
|
133
|
-
kept.push({ byteStart:
|
|
166
|
+
if (!open) return;
|
|
167
|
+
kept.push({ byteStart: windowStart, byteEnd: windowEnd, priority, urgent: urgency === Urgency.NEAR,
|
|
134
168
|
behind: urgency === Urgency.BEHIND, deferred: urgency === Urgency.TAIL,
|
|
135
|
-
...(Number.isFinite(
|
|
169
|
+
...(Number.isFinite(windowDeadline) ? { deadlineAt: windowDeadline } : {}) });
|
|
136
170
|
};
|
|
137
171
|
for (const part of parts) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
172
|
+
const deadlineAt = bands[owners[part]].deadlineAt;
|
|
173
|
+
if (open && starts[part] <= windowEnd + 1) {
|
|
174
|
+
windowEnd = Math.max(windowEnd, ends[part]);
|
|
175
|
+
windowDeadline = Math.min(windowDeadline, deadlineAt);
|
|
141
176
|
} else {
|
|
142
177
|
close();
|
|
143
|
-
|
|
178
|
+
open = true;
|
|
179
|
+
windowStart = starts[part];
|
|
180
|
+
windowEnd = ends[part];
|
|
181
|
+
windowDeadline = deadlineAt;
|
|
144
182
|
}
|
|
145
183
|
}
|
|
146
184
|
close();
|
|
147
185
|
}
|
|
148
186
|
return kept;
|
|
149
187
|
}
|
|
188
|
+
|
|
189
|
+
/** The band a zone's bytes are fetched in. */
|
|
190
|
+
function bandOf(zone, now) {
|
|
191
|
+
return { urgency: levelOfMapZone(zone), priority: priorityOfMapZone(zone), deadlineAt: deadlineOfMapZone(zone, now) };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Which of the parts naming each exact byte range is the best: by band
|
|
196
|
+
* ({@link compareBands}), the first of equals. Kept by the range's two numbers
|
|
197
|
+
* rather than a string made of them: making a key of each of a film's ten
|
|
198
|
+
* thousand ranges was most of what packing its map cost (r26,
|
|
199
|
+
* torrent-tv/meta#178).
|
|
200
|
+
*
|
|
201
|
+
* @returns {Map<number, Map<number, number>>} start → end → the best part.
|
|
202
|
+
*/
|
|
203
|
+
function bestOfRepeats(count, startOf, endOf, bandOfPart) {
|
|
204
|
+
const best = new Map();
|
|
205
|
+
for (let part = 0; part < count; part++) {
|
|
206
|
+
const start = startOf(part);
|
|
207
|
+
const end = endOf(part);
|
|
208
|
+
let byEnd = best.get(start);
|
|
209
|
+
if (!byEnd) best.set(start, byEnd = new Map());
|
|
210
|
+
const kept = byEnd.get(end);
|
|
211
|
+
if (kept === undefined || compareBands(bandOfPart(part), bandOfPart(kept)) < 0) byEnd.set(end, part);
|
|
212
|
+
}
|
|
213
|
+
return best;
|
|
214
|
+
}
|
|
@@ -102,8 +102,11 @@ function merged(ranges) {
|
|
|
102
102
|
* The map's byte zones with each output's next run input raised to its first
|
|
103
103
|
* segment's place.
|
|
104
104
|
*
|
|
105
|
-
* @template {{ outputKey: string, index: number, priority: number, urgent?: boolean, behind?: boolean, deferred?: boolean,
|
|
106
|
-
*
|
|
105
|
+
* @template {{ outputKey: string, index: number, priority: number, urgent?: boolean, behind?: boolean, deferred?: boolean,
|
|
106
|
+
* ranges?: Array<[number, number]>, byteStart?: number, byteEnd?: number }} Zone
|
|
107
|
+
* @param {Zone[]} zones - One or more per segment, as the map's pass made
|
|
108
|
+
* them: a segment's zone names all its ranges (`ranges`), any other one
|
|
109
|
+
* range (`byteStart`-`byteEnd`).
|
|
107
110
|
* @param {(outputKey: string) => number | null} targetBytesOf - What the
|
|
108
111
|
* output's next run would be given to copy; nothing is raised without it.
|
|
109
112
|
* @returns {Zone[]} In their order; raised zones are copies.
|
|
@@ -138,7 +141,7 @@ export function expandToTargetInputs(zones, targetBytesOf) {
|
|
|
138
141
|
if (zone.index === run.first.index) {
|
|
139
142
|
let ranges = held.get(zone.outputKey);
|
|
140
143
|
if (!ranges) held.set(zone.outputKey, ranges = []);
|
|
141
|
-
ranges.push(
|
|
144
|
+
ranges.push(...rangesOf(zone));
|
|
142
145
|
} else if (zone.index > run.first.index) {
|
|
143
146
|
const list = run.after.get(zone.index);
|
|
144
147
|
if (list) list.push(zone);
|
|
@@ -153,7 +156,7 @@ export function expandToTargetInputs(zones, targetBytesOf) {
|
|
|
153
156
|
// or not yet worked out, ends it.
|
|
154
157
|
for (let index = run.first.index + 1; run.after.has(index); index++) {
|
|
155
158
|
const list = run.after.get(index);
|
|
156
|
-
const ranges = list.
|
|
159
|
+
const ranges = list.flatMap(rangesOf);
|
|
157
160
|
if (holding.total + holding.adds(ranges) > run.target) break;
|
|
158
161
|
holding.add(ranges);
|
|
159
162
|
for (const zone of list) {
|
|
@@ -164,3 +167,8 @@ export function expandToTargetInputs(zones, targetBytesOf) {
|
|
|
164
167
|
}
|
|
165
168
|
return raised.size === 0 ? zones : zones.map(zone => raised.get(zone) ?? zone);
|
|
166
169
|
}
|
|
170
|
+
|
|
171
|
+
/** The ranges a zone names, inclusive. */
|
|
172
|
+
function rangesOf(zone) {
|
|
173
|
+
return Array.isArray(zone.ranges) ? zone.ranges : [[zone.byteStart, zone.byteEnd]];
|
|
174
|
+
}
|