@torrent-tv/proxy 2.88.2 → 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,7 @@
|
|
|
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
|
+
|
|
1
5
|
## 2.88.2
|
|
2
6
|
|
|
3
7
|
- **Fix**: Report the first failing playback forecast boundary and per-track ranges instead of only a generic no-safe-start result.
|
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
|
}
|
|
@@ -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
|
}
|
|
@@ -625,21 +625,26 @@ function isSafeSchedule(completions, productions, trackState, position, duration
|
|
|
625
625
|
return { safe: atEnd.safe, bufferedAtStart: atStart.available, neededSeconds: atStart.neededSeconds, failure: atStart.failure };
|
|
626
626
|
}
|
|
627
627
|
|
|
628
|
-
function addRange(ranges, start, end) {
|
|
628
|
+
function addRange(ranges, start, end, joinEnd) {
|
|
629
629
|
const merged = [];
|
|
630
|
-
let next = { start, end };
|
|
630
|
+
let next = { start, end, ...(Number.isFinite(joinEnd) ? { joinEnd } : {}) };
|
|
631
631
|
let inserted = false;
|
|
632
632
|
for (const range of ranges) {
|
|
633
|
-
|
|
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) {
|
|
634
636
|
merged.push(range);
|
|
635
|
-
} else if (next.end < range.start) {
|
|
637
|
+
} else if (Math.max(next.end, next.joinEnd ?? next.end) < range.start) {
|
|
636
638
|
if (!inserted) {
|
|
637
639
|
merged.push(next);
|
|
638
640
|
inserted = true;
|
|
639
641
|
}
|
|
640
642
|
merged.push(range);
|
|
641
643
|
} else {
|
|
642
|
-
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
|
+
} : {}) };
|
|
643
648
|
}
|
|
644
649
|
}
|
|
645
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]]}
|