@torrent-tv/proxy 2.105.0 → 2.105.2
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
|
@@ -155,7 +155,11 @@ export class ContainerOrchestrator {
|
|
|
155
155
|
// picture's first input of an AVI waited about 4 s behind the sound's
|
|
156
156
|
// table of the whole film it does not use (r38, torrent-tv/meta#178).
|
|
157
157
|
const aside = this.#answersWithoutReading(key, statement);
|
|
158
|
-
|
|
158
|
+
// Chapter discovery can walk a whole file when the format has no chapter
|
|
159
|
+
// index. Keep it serial with other chapter reads, but out of the queue used
|
|
160
|
+
// to prepare playback inputs for this file.
|
|
161
|
+
const queueKey = statement === "chapters" ? `${key}:chapters` : key;
|
|
162
|
+
const previous = aside ? Promise.resolve() : this.#reads.get(queueKey) ?? Promise.resolve();
|
|
159
163
|
const askedAt = performance.now();
|
|
160
164
|
const reading = previous.catch(() => undefined).then(async () => {
|
|
161
165
|
if (!isCurrent()) return { result: { kind: "terminal", reason: "request-obsolete", requestId: params.requestId } };
|
|
@@ -168,7 +172,7 @@ export class ContainerOrchestrator {
|
|
|
168
172
|
readMs: endedAt - startedAt, cached: aside, result: result.kind, reason: result.reason, ranges: result.ranges });
|
|
169
173
|
return { result, revision };
|
|
170
174
|
});
|
|
171
|
-
if (!aside) this.#reads.set(
|
|
175
|
+
if (!aside) this.#reads.set(queueKey, reading);
|
|
172
176
|
try {
|
|
173
177
|
const { result, revision } = await reading;
|
|
174
178
|
if (!isCurrent()) return { kind: "terminal", reason: "request-obsolete", requestId: params.requestId };
|
|
@@ -177,7 +181,7 @@ export class ContainerOrchestrator {
|
|
|
177
181
|
await params.onReadResult?.(statement, result, revision);
|
|
178
182
|
return result;
|
|
179
183
|
} finally {
|
|
180
|
-
if (this.#reads.get(
|
|
184
|
+
if (this.#reads.get(queueKey) === reading) this.#reads.delete(queueKey);
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
|
|
@@ -11,11 +11,12 @@ const TOP_LEVEL = new Set([SEEK_HEAD, CHAPTERS, CLUSTER, 0x1549a966, 0x1654ae6b,
|
|
|
11
11
|
async function header(reader, at, limit) {
|
|
12
12
|
reader.step();
|
|
13
13
|
const vint = async (position, marker) => {
|
|
14
|
-
const
|
|
14
|
+
const firstBytes = await reader.bytes(position, 1);
|
|
15
|
+
const first = firstBytes[0];
|
|
15
16
|
let n = 1;
|
|
16
17
|
while (n <= 8 && !(first & (0x80 >> (n - 1)))) n++;
|
|
17
18
|
if (n > (marker ? 4 : 8) || position + n > limit) throw new Error("Invalid chapter EBML header");
|
|
18
|
-
const bytes = await reader.bytes(position, n);
|
|
19
|
+
const bytes = n === 1 ? firstBytes : Buffer.concat([firstBytes, await reader.bytes(position + 1, n - 1)]);
|
|
19
20
|
const value = readVint(bytes, 0, marker);
|
|
20
21
|
const mask = 2 ** (8 - n) - 1;
|
|
21
22
|
return { ...value, unknown: !marker && (bytes[0] & mask) === mask && bytes.subarray(1).every((byte) => byte === 255) };
|
|
@@ -575,6 +575,7 @@ export class ViewerRequests {
|
|
|
575
575
|
// still answered by the output it was made against — see the two deadlines
|
|
576
576
|
// in `Assignments.js`.
|
|
577
577
|
viewer.assignments.statedGeneration(generation);
|
|
578
|
+
this.#beginSeekWait(named, consumerId, generation);
|
|
578
579
|
this.#host.outputs.touch(named);
|
|
579
580
|
// EVERY REQUEST THIS VIEWER HAS OUTSTANDING WAS MADE FOR WHERE THEY WERE,
|
|
580
581
|
// and hls.js keeps one fragment load outstanding per track: a request held
|
|
@@ -601,6 +602,40 @@ export class ViewerRequests {
|
|
|
601
602
|
return true;
|
|
602
603
|
}
|
|
603
604
|
|
|
605
|
+
#beginSeekWait(output, consumerId, generation) {
|
|
606
|
+
const file = output?.file;
|
|
607
|
+
if (!Number.isSafeInteger(generation) || !file || typeof file.sourceKey !== "string" ||
|
|
608
|
+
!Number.isSafeInteger(file.fileIndex) || file.fileIndex < 0) return;
|
|
609
|
+
const waitId = `${file.sourceKey}:${file.fileIndex}:${generation}`;
|
|
610
|
+
const key = `${consumerId}:${waitId}`;
|
|
611
|
+
const fileName = file.relativePath ?? file.name ?? "";
|
|
612
|
+
const extension = typeof fileName === "string" ? fileName.split(".").at(-1)?.toLowerCase() ?? "" : "";
|
|
613
|
+
const viewer = this.#host.viewers.get(consumerId);
|
|
614
|
+
const link = linkMeasurementOf(viewer);
|
|
615
|
+
const features = {
|
|
616
|
+
extension,
|
|
617
|
+
fileLength: Number.isFinite(file.length) && file.length > 0 ? file.length : null,
|
|
618
|
+
downloadSpeed: null,
|
|
619
|
+
linkMbps: link.mbps,
|
|
620
|
+
linkSampleAgeMs: link.ageMs,
|
|
621
|
+
preparedOutputBytes: this.#preparedOutputBytes(file.sourceKey, file.fileIndex),
|
|
622
|
+
fileAvailable: null,
|
|
623
|
+
headerRemaining: null,
|
|
624
|
+
peers: null,
|
|
625
|
+
videoOperation: null,
|
|
626
|
+
audioOperation: null,
|
|
627
|
+
videoSpeedX: null,
|
|
628
|
+
audioSpeedX: null
|
|
629
|
+
};
|
|
630
|
+
// A seek is a new waiting action even when a caller repeats the same
|
|
631
|
+
// generation. Start its clock at the explicit seek request, before the
|
|
632
|
+
// next readiness poll can return the first current-work estimate.
|
|
633
|
+
this.#earlyWaits.delete(key);
|
|
634
|
+
const context = this.#setEarlyWait({ consumerId, generation, sourceKey: file.sourceKey,
|
|
635
|
+
fileIndex: file.fileIndex, waitId, features, revision: 0, current: null, waitElapsedMs: 0 });
|
|
636
|
+
this.#recordEarlyWaitSnapshot(context, features);
|
|
637
|
+
}
|
|
638
|
+
|
|
604
639
|
/**
|
|
605
640
|
* Whether this viewer still takes a request made in the generation it states.
|
|
606
641
|
*
|