@torrent-tv/proxy 2.98.7 → 2.98.8
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
|
@@ -60,7 +60,13 @@ export class ViewerRequests {
|
|
|
60
60
|
#host;
|
|
61
61
|
/** @type {WeakMap<object, Map<string, object>>} */
|
|
62
62
|
#rateStateBySession = new WeakMap();
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* The pieces of each output as the start forecast read them last (see
|
|
65
|
+
* `#segmentsOf`), and what the forecast keeps of their time geometry while
|
|
66
|
+
* they stand (`playback-readiness.js` `timescaleOfPieces`).
|
|
67
|
+
*
|
|
68
|
+
* @type {WeakMap<object, { pieces: object[], geometry: object }>}
|
|
69
|
+
*/
|
|
64
70
|
#forecastPieces = new WeakMap();
|
|
65
71
|
|
|
66
72
|
/**
|
|
@@ -658,6 +664,7 @@ export class ViewerRequests {
|
|
|
658
664
|
this.#host.initialOutputBitsPerMediaSecondOf?.(output) ?? 0,
|
|
659
665
|
readings: trackRates,
|
|
660
666
|
segments,
|
|
667
|
+
geometry: this.#forecastPieces.get(output)?.geometry,
|
|
661
668
|
readySegmentIndices: [...inventory.keys()],
|
|
662
669
|
segmentSizesBytes: inventory
|
|
663
670
|
});
|
|
@@ -808,11 +815,12 @@ export class ViewerRequests {
|
|
|
808
815
|
#segmentsOf(output, sessionInit, inventory, positionSeconds) {
|
|
809
816
|
const timeline = output.timeline;
|
|
810
817
|
const count = timeline?.segmentCount ?? 0;
|
|
811
|
-
let
|
|
812
|
-
if (!
|
|
813
|
-
|
|
814
|
-
this.#forecastPieces.set(output,
|
|
818
|
+
let forecast = this.#forecastPieces.get(output);
|
|
819
|
+
if (!forecast) {
|
|
820
|
+
forecast = { pieces: [], geometry: {} };
|
|
821
|
+
this.#forecastPieces.set(output, forecast);
|
|
815
822
|
}
|
|
823
|
+
const kept = forecast.pieces;
|
|
816
824
|
const segments = [];
|
|
817
825
|
for (let index = 0; index < count; index++) {
|
|
818
826
|
const endSeconds = timeline.publishedStartOf(index + 1);
|
|
@@ -139,21 +139,16 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
139
139
|
range.end > range.start).map((range) => ({ start: fromSeconds(range.start), end: fromSeconds(range.end) }))
|
|
140
140
|
: null);
|
|
141
141
|
// One timescale in which every time of this forecast is a whole number of
|
|
142
|
-
// ticks, so that adding and comparing never seek a common denominator.
|
|
142
|
+
// ticks, so that adding and comparing never seek a common denominator. A
|
|
143
|
+
// track's pieces give it the same timescale while they are the same pieces.
|
|
143
144
|
const unit = commonTimescale([
|
|
144
145
|
fromSeconds(finiteNonNegative(input.positionSeconds)), fromSeconds(duration),
|
|
145
146
|
fromSeconds(finiteNonNegative(input.bufferedAheadSeconds)), REPORTED_TIME_ERROR, ...clocks,
|
|
146
147
|
...reported.flatMap((ranges) => (ranges ?? []).flatMap(({ start, end }) => [start, end])),
|
|
147
|
-
...tracks.
|
|
148
|
-
...(segment.mediaRanges?.timescale ? [mediaTime(0n, segment.mediaRanges.timescale)] : []),
|
|
149
|
-
...exactBoundsOf(segment)
|
|
150
|
-
]))
|
|
148
|
+
...tracks.map((track) => mediaTime(0n, timescaleOfPieces(track)))
|
|
151
149
|
]);
|
|
152
150
|
const zero = mediaTime(0n, unit);
|
|
153
|
-
const segmentsOf = tracks.map((track, trackIndex) =>
|
|
154
|
-
(Array.isArray(track.segments) ? track.segments : []).map((segment) => ({
|
|
155
|
-
...segment, exact: exactTimesOf(segment), ranges: playerRanges(segment.mediaRanges, rescale(clocks[trackIndex], unit), unit)
|
|
156
|
-
})), unit));
|
|
151
|
+
const segmentsOf = tracks.map((track, trackIndex) => placedPieces(track, rescale(clocks[trackIndex], unit), unit));
|
|
157
152
|
const origin = latest(zero, ...segmentsOf.map((segments) =>
|
|
158
153
|
segments.find(({ index }) => index === 0)?.ranges?.[0]?.start ?? zero));
|
|
159
154
|
const at = latest(origin, rescale(fromSeconds(finiteNonNegative(input.positionSeconds)), unit));
|
|
@@ -645,10 +640,69 @@ function timesOf(segment) {
|
|
|
645
640
|
return segment.exact ?? exactTimesOf(segment);
|
|
646
641
|
}
|
|
647
642
|
|
|
648
|
-
/**
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
643
|
+
/**
|
|
644
|
+
* WHAT A TRACK'S PIECES GIVE THE FORECAST'S TIME, KEPT BETWEEN POLLS while
|
|
645
|
+
* they are the same pieces. The forecast runs on every poll of a waiting
|
|
646
|
+
* viewer, and gathering the edges of every remaining piece for the timescale,
|
|
647
|
+
* then placing each on the page's clock, was about 3 s of the proxy's main
|
|
648
|
+
* thread in r35's first 15 minutes on Home Assistant (torrent-tv/meta#178).
|
|
649
|
+
* `track.geometry` is kept by whoever keeps the pieces; without it nothing is
|
|
650
|
+
* kept, and the answer is the same either way: a placement is reused only for
|
|
651
|
+
* the same pieces, the same timescale and the same clock offset, so no time is
|
|
652
|
+
* rounded for the reuse.
|
|
653
|
+
*
|
|
654
|
+
* @param {{ segments?: object[], geometry?: object }} track
|
|
655
|
+
* @returns {bigint} The least timescale holding every edge and every served tick of its pieces.
|
|
656
|
+
*/
|
|
657
|
+
function timescaleOfPieces(track) {
|
|
658
|
+
const segments = Array.isArray(track.segments) ? track.segments : [];
|
|
659
|
+
const geometry = track.geometry;
|
|
660
|
+
if (geometry && samePieces(geometry.pieces, segments)) return geometry.timescale;
|
|
661
|
+
const distinct = new Set();
|
|
662
|
+
for (const segment of segments) {
|
|
663
|
+
if (segment.mediaRanges?.timescale) distinct.add(segment.mediaRanges.timescale);
|
|
664
|
+
const { start, end } = exactTimesOf(segment);
|
|
665
|
+
if (start) distinct.add(start.timescale);
|
|
666
|
+
if (end) distinct.add(end.timescale);
|
|
667
|
+
}
|
|
668
|
+
const timescale = commonTimescale([...distinct].map((value) => mediaTime(0n, value)));
|
|
669
|
+
if (geometry) {
|
|
670
|
+
geometry.pieces = segments.map((segment) => ({ segment, index: segment.index, startSeconds: segment.startSeconds,
|
|
671
|
+
endSeconds: segment.endSeconds, mediaRanges: segment.mediaRanges }));
|
|
672
|
+
geometry.timescale = timescale;
|
|
673
|
+
geometry.placed = null;
|
|
674
|
+
}
|
|
675
|
+
return timescale;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* A track's pieces on the page's clock in `unit`: read pieces at their served
|
|
680
|
+
* ranges, the others placed between them. Reused while the pieces are those
|
|
681
|
+
* {@link timescaleOfPieces} last took and `unit` and `clock` are the same.
|
|
682
|
+
*
|
|
683
|
+
* @param {{ segments?: object[], geometry?: object }} track
|
|
684
|
+
* @param {import("./media-time.js").MediaTime} clock - In `unit`.
|
|
685
|
+
* @param {bigint} unit
|
|
686
|
+
*/
|
|
687
|
+
function placedPieces(track, clock, unit) {
|
|
688
|
+
const geometry = track.geometry;
|
|
689
|
+
if (geometry?.placed && geometry.placedUnit === unit && geometry.placedClock === clock.ticks) return geometry.placed;
|
|
690
|
+
const placed = predictedMediaSegments((Array.isArray(track.segments) ? track.segments : []).map((segment) => ({
|
|
691
|
+
...segment, exact: exactTimesOf(segment), ranges: playerRanges(segment.mediaRanges, clock, unit)
|
|
692
|
+
})), unit);
|
|
693
|
+
if (geometry) Object.assign(geometry, { placed, placedUnit: unit, placedClock: clock.ticks });
|
|
694
|
+
return placed;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/** Whether `segments` are the pieces `kept` recorded, field by field the forecast reads. */
|
|
698
|
+
function samePieces(kept, segments) {
|
|
699
|
+
if (!Array.isArray(kept) || kept.length !== segments.length) return false;
|
|
700
|
+
for (let index = 0; index < segments.length; index++) {
|
|
701
|
+
const segment = segments[index], was = kept[index];
|
|
702
|
+
if (segment !== was.segment || segment.index !== was.index || segment.startSeconds !== was.startSeconds ||
|
|
703
|
+
segment.endSeconds !== was.endSeconds || segment.mediaRanges !== was.mediaRanges) return false;
|
|
704
|
+
}
|
|
705
|
+
return true;
|
|
652
706
|
}
|
|
653
707
|
|
|
654
708
|
/**
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
export class SegmentInputTable {
|
|
13
|
-
/** `sourceKey` → output → segment →
|
|
13
|
+
/** `sourceKey` → output → segment → its files' ranges, and the list `inputsFor` answers while they stand. @type {Map<string, Map<string, Map<number, { files: Map<number, { joined: Array<[number, number]>, ranges: Array<{ start: number, end: number }> }>, inputs: Array<{ sourceId: string, ranges: Array<{ start: number, end: number }> }> | null }>>>} */
|
|
14
14
|
#bySource = new Map();
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -25,15 +25,19 @@ export class SegmentInputTable {
|
|
|
25
25
|
if (!outputs) this.#bySource.set(sourceKey, outputs = new Map());
|
|
26
26
|
let segments = outputs.get(outputKey);
|
|
27
27
|
if (!segments) outputs.set(outputKey, segments = new Map());
|
|
28
|
-
let
|
|
29
|
-
if (!
|
|
28
|
+
let segment = segments.get(index);
|
|
29
|
+
if (!segment) segments.set(index, segment = { files: new Map(), inputs: null });
|
|
30
30
|
// The same reading keeps its list: whoever compares inputs compares them by it.
|
|
31
|
-
if (files.get(fileIndex)?.joined === joined) return;
|
|
32
|
-
files.set(fileIndex, { joined, ranges: joined.map(([start, end]) => ({ start, end: end + 1 })) });
|
|
31
|
+
if (segment.files.get(fileIndex)?.joined === joined) return;
|
|
32
|
+
segment.files.set(fileIndex, { joined, ranges: joined.map(([start, end]) => ({ start, end: end + 1 })) });
|
|
33
|
+
segment.inputs = null;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
|
-
* The bytes one segment reads, by file, or null where none are known.
|
|
37
|
+
* The bytes one segment reads, by file, or null where none are known. The
|
|
38
|
+
* same list while they stand: the start forecast asks for every segment of
|
|
39
|
+
* the film on each poll, and keeps what it derived from a segment whose
|
|
40
|
+
* inputs are the same list.
|
|
37
41
|
*
|
|
38
42
|
* @param {string} sourceKey
|
|
39
43
|
* @param {string} outputKey
|
|
@@ -41,9 +45,10 @@ export class SegmentInputTable {
|
|
|
41
45
|
* @returns {Array<{ sourceId: string, ranges: Array<{ start: number, end: number }> }> | null}
|
|
42
46
|
*/
|
|
43
47
|
inputsFor(sourceKey, outputKey, index) {
|
|
44
|
-
const
|
|
45
|
-
if (!files
|
|
46
|
-
|
|
48
|
+
const segment = this.#bySource.get(sourceKey)?.get(outputKey)?.get(index);
|
|
49
|
+
if (!segment?.files.size) return null;
|
|
50
|
+
segment.inputs ??= [...segment.files].map(([fileIndex, { ranges }]) => ({ sourceId: `${sourceKey}:${fileIndex}`, ranges }));
|
|
51
|
+
return segment.inputs;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
/** @param {string} sourceKey */
|