@torrent-tv/proxy 2.96.9 → 2.96.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
package/server.js
CHANGED
|
@@ -54,6 +54,8 @@ import { wireOutputs } from "./services/server/wire-outputs.js";
|
|
|
54
54
|
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
|
+
import { joinedWithinPieces } from "./services/torrent/demand/pieces.js";
|
|
58
|
+
import { withoutRepeatedZones } from "./services/torrent/map-zones.js";
|
|
57
59
|
import { SubtitleOrchestrator } from "./services/media/SubtitleOrchestrator.js";
|
|
58
60
|
import { containerOrchestrator, CONTAINER_HEAD_BYTES, describeWorkTags } from "./services/media/ContainerOrchestrator.js";
|
|
59
61
|
import { readPlaybackDeclarations } from "./services/media/read-playback-declarations.js";
|
|
@@ -434,6 +436,9 @@ export async function startProxyServer({
|
|
|
434
436
|
const media = await containerOrchestrator.inspect(params, "media-info");
|
|
435
437
|
if (media.kind !== "result" || !map.isCurrent()) return [];
|
|
436
438
|
const shift = Number(media.value?.startTimeSeconds) || 0;
|
|
439
|
+
// A zone's ranges are stated to the torrent at the precision it fetches:
|
|
440
|
+
// ranges closer than a piece hold the same pieces as one.
|
|
441
|
+
const pieceLength = Number(torrentPool.knownTorrent(map.sourceKey)?.pieceLength);
|
|
437
442
|
const converted = [];
|
|
438
443
|
const demands = [];
|
|
439
444
|
for (const output of outputParts.outputs.values()) {
|
|
@@ -483,7 +488,7 @@ export async function startProxyServer({
|
|
|
483
488
|
modes: Object.fromEntries(wanted.map(track => [track.trackNumber, modes.get(track)])) };
|
|
484
489
|
const { tracks: _choices, owner: _owner, sourceInterval: _sourceInterval, ...demand } = zone;
|
|
485
490
|
const convert = ranges => {
|
|
486
|
-
for (const [byteStart, byteEnd] of ranges) converted.push({ ...demand,
|
|
491
|
+
for (const [byteStart, byteEnd] of joinedWithinPieces(ranges, pieceLength)) converted.push({ ...demand,
|
|
487
492
|
downloadInterval: { from: zone.owner.from, to: zone.owner.to }, byteStart, byteEnd });
|
|
488
493
|
};
|
|
489
494
|
// The bytes one segment needs are a fact of the file and the output, so
|
|
@@ -529,7 +534,9 @@ export async function startProxyServer({
|
|
|
529
534
|
segmentSourceRanges.set(rangesKey, input.ranges);
|
|
530
535
|
convert(input.ranges);
|
|
531
536
|
}
|
|
532
|
-
|
|
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);
|
|
533
540
|
}
|
|
534
541
|
});
|
|
535
542
|
const mediaReads = new MediaReadRequests({
|
|
@@ -106,3 +106,29 @@ export function nearestFirst({ byteStart, byteEnd, parts }) {
|
|
|
106
106
|
}
|
|
107
107
|
return ranges;
|
|
108
108
|
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Byte ranges joined wherever the gap between two of them is shorter than a
|
|
112
|
+
* piece: the pieces they hold are the same, and there are fewer of them.
|
|
113
|
+
*
|
|
114
|
+
* A gap shorter than a piece cannot hold a whole piece, so every piece it
|
|
115
|
+
* touches is touched by a range beside it. The soundtrack of an AVI read from
|
|
116
|
+
* its own packets is one range per packet, interleaved with the picture's
|
|
117
|
+
* bytes: field 2026-10-10 (torrent-tv/meta#166), a map of 350 191 zones took
|
|
118
|
+
* 20.8 s to reach the torrent thread and grew its heap to 818 MB, for pieces of
|
|
119
|
+
* 4 MiB that the whole soundtrack touches anyway.
|
|
120
|
+
*
|
|
121
|
+
* @param {Array<[number, number]>} ranges - Inclusive byte ranges in ascending order.
|
|
122
|
+
* @param {number} pieceLength
|
|
123
|
+
* @returns {Array<[number, number]>} The same ranges when the piece length is not usable.
|
|
124
|
+
*/
|
|
125
|
+
export function joinedWithinPieces(ranges, pieceLength) {
|
|
126
|
+
if (!Number.isFinite(pieceLength) || pieceLength <= 0) return ranges;
|
|
127
|
+
const joined = [];
|
|
128
|
+
for (const [start, end] of ranges) {
|
|
129
|
+
const previous = joined.at(-1);
|
|
130
|
+
if (previous && start >= previous[0] && start - previous[1] - 1 < pieceLength) previous[1] = Math.max(previous[1], end);
|
|
131
|
+
else joined.push([start, end]);
|
|
132
|
+
}
|
|
133
|
+
return joined;
|
|
134
|
+
}
|
|
@@ -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
|
});
|