@torrent-tv/proxy 2.107.22 → 2.107.24
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
|
@@ -73,7 +73,6 @@ import { nativeSourceMap, nativeOriginalSourceMap } from "./services/viewer/Nati
|
|
|
73
73
|
import { MediaReadRequests } from "./services/media/MediaReadRequests.js";
|
|
74
74
|
import { SegmentInputs } from "./services/media/SegmentInputs.js";
|
|
75
75
|
import { directSegmentInput } from "./services/media/direct-segment-input.js";
|
|
76
|
-
import { piecesRunPastEnd } from "./services/encode/output/Timeline.js";
|
|
77
76
|
import { ContainerTrack } from "./services/media/tracks/ContainerTrack.js";
|
|
78
77
|
import { pauseCoefficient, viewerStartsOn } from "./services/viewer/PriorityMap.js";
|
|
79
78
|
import { coalescing } from "./utils/coalesce.js";
|
|
@@ -717,9 +716,9 @@ export async function startProxyServer({
|
|
|
717
716
|
const revision = outputParts.segmentStore.revisionOf(output.outputKey);
|
|
718
717
|
const links = consumers.map(viewer => viewer.linkReading()?.linkMbps)
|
|
719
718
|
.filter(value => Number.isFinite(value) && value > 0);
|
|
720
|
-
//
|
|
721
|
-
//
|
|
722
|
-
played.push({ outputKey: output.outputKey, grid,
|
|
719
|
+
// Every piece answers for its exact ending boundary, as the encode
|
|
720
|
+
// plan reads it: the player may request the preceding piece there.
|
|
721
|
+
played.push({ outputKey: output.outputKey, grid,
|
|
723
722
|
encodeSpeed: outputParts.encodeCost.latestSpeedReadingOf(output)?.speed,
|
|
724
723
|
bytesPerSecond: bytesPerSecondOf(output, revision), linkMbps: links.length ? Math.min(...links) : null,
|
|
725
724
|
readyRevision: revision, ready: () => outputParts.segmentStore.sizesOf(output.outputKey),
|
|
@@ -26,7 +26,6 @@ import { cutOf, cutShiftOf, judgeNeighbors, judgePiece, leadingSecondsOf, lacksO
|
|
|
26
26
|
import { leadingPictures, presentationSegment } from "./segment-formats/presentation-segment.js";
|
|
27
27
|
import { InputFailures, failedAdmittedInput } from "./InputFailures.js";
|
|
28
28
|
import { readProcessCpuSeconds } from "./host-load.js";
|
|
29
|
-
import { piecesRunPastEnd } from "./output/Timeline.js";
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
/**
|
|
@@ -616,13 +615,11 @@ export class EncodeRuns {
|
|
|
616
615
|
// each: 454 pieces against 401 on the field file.
|
|
617
616
|
for (const [address, sessions] of byOutput) {
|
|
618
617
|
const timeline = sessions[0].timeline;
|
|
619
|
-
const spec = sessions[0].spec;
|
|
620
618
|
this.#host.encodeOrchestrator.notePriorityMap(
|
|
621
619
|
address,
|
|
622
620
|
timeline?.inSegments?.(
|
|
623
621
|
this.#host.priority.mapForOutput(address),
|
|
624
|
-
Number(timeline?.segmentCount) || 0
|
|
625
|
-
{ runsPastEnd: piecesRunPastEnd(spec) }
|
|
622
|
+
Number(timeline?.segmentCount) || 0
|
|
626
623
|
) ?? []
|
|
627
624
|
);
|
|
628
625
|
}
|
|
@@ -42,32 +42,6 @@
|
|
|
42
42
|
* reveal where the file's cuts really are.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
* Whether an output's pieces may hold a little past their published end: a
|
|
47
|
-
* piece ends at the first frame boundary at or after it, and the player places
|
|
48
|
-
* the next piece where this one truly ends (hls.js `updateFragPTSDTS`). A
|
|
49
|
-
* viewer standing at a published edge is then asked for by the piece BEFORE
|
|
50
|
-
* it. True of a copied picture, whose pieces end at a packet, and of sound
|
|
51
|
-
* however it is made: copied sound ends at a packet, and encoded sound at a
|
|
52
|
-
* frame of the encoder — 1024 samples of AAC, 21.3 ms at 48 kHz. Only a
|
|
53
|
-
* re-encoded picture, given a keyframe at every cut, ends where it is published.
|
|
54
|
-
*
|
|
55
|
-
* Field 2026-10-09, MP3 copied out of an AVI: a seek to 4000 s asked for the
|
|
56
|
-
* piece ending at 4000 s. Field r43 (Home Assistant, 2026-10-15), a copied
|
|
57
|
-
* Matroska picture: a seek to 1000.000 s, the published start of #96, asked for
|
|
58
|
-
* #95 on both tracks; the picture's pieces were not counted as copies, nothing
|
|
59
|
-
* made #95, and the picture stood 131 s until the viewer moved. With the
|
|
60
|
-
* picture counted (r44), its #95 was made in 6.4 s and the soundtrack's, AAC
|
|
61
|
-
* encoded from the film (`its muxer counts cuts -0.021333s`), was still not
|
|
62
|
-
* made (torrent-tv/meta#178).
|
|
63
|
-
*
|
|
64
|
-
* @param {{ video?: object | null, audio?: object | null, transcodesVideo?: boolean, transcodesAudio?: boolean } | null | undefined} spec
|
|
65
|
-
* @returns {boolean}
|
|
66
|
-
*/
|
|
67
|
-
export function piecesRunPastEnd(spec) {
|
|
68
|
-
return (Boolean(spec?.video) && spec.transcodesVideo !== true) || Boolean(spec?.audio);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
45
|
export class Timeline {
|
|
72
46
|
/**
|
|
73
47
|
* @param {object} params
|
|
@@ -185,13 +159,10 @@ export class Timeline {
|
|
|
185
159
|
* @param {import("../../viewer/PriorityMap.js").PriorityMap} map - What every
|
|
186
160
|
* viewer of this output wants, in seconds of film.
|
|
187
161
|
* @param {number} segmentCount - How many pieces this output has.
|
|
188
|
-
* @param {{ runsPastEnd?: boolean }} [shape] - `runsPastEnd`: the output's
|
|
189
|
-
* pieces copy a stream, which is cut where its packets end, so a piece
|
|
190
|
-
* holds sound past its published end by up to one packet.
|
|
191
162
|
* @returns {import("../../viewer/PriorityMap.js").DemandZone[]} Runs of pieces
|
|
192
163
|
* that agree. Empty where nothing is stated, which says nobody is coming.
|
|
193
164
|
*/
|
|
194
|
-
inSegments(map, segmentCount
|
|
165
|
+
inSegments(map, segmentCount) {
|
|
195
166
|
if (!(segmentCount > 0) || !map || !(map.durationSeconds > 0)) {
|
|
196
167
|
return [];
|
|
197
168
|
}
|
|
@@ -202,17 +173,16 @@ export class Timeline {
|
|
|
202
173
|
// whole, so it is wanted as soon as the soonest second inside it is
|
|
203
174
|
// wanted — and the piece a viewer is standing in the middle of is wanted
|
|
204
175
|
// exactly as much as the second under their feet.
|
|
205
|
-
// A
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
// 4000 s, which nothing made because second 4000 belonged to the next.
|
|
176
|
+
// A PIECE ALSO HOLDS THE SECOND ITS END FALLS IN. At an exact playlist
|
|
177
|
+
// boundary hls.js may request the fragment ending there before it asks
|
|
178
|
+
// for the one beginning there. Excluding that second from this piece can
|
|
179
|
+
// leave the request waiting while the plan starts at the following one.
|
|
180
|
+
// Copied streams also run past their published end by up to one packet,
|
|
181
|
+
// and hls.js carries that parsed end to every later piece.
|
|
212
182
|
const from = Math.max(0, Math.floor(this.publishedStartOf(index)));
|
|
213
183
|
const end = this.publishedStartOf(index + 1);
|
|
214
184
|
const until = index + 1 < segmentCount
|
|
215
|
-
? Math.max(from + 1,
|
|
185
|
+
? Math.max(from + 1, Math.floor(end) + 1)
|
|
216
186
|
: map.durationSeconds;
|
|
217
187
|
// Asked of the map for the piece's seconds as a whole, not second by
|
|
218
188
|
// second: a walk of every second of the film for every output on every
|
|
@@ -273,10 +273,9 @@ class Segment {
|
|
|
273
273
|
this.from = from;
|
|
274
274
|
this.to = to;
|
|
275
275
|
/**
|
|
276
|
-
* Up to which second the map's zones are read for it: its end
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* standing at its end for this piece, and its bytes are fetched for them.
|
|
276
|
+
* Up to which second the map's zones are read for it: its end and the exact
|
|
277
|
+
* end boundary. The encode plan includes that boundary for every output,
|
|
278
|
+
* since the player may request the piece ending there before the next one.
|
|
280
279
|
*/
|
|
281
280
|
this.reach = reach;
|
|
282
281
|
/** Whether a zone of the map overlaps it. */
|
|
@@ -311,16 +310,15 @@ class Segment {
|
|
|
311
310
|
|
|
312
311
|
/** One output: its grid, how long its segments take to make and deliver, what is made, and its next input. */
|
|
313
312
|
class OutputState {
|
|
314
|
-
constructor(outputKey, grid
|
|
313
|
+
constructor(outputKey, grid) {
|
|
315
314
|
this.outputKey = outputKey;
|
|
316
315
|
this.grid = grid;
|
|
317
|
-
this.runsPastEnd = runsPastEnd;
|
|
318
316
|
/** @type {Segment[]} */
|
|
319
317
|
this.segments = [];
|
|
320
318
|
for (let index = 0; index + 1 < grid.length; index++) {
|
|
321
319
|
const end = grid[index + 1];
|
|
322
320
|
this.segments.push(new Segment(this, index, grid[index], end,
|
|
323
|
-
|
|
321
|
+
index + 2 < grid.length ? Math.max(end, Math.floor(end) + 1) : end));
|
|
324
322
|
}
|
|
325
323
|
this.encodeSpeed = undefined;
|
|
326
324
|
this.bytesPerSecond = undefined;
|
|
@@ -380,7 +378,7 @@ export class PlaybackProjection {
|
|
|
380
378
|
* @param {object} params
|
|
381
379
|
* @param {Array<{ from: number, to: number, priority: number, urgent?: boolean, behind?: boolean, deferred?: boolean,
|
|
382
380
|
* deadlineAt?: number }>} params.zones - The file's map zones, deadlines as instants.
|
|
383
|
-
* @param {Array<{ outputKey: string, grid: number[],
|
|
381
|
+
* @param {Array<{ outputKey: string, grid: number[], encodeSpeed?: number, bytesPerSecond?: number | null,
|
|
384
382
|
* linkMbps?: number | null, readyRevision: unknown, ready: () => Map<number, number>,
|
|
385
383
|
* target: number | null }>} params.outputs - Every output of the file somebody is watching, with
|
|
386
384
|
* what decides how long its segments take to make and deliver (`sourceDeadline`), what it has made
|
|
@@ -401,13 +399,13 @@ export class PlaybackProjection {
|
|
|
401
399
|
for (const info of outputs) {
|
|
402
400
|
seen.add(info.outputKey);
|
|
403
401
|
let output = this.#outputs.get(info.outputKey);
|
|
404
|
-
if (output &&
|
|
402
|
+
if (output && output.grid !== info.grid) {
|
|
405
403
|
this.#dropOutput(output);
|
|
406
404
|
output = undefined;
|
|
407
405
|
}
|
|
408
406
|
const fresh = !output;
|
|
409
407
|
if (fresh) {
|
|
410
|
-
output = new OutputState(info.outputKey, info.grid
|
|
408
|
+
output = new OutputState(info.outputKey, info.grid);
|
|
411
409
|
this.#outputs.set(info.outputKey, output);
|
|
412
410
|
}
|
|
413
411
|
// How long a segment of it takes to make and deliver.
|
|
@@ -436,8 +434,9 @@ export class PlaybackProjection {
|
|
|
436
434
|
for (const segment of output.segments) if (this.#demand(segment, zones, ordered, nextBase, leadMoved)) reworked++;
|
|
437
435
|
} else {
|
|
438
436
|
for (const { from, to } of stretches) {
|
|
439
|
-
// A
|
|
440
|
-
|
|
437
|
+
// A viewer at an exact playlist boundary may request the piece
|
|
438
|
+
// ending there before requesting the one that starts there.
|
|
439
|
+
for (let index = Math.max(0, segmentAt(output.grid, from) - 1);
|
|
441
440
|
index < output.segments.length && output.segments[index].from < to; index++) {
|
|
442
441
|
if (this.#demand(output.segments[index], zones, ordered, nextBase, false)) reworked++;
|
|
443
442
|
}
|
|
@@ -552,7 +552,9 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
552
552
|
let after = contiguousEnd(delivered, at);
|
|
553
553
|
for (const completion of completions.filter((item) => item.trackIndex === trackIndex)) {
|
|
554
554
|
const before = after;
|
|
555
|
-
|
|
555
|
+
const ranges = compare(before, at) === 0
|
|
556
|
+
? withSeekStartTolerance(completion.segment.ranges, at, unit) : completion.segment.ranges;
|
|
557
|
+
for (const range of ranges) addMediaRange(delivered, range);
|
|
556
558
|
after = contiguousEnd(delivered, at);
|
|
557
559
|
const ahead = secondsAhead(earliest(before, trackEnd), at);
|
|
558
560
|
constrain(completion.at - ahead, "delivery", trackIndex, completion.segment.index, completion.at, ahead, false,
|
|
@@ -581,7 +583,9 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
581
583
|
}
|
|
582
584
|
for (const production of productions.filter((item) => item.trackIndex === trackIndex)) {
|
|
583
585
|
const before = contiguousEnd(prepared, at);
|
|
584
|
-
|
|
586
|
+
const ranges = compare(before, at) === 0
|
|
587
|
+
? withSeekStartTolerance(production.segment.ranges, at, unit) : production.segment.ranges;
|
|
588
|
+
for (const range of ranges) addMediaRange(prepared, range);
|
|
585
589
|
// Supply interruption coverage is stock held by the proxy as well as
|
|
586
590
|
// the browser. It is not a browser buffer-size admission rule.
|
|
587
591
|
// The reserve is bounded by what is left of the track, not of the window.
|
|
@@ -851,6 +855,25 @@ function heldRanges(ranges, segments, unit, position) {
|
|
|
851
855
|
return held;
|
|
852
856
|
}
|
|
853
857
|
|
|
858
|
+
/**
|
|
859
|
+
* Include the first arriving range when its start is within the media
|
|
860
|
+
* element's allowance for a seek target. This does not bridge later gaps.
|
|
861
|
+
*
|
|
862
|
+
* @param {import("./media-time.js").MediaRange[]} ranges
|
|
863
|
+
* @param {import("./media-time.js").MediaTime} position
|
|
864
|
+
* @param {bigint} unit
|
|
865
|
+
* @returns {import("./media-time.js").MediaRange[]}
|
|
866
|
+
*/
|
|
867
|
+
function withSeekStartTolerance(ranges, position, unit) {
|
|
868
|
+
const first = ranges.reduce((earliestRange, range) =>
|
|
869
|
+
!earliestRange || compare(range.start, earliestRange.start) < 0 ? range : earliestRange, null);
|
|
870
|
+
const toleranceEnd = add(position, rescale(fromSeconds(CLIENT_RANGE_START_TOLERANCE_SECONDS), unit));
|
|
871
|
+
if (!first || compare(first.start, position) <= 0 || compare(first.start, toleranceEnd) > 0) {
|
|
872
|
+
return ranges;
|
|
873
|
+
}
|
|
874
|
+
return ranges.map((range) => range === first ? { ...range, start: position } : range);
|
|
875
|
+
}
|
|
876
|
+
|
|
854
877
|
/**
|
|
855
878
|
* The pieces of a track this forecast places in exact time: those still to
|
|
856
879
|
* play (ending after `position`) that start before `horizonEnd`, the first
|