@torrent-tv/proxy 2.99.3 → 2.99.4
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
|
@@ -251,11 +251,18 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
251
251
|
const processingCoverage = numericCoverage((Array.isArray(track.processingRanges) ? track.processingRanges : [])
|
|
252
252
|
.filter(range => Number.isFinite(range?.start) && Number.isFinite(range?.end) && range.end > range.start));
|
|
253
253
|
const measuredBitsPerMediaSecond = state.bitsPerMediaSecond;
|
|
254
|
+
const trackSources = Array.isArray(track.sourceIds) ? track.sourceIds : [];
|
|
254
255
|
for (const segment of segments) {
|
|
255
|
-
// Media the page already holds is not transferred again
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
// Media the page already holds is not transferred again: every range of
|
|
257
|
+
// the piece still ahead of the viewer lies inside what the page holds.
|
|
258
|
+
let held = segment.ranges.length > 0;
|
|
259
|
+
for (const range of segment.ranges) {
|
|
260
|
+
if (compare(range.end, at) > 0 && !contains(state.clientRanges, latest(range.start, at), range.end)) {
|
|
261
|
+
held = false;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (held) {
|
|
259
266
|
continue;
|
|
260
267
|
}
|
|
261
268
|
const exactBytes = sizeOf(track.segmentSizesBytes, segment.index);
|
|
@@ -275,28 +282,30 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
275
282
|
const remainingProcessing = uncoveredIntervals({ start: workStart, end: segment.endSeconds }, processingCoverage);
|
|
276
283
|
const encodeWork = produced ? 0 : remainingProcessing.reduce((sum, part) => sum + part.end - part.start, 0);
|
|
277
284
|
|
|
278
|
-
|
|
285
|
+
let sourceByteIntervals = NO_SOURCE_BYTES;
|
|
279
286
|
if (!produced && encodeWork > 0) {
|
|
287
|
+
// Each source the piece reads, in order and once: the track's own, then
|
|
288
|
+
// those its inputs name. Gathered with loops and no copies, because
|
|
289
|
+
// this runs for every piece still to play on every poll of a waiting
|
|
290
|
+
// viewer, and the sets and filtered lists it built were a quarter of
|
|
291
|
+
// the forecast's time on a two-hour film (torrent-tv/meta#178).
|
|
280
292
|
const exactInputs = Array.isArray(segment.sourceInputs) ? segment.sourceInputs : null;
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
293
|
+
const count = trackSources.length + (exactInputs?.length ?? 0);
|
|
294
|
+
for (let place = 0; place < count; place += 1) {
|
|
295
|
+
const sourceId = place < trackSources.length ? trackSources[place]
|
|
296
|
+
: exactInputs[place - trackSources.length]?.sourceId;
|
|
297
|
+
if (namedBefore(sourceId, place, trackSources, exactInputs)) continue;
|
|
286
298
|
const source = sourceState.get(sourceId);
|
|
287
299
|
if (!source) {
|
|
288
300
|
unknownReason ??= "source-measurement-unavailable";
|
|
289
301
|
} else if (source.source.complete !== true) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
} else sourceByteIntervals.set(sourceId, ranges);
|
|
297
|
-
continue;
|
|
302
|
+
const ranges = exactInputs ? inputRangesOf(exactInputs, sourceId) : null;
|
|
303
|
+
if (ranges === null) {
|
|
304
|
+
unknownReason ??= "source-input-ranges-unavailable";
|
|
305
|
+
} else {
|
|
306
|
+
if (sourceByteIntervals === NO_SOURCE_BYTES) sourceByteIntervals = new Map();
|
|
307
|
+
sourceByteIntervals.set(sourceId, ranges);
|
|
298
308
|
}
|
|
299
|
-
unknownReason ??= "source-input-ranges-unavailable";
|
|
300
309
|
}
|
|
301
310
|
}
|
|
302
311
|
}
|
|
@@ -456,10 +465,14 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
456
465
|
const prepared = state.clientRanges.map((range) => ({ ...range }));
|
|
457
466
|
const lastRange = state.segments.at(-1).ranges.at(-1);
|
|
458
467
|
const trackEnd = lastRange ? earliest(end, lastRange.end) : end;
|
|
468
|
+
// What the page holds without a gap from the viewer only changes when a
|
|
469
|
+
// piece is added to it, so the end after one piece is the end before the
|
|
470
|
+
// next.
|
|
471
|
+
let after = contiguousEnd(delivered, at);
|
|
459
472
|
for (const completion of completions.filter((item) => item.trackIndex === trackIndex)) {
|
|
460
|
-
const before =
|
|
473
|
+
const before = after;
|
|
461
474
|
for (const range of completion.segment.ranges) addMediaRange(delivered, range);
|
|
462
|
-
|
|
475
|
+
after = contiguousEnd(delivered, at);
|
|
463
476
|
const ahead = secondsAhead(earliest(before, trackEnd), at);
|
|
464
477
|
constrain(completion.at - ahead, "delivery", trackIndex, completion.segment.index, completion.at, ahead, false);
|
|
465
478
|
if (compare(after, before) <= 0 && completion.segment.ranges.some(({ start }) => compare(start, before) > 0)) {
|
|
@@ -468,7 +481,7 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
468
481
|
return result(false, null, buffered, reserve, null, 'media-continuity-unavailable', preparedSegments);
|
|
469
482
|
}
|
|
470
483
|
}
|
|
471
|
-
if (compare(
|
|
484
|
+
if (compare(after, trackEnd) < 0) {
|
|
472
485
|
// Everything this track still needs has been delivered in the schedule
|
|
473
486
|
// and its media still stops short of the end: a hole between ranges
|
|
474
487
|
// already held, which no arrival crosses.
|
|
@@ -807,6 +820,41 @@ function latestArrival(interval, ranges = []) {
|
|
|
807
820
|
return finish;
|
|
808
821
|
}
|
|
809
822
|
|
|
823
|
+
/** A piece that reads no source byte still to arrive; never written to. */
|
|
824
|
+
const NO_SOURCE_BYTES = new Map();
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Whether the source named at `place` of a piece's sources — the track's own,
|
|
828
|
+
* then those its inputs name — was named at an earlier place.
|
|
829
|
+
*/
|
|
830
|
+
function namedBefore(sourceId, place, trackSources, exactInputs) {
|
|
831
|
+
for (let earlier = 0; earlier < place; earlier += 1) {
|
|
832
|
+
const named = earlier < trackSources.length ? trackSources[earlier]
|
|
833
|
+
: exactInputs[earlier - trackSources.length]?.sourceId;
|
|
834
|
+
if (named === sourceId) return true;
|
|
835
|
+
}
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* The byte ranges a piece reads of one source, in the order its inputs state
|
|
841
|
+
* them, or null when an input of that source states none or one that is not a
|
|
842
|
+
* non-empty run of whole bytes.
|
|
843
|
+
*/
|
|
844
|
+
function inputRangesOf(exactInputs, sourceId) {
|
|
845
|
+
let ranges = null;
|
|
846
|
+
for (const input of exactInputs) {
|
|
847
|
+
if (input?.sourceId !== sourceId) continue;
|
|
848
|
+
if (!Array.isArray(input.ranges) || input.ranges.length === 0) return null;
|
|
849
|
+
for (const range of input.ranges) {
|
|
850
|
+
if (!range || !Number.isSafeInteger(range.start) || !Number.isSafeInteger(range.end) ||
|
|
851
|
+
range.start < 0 || range.end <= range.start) return null;
|
|
852
|
+
}
|
|
853
|
+
ranges = ranges === null ? input.ranges : ranges.concat(input.ranges);
|
|
854
|
+
}
|
|
855
|
+
return ranges;
|
|
856
|
+
}
|
|
857
|
+
|
|
810
858
|
/** Exact gaps in sorted disjoint numeric coverage, without per-range splitting. */
|
|
811
859
|
function uncoveredIntervals(interval, served) {
|
|
812
860
|
let low = 0, high = served.length;
|