@torrent-tv/proxy 2.95.28 → 2.95.30
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
|
@@ -232,13 +232,8 @@ export class AviContainer extends Container {
|
|
|
232
232
|
} else if (type === "audio") {
|
|
233
233
|
if (strf.length < 16) throw new Error("AVI wave format is truncated.");
|
|
234
234
|
const tag = strf.readUInt16LE(0);
|
|
235
|
-
// WAVEFORMATEX states the average bytes per second the stream carries
|
|
236
|
-
// (`nAvgBytesPerSec`); zero states nothing. Without it a soundtrack the
|
|
237
|
-
// browser plays is re-encoded only because its copy has no figure to
|
|
238
|
-
// ask the link about (torrent-tv/meta#159).
|
|
239
235
|
track = new AudioTrack({ ...params, codecId: waveCodec(tag),
|
|
240
236
|
channels: strf.readUInt16LE(2), samplingFrequency: strf.readUInt32LE(4),
|
|
241
|
-
bitrateKbps: strf.readUInt32LE(8) * 8 / 1000,
|
|
242
237
|
codecPrivateB64: strf.subarray(18).toString("base64") });
|
|
243
238
|
track.blockAlign = strf.readUInt16LE(12);
|
|
244
239
|
track.matroskaCodecId = "A_MS/ACM";
|
|
@@ -78,8 +78,6 @@ export class AudioTrack extends ContainerTrack {
|
|
|
78
78
|
* @param {boolean} params.isVisualImpaired - FlagVisualImpaired / descriptive audio
|
|
79
79
|
* @param {number | null} params.channels
|
|
80
80
|
* @param {number | null} params.samplingFrequency
|
|
81
|
-
* @param {number | null} [params.bitrateKbps] - The rate the file states for
|
|
82
|
-
* this track, in kbit/s: an average, not a bound.
|
|
83
81
|
*/
|
|
84
82
|
constructor(params) {
|
|
85
83
|
super({ ...params, type: "audio" });
|
|
@@ -88,7 +86,6 @@ export class AudioTrack extends ContainerTrack {
|
|
|
88
86
|
this.isVisualImpaired = params.isVisualImpaired === true;
|
|
89
87
|
this.channels = Number.isFinite(params.channels) ? params.channels : null;
|
|
90
88
|
this.samplingFrequency = Number.isFinite(params.samplingFrequency) ? params.samplingFrequency : null;
|
|
91
|
-
this.bitrateKbps = Number.isFinite(params.bitrateKbps) && params.bitrateKbps > 0 ? params.bitrateKbps : null;
|
|
92
89
|
this.bitDepth = Number.isSafeInteger(params.bitDepth) && params.bitDepth > 0 ? params.bitDepth : null;
|
|
93
90
|
this.codecDelaySeconds = Number.isFinite(params.codecDelaySeconds) && params.codecDelaySeconds >= 0 ? params.codecDelaySeconds : 0;
|
|
94
91
|
// What the codec is configured as, and the most that configuration allows
|
|
@@ -614,6 +614,10 @@ export class ViewerRequests {
|
|
|
614
614
|
id: sourceId,
|
|
615
615
|
complete: stats?.fileAvailable === true,
|
|
616
616
|
residence: stats?.residence,
|
|
617
|
+
fileOffset: stats?.fileOffset,
|
|
618
|
+
fileLength: stats?.fileLength,
|
|
619
|
+
pieceLength: stats?.pieceLength,
|
|
620
|
+
downloadRateReadings: this.#sourceRateReadings(measurement, sourceId, stats, now),
|
|
617
621
|
downloadForecast,
|
|
618
622
|
});
|
|
619
623
|
}
|
|
@@ -718,10 +722,11 @@ export class ViewerRequests {
|
|
|
718
722
|
bufferLimitSeconds: input.bufferLimitSeconds,
|
|
719
723
|
lookaheadSeconds: input.lookaheadSeconds,
|
|
720
724
|
link: input.linkReadings,
|
|
721
|
-
sources: input.sources.map(({ id, complete, residence, downloadForecast }) => ({
|
|
725
|
+
sources: input.sources.map(({ id, complete, residence, downloadForecast, downloadRateReadings }) => ({
|
|
722
726
|
id, complete, residenceRanges: residence?.length ?? 0,
|
|
723
727
|
forecastAt: downloadForecast?.measuredAt ?? null, plannedPieces: downloadForecast?.ranges.length ?? 0,
|
|
724
|
-
unknownPieces: downloadForecast?.ranges.filter(range => range.availableAt === null).length ?? 0
|
|
728
|
+
unknownPieces: downloadForecast?.ranges.filter(range => range.availableAt === null).length ?? 0,
|
|
729
|
+
downloadRateBytesPerSecond: downloadRateReadings?.lastValue ?? null
|
|
725
730
|
})),
|
|
726
731
|
tracks: tracks.map(({ id, processedSeconds, readings, segments, readySegmentIndices }) => ({
|
|
727
732
|
id, processedSeconds, readings, segments: segments.length, prepared: readySegmentIndices.length
|
|
@@ -740,7 +745,7 @@ export class ViewerRequests {
|
|
|
740
745
|
const id = typeof consumerId === "string" ? consumerId : "";
|
|
741
746
|
let state = byViewer.get(id);
|
|
742
747
|
if (!state) {
|
|
743
|
-
state = { link: new this.#host.playbackReadiness.RateTrend(this.#host.lookaheadSeconds), tracks: new Map() };
|
|
748
|
+
state = { link: new this.#host.playbackReadiness.RateTrend(this.#host.lookaheadSeconds), tracks: new Map(), sources: new Map() };
|
|
744
749
|
byViewer.set(id, state);
|
|
745
750
|
}
|
|
746
751
|
return state;
|
|
@@ -764,4 +769,16 @@ export class ViewerRequests {
|
|
|
764
769
|
}
|
|
765
770
|
return trend.snapshot();
|
|
766
771
|
}
|
|
772
|
+
|
|
773
|
+
#sourceRateReadings(state, id, stats, now) {
|
|
774
|
+
let trend = state.sources.get(id);
|
|
775
|
+
if (!trend) {
|
|
776
|
+
trend = new this.#host.playbackReadiness.RateTrend(this.#host.lookaheadSeconds);
|
|
777
|
+
state.sources.set(id, trend);
|
|
778
|
+
}
|
|
779
|
+
if (Number.isFinite(stats?.downloadSpeed) && stats.downloadSpeed >= 0) {
|
|
780
|
+
trend.add(now, stats.downloadSpeed);
|
|
781
|
+
}
|
|
782
|
+
return trend.snapshot();
|
|
783
|
+
}
|
|
767
784
|
}
|
|
@@ -2118,6 +2118,7 @@ export class TorrentPool {
|
|
|
2118
2118
|
fileDownloaded,
|
|
2119
2119
|
fileLength,
|
|
2120
2120
|
fileOffset: file.offset,
|
|
2121
|
+
pieceLength: torrent.pieceLength,
|
|
2121
2122
|
residence,
|
|
2122
2123
|
fileAvailable: fileLength > 0 && residence.length > 0 && residence.every(range => range.location !== "missing"),
|
|
2123
2124
|
// Compatibility fields report the same current urgent map demand.
|
|
@@ -193,7 +193,7 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
193
193
|
}
|
|
194
194
|
const sourceState = new Map();
|
|
195
195
|
for (const [id, source] of sources) {
|
|
196
|
-
sourceState.set(id, { source });
|
|
196
|
+
sourceState.set(id, { source, rate: rateCurve(source.downloadRateReadings, now), serviceFinish: 0 });
|
|
197
197
|
}
|
|
198
198
|
const link = rateCurve(input.linkReadings, now);
|
|
199
199
|
let unknownReason = null;
|
|
@@ -318,11 +318,43 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
318
318
|
const { track, trackIndex, segment, sizeBits, encodeWork, sourceByteIntervals } = job;
|
|
319
319
|
let sourceArrivalAt = 0;
|
|
320
320
|
for (const [sourceId, ranges] of sourceByteIntervals) {
|
|
321
|
+
const source = sourceState.get(sourceId);
|
|
322
|
+
if (!source) {
|
|
323
|
+
return result(false, null, buffered, reserve, null, "source-measurement-unavailable", preparedSegments);
|
|
324
|
+
}
|
|
321
325
|
for (const interval of ranges) {
|
|
322
326
|
const missing = uncoveredIntervals(interval, sourceByteCoverage.get(sourceId) ?? []);
|
|
323
|
-
if (missing.length)
|
|
327
|
+
if (missing.length === 0) {
|
|
328
|
+
sourceArrivalAt = Math.max(sourceArrivalAt, latestArrival(interval, sourceArrivals.get(sourceId)));
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Explicit piece arrivals remain authoritative. For still-unmapped
|
|
333
|
+
// pieces, project only the media-order demand from the torrent's latest
|
|
334
|
+
// measured download service; no supplier or positive rate means no
|
|
335
|
+
// estimated arrival.
|
|
336
|
+
const { source: facts, rate } = source;
|
|
337
|
+
if (!rate || !(rate.rateAt(0) > 0) || !Number.isSafeInteger(facts.pieceLength) ||
|
|
338
|
+
facts.pieceLength <= 0 || !Number.isSafeInteger(facts.fileOffset) ||
|
|
339
|
+
!Number.isSafeInteger(facts.fileLength) || facts.fileLength <= 0) {
|
|
340
|
+
return { ...result(false, null, buffered, reserve, null,
|
|
341
|
+
rate ? "service-not-advancing" : "download-rate-unavailable", preparedSegments),
|
|
324
342
|
unavailableSource: { sourceId, segmentIndex: segment.index, range: interval, missing } };
|
|
325
|
-
|
|
343
|
+
}
|
|
344
|
+
const estimatedPieces = sourcePiecesFor(missing, facts);
|
|
345
|
+
let finish = Math.max(source.serviceFinish, sourceArrivalAt);
|
|
346
|
+
const service = sourceByteService.get(sourceId) ?? [];
|
|
347
|
+
for (const pieceRange of estimatedPieces) {
|
|
348
|
+
const stillMissing = uncoveredIntervals(pieceRange, sourceByteCoverage.get(sourceId) ?? []);
|
|
349
|
+
for (const part of stillMissing) {
|
|
350
|
+
finish = rate.finish(part.end - part.start, finish);
|
|
351
|
+
service.push({ start: part.start, end: part.end, begin: finish, finish });
|
|
352
|
+
sourceArrivalAt = Math.max(sourceArrivalAt, finish);
|
|
353
|
+
}
|
|
354
|
+
sourceByteCoverage.set(sourceId, numericCoverage(service));
|
|
355
|
+
sourceArrivals.set(sourceId, indexedArrivals(service));
|
|
356
|
+
}
|
|
357
|
+
source.serviceFinish = finish;
|
|
326
358
|
}
|
|
327
359
|
}
|
|
328
360
|
// Production accepts a complete held segment input. It cannot overlap its
|
|
@@ -536,6 +568,22 @@ function uncoveredIntervals(interval, served) {
|
|
|
536
568
|
return missing;
|
|
537
569
|
}
|
|
538
570
|
|
|
571
|
+
/** Expand missing file bytes to the whole torrent pieces the reader must hold. */
|
|
572
|
+
function sourcePiecesFor(ranges, source) {
|
|
573
|
+
const pieces = [];
|
|
574
|
+
for (const range of ranges) {
|
|
575
|
+
if (!(range.end > range.start)) continue;
|
|
576
|
+
const first = Math.floor((source.fileOffset + range.start) / source.pieceLength);
|
|
577
|
+
const last = Math.ceil((source.fileOffset + range.end) / source.pieceLength) - 1;
|
|
578
|
+
for (let index = first; index <= last; index += 1) {
|
|
579
|
+
const start = Math.max(0, index * source.pieceLength - source.fileOffset);
|
|
580
|
+
const end = Math.min(source.fileLength, (index + 1) * source.pieceLength - source.fileOffset);
|
|
581
|
+
if (end > start) pieces.push({ start, end });
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return numericCoverage(pieces);
|
|
585
|
+
}
|
|
586
|
+
|
|
539
587
|
/**
|
|
540
588
|
* Ranges for pieces not read yet, placed in the clock of the pieces that were.
|
|
541
589
|
* A cut time of the playlist is mapped linearly between the nearest measured
|