@torrent-tv/proxy 2.99.16 → 2.99.18
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
|
@@ -21,6 +21,7 @@ import { computeOutputDimensions } from "./args.js";
|
|
|
21
21
|
import { buildAdmittedCommand } from "./admitted-command.js";
|
|
22
22
|
import { buildOriginalCommand } from "./source-command.js";
|
|
23
23
|
import { writeAdmittedInput } from "./AdmittedInput.js";
|
|
24
|
+
import { nextPacketInputs } from "./next-packet-input.js";
|
|
24
25
|
import { cutOf, cutShiftOf, judgeNeighbors, judgePiece, leadingSecondsOf, lacksOnlyItsLeadingPictures } from "./piece-completeness.js";
|
|
25
26
|
import { leadingPictures, presentationSegment } from "./segment-formats/presentation-segment.js";
|
|
26
27
|
import { InputFailures, failedAdmittedInput } from "./InputFailures.js";
|
|
@@ -1262,23 +1263,23 @@ export class EncodeRuns {
|
|
|
1262
1263
|
}
|
|
1263
1264
|
} else if (admittedInput) {
|
|
1264
1265
|
run.admittedInputKeys = new Map([[safeIndex, inputKey]]);
|
|
1265
|
-
|
|
1266
|
+
const nextIndex = startIndex + 1;
|
|
1266
1267
|
const canAppend = index => this.isLive(session) && run.isAlive &&
|
|
1267
1268
|
this.#host.encodeOrchestrator.wantedSegmentsOn(session.outputKey).some(window => index >= window.from && index <= window.to) &&
|
|
1268
1269
|
!this.liveRunsOf(session).some(other => other !== run && index >= other.from && index <= other.to) &&
|
|
1269
1270
|
!this.#host.producedNumbers(session).includes(index);
|
|
1271
|
+
// Only an input already prepared is appended, and the one after it is
|
|
1272
|
+
// begun at once (`next-packet-input.js`).
|
|
1273
|
+
const following = nextPacketInputs({ take: index => this.#host.encodeInputs.take(session, index, index),
|
|
1274
|
+
canAppend, first: nextIndex, last: ordered?.to ?? startIndex });
|
|
1270
1275
|
void writeAdmittedInput(admittedInput, run.process.stdin, { next: async () => {
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
if (!next) return null;
|
|
1274
|
-
if (!canAppend(nextIndex)) { next.release(); return null; }
|
|
1275
|
-
run.admittedInputKeys.set(nextIndex, this.#inputFailures.key(next, inputParameters));
|
|
1276
|
-
run.to = nextIndex++;
|
|
1276
|
+
const next = following.next(index => { run.to = index; });
|
|
1277
|
+
if (next) run.admittedInputKeys.set(run.to, this.#inputFailures.key(next, inputParameters));
|
|
1277
1278
|
return next;
|
|
1278
|
-
}
|
|
1279
|
+
} }).catch(error => {
|
|
1279
1280
|
this.#host.logger.warn(`encode input output=${session.outputKey} segment=${safeIndex} write failed: ${error.message}`);
|
|
1280
1281
|
run.process?.stdin?.destroy();
|
|
1281
|
-
});
|
|
1282
|
+
}).finally(() => following.release());
|
|
1282
1283
|
}
|
|
1283
1284
|
this.#runsByInputToken.set(inputToken, run);
|
|
1284
1285
|
this.#host.encodersChanged?.();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Which input a run fed packet by packet goes on with.
|
|
3
|
+
*
|
|
4
|
+
* Such a run keeps its input open while more is appended, and FFmpeg closes a
|
|
5
|
+
* piece only once it reads past the piece's end, so a piece is published when
|
|
6
|
+
* the input after it arrives or the input ends. Waited for, the next input
|
|
7
|
+
* held each piece back for as long as preparing it took: on Home Assistant a
|
|
8
|
+
* soundtrack's #7 waited 14 s for #8's attempt, which ended refused memory, and
|
|
9
|
+
* the picture stood at 61.8 s (r54, r55, torrent-tv/meta#178).
|
|
10
|
+
*
|
|
11
|
+
* So a run is given the next input only when it is ready, and the one after it
|
|
12
|
+
* is begun as soon as one is taken: the run goes on while preparation keeps
|
|
13
|
+
* ahead of it, and ends, publishing its last piece, when it does not. The plan
|
|
14
|
+
* starts the next run on the input being prepared.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A source of the inputs after a run's first.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} params
|
|
21
|
+
* @param {(index: number) => object | null} params.take - The input of one
|
|
22
|
+
* piece if ready, beginning its preparation otherwise (`EncodeInputs.take`).
|
|
23
|
+
* @param {(index: number) => boolean} params.canAppend - Whether the piece may
|
|
24
|
+
* still be made by this run.
|
|
25
|
+
* @param {number} params.first - The first piece after the run's own.
|
|
26
|
+
* @param {number} params.last - The last piece the run may reach.
|
|
27
|
+
* @returns {{ next: (onTaken?: (index: number) => void) => object | null, release: () => void }}
|
|
28
|
+
*/
|
|
29
|
+
export function nextPacketInputs({ take, canAppend, first, last }) {
|
|
30
|
+
let index = first;
|
|
31
|
+
let ahead = null;
|
|
32
|
+
const begin = at => {
|
|
33
|
+
if (at <= last && canAppend(at)) ahead = take(at);
|
|
34
|
+
};
|
|
35
|
+
begin(index);
|
|
36
|
+
return {
|
|
37
|
+
next(onTaken) {
|
|
38
|
+
if (index > last || !canAppend(index)) return null;
|
|
39
|
+
const input = ahead ?? take(index);
|
|
40
|
+
ahead = null;
|
|
41
|
+
if (!input) return null;
|
|
42
|
+
if (!canAppend(index)) {
|
|
43
|
+
input.release();
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
onTaken?.(index);
|
|
47
|
+
index += 1;
|
|
48
|
+
begin(index);
|
|
49
|
+
return input;
|
|
50
|
+
},
|
|
51
|
+
release() {
|
|
52
|
+
ahead?.release();
|
|
53
|
+
ahead = null;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -834,6 +834,17 @@ export class MatroskaContainer extends Container {
|
|
|
834
834
|
const declared = new Map([[id, track]]);
|
|
835
835
|
const asked = interval ? { ...interval, trackIds: [id] } : null;
|
|
836
836
|
const depth = track.type === "video" ? track.reorderDepth : 0;
|
|
837
|
+
// WHERE THE WALK WILL READ, as the Cues name it (`packetWalkRanges`):
|
|
838
|
+
// read a portion at a time across cluster boundaries too. Read a
|
|
839
|
+
// cluster's header and its contents apart, a soundtrack piece took
|
|
840
|
+
// about sixteen reads of the torrent thread, each waiting for its loop
|
|
841
|
+
// (0.5-0.8 s late on Home Assistant), and 7-12 s to prepare with every
|
|
842
|
+
// byte held (r56, torrent-tv/meta#178).
|
|
843
|
+
const walked = interval ? await this.packetWalkRanges(interval) : null;
|
|
844
|
+
const readsThrough = walked ? start => {
|
|
845
|
+
const range = walked.ranges.find(([from, to]) => from <= start && start <= to);
|
|
846
|
+
return range ? range[1] + 1 : null;
|
|
847
|
+
} : null;
|
|
837
848
|
let current = null;
|
|
838
849
|
const covered = () => {
|
|
839
850
|
if (!asked) return false;
|
|
@@ -851,7 +862,7 @@ export class MatroskaContainer extends Container {
|
|
|
851
862
|
const following = readings.find(reading => reading.startAt > current.startAt);
|
|
852
863
|
const outcome = await walkMatroskaPackets({ readRange: this.readRange, fileSize: this.fileSize,
|
|
853
864
|
portionBytes: this.portionBytes, layout, tracks, trackIds: [id], interval, packetMemory: this.packetMemory,
|
|
854
|
-
state: current, until: following?.startAt ?? null, isCovered: covered });
|
|
865
|
+
state: current, until: following?.startAt ?? null, isCovered: covered, readsThrough });
|
|
855
866
|
if (outcome !== "until") break;
|
|
856
867
|
current.next = following;
|
|
857
868
|
}
|
|
@@ -40,9 +40,13 @@ function recordedTracks(tracks, trackIds) {
|
|
|
40
40
|
* Only the named tracks are recorded: a soundtrack read on its own kept the
|
|
41
41
|
* picture's table as well, half of 37.9 MB on r47's order (torrent-tv/meta#178).
|
|
42
42
|
*
|
|
43
|
+
* `readsThrough(start)` is one past the last byte the walk will read from
|
|
44
|
+
* `start` on, where the caller knows it: those are read a portion at a time
|
|
45
|
+
* across clusters too.
|
|
46
|
+
*
|
|
43
47
|
* @returns {Promise<"covered" | "until" | "end">}
|
|
44
48
|
*/
|
|
45
|
-
export async function walkMatroskaPackets({ readRange, fileSize, portionBytes, layout, tracks, trackIds, interval, packetMemory, state, until = null, isCovered = null }) {
|
|
49
|
+
export async function walkMatroskaPackets({ readRange, fileSize, portionBytes, layout, tracks, trackIds, interval, packetMemory, state, until = null, isCovered = null, readsThrough = null }) {
|
|
46
50
|
// INSIDE A CLUSTER EVERY BLOCK HEADER IS READ, so its bytes are read a
|
|
47
51
|
// portion at a time up to the cluster's end, and never past it. Read a
|
|
48
52
|
// header at a time, each a read of a few bytes from the torrent thread, the
|
|
@@ -55,7 +59,7 @@ export async function walkMatroskaPackets({ readRange, fileSize, portionBytes, l
|
|
|
55
59
|
// Between clusters only their headers are read.
|
|
56
60
|
let readableUntil = null;
|
|
57
61
|
const reader = new ElementReader({ read: readRange, fileSize, portionBytes, prefetch: true,
|
|
58
|
-
readableUntil: start => readableUntil ?? start });
|
|
62
|
+
readableUntil: start => Math.max(readableUntil ?? start, readsThrough?.(start) ?? start) });
|
|
59
63
|
const declared = recordedTracks(tracks, trackIds);
|
|
60
64
|
if (!state.packets) {
|
|
61
65
|
state.packets = new Map([...declared.keys()].map(id => [id, new PacketRecords(packetMemory)]));
|