@torrent-tv/proxy 2.99.0 → 2.99.1
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
|
@@ -66,6 +66,7 @@ import { nativeSourceMap, nativeOriginalSourceMap } from "./services/viewer/Nati
|
|
|
66
66
|
import { MediaReadRequests } from "./services/media/MediaReadRequests.js";
|
|
67
67
|
import { SegmentInputs } from "./services/media/SegmentInputs.js";
|
|
68
68
|
import { directSegmentInput } from "./services/media/direct-segment-input.js";
|
|
69
|
+
import { piecesRunPastEnd } from "./services/encode/output/Timeline.js";
|
|
69
70
|
import { ContainerTrack } from "./services/media/tracks/ContainerTrack.js";
|
|
70
71
|
import { pauseCoefficient, viewerStartsOn } from "./services/viewer/PriorityMap.js";
|
|
71
72
|
import { coalescing } from "./utils/coalesce.js";
|
|
@@ -626,7 +627,9 @@ export async function startProxyServer({
|
|
|
626
627
|
const revision = outputParts.segmentStore.revisionOf(output.outputKey);
|
|
627
628
|
const links = consumers.map(viewer => viewer.linkReading()?.linkMbps)
|
|
628
629
|
.filter(value => Number.isFinite(value) && value > 0);
|
|
629
|
-
|
|
630
|
+
// A piece copying a stream answers for the second its end falls in,
|
|
631
|
+
// as the encode plan reads it (`piecesRunPastEnd`).
|
|
632
|
+
played.push({ outputKey: output.outputKey, grid, runsPastEnd: piecesRunPastEnd(output.spec),
|
|
630
633
|
encodeSpeed: outputParts.encodeCost.latestSpeedReadingOf(output)?.speed,
|
|
631
634
|
bytesPerSecond: bytesPerSecondOf(output, revision), linkMbps: links.length ? Math.min(...links) : null,
|
|
632
635
|
readyRevision: revision, ready: () => outputParts.segmentStore.sizesOf(output.outputKey),
|
|
@@ -25,6 +25,7 @@ import { cutOf, cutShiftOf, judgeNeighbors, judgePiece, leadingSecondsOf, lacksO
|
|
|
25
25
|
import { leadingPictures, presentationSegment } from "./segment-formats/presentation-segment.js";
|
|
26
26
|
import { InputFailures, failedAdmittedInput } from "./InputFailures.js";
|
|
27
27
|
import { readProcessCpuSeconds } from "./host-load.js";
|
|
28
|
+
import { piecesRunPastEnd } from "./output/Timeline.js";
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -599,7 +600,7 @@ export class EncodeRuns {
|
|
|
599
600
|
timeline?.inSegments?.(
|
|
600
601
|
this.#host.priority.mapForOutput(address),
|
|
601
602
|
Number(timeline?.segmentCount) || 0,
|
|
602
|
-
{ runsPastEnd:
|
|
603
|
+
{ runsPastEnd: piecesRunPastEnd(spec) }
|
|
603
604
|
) ?? []
|
|
604
605
|
);
|
|
605
606
|
}
|
|
@@ -42,6 +42,27 @@
|
|
|
42
42
|
* reveal where the file's cuts really are.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Whether an output's pieces copy a stream, picture or sound, and so may hold
|
|
47
|
+
* a little past their published end: a copy ends at the first packet boundary
|
|
48
|
+
* at or after it, and the player places the next piece where this one truly
|
|
49
|
+
* ends (hls.js `updateFragPTSDTS`). A viewer standing at a published edge is
|
|
50
|
+
* then asked for by the piece BEFORE it.
|
|
51
|
+
*
|
|
52
|
+
* Field 2026-10-09, MP3 copied out of an AVI: a seek to 4000 s asked for the
|
|
53
|
+
* piece ending at 4000 s. Field r43 (Home Assistant, 2026-10-15), a copied
|
|
54
|
+
* Matroska picture: a seek to 1000.000 s, the published start of #96, asked for
|
|
55
|
+
* #95 on both tracks; the picture's pieces were not counted as copies, nothing
|
|
56
|
+
* made #95, and the picture stood 131 s until the viewer moved
|
|
57
|
+
* (torrent-tv/meta#178).
|
|
58
|
+
*
|
|
59
|
+
* @param {{ video?: object | null, audio?: object | null, transcodesVideo?: boolean, transcodesAudio?: boolean } | null | undefined} spec
|
|
60
|
+
* @returns {boolean}
|
|
61
|
+
*/
|
|
62
|
+
export function piecesRunPastEnd(spec) {
|
|
63
|
+
return (Boolean(spec?.video) && spec.transcodesVideo !== true) || (Boolean(spec?.audio) && spec.transcodesAudio !== true);
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
export class Timeline {
|
|
46
67
|
/**
|
|
47
68
|
* @param {object} params
|
|
@@ -267,11 +267,18 @@ class RangeEntry {
|
|
|
267
267
|
|
|
268
268
|
/** One output segment as the projection holds it. */
|
|
269
269
|
class Segment {
|
|
270
|
-
constructor(output, index, from, to) {
|
|
270
|
+
constructor(output, index, from, to, reach = to) {
|
|
271
271
|
this.output = output;
|
|
272
272
|
this.index = index;
|
|
273
273
|
this.from = from;
|
|
274
274
|
this.to = to;
|
|
275
|
+
/**
|
|
276
|
+
* Up to which second the map's zones are read for it: its end, and for a
|
|
277
|
+
* piece that copies a stream the second its end falls in as well, as the
|
|
278
|
+
* encode plan reads it (`Timeline.inSegments`): the player asks a viewer
|
|
279
|
+
* standing at its end for this piece, and its bytes are fetched for them.
|
|
280
|
+
*/
|
|
281
|
+
this.reach = reach;
|
|
275
282
|
/** Whether a zone of the map overlaps it. */
|
|
276
283
|
this.demanded = false;
|
|
277
284
|
/** Whether the output has it made. */
|
|
@@ -304,13 +311,16 @@ class Segment {
|
|
|
304
311
|
|
|
305
312
|
/** One output: its grid, how long its segments take to make and deliver, what is made, and its next input. */
|
|
306
313
|
class OutputState {
|
|
307
|
-
constructor(outputKey, grid) {
|
|
314
|
+
constructor(outputKey, grid, runsPastEnd = false) {
|
|
308
315
|
this.outputKey = outputKey;
|
|
309
316
|
this.grid = grid;
|
|
317
|
+
this.runsPastEnd = runsPastEnd;
|
|
310
318
|
/** @type {Segment[]} */
|
|
311
319
|
this.segments = [];
|
|
312
320
|
for (let index = 0; index + 1 < grid.length; index++) {
|
|
313
|
-
|
|
321
|
+
const end = grid[index + 1];
|
|
322
|
+
this.segments.push(new Segment(this, index, grid[index], end,
|
|
323
|
+
runsPastEnd && index + 2 < grid.length ? Math.max(end, Math.floor(end) + 1) : end));
|
|
314
324
|
}
|
|
315
325
|
this.encodeSpeed = undefined;
|
|
316
326
|
this.bytesPerSecond = undefined;
|
|
@@ -370,7 +380,7 @@ export class PlaybackProjection {
|
|
|
370
380
|
* @param {object} params
|
|
371
381
|
* @param {Array<{ from: number, to: number, priority: number, urgent?: boolean, behind?: boolean, deferred?: boolean,
|
|
372
382
|
* deadlineAt?: number }>} params.zones - The file's map zones, deadlines as instants.
|
|
373
|
-
* @param {Array<{ outputKey: string, grid: number[], encodeSpeed?: number, bytesPerSecond?: number | null,
|
|
383
|
+
* @param {Array<{ outputKey: string, grid: number[], runsPastEnd?: boolean, encodeSpeed?: number, bytesPerSecond?: number | null,
|
|
374
384
|
* linkMbps?: number | null, readyRevision: unknown, ready: () => Map<number, number>,
|
|
375
385
|
* target: number | null }>} params.outputs - Every output of the file somebody is watching, with
|
|
376
386
|
* what decides how long its segments take to make and deliver (`sourceDeadline`), what it has made
|
|
@@ -391,13 +401,13 @@ export class PlaybackProjection {
|
|
|
391
401
|
for (const info of outputs) {
|
|
392
402
|
seen.add(info.outputKey);
|
|
393
403
|
let output = this.#outputs.get(info.outputKey);
|
|
394
|
-
if (output && output.grid !== info.grid) {
|
|
404
|
+
if (output && (output.grid !== info.grid || output.runsPastEnd !== Boolean(info.runsPastEnd))) {
|
|
395
405
|
this.#dropOutput(output);
|
|
396
406
|
output = undefined;
|
|
397
407
|
}
|
|
398
408
|
const fresh = !output;
|
|
399
409
|
if (fresh) {
|
|
400
|
-
output = new OutputState(info.outputKey, info.grid);
|
|
410
|
+
output = new OutputState(info.outputKey, info.grid, Boolean(info.runsPastEnd));
|
|
401
411
|
this.#outputs.set(info.outputKey, output);
|
|
402
412
|
}
|
|
403
413
|
// How long a segment of it takes to make and deliver.
|
|
@@ -426,7 +436,9 @@ export class PlaybackProjection {
|
|
|
426
436
|
for (const segment of output.segments) if (this.#demand(segment, zones, ordered, nextBase, leadMoved)) reworked++;
|
|
427
437
|
} else {
|
|
428
438
|
for (const { from, to } of stretches) {
|
|
429
|
-
|
|
439
|
+
// A piece reaching past its end is read for the stretch after it too.
|
|
440
|
+
for (let index = Math.max(0, segmentAt(output.grid, from) - (output.runsPastEnd ? 1 : 0));
|
|
441
|
+
index < output.segments.length && output.segments[index].from < to; index++) {
|
|
430
442
|
if (this.#demand(output.segments[index], zones, ordered, nextBase, false)) reworked++;
|
|
431
443
|
}
|
|
432
444
|
}
|
|
@@ -542,7 +554,7 @@ export class PlaybackProjection {
|
|
|
542
554
|
#demand(segment, zones, ordered, base, leadMoved) {
|
|
543
555
|
// The zones it overlaps, folded as they are met.
|
|
544
556
|
let owner = null, ownerDeadline = Infinity, priority = 0, urgent = false, behind = true, deferred = true;
|
|
545
|
-
const { from, to } = segment;
|
|
557
|
+
const { from, reach: to } = segment;
|
|
546
558
|
for (let at = ordered ? firstEndingAfter(zones, from) : 0; at < zones.length; at += 1) {
|
|
547
559
|
const zone = zones[at];
|
|
548
560
|
if (ordered && !(zone.from < to)) break;
|
|
@@ -592,8 +604,8 @@ export class PlaybackProjection {
|
|
|
592
604
|
let owner = null, ownerDeadline = Infinity;
|
|
593
605
|
for (let at = this.#ordered ? firstEndingAfter(zones, segment.from) : 0; at < zones.length; at += 1) {
|
|
594
606
|
const zone = zones[at];
|
|
595
|
-
if (this.#ordered && !(zone.from < segment.
|
|
596
|
-
if (!(zone.priority > 0) || !(zone.from < segment.
|
|
607
|
+
if (this.#ordered && !(zone.from < segment.reach)) break;
|
|
608
|
+
if (!(zone.priority > 0) || !(zone.from < segment.reach && zone.to > segment.from)) continue;
|
|
597
609
|
const deadline = Number.isFinite(zone.deadlineAt) ? zone.deadlineAt + Math.max(0, segment.from - zone.from) * 1000 : Infinity;
|
|
598
610
|
if (owner === null || deadline < ownerDeadline) { owner = zone; ownerDeadline = deadline; }
|
|
599
611
|
}
|