@torrent-tv/proxy 2.95.32 → 2.95.34
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
|
@@ -612,12 +612,13 @@ export class ViewerRequests {
|
|
|
612
612
|
})).filter(range => range.end > range.start) } : sharedForecast;
|
|
613
613
|
sourceMeasurements.set(sourceId, {
|
|
614
614
|
id: sourceId,
|
|
615
|
+
serviceKey: output.file.sourceKey,
|
|
615
616
|
complete: stats?.fileAvailable === true,
|
|
616
617
|
residence: stats?.residence,
|
|
617
618
|
fileOffset: stats?.fileOffset,
|
|
618
619
|
fileLength: stats?.fileLength,
|
|
619
620
|
pieceLength: stats?.pieceLength,
|
|
620
|
-
downloadRateReadings: this.#sourceRateReadings(measurement,
|
|
621
|
+
downloadRateReadings: this.#sourceRateReadings(measurement, output.file.sourceKey, stats, now),
|
|
621
622
|
downloadForecast,
|
|
622
623
|
});
|
|
623
624
|
}
|
|
@@ -37,6 +37,10 @@ import {
|
|
|
37
37
|
ZERO
|
|
38
38
|
} from "./media-time.js";
|
|
39
39
|
|
|
40
|
+
// Match the media element's allowance for a range that starts a few frames
|
|
41
|
+
// after its current position (see server/public/domain/buffer-metrics.js).
|
|
42
|
+
const CLIENT_RANGE_START_TOLERANCE_SECONDS = 0.25;
|
|
43
|
+
|
|
40
44
|
/**
|
|
41
45
|
* The mean rate predicted over a horizon by the integral of the measured rate.
|
|
42
46
|
* Polling does not change the last measured service rate.
|
|
@@ -99,7 +103,8 @@ export class RateTrend {
|
|
|
99
103
|
* @param {number} input.lookaheadSeconds
|
|
100
104
|
* @param {number} input.now
|
|
101
105
|
* @param {boolean} [input.requiredAudio]
|
|
102
|
-
* @param {Array<{ id: string, complete: boolean,
|
|
106
|
+
* @param {Array<{ id: string, serviceKey?: string, complete: boolean, fileOffset?: number,
|
|
107
|
+
* fileLength?: number, pieceLength?: number, downloadRateReadings?: object,
|
|
103
108
|
* downloadForecast?: { ranges: Array<{ start: number, end: number, availableAt: number | null }> } }>} input.sources
|
|
104
109
|
* @param {Array<{ id: string, sourceIds: string[], processedSeconds: number,
|
|
105
110
|
* bitsPerMediaSecond: number, readings: Array<{ at: number, value: number }>,
|
|
@@ -147,7 +152,7 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
147
152
|
const position = toSeconds(at);
|
|
148
153
|
const remaining = Math.max(0, duration - position);
|
|
149
154
|
const measuredRanges = reported.map((ranges, trackIndex) => ranges ?
|
|
150
|
-
heldRanges(ranges, segmentsOf[trackIndex], unit) : null);
|
|
155
|
+
heldRanges(ranges, segmentsOf[trackIndex], unit, at) : null);
|
|
151
156
|
const measured = measuredRanges.length > 0 && measuredRanges.every(Boolean);
|
|
152
157
|
const heldEnd = measured ? earliest(...measuredRanges.map((ranges) => contiguousEnd(ranges, at))) : null;
|
|
153
158
|
// The figure reported back is what the page said it holds; the widening by
|
|
@@ -192,8 +197,16 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
192
197
|
return result(false, null, buffered, reserve, null, "timeline-unavailable", 0);
|
|
193
198
|
}
|
|
194
199
|
const sourceState = new Map();
|
|
200
|
+
// A torrent has one download service, even when required media lives in separate files.
|
|
201
|
+
const sourceServices = new Map();
|
|
195
202
|
for (const [id, source] of sources) {
|
|
196
|
-
|
|
203
|
+
const serviceKey = typeof source.serviceKey === "string" ? source.serviceKey : id;
|
|
204
|
+
let service = sourceServices.get(serviceKey);
|
|
205
|
+
if (!service) {
|
|
206
|
+
service = { rate: rateCurve(source.downloadRateReadings, now), finish: 0 };
|
|
207
|
+
sourceServices.set(serviceKey, service);
|
|
208
|
+
}
|
|
209
|
+
sourceState.set(id, { source, service });
|
|
197
210
|
}
|
|
198
211
|
const link = rateCurve(input.linkReadings, now);
|
|
199
212
|
let unknownReason = null;
|
|
@@ -333,7 +346,8 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
333
346
|
// pieces, project only the media-order demand from the torrent's latest
|
|
334
347
|
// measured download service; no supplier or positive rate means no
|
|
335
348
|
// estimated arrival.
|
|
336
|
-
const { source: facts,
|
|
349
|
+
const { source: facts, service: sourceService } = source;
|
|
350
|
+
const { rate } = sourceService;
|
|
337
351
|
if (!rate || !(rate.rateAt(0) > 0) || !Number.isSafeInteger(facts.pieceLength) ||
|
|
338
352
|
facts.pieceLength <= 0 || !Number.isSafeInteger(facts.fileOffset) ||
|
|
339
353
|
!Number.isSafeInteger(facts.fileLength) || facts.fileLength <= 0) {
|
|
@@ -342,7 +356,7 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
342
356
|
unavailableSource: { sourceId, segmentIndex: segment.index, range: interval, missing } };
|
|
343
357
|
}
|
|
344
358
|
const estimatedPieces = sourcePiecesFor(missing, facts);
|
|
345
|
-
let finish = Math.max(
|
|
359
|
+
let finish = Math.max(sourceService.finish, sourceArrivalAt);
|
|
346
360
|
const service = sourceByteService.get(sourceId) ?? [];
|
|
347
361
|
for (const pieceRange of estimatedPieces) {
|
|
348
362
|
const stillMissing = uncoveredIntervals(pieceRange, sourceByteCoverage.get(sourceId) ?? []);
|
|
@@ -354,7 +368,7 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
354
368
|
sourceByteCoverage.set(sourceId, numericCoverage(service));
|
|
355
369
|
sourceArrivals.set(sourceId, indexedArrivals(service));
|
|
356
370
|
}
|
|
357
|
-
|
|
371
|
+
sourceService.finish = finish;
|
|
358
372
|
}
|
|
359
373
|
}
|
|
360
374
|
// Production accepts a complete held segment input. It cannot overlap its
|
|
@@ -501,13 +515,18 @@ function joinedPiece(served) {
|
|
|
501
515
|
* @param {bigint} unit
|
|
502
516
|
* @returns {import("./media-time.js").MediaRange[]}
|
|
503
517
|
*/
|
|
504
|
-
function heldRanges(ranges, segments, unit) {
|
|
518
|
+
function heldRanges(ranges, segments, unit, position) {
|
|
505
519
|
const pieces = segments.flatMap((segment) => segment.ranges ?? []);
|
|
506
520
|
const zero = mediaTime(0n, unit);
|
|
507
521
|
const error = rescale(REPORTED_TIME_ERROR, unit);
|
|
522
|
+
const startTolerance = add(position, rescale(fromSeconds(CLIENT_RANGE_START_TOLERANCE_SECONDS), unit));
|
|
508
523
|
const held = [];
|
|
509
524
|
for (const range of ranges) {
|
|
510
|
-
const
|
|
525
|
+
const reportedStart = rescale(range.start, unit);
|
|
526
|
+
let start = latest(zero, subtract(reportedStart, error));
|
|
527
|
+
if (compare(reportedStart, position) > 0 && compare(reportedStart, startTolerance) <= 0) {
|
|
528
|
+
start = position;
|
|
529
|
+
}
|
|
511
530
|
const end = add(rescale(range.end, unit), error);
|
|
512
531
|
const inside = pieces.filter((piece) => contains([{ start, end }], piece.start, piece.end));
|
|
513
532
|
addMediaRange(held, { start, end, frame: latest(zero, ...inside.map(({ frame }) => frame)) });
|