@torrent-tv/proxy 2.89.6 → 2.89.8

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.
Files changed (92) hide show
  1. package/AGENTS.md +32 -12
  2. package/CHANGELOG.md +2340 -2330
  3. package/assets/calibration/audio/NOTICE.md +19 -19
  4. package/assets/calibration/audio/manifest.json +98 -98
  5. package/biome.json +412 -412
  6. package/docs/browser-proxy-contract.md +155 -155
  7. package/docs/container-architecture.md +273 -273
  8. package/docs/encode-run-state.md +1 -1
  9. package/docs/playback-start-forecast.md +113 -98
  10. package/package.json +51 -50
  11. package/routes/api/sources/files/get.js +106 -106
  12. package/scripts/build-audio-samples.mjs +31 -31
  13. package/services/encode/EncodeRun.js +67 -15
  14. package/services/encode/EncodeRuns.js +19 -7
  15. package/services/encode/OutputOpening.js +784 -784
  16. package/services/encode/audio-calibration.js +67 -67
  17. package/services/encode/audio-work-rate.js +15 -15
  18. package/services/encode/encode-exit.js +2 -0
  19. package/services/encode/hwaccel.js +1894 -1894
  20. package/services/encode/output/index.js +24 -24
  21. package/services/encode/piece-completeness.js +60 -2
  22. package/services/encode/run-command.js +757 -755
  23. package/services/encode/segment-formats/index.js +1 -0
  24. package/services/encode/segment-formats/mp4-boxes.js +8 -1
  25. package/services/media/ContainerOrchestrator.js +202 -202
  26. package/services/media/KeyframeTables.js +356 -356
  27. package/services/media/SubtitleCues.js +592 -592
  28. package/services/media/audio-inventory.js +299 -299
  29. package/services/media/container/AviContainer.js +268 -268
  30. package/services/media/container/Container.js +632 -632
  31. package/services/media/container/ContainerFactory.js +38 -38
  32. package/services/media/container/MatroskaContainer.js +1031 -1031
  33. package/services/media/container/Mp4Container.js +1354 -1354
  34. package/services/media/container/SubtitleFileContainer.js +339 -339
  35. package/services/media/container/ebml-stream.js +174 -174
  36. package/services/media/container/matroska-clusters.js +1165 -1165
  37. package/services/media/container/unavailable.js +77 -77
  38. package/services/media/tracks/ContainerTrack.js +84 -84
  39. package/services/media/tracks/TextSubtitleTrack.js +295 -295
  40. package/services/storage/piece-store/shared-piece-store.js +1937 -1937
  41. package/services/storage/segment-store/SegmentStore.js +1294 -1294
  42. package/services/torrent/Contents.js +382 -382
  43. package/services/torrent/episode-naming.js +182 -182
  44. package/services/torrent/torrent-pool.js +2688 -2688
  45. package/services/torrent/worker/client.js +836 -836
  46. package/services/torrent/worker/held-bytes.js +231 -231
  47. package/services/torrent/worker/worker.js +1137 -1137
  48. package/services/transport/data-channel-handler.js +1373 -1373
  49. package/services/viewer/PriorityMap.js +421 -421
  50. package/test/audio-calibration.test.js +23 -23
  51. package/test/auto-quality-step.test.js +762 -762
  52. package/test/coalesce.test.js +39 -39
  53. package/test/coverage-follows-the-disk.test.js +5 -0
  54. package/test/decode-cost.test.js +475 -475
  55. package/test/encode-run.test.js +58 -0
  56. package/test/episode-naming.test.js +131 -131
  57. package/test/failed-encoder-still-serves-what-exists.test.js +1 -1
  58. package/test/fixtures/audio-frame-boundary.json +1 -1
  59. package/test/fixtures/refused-audio-20261002.json +10 -0
  60. package/test/fmp4-presentation-clock.test.js +4 -2
  61. package/test/format-keyed-outputs.test.js +78 -78
  62. package/test/health-metrics.test.js +11 -13
  63. package/test/held-read-ownership.test.js +77 -77
  64. package/test/helpers/encode-run.js +14 -6
  65. package/test/helpers/held-file.js +77 -77
  66. package/test/helpers/manager.js +62 -62
  67. package/test/helpers/matroska-file.js +296 -296
  68. package/test/keyframe-table-budget.test.js +123 -123
  69. package/test/matroska-cluster-discovery.test.js +323 -323
  70. package/test/one-output-one-session.test.js +273 -273
  71. package/test/output-speed.test.js +6 -1
  72. package/test/piece-completeness.test.js +37 -1
  73. package/test/quality-variants.test.js +1274 -1270
  74. package/test/rate-control-in-the-output.test.js +4 -3
  75. package/test/reads-not-yet-arrived.test.js +218 -218
  76. package/test/seek-frees-the-viewers-requests.test.js +1 -1
  77. package/test/segment-closed-on-both-branches.test.js +114 -114
  78. package/test/segment-judged-by-its-own-header.test.js +222 -222
  79. package/test/segment-serve-wiring.test.js +481 -481
  80. package/test/serving-existing-output.test.js +111 -111
  81. package/test/source-files-route.test.js +113 -113
  82. package/test/subtitle-cue-walk.test.js +385 -385
  83. package/test/subtitle-metadata.test.js +15 -15
  84. package/test/subtitle-plan-waits.test.js +125 -125
  85. package/test/subtitle-track-numbering.test.js +391 -391
  86. package/test/two-viewers-one-picture.test.js +508 -508
  87. package/test/viewer-requests-admission.test.js +92 -92
  88. package/test/viewer-starts-on-map.test.js +27 -27
  89. package/test/viewer-states.test.js +5 -2
  90. package/test/viewing-generations.test.js +390 -390
  91. package/test/whole-file-held-read.test.js +30 -30
  92. package/utils/coalesce.js +41 -41
