@torrent-tv/proxy 2.88.1 → 2.88.3
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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 2.88.3
|
|
2
|
+
|
|
3
|
+
- **Fix**: Forecast audio continuity from coded-frame timestamps and durations using the browser's frame-join boundary. Adjacent AAC fragments that Chrome buffers continuously no longer cause an impossible-start forecast; actual gaps and missing deliveries still prevent an unsafe start. Verified against captured segment metadata and independent Chrome SourceBuffer appends.
|
|
4
|
+
|
|
5
|
+
## 2.88.2
|
|
6
|
+
|
|
7
|
+
- **Fix**: Report the first failing playback forecast boundary and per-track ranges instead of only a generic no-safe-start result.
|
|
8
|
+
|
|
1
9
|
## 2.88.1
|
|
2
10
|
|
|
3
11
|
- **New**: Preserve bounded ASS/SSA work-title and release-year header fields in a WebVTT metadata note, without additional subtitle reads.
|
package/package.json
CHANGED
|
@@ -244,12 +244,20 @@ export function readPresentationRanges(raw) {
|
|
|
244
244
|
sample.start - previous.end <= precision + Number.EPSILON * Math.max(1, sample.end) * 8)) {
|
|
245
245
|
previous.end = Math.max(previous.end, sample.end);
|
|
246
246
|
} else merged.push({ ...sample });
|
|
247
|
+
// Preserve coded-frame timing separately from the presentation interval.
|
|
248
|
+
// Chromium joins an adjacent frame within twice the maximum coded-frame
|
|
249
|
+
// duration of the stream (SourceBufferRange::GetFudgeRoom), measured from
|
|
250
|
+
// the last frame's timestamp, not from the segment's nominal cut.
|
|
251
|
+
const range = merged.at(-1);
|
|
252
|
+
range.lastFrameStart = Math.max(range.lastFrameStart ?? sample.start, sample.start);
|
|
253
|
+
range.maxFrameDuration = Math.max(range.maxFrameDuration ?? 0, sample.end - sample.start);
|
|
247
254
|
}
|
|
248
255
|
// Movie edits quantize independent pieces to movie ticks. Preserve that
|
|
249
256
|
// declared resolution at joins; it is not a playback buffer threshold.
|
|
250
257
|
for (const range of merged) range.end += precision;
|
|
251
258
|
const decoded = decodeRanges.get(id);
|
|
252
|
-
return { id, ranges: merged,
|
|
259
|
+
return { id, ranges: merged.map(({ start, end }) => ({ start, end })),
|
|
260
|
+
frameRanges: merged, kind: kinds.get(id), precision,
|
|
253
261
|
decodeStart: Math.min(...decoded.map(({ start }) => start)),
|
|
254
262
|
decodeEnd: Math.max(...decoded.map(({ end }) => end)) };
|
|
255
263
|
});
|
|
@@ -280,8 +288,11 @@ export function continuePresentationRanges(ranges, nextRanges) {
|
|
|
280
288
|
}
|
|
281
289
|
|
|
282
290
|
function intersectPresentationTracks(tracks) {
|
|
283
|
-
return tracks.map(({ ranges }) => ranges).reduce((common, ranges) => common.flatMap((left) => ranges
|
|
284
|
-
.map((right) => ({ start: Math.max(left.start, right.start), end: Math.min(left.end, right.end)
|
|
291
|
+
return tracks.slice(1).map(({ ranges }) => ranges).reduce((common, ranges) => common.flatMap((left) => ranges
|
|
292
|
+
.map((right) => ({ start: Math.max(left.start, right.start), end: Math.min(left.end, right.end),
|
|
293
|
+
...(left.joinEnd !== undefined || right.joinEnd !== undefined ? {
|
|
294
|
+
joinEnd: Math.min(left.joinEnd ?? left.end, right.joinEnd ?? right.end)
|
|
295
|
+
} : {}) }))
|
|
285
296
|
.filter(({ start, end }) => end > start)), tracks[0]?.ranges ?? []);
|
|
286
297
|
}
|
|
287
298
|
|
|
@@ -290,11 +301,15 @@ export function translatePresentationRanges(ranges, initBytes, timestampOffsetSe
|
|
|
290
301
|
const tracks = presentationTracks.get(ranges);
|
|
291
302
|
if (!tracks || !initBytes?.length || !Number.isFinite(timestampOffsetSeconds)) return ranges;
|
|
292
303
|
const edits = readTrackEditOffsets(initBytes);
|
|
293
|
-
return intersectPresentationTracks(tracks.map(({ id, ranges: held }) => ({
|
|
304
|
+
return intersectPresentationTracks(tracks.map(({ id, ranges: held, frameRanges, kind }) => ({
|
|
294
305
|
id,
|
|
295
|
-
ranges: held.map(({ start, end }) => ({
|
|
306
|
+
ranges: held.map(({ start, end }, index) => ({
|
|
296
307
|
start: Math.max(0, start + timestampOffsetSeconds - (edits.get(id) ?? 0)),
|
|
297
|
-
end: end + timestampOffsetSeconds - (edits.get(id) ?? 0)
|
|
308
|
+
end: end + timestampOffsetSeconds - (edits.get(id) ?? 0),
|
|
309
|
+
...(kind === "soun" ? {
|
|
310
|
+
joinEnd: frameRanges[index].lastFrameStart + 2 * frameRanges[index].maxFrameDuration +
|
|
311
|
+
timestampOffsetSeconds - (edits.get(id) ?? 0)
|
|
312
|
+
} : {})
|
|
298
313
|
})).filter(({ start, end }) => end > start)
|
|
299
314
|
})));
|
|
300
315
|
}
|
|
@@ -327,7 +327,7 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
327
327
|
: productions;
|
|
328
328
|
const safety = isSafeSchedule(usefulCompletions, usefulProductions, trackState, position, duration,
|
|
329
329
|
buffered, capacity, reserve, startDelaySeconds);
|
|
330
|
-
return { safe: safety.safe,
|
|
330
|
+
return { safe: safety.safe, failure: safety.failure,
|
|
331
331
|
bufferedAtStart: safety.bufferedAtStart,
|
|
332
332
|
neededSeconds: safety.neededSeconds,
|
|
333
333
|
completionTimes: usefulCompletions.map(({ at }) => at),
|
|
@@ -346,7 +346,7 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
346
346
|
}
|
|
347
347
|
const searchLimit = maximumUsefulDelay;
|
|
348
348
|
if (!(searchLimit > 0)) {
|
|
349
|
-
return result(false, null, buffered, reserve, null, "no-safe-start-found", preparedSegments);
|
|
349
|
+
return { ...result(false, null, buffered, reserve, null, "no-safe-start-found", preparedSegments), failure: immediate.failure };
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
let low = 0;
|
|
@@ -369,7 +369,7 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
if (high === null) {
|
|
372
|
-
return result(false, null, buffered, reserve, null, "no-safe-start-found", preparedSegments);
|
|
372
|
+
return { ...result(false, null, buffered, reserve, null, "no-safe-start-found", preparedSegments), failure: immediate.failure };
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
while (true) {
|
|
@@ -534,15 +534,15 @@ function isSafeSchedule(completions, productions, trackState, position, duration
|
|
|
534
534
|
while (productionIndex < orderedProductions.length && orderedProductions[productionIndex].at <= time) {
|
|
535
535
|
const { trackIndex, segment } = orderedProductions[productionIndex];
|
|
536
536
|
for (const range of segment.mediaRanges ?? [{ start: segment.startSeconds, end: segment.endSeconds }]) {
|
|
537
|
-
addRange(preparedRanges[trackIndex], range.start, range.end);
|
|
537
|
+
addRange(preparedRanges[trackIndex], range.start, range.end, range.joinEnd);
|
|
538
538
|
}
|
|
539
539
|
productionIndex += 1;
|
|
540
540
|
}
|
|
541
541
|
while (completionIndex < orderedCompletions.length && orderedCompletions[completionIndex].at <= time) {
|
|
542
542
|
const { trackIndex, segment } = orderedCompletions[completionIndex];
|
|
543
543
|
for (const range of segment.mediaRanges ?? [{ start: segment.startSeconds, end: segment.endSeconds }]) {
|
|
544
|
-
addRange(clientRanges[trackIndex], range.start, range.end);
|
|
545
|
-
addRange(preparedRanges[trackIndex], range.start, range.end);
|
|
544
|
+
addRange(clientRanges[trackIndex], range.start, range.end, range.joinEnd);
|
|
545
|
+
addRange(preparedRanges[trackIndex], range.start, range.end, range.joinEnd);
|
|
546
546
|
}
|
|
547
547
|
completionIndex += 1;
|
|
548
548
|
}
|
|
@@ -559,6 +559,12 @@ function isSafeSchedule(completions, productions, trackState, position, duration
|
|
|
559
559
|
const available = Math.min(capacity, ...clientRanges.map((trackRanges, index) =>
|
|
560
560
|
playerPosition >= trackEnd(index) ? Number.POSITIVE_INFINITY :
|
|
561
561
|
Math.max(0, contiguousEnd(trackRanges, playerPosition) - playerPosition)));
|
|
562
|
+
const trackStatus = clientRanges.map((ranges, index) => ({
|
|
563
|
+
track: trackState[index].track.id,
|
|
564
|
+
end: contiguousEnd(ranges, playerPosition),
|
|
565
|
+
next: ranges.find(range => range.start > playerPosition)?.start ?? null,
|
|
566
|
+
last: trackEnd(index)
|
|
567
|
+
}));
|
|
562
568
|
let neededSeconds = 0;
|
|
563
569
|
let stockSafe = true;
|
|
564
570
|
for (let trackIndex = 0; trackIndex < trackState.length; trackIndex += 1) {
|
|
@@ -575,14 +581,15 @@ function isSafeSchedule(completions, productions, trackState, position, duration
|
|
|
575
581
|
safe: (!checkClient || available > 0) && stockSafe,
|
|
576
582
|
stockSafe,
|
|
577
583
|
available,
|
|
578
|
-
neededSeconds
|
|
584
|
+
neededSeconds,
|
|
585
|
+
failure: { time, playerPosition, available, stockSafe, tracks: trackStatus }
|
|
579
586
|
};
|
|
580
587
|
};
|
|
581
588
|
|
|
582
589
|
addThrough(startDelay);
|
|
583
590
|
const atStart = safeAt(startDelay);
|
|
584
591
|
if (!atStart.safe) {
|
|
585
|
-
return { safe: false, bufferedAtStart: atStart.available, neededSeconds: atStart.neededSeconds };
|
|
592
|
+
return { safe: false, bufferedAtStart: atStart.available, neededSeconds: atStart.neededSeconds, failure: atStart.failure };
|
|
586
593
|
}
|
|
587
594
|
|
|
588
595
|
const playbackEnd = startDelay + Math.max(0, duration - position);
|
|
@@ -605,34 +612,39 @@ function isSafeSchedule(completions, productions, trackState, position, duration
|
|
|
605
612
|
for (const time of eventTimes) {
|
|
606
613
|
const beforeArrival = safeAt(time, false);
|
|
607
614
|
if (!beforeArrival.stockSafe) {
|
|
608
|
-
return { safe: false, bufferedAtStart: atStart.available, neededSeconds: beforeArrival.neededSeconds };
|
|
615
|
+
return { safe: false, bufferedAtStart: atStart.available, neededSeconds: beforeArrival.neededSeconds, failure: beforeArrival.failure };
|
|
609
616
|
}
|
|
610
617
|
addThrough(time);
|
|
611
618
|
const afterArrival = safeAt(time);
|
|
612
619
|
if (!afterArrival.safe) {
|
|
613
|
-
return { safe: false, bufferedAtStart: atStart.available, neededSeconds: afterArrival.neededSeconds };
|
|
620
|
+
return { safe: false, bufferedAtStart: atStart.available, neededSeconds: afterArrival.neededSeconds, failure: afterArrival.failure };
|
|
614
621
|
}
|
|
615
622
|
}
|
|
616
623
|
|
|
617
624
|
const atEnd = safeAt(playbackEnd);
|
|
618
|
-
return { safe: atEnd.safe, bufferedAtStart: atStart.available, neededSeconds: atStart.neededSeconds };
|
|
625
|
+
return { safe: atEnd.safe, bufferedAtStart: atStart.available, neededSeconds: atStart.neededSeconds, failure: atStart.failure };
|
|
619
626
|
}
|
|
620
627
|
|
|
621
|
-
function addRange(ranges, start, end) {
|
|
628
|
+
function addRange(ranges, start, end, joinEnd) {
|
|
622
629
|
const merged = [];
|
|
623
|
-
let next = { start, end };
|
|
630
|
+
let next = { start, end, ...(Number.isFinite(joinEnd) ? { joinEnd } : {}) };
|
|
624
631
|
let inserted = false;
|
|
625
632
|
for (const range of ranges) {
|
|
626
|
-
|
|
633
|
+
// The declared coded-frame join boundary connects two ranges only after
|
|
634
|
+
// both have arrived. It never extends an isolated range's playable end.
|
|
635
|
+
if (Math.max(range.end, range.joinEnd ?? range.end) < next.start) {
|
|
627
636
|
merged.push(range);
|
|
628
|
-
} else if (next.end < range.start) {
|
|
637
|
+
} else if (Math.max(next.end, next.joinEnd ?? next.end) < range.start) {
|
|
629
638
|
if (!inserted) {
|
|
630
639
|
merged.push(next);
|
|
631
640
|
inserted = true;
|
|
632
641
|
}
|
|
633
642
|
merged.push(range);
|
|
634
643
|
} else {
|
|
635
|
-
next = { start: Math.min(next.start, range.start), end: Math.max(next.end, range.end)
|
|
644
|
+
next = { start: Math.min(next.start, range.start), end: Math.max(next.end, range.end),
|
|
645
|
+
...(next.joinEnd !== undefined || range.joinEnd !== undefined ? {
|
|
646
|
+
joinEnd: Math.max(next.joinEnd ?? next.end, range.joinEnd ?? range.end)
|
|
647
|
+
} : {}) };
|
|
636
648
|
}
|
|
637
649
|
}
|
|
638
650
|
if (!inserted) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { readPresentationRanges, translatePresentationRanges, walkBoxes } from "../services/encode/segment-formats/mp4-boxes.js";
|
|
5
|
+
import { predictPlaybackReadiness } from "../services/server/playback-readiness.js";
|
|
6
|
+
|
|
7
|
+
const evidence = JSON.parse(readFileSync(new URL("./fixtures/audio-frame-boundary.json", import.meta.url), "utf8"));
|
|
8
|
+
const header = Buffer.from(evidence.files["init.mp4"], "base64");
|
|
9
|
+
|
|
10
|
+
function ranges(name, shiftedFrames = 0) {
|
|
11
|
+
const bytes = Buffer.from(evidence.files[name], "base64");
|
|
12
|
+
let frameTicks;
|
|
13
|
+
walkBoxes(bytes, (type, start) => {
|
|
14
|
+
if (type === "tfhd") {
|
|
15
|
+
const flags = bytes.readUIntBE(start + 1, 3);
|
|
16
|
+
const at = start + 8 + ((flags & 1) ? 8 : 0) + ((flags & 2) ? 4 : 0);
|
|
17
|
+
if (flags & 8) frameTicks = bytes.readUInt32BE(at);
|
|
18
|
+
}
|
|
19
|
+
if (type === "tfdt" && shiftedFrames) {
|
|
20
|
+
assert.ok(frameTicks > 0);
|
|
21
|
+
bytes.writeBigUInt64BE(bytes.readBigUInt64BE(start + 4) + BigInt(frameTicks * shiftedFrames), start + 4);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return translatePresentationRanges(readPresentationRanges(Buffer.concat([header, bytes])), header, 0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function state(second = ranges("segment-00172.mp4")) {
|
|
28
|
+
const first = ranges("segment-00171.mp4");
|
|
29
|
+
const now = 1000;
|
|
30
|
+
return {
|
|
31
|
+
now,
|
|
32
|
+
positionSeconds: first[0].start,
|
|
33
|
+
durationSeconds: second.at(-1).end,
|
|
34
|
+
bufferLimitSeconds: second.at(-1).end - first[0].start,
|
|
35
|
+
reserveSeconds: 0,
|
|
36
|
+
lookaheadSeconds: second.at(-1).end - first[0].start,
|
|
37
|
+
sources: [{ id: "source", complete: true }],
|
|
38
|
+
linkReadings: [{ at: now, value: 1_000_000 }],
|
|
39
|
+
tracks: [{
|
|
40
|
+
id: "audio", sourceIds: ["source"], clientRanges: [
|
|
41
|
+
{ start: evidence.browserBufferedRanges[0][0], end: evidence.browserBufferedRanges[0][1] }
|
|
42
|
+
],
|
|
43
|
+
readings: [{ at: now, value: 1 }],
|
|
44
|
+
readySegmentIndices: [0, 1], segmentSizesBytes: { 0: 170000, 1: 170000 },
|
|
45
|
+
segments: [
|
|
46
|
+
{ index: 0, startSeconds: first[0].start, endSeconds: second[0].start, mediaRanges: first },
|
|
47
|
+
{ index: 1, startSeconds: second[0].start, endSeconds: second.at(-1).end, mediaRanges: second }
|
|
48
|
+
]
|
|
49
|
+
}]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
test("captured AAC boundary agrees with Chrome's continuous buffered range", () => {
|
|
54
|
+
const input = state();
|
|
55
|
+
assert.equal(predictPlaybackReadiness(input).ready, true);
|
|
56
|
+
for (const segment of input.tracks[0].segments) {
|
|
57
|
+
segment.mediaRanges = segment.mediaRanges.map(({ start, end }) => ({ start, end }));
|
|
58
|
+
}
|
|
59
|
+
assert.equal(predictPlaybackReadiness(input).reason, "no-safe-start-found");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("four missing AAC frames preserve the discontinuity observed in Chrome", () => {
|
|
63
|
+
assert.equal(predictPlaybackReadiness(state(ranges("segment-00172.mp4", 4))).reason, "no-safe-start-found");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("a join boundary never supplies audio before the adjacent segment arrives", () => {
|
|
67
|
+
const input = state();
|
|
68
|
+
input.linkReadings[0].value = 1;
|
|
69
|
+
assert.notEqual(predictPlaybackReadiness(input).ready, true);
|
|
70
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"description":"MP4 metadata boxes captured from adjacent AAC segments. Media payloads are omitted.","files":{"init.mp4":"AAAAHGZ0eXBpc281AAACAGlzbzVpc282bXA0MQAAAvRtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAAAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAB9nRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAAAAAAAAAAEAAAAAAVttZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAALuAAAAAAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEGbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADKc3RibAAAAH5zdHNkAAAAAAAAAAEAAABubXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAALuAAAAAAAA2ZXNkcwAAAAADgICAJQABAASAgIAXQBUAAAAAAfQAAAH0AAWAgIAFEZBW5QAGgICAAQIAAAAUYnRydAAAAAAAAfQAAAH0AAAAABBzdHRzAAAAAAAAAAAAAAAQc3RzYwAAAAAAAAAAAAAAFHN0c3oAAAAAAAAAAAAAAAAAAAAQc3RjbwAAAAAAAAAAAAAAE3VkdGEAAAALbmFtZVJVUwAAAChtdmV4AAAAIHRyZXgAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjYyLjEyLjEwMg==","segment-00171.mp4":"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","segment-00172.mp4":"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"},"browserBufferedRanges":[[1782.997,1793.428999],[1782.997,1803.827333]]}
|