@torrent-tv/proxy 2.88.6 → 2.89.1
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/AGENTS.md +21 -12
- package/CHANGELOG.md +2285 -2267
- package/assets/calibration/audio/NOTICE.md +19 -19
- package/assets/calibration/audio/manifest.json +98 -98
- package/docs/container-architecture.md +77 -22
- package/docs/playback-start-forecast.md +56 -56
- package/main2.cpuprofile +1 -0
- package/package.json +1 -1
- package/scripts/build-audio-samples.mjs +31 -31
- package/server.js +42 -27
- package/services/encode/OutputOpening.js +5 -4
- package/services/encode/audio-calibration.js +67 -67
- package/services/encode/audio-work-rate.js +15 -15
- package/services/media/ContainerOrchestrator.js +99 -46
- package/services/media/KeyframeTables.js +61 -21
- package/services/media/SubtitleCues.js +255 -183
- package/services/media/container/AviContainer.js +5 -3
- package/services/media/container/Container.js +66 -43
- package/services/media/container/ContainerFactory.js +8 -26
- package/services/media/container/MatroskaContainer.js +719 -1182
- package/services/media/container/Mp4Container.js +83 -73
- package/services/media/container/ebml-stream.js +174 -0
- package/services/media/container/matroska-clusters.js +1165 -0
- package/services/media/container/unavailable.js +77 -0
- package/services/media/tracks/ContainerTrack.js +9 -0
- package/services/media/tracks/TextSubtitleTrack.js +9 -1
- package/services/server/playback-readiness.js +516 -516
- package/services/torrent/worker/client.js +836 -765
- package/services/torrent/worker/held-bytes.js +84 -8
- package/services/torrent/worker/worker.js +10 -2
- package/services/transport/data-channel-handler.js +7 -3
- package/services/viewer/PriorityMap.js +31 -0
- package/test/audio-calibration.test.js +23 -23
- package/test/coalesce.test.js +39 -0
- package/test/held-read-ownership.test.js +77 -0
- package/test/helpers/held-file.js +50 -2
- package/test/helpers/matroska-file.js +296 -0
- package/test/keyframe-table-budget.test.js +21 -0
- package/test/matroska-cluster-discovery.test.js +323 -0
- package/test/reads-not-yet-arrived.test.js +218 -0
- package/test/subtitle-cue-walk.test.js +16 -1
- package/test/subtitle-plan-waits.test.js +125 -0
- package/test/subtitle-track-numbering.test.js +8 -3
- package/test/viewer-starts-on-map.test.js +27 -0
- package/test/whole-file-held-read.test.js +30 -0
- package/utils/coalesce.js +41 -0
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
* it. Cancelling it would make every later viewer pay the whole wait again;
|
|
17
17
|
* 3. **a read that THREW is not an answer.** "The head is not downloaded" says
|
|
18
18
|
* nothing about the file; turning it into "this file has no keyframes" makes
|
|
19
|
-
* every picture of it a re-encode for as long as the process lives.
|
|
19
|
+
* every picture of it a re-encode for as long as the process lives. Bytes
|
|
20
|
+
* that have not arrived reach here as `BytesUnavailable` — until 2026-10-01
|
|
21
|
+
* they came back as a reading of no times, which `KeyframeTable.learn`
|
|
22
|
+
* records as answered, so point 3 held only for reads that crashed. When
|
|
23
|
+
* pieces of such a file arrive the read is made again
|
|
24
|
+
* (`readAgainIfUnanswered`), and its answer lands in the same table.
|
|
20
25
|
*
|
|
21
26
|
* **What it does NOT know.** Where the bytes come from. The reader is one
|
|
22
27
|
* function handed in at construction — on this proxy it crosses to the torrent
|
|
@@ -27,20 +32,16 @@
|
|
|
27
32
|
* transport inside a layer that must not have one, and a second answer to a
|
|
28
33
|
* question with one owner.
|
|
29
34
|
*
|
|
30
|
-
* **Why this is not `ContainerOrchestrator
|
|
31
|
-
*
|
|
32
|
-
* container
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* worker's is the parse, memoized on the container it parsed, and the main
|
|
37
|
-
* thread's is the answer, which nothing in the worker ever reads back. Three
|
|
38
|
-
* directories of the storage layer are kept apart for the same reason, and it
|
|
39
|
-
* is stated here for the same reason too — an unexplained second home reads as
|
|
40
|
-
* a second owner.
|
|
35
|
+
* **Why this is not `ContainerOrchestrator`.** Both live on the main thread
|
|
36
|
+
* (the parse moved there on 2026-09-15). That one holds the file's one
|
|
37
|
+
* container, which keeps its own reading of the Cues table; this keeps the
|
|
38
|
+
* ANSWER the sessions hold — the keyframe times taken from that reading, and
|
|
39
|
+
* the wait around it, which a container cannot have because it does not know
|
|
40
|
+
* there is a viewer.
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
43
|
import { KeyframeTable } from "./container/KeyframeTable.js";
|
|
44
|
+
import { isUnavailable } from "./container/unavailable.js";
|
|
44
45
|
import { logger as defaultLogger } from "../../utils/logger.js";
|
|
45
46
|
|
|
46
47
|
/**
|
|
@@ -187,10 +188,17 @@ export class KeyframeTables {
|
|
|
187
188
|
return table;
|
|
188
189
|
})
|
|
189
190
|
.catch((error) => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
if (isUnavailable(error)) {
|
|
192
|
+
this.#logger.info(
|
|
193
|
+
`keyframe index "${logName}": not downloaded yet after ${Date.now() - startedMs}ms — ` +
|
|
194
|
+
"read again when pieces of the file arrive"
|
|
195
|
+
);
|
|
196
|
+
} else {
|
|
197
|
+
this.#logger.warn(
|
|
198
|
+
`keyframe index "${logName}": the read failed after ${Date.now() - startedMs}ms — ` +
|
|
199
|
+
`${error?.message ?? error}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
194
202
|
throw error;
|
|
195
203
|
})
|
|
196
204
|
.finally(() => {
|
|
@@ -220,6 +228,25 @@ export class KeyframeTables {
|
|
|
220
228
|
}
|
|
221
229
|
}
|
|
222
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Read again a table somebody asked for and nobody has answered.
|
|
233
|
+
*
|
|
234
|
+
* Called when pieces of the file arrive. A table nobody asked for is left
|
|
235
|
+
* alone — this is not a reason to read every file of a torrent — and one
|
|
236
|
+
* already read, or being read, is not touched.
|
|
237
|
+
*
|
|
238
|
+
* @param {{ sourceKey: string, fileIndex: number, logName?: string }} params
|
|
239
|
+
* @returns {void}
|
|
240
|
+
*/
|
|
241
|
+
readAgainIfUnanswered(params) {
|
|
242
|
+
const key = KeyframeTables.keyFor(params.sourceKey, params.fileIndex);
|
|
243
|
+
const table = this.#byFile.get(key);
|
|
244
|
+
if (!table || table.answered || this.#reading.has(key)) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
void this.warm(params);
|
|
248
|
+
}
|
|
249
|
+
|
|
223
250
|
/**
|
|
224
251
|
* The same read, with a bound on how long THIS caller waits for it.
|
|
225
252
|
*
|
|
@@ -232,7 +259,8 @@ export class KeyframeTables {
|
|
|
232
259
|
* @param {{ sourceKey: string, fileIndex: number, logName?: string }} params
|
|
233
260
|
* @returns {Promise<{ table: KeyframeTable, arrived: boolean }>} `arrived` is
|
|
234
261
|
* about THIS wait. False with an unanswered table means the read is still
|
|
235
|
-
* running,
|
|
262
|
+
* running, or found the bytes not downloaded yet and will be made again
|
|
263
|
+
* when they arrive — neither is the same as a file with no keyframes.
|
|
236
264
|
*/
|
|
237
265
|
async within(params) {
|
|
238
266
|
const read = this.read(params);
|
|
@@ -242,11 +270,23 @@ export class KeyframeTables {
|
|
|
242
270
|
// A caller must not be held open by this timer alone.
|
|
243
271
|
timer?.unref?.();
|
|
244
272
|
});
|
|
245
|
-
|
|
273
|
+
// Bytes not downloaded yet are "not arrived" to this caller: the table
|
|
274
|
+
// stays unanswered and is read again when pieces of the file arrive.
|
|
275
|
+
// Thrown out of here, a shortage of bytes would fail the opening of an
|
|
276
|
+
// output. Any other failure is still the caller's to see.
|
|
277
|
+
const answer = await Promise.race([
|
|
278
|
+
read.then(
|
|
279
|
+
(table) => ({ table, arrived: true }),
|
|
280
|
+
(error) => {
|
|
281
|
+
if (isUnavailable(error)) {
|
|
282
|
+
return { table: this.of(params), arrived: false };
|
|
283
|
+
}
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
),
|
|
287
|
+
budget
|
|
288
|
+
]);
|
|
246
289
|
clearTimeout(timer);
|
|
247
|
-
// Nothing is added here to swallow a late rejection: the race is holding a
|
|
248
|
-
// handler on that promise already, and a second one would only look like it
|
|
249
|
-
// was doing something.
|
|
250
290
|
return answer ?? { table: this.of(params), arrived: false };
|
|
251
291
|
}
|
|
252
292
|
|