@@ -1,174 +1,174 @@
1
- /**
2
- * @file EBML elements read where they lie, a portion at a time.
3
- *
4
- * `ebml-reader.js` parses a buffer the caller already holds. That is enough for
5
- * a head window, and it is not enough for the elements that matter here: a Cues
6
- * table whose size is the file's business, a Tracks element pushed out of the
7
- * head by a large CodecPrivate, a cluster of a high-bitrate film. Reading any
8
- * of them "in one go" needs a bound, and a bound is a statement about this
9
- * program dressed up as a statement about the file — an element over it was
10
- * reported as absent.
11
- *
12
- * This reads element HEADERS one at a time and element DATA only when asked,
13
- * through a window no larger than one portion. The portion is handed in by
14
- * whoever knows the medium (a torrent's piece length) and is never chosen here.
15
- * Skipping an element costs one header read; an element whose data is wanted
16
- * and is larger than a portion is refused by name, with its size.
17
- *
18
- * Grammar only, like `ebml-reader.js`: nothing here knows Matroska's ids.
19
- */
20
-
21
- import { readVint } from "./ebml-reader.js";
22
-
23
- /** An element id is at most four bytes and a size at most eight (RFC 8794 §4). */
24
- export const MAX_HEADER_BYTES = 12;
25
-
26
- /**
27
- * Whether a size field says "unknown" — every value bit set (RFC 8794 §6.2).
28
- *
29
- * @param {{ value: number, length: number }} size
30
- * @returns {boolean}
31
- */
32
- export function sizeIsUnknown(size) {
33
- return size.value === 2 ** (7 * size.length) - 1;
34
- }
35
-
36
- /**
37
- * @typedef {object} ElementHeader
38
- * @property {number} id
39
- * @property {number} at - Where the element begins, its id included.
40
- * @property {number} dataOffset - Where its data begins.
41
- * @property {number | null} size - Null when the size is unknown.
42
- * @property {number | null} end - One past the last data byte, null when unknown.
43
- */
44
-
45
- export class ElementReader {
46
- /** @type {(start: number, end: number) => Promise<Buffer>} */
47
- #read;
48
-
49
- /** @type {number} */
50
- #fileSize;
51
-
52
- /** @type {number} */
53
- #portion;
54
-
55
- /** @type {boolean} */
56
- #prefetch;
57
-
58
- /** @type {(start: number) => number} */
59
- #readableUntil;
60
-
61
- /** @type {Buffer | null} */
62
- #window = null;
63
-
64
- /** @type {number} */
65
- #windowAt = 0;
66
-
67
- /**
68
- * @param {object} params
69
- * @param {(start: number, end: number) => Promise<Buffer>} params.read - Answers
70
- * with every byte of the range or throws (`strictReader`).
71
- * @param {number} params.fileSize
72
- * @param {number} [params.portionBytes] - The largest read this makes. Absent,
73
- * an element is read in one go, which is right for a file on a local disk.
74
- * @param {boolean} [params.prefetch] - Read a whole portion on a miss. Right
75
- * when every byte of a span will be looked at (a cluster being walked);
76
- * wrong when only a few headers will (the elements of a Segment, where a
77
- * fetching read of a portion per header would pull megabytes for nothing).
78
- * @param {(start: number) => number} [params.readableUntil] - One past the
79
- * last byte that can be read from `start` in one go. A read of what is
80
- * downloaded must not run a prefetch past the end of the downloaded run,
81
- * or a cluster that is whole would fail for the bytes after it.
82
- */
83
- constructor({ read, fileSize, portionBytes = Number.POSITIVE_INFINITY, prefetch = false, readableUntil }) {
84
- this.#read = read;
85
- this.#fileSize = fileSize;
86
- this.#readableUntil = typeof readableUntil === "function" ? readableUntil : () => fileSize;
87
- this.#portion = Number.isFinite(portionBytes) && portionBytes > 0 ? portionBytes : Number.POSITIVE_INFINITY;
88
- this.#prefetch = prefetch;
89
- }
90
-
91
- /** @returns {number} */
92
- get portionBytes() {
93
- return this.#portion;
94
- }
95
-
96
- /**
97
- * `length` bytes from `start`, or fewer where the file ends first.
98
- *
99
- * @param {number} start
100
- * @param {number} length
101
- * @returns {Promise<Buffer>}
102
- */
103
- async bytes(start, length) {
104
- const end = Math.min(this.#fileSize, start + length);
105
- if (end <= start) {
106
- return Buffer.alloc(0);
107
- }
108
- const window = this.#window;
109
- if (window && start >= this.#windowAt && end <= this.#windowAt + window.length) {
110
- return window.subarray(start - this.#windowAt, end - this.#windowAt);
111
- }
112
- const reach = Math.min(this.#fileSize, Math.max(end, this.#readableUntil(start)));
113
- const span = this.#prefetch ? Math.max(end - start, Math.min(this.#portion, reach - start)) : end - start;
114
- const got = await this.#read(start, start + span - 1);
115
- this.#window = got;
116
- this.#windowAt = start;
117
- return got.subarray(0, end - start);
118
- }
119
-
120
- /**
121
- * The element whose header begins at `at`.
122
- *
123
- * @param {number} at
124
- * @param {number} limit - One past the last byte the element may occupy.
125
- * @returns {Promise<ElementHeader | null>} Null when the bytes there are not
126
- * an element header — a malformed vint, or no room for one.
127
- */
128
- async header(at, limit) {
129
- const room = Math.min(limit, this.#fileSize) - at;
130
- if (room <= 1) {
131
- return null;
132
- }
133
- const bytes = await this.bytes(at, Math.min(MAX_HEADER_BYTES, room));
134
- const id = readVint(bytes, 0, true);
135
- if (!id) {
136
- return null;
137
- }
138
- const size = readVint(bytes, id.length, false);
139
- if (!size) {
140
- return null;
141
- }
142
- const dataOffset = at + id.length + size.length;
143
- if (sizeIsUnknown(size)) {
144
- return { id: id.value, at, dataOffset, size: null, end: null };
145
- }
146
- return { id: id.value, at, dataOffset, size: size.value, end: dataOffset + size.value };
147
- }
148
-
149
- /**
150
- * An element's data, copied out of the window so the window can move on.
151
- *
152
- * @param {ElementHeader} element
153
- * @returns {Promise<Buffer | null>} Null when the data is larger than one
154
- * portion: refused, never assembled.
155
- */
156
- async data(element) {
157
- if (element.size === null || element.size > this.#portion) {
158
- return null;
159
- }
160
- return Buffer.from(await this.bytes(element.dataOffset, element.size));
161
- }
162
-
163
- /**
164
- * The first `length` bytes of an element's data, without reading the rest.
165
- *
166
- * @param {ElementHeader} element
167
- * @param {number} length
168
- * @returns {Promise<Buffer>}
169
- */
170
- async peek(element, length) {
171
- const available = element.size === null ? length : Math.min(length, element.size);
172
- return this.bytes(element.dataOffset, available);
173
- }
174
- }
1
+ /**
2
+ * @file EBML elements read where they lie, a portion at a time.
3
+ *
4
+ * `ebml-reader.js` parses a buffer the caller already holds. That is enough for
5
+ * a head window, and it is not enough for the elements that matter here: a Cues
6
+ * table whose size is the file's business, a Tracks element pushed out of the
7
+ * head by a large CodecPrivate, a cluster of a high-bitrate film. Reading any
8
+ * of them "in one go" needs a bound, and a bound is a statement about this
9
+ * program dressed up as a statement about the file — an element over it was
10
+ * reported as absent.
11
+ *
12
+ * This reads element HEADERS one at a time and element DATA only when asked,
13
+ * through a window no larger than one portion. The portion is handed in by
14
+ * whoever knows the medium (a torrent's piece length) and is never chosen here.
15
+ * Skipping an element costs one header read; an element whose data is wanted
16
+ * and is larger than a portion is refused by name, with its size.
17
+ *
18
+ * Grammar only, like `ebml-reader.js`: nothing here knows Matroska's ids.
19
+ */
20
+
21
+ import { readVint } from "./ebml-reader.js";
22
+
23
+ /** An element id is at most four bytes and a size at most eight (RFC 8794 §4). */
24
+ export const MAX_HEADER_BYTES = 12;
25
+
26
+ /**
27
+ * Whether a size field says "unknown" — every value bit set (RFC 8794 §6.2).
28
+ *
29
+ * @param {{ value: number, length: number }} size
30
+ * @returns {boolean}
31
+ */
32
+ export function sizeIsUnknown(size) {
33
+ return size.value === 2 ** (7 * size.length) - 1;
34
+ }
35
+
36
+ /**
37
+ * @typedef {object} ElementHeader
38
+ * @property {number} id
39
+ * @property {number} at - Where the element begins, its id included.
40
+ * @property {number} dataOffset - Where its data begins.
41
+ * @property {number | null} size - Null when the size is unknown.
42
+ * @property {number | null} end - One past the last data byte, null when unknown.
43
+ */
44
+
45
+ export class ElementReader {
46
+ /** @type {(start: number, end: number) => Promise<Buffer>} */
47
+ #read;
48
+
49
+ /** @type {number} */
50
+ #fileSize;
51
+
52
+ /** @type {number} */
53
+ #portion;
54
+
55
+ /** @type {boolean} */
56
+ #prefetch;
57
+
58
+ /** @type {(start: number) => number} */
59
+ #readableUntil;
60
+
61
+ /** @type {Buffer | null} */
62
+ #window = null;
63
+
64
+ /** @type {number} */
65
+ #windowAt = 0;
66
+
67
+ /**
68
+ * @param {object} params
69
+ * @param {(start: number, end: number) => Promise<Buffer>} params.read - Answers
70
+ * with every byte of the range or throws (`strictReader`).
71
+ * @param {number} params.fileSize
72
+ * @param {number} [params.portionBytes] - The largest read this makes. Absent,
73
+ * an element is read in one go, which is right for a file on a local disk.
74
+ * @param {boolean} [params.prefetch] - Read a whole portion on a miss. Right
75
+ * when every byte of a span will be looked at (a cluster being walked);
76
+ * wrong when only a few headers will (the elements of a Segment, where a
77
+ * fetching read of a portion per header would pull megabytes for nothing).
78
+ * @param {(start: number) => number} [params.readableUntil] - One past the
79
+ * last byte that can be read from `start` in one go. A read of what is
80
+ * downloaded must not run a prefetch past the end of the downloaded run,
81
+ * or a cluster that is whole would fail for the bytes after it.
82
+ */
83
+ constructor({ read, fileSize, portionBytes = Number.POSITIVE_INFINITY, prefetch = false, readableUntil }) {
84
+ this.#read = read;
85
+ this.#fileSize = fileSize;
86
+ this.#readableUntil = typeof readableUntil === "function" ? readableUntil : () => fileSize;
87
+ this.#portion = Number.isFinite(portionBytes) && portionBytes > 0 ? portionBytes : Number.POSITIVE_INFINITY;
88
+ this.#prefetch = prefetch;
89
+ }
90
+
91
+ /** @returns {number} */
92
+ get portionBytes() {
93
+ return this.#portion;
94
+ }
95
+
96
+ /**
97
+ * `length` bytes from `start`, or fewer where the file ends first.
98
+ *
99
+ * @param {number} start
100
+ * @param {number} length
101
+ * @returns {Promise<Buffer>}
102
+ */
103
+ async bytes(start, length) {
104
+ const end = Math.min(this.#fileSize, start + length);
105
+ if (end <= start) {
106
+ return Buffer.alloc(0);
107
+ }
108
+ const window = this.#window;
109
+ if (window && start >= this.#windowAt && end <= this.#windowAt + window.length) {
110
+ return window.subarray(start - this.#windowAt, end - this.#windowAt);
111
+ }
112
+ const reach = Math.min(this.#fileSize, Math.max(end, this.#readableUntil(start)));
113
+ const span = this.#prefetch ? Math.max(end - start, Math.min(this.#portion, reach - start)) : end - start;
114
+ const got = await this.#read(start, start + span - 1);
115
+ this.#window = got;
116
+ this.#windowAt = start;
117
+ return got.subarray(0, end - start);
118
+ }
119
+
120
+ /**
121
+ * The element whose header begins at `at`.
122
+ *
123
+ * @param {number} at
124
+ * @param {number} limit - One past the last byte the element may occupy.
125
+ * @returns {Promise<ElementHeader | null>} Null when the bytes there are not
126
+ * an element header — a malformed vint, or no room for one.
127
+ */
128
+ async header(at, limit) {
129
+ const room = Math.min(limit, this.#fileSize) - at;
130
+ if (room <= 1) {
131
+ return null;
132
+ }
133
+ const bytes = await this.bytes(at, Math.min(MAX_HEADER_BYTES, room));
134
+ const id = readVint(bytes, 0, true);
135
+ if (!id) {
136
+ return null;
137
+ }
138
+ const size = readVint(bytes, id.length, false);
139
+ if (!size) {
140
+ return null;
141
+ }
142
+ const dataOffset = at + id.length + size.length;
143
+ if (sizeIsUnknown(size)) {
144
+ return { id: id.value, at, dataOffset, size: null, end: null };
145
+ }
146
+ return { id: id.value, at, dataOffset, size: size.value, end: dataOffset + size.value };
147
+ }
148
+
149
+ /**
150
+ * An element's data, copied out of the window so the window can move on.
151
+ *
152
+ * @param {ElementHeader} element
153
+ * @returns {Promise<Buffer | null>} Null when the data is larger than one
154
+ * portion: refused, never assembled.
155
+ */
156
+ async data(element) {
157
+ if (element.size === null || element.size > this.#portion) {
158
+ return null;
159
+ }
160
+ return Buffer.from(await this.bytes(element.dataOffset, element.size));
161
+ }
162
+
163
+ /**
164
+ * The first `length` bytes of an element's data, without reading the rest.
165
+ *
166
+ * @param {ElementHeader} element
167
+ * @param {number} length
168
+ * @returns {Promise<Buffer>}
169
+ */
170
+ async peek(element, length) {
171
+ const available = element.size === null ? length : Math.min(length, element.size);
172
+ return this.bytes(element.dataOffset, available);
173
+ }
174
+ }