@torrent-tv/proxy 2.100.1 → 2.100.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/package.json
CHANGED
package/server.js
CHANGED
|
@@ -892,6 +892,7 @@ export async function startProxyServer({
|
|
|
892
892
|
const params = await containerOver({ sourceKey: output.file.sourceKey, fileIndex });
|
|
893
893
|
if (!params) return { kind: "needs-source" };
|
|
894
894
|
if (timing) {
|
|
895
|
+
params.walkAccount = timing.walk ??= { lockMs: 0, reads: 0, bytes: 0 };
|
|
895
896
|
params.onTimed = (_statement, waitedMs, readMs) => {
|
|
896
897
|
timing.reads += 1;
|
|
897
898
|
timing.queuedMs += waitedMs;
|
|
@@ -971,10 +972,16 @@ export async function startProxyServer({
|
|
|
971
972
|
const answer = input.read(source.fileIndex, start, end);
|
|
972
973
|
return answer && typeof answer.then !== "function" && answer.end === end ? answer.bytes : null;
|
|
973
974
|
};
|
|
975
|
+
const holdsUntil = start => {
|
|
976
|
+
const range = source.input.ranges.find(([first, last]) => first <= start && start <= last);
|
|
977
|
+
return range ? range[1] + 1 : null;
|
|
978
|
+
};
|
|
974
979
|
const startedAt = Date.now();
|
|
975
|
-
void container.learnPackets({ from: source.input.from, to: source.input.to, trackIds: [...sounds] }, read).then(learned =>
|
|
980
|
+
void container.learnPackets({ from: source.input.from, to: source.input.to, trackIds: [...sounds] }, read, holdsUntil).then(learned =>
|
|
976
981
|
logger.info(`encode input #${from}..#${to} of ${output.outputKey}: soundtrack packets of track(s) ${[...sounds].join(",")} ` +
|
|
977
|
-
`over ${source.input.from.toFixed(3)}-${source.input.to.toFixed(3)}s
|
|
982
|
+
`over ${source.input.from.toFixed(3)}-${source.input.to.toFixed(3)}s ` +
|
|
983
|
+
(learned === "walked" ? "walked" : `not all walked (${learned === "memory" ? "memory refused" : "the copy does not hold the rest"})`) +
|
|
984
|
+
` from its copy in ${Date.now() - startedAt}ms`),
|
|
978
985
|
error => logger.warn(`encode input #${from}..#${to} of ${output.outputKey}: walking the soundtrack from its copy failed: ${error?.message ?? error}`));
|
|
979
986
|
}
|
|
980
987
|
},
|
|
@@ -648,6 +648,9 @@ function describeTiming(timing, now) {
|
|
|
648
648
|
`${timing.resolutions} resolution(s) ${timing.resolveMs}ms ` +
|
|
649
649
|
`(${timing.reads} file read(s): queued ${timing.queuedMs}ms, reading ${timing.readMs}ms), ` +
|
|
650
650
|
`held ranges ${timing.heldMs}ms, budget ${timing.budgetMs}ms` +
|
|
651
|
+
// Where a packet input's walk spent its read: waiting for the same
|
|
652
|
+
// track's earlier walks, or reading the file (r62, torrent-tv/meta#178).
|
|
653
|
+
(timing.walk ? `; packet walk waited ${timing.walk.lockMs}ms for its track and read ${timing.walk.reads} time(s), ${timing.walk.bytes} bytes of the file` : "") +
|
|
651
654
|
(timing.openApproximate ? "; sized by the host's startup open, this output has not opened yet" : "");
|
|
652
655
|
}
|
|
653
656
|
|
|
@@ -215,7 +215,7 @@ export class ContainerOrchestrator {
|
|
|
215
215
|
value = await container.readSourceRanges(params.packetInterval);
|
|
216
216
|
if (value.kind !== "result") return { ...value, requestId };
|
|
217
217
|
} else if (statement === "packets") {
|
|
218
|
-
value = await container.readPacketIndex(params.packetInterval, { isCurrent: params.isCurrent });
|
|
218
|
+
value = await container.readPacketIndex(params.packetInterval, { isCurrent: params.isCurrent, account: params.walkAccount });
|
|
219
219
|
if (value === null) return { kind: "terminal", reason: "request-obsolete", requestId };
|
|
220
220
|
await value?.prepareAudioDependencies?.(params.packetInterval, container.readRange);
|
|
221
221
|
} else {
|
|
@@ -789,8 +789,10 @@ export class MatroskaContainer extends Container {
|
|
|
789
789
|
* A walk whose request is no longer current stops between clusters and
|
|
790
790
|
* answers null. `readRange`, when given, is where this one walk reads
|
|
791
791
|
* instead of the file's own reader; what it does not hold is unavailable.
|
|
792
|
+
* `account`, when given, is told how long each track's walk waited for
|
|
793
|
+
* that track's earlier walks and what it read (`lockMs`, `reads`, `bytes`).
|
|
792
794
|
*/
|
|
793
|
-
async readPacketIndex(interval, { isCurrent, readRange } = {}) {
|
|
795
|
+
async readPacketIndex(interval, { isCurrent, readRange, holdsUntil, account } = {}) {
|
|
794
796
|
if (this.#packets) return this.#packets;
|
|
795
797
|
const layout = await this.#segmentLayout();
|
|
796
798
|
if (!layout) throw new Error("Matroska Segment is absent.");
|
|
@@ -810,7 +812,7 @@ export class MatroskaContainer extends Container {
|
|
|
810
812
|
let whole = true;
|
|
811
813
|
for (const track of requested) {
|
|
812
814
|
const chain = await this.#readTrackPackets(layout, tracks, track, start, interval, isCurrent,
|
|
813
|
-
readRange ? strictReader(readRange, this.fileSize) : this.readRange);
|
|
815
|
+
readRange ? strictReader(readRange, this.fileSize) : this.readRange, account, readRange ? holdsUntil : null);
|
|
814
816
|
if (!chain) return null;
|
|
815
817
|
streams.set(track.trackNumber, chain.records);
|
|
816
818
|
if (chain.reachesEnd) complete.add(track.trackNumber);
|
|
@@ -835,15 +837,18 @@ export class MatroskaContainer extends Container {
|
|
|
835
837
|
*
|
|
836
838
|
* @param {{ from: number, to: number, trackIds: number[] }} interval
|
|
837
839
|
* @param {(start: number, end: number) => Buffer | null} readRange - Null where it does not hold the bytes.
|
|
838
|
-
* @
|
|
840
|
+
* @param {(start: number) => number | null} [holdsUntil] - One past the last byte held from `start` on, if known.
|
|
841
|
+
* @returns {Promise<"walked" | "missing" | "memory">} Whether the
|
|
842
|
+
* interval's packets are recorded, or what stopped the walk: bytes the
|
|
843
|
+
* copy does not hold, or memory refused.
|
|
839
844
|
*/
|
|
840
|
-
async learnPackets(interval, readRange) {
|
|
845
|
+
async learnPackets(interval, readRange, holdsUntil = null) {
|
|
841
846
|
try {
|
|
842
|
-
return await this.readPacketIndex(interval, { readRange }) !== null;
|
|
847
|
+
return await this.readPacketIndex(interval, { readRange, holdsUntil }) !== null ? "walked" : "missing";
|
|
843
848
|
} catch (error) {
|
|
844
|
-
//
|
|
845
|
-
|
|
846
|
-
if (
|
|
849
|
+
// The sound's own walk reads the rest later, from where this one stopped.
|
|
850
|
+
if (isUnavailable(error)) return "missing";
|
|
851
|
+
if (error instanceof IndexMemoryUnavailable) return "memory";
|
|
847
852
|
throw error;
|
|
848
853
|
}
|
|
849
854
|
}
|
|
@@ -861,13 +866,21 @@ export class MatroskaContainer extends Container {
|
|
|
861
866
|
*
|
|
862
867
|
* @returns {Promise<{ records: PacketRecordsChain, startAt: number, reachesEnd: boolean }>}
|
|
863
868
|
*/
|
|
864
|
-
async #readTrackPackets(layout, tracks, track, start, interval, isCurrent,
|
|
869
|
+
async #readTrackPackets(layout, tracks, track, start, interval, isCurrent, reader, account = null, holdsUntil = null) {
|
|
865
870
|
const id = track.trackNumber;
|
|
866
871
|
const previous = this.#packetWalks.get(id) ?? Promise.resolve();
|
|
867
872
|
let finished;
|
|
868
873
|
const walk = previous.then(() => new Promise(resolve => { finished = resolve; }));
|
|
869
874
|
this.#packetWalks.set(id, walk);
|
|
875
|
+
const waitedFrom = Date.now();
|
|
870
876
|
await previous;
|
|
877
|
+
const read = account ? async (start, end) => {
|
|
878
|
+
const bytes = await reader(start, end);
|
|
879
|
+
account.reads += 1;
|
|
880
|
+
account.bytes += bytes.length;
|
|
881
|
+
return bytes;
|
|
882
|
+
} : reader;
|
|
883
|
+
if (account) account.lockMs += Date.now() - waitedFrom;
|
|
871
884
|
try {
|
|
872
885
|
const readings = this.#packetReadings.get(id) ?? [];
|
|
873
886
|
this.#packetReadings.set(id, readings);
|
|
@@ -918,7 +931,7 @@ export class MatroskaContainer extends Container {
|
|
|
918
931
|
const following = readings.find(reading => reading.startAt > current.startAt);
|
|
919
932
|
const outcome = await walkMatroskaPackets({ readRange: read, fileSize: this.fileSize,
|
|
920
933
|
portionBytes: this.portionBytes, layout, tracks, trackIds: [id], interval, packetMemory: this.packetMemory,
|
|
921
|
-
state: current, until: following?.startAt ?? null, isCovered: covered, readsThrough, isCurrent });
|
|
934
|
+
state: current, until: following?.startAt ?? null, isCovered: covered, readsThrough, isCurrent, holdsUntil });
|
|
922
935
|
if (outcome === "obsolete") return null;
|
|
923
936
|
if (outcome !== "until") break;
|
|
924
937
|
current.next = following;
|
|
@@ -49,7 +49,7 @@ function recordedTracks(tracks, trackIds) {
|
|
|
49
49
|
*
|
|
50
50
|
* @returns {Promise<"covered" | "until" | "end" | "obsolete">}
|
|
51
51
|
*/
|
|
52
|
-
export async function walkMatroskaPackets({ readRange, fileSize, portionBytes, layout, tracks, trackIds, interval, packetMemory, state, until = null, isCovered = null, readsThrough = null, isCurrent = null }) {
|
|
52
|
+
export async function walkMatroskaPackets({ readRange, fileSize, portionBytes, layout, tracks, trackIds, interval, packetMemory, state, until = null, isCovered = null, readsThrough = null, isCurrent = null, holdsUntil = null }) {
|
|
53
53
|
// INSIDE A CLUSTER EVERY BLOCK HEADER IS READ, so its bytes are read a
|
|
54
54
|
// portion at a time up to the cluster's end, and never past it. Read a
|
|
55
55
|
// header at a time, each a read of a few bytes from the torrent thread, the
|
|
@@ -61,8 +61,14 @@ export async function walkMatroskaPackets({ readRange, fileSize, portionBytes, l
|
|
|
61
61
|
// (r54). The headers lie throughout the portion, so it is all needed.
|
|
62
62
|
// Between clusters only their headers are read.
|
|
63
63
|
let readableUntil = null;
|
|
64
|
+
// `holdsUntil` bounds a portion by what the reader holds, so a walk over a
|
|
65
|
+
// copy reads up to its end what it needs, not a portion past it (r63).
|
|
64
66
|
const reader = new ElementReader({ read: readRange, fileSize, portionBytes, prefetch: true,
|
|
65
|
-
readableUntil: start =>
|
|
67
|
+
readableUntil: start => {
|
|
68
|
+
const reach = Math.max(readableUntil ?? start, readsThrough?.(start) ?? start);
|
|
69
|
+
const held = holdsUntil?.(start);
|
|
70
|
+
return Number.isFinite(held) ? Math.min(reach, held) : reach;
|
|
71
|
+
} });
|
|
66
72
|
const declared = recordedTracks(tracks, trackIds);
|
|
67
73
|
if (!state.packets) {
|
|
68
74
|
state.packets = new Map([...declared.keys()].map(id => [id, new PacketRecords(packetMemory)]));
|