@torrent-tv/proxy 2.89.4 → 2.89.6

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.
@@ -1,96 +1,114 @@
1
- /**
2
- * @file A piece is closed by one of two writers, and both must say so.
3
- *
4
- * The `segment` muxer writes under a working name and reports the closure on a
5
- * channel of its own, which reaches `SegmentStore.publish`. The `hls` muxer
6
- * writes through a temporary name of its own (`+temp_file`) and reports
7
- * nothing: the file simply appears under the name it is served as. That branch
8
- * is taken by every re-encoded output cut on the even grid — an ordinary
9
- * quality step — and for it a wait could only end on its deadline, so a segment
10
- * lying finished on disk was answered 503 after the whole hold.
11
- */
12
-
13
- import test from "node:test";
14
- import assert from "node:assert/strict";
15
- import { mkdtempSync, renameSync, rmSync, writeFileSync } from "node:fs";
16
- import os from "node:os";
17
- import path from "node:path";
18
- import { SegmentStore } from "../services/storage/segment-store/SegmentStore.js";
19
- import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
20
-
21
- const KEY = "torrent:abc:fmt=fmp4:grid=uniform@0:video-only:v=libx264/854x480";
22
-
23
- /** A deadline is the backstop of these waits, never what they measure. */
24
- const BACKSTOP_MS = 10_000;
25
-
26
- /**
27
- * @returns {{ store: SegmentStore, root: string }}
28
- */
29
- function storeInATempRoot() {
30
- const root = mkdtempSync(path.join(os.tmpdir(), "segment-closed-"));
31
- const store = new SegmentStore({ root, logger: { info: () => {}, warn: () => {} } });
32
- store.useFormat(KEY, fmp4Format);
33
- return { store, root };
34
- }
35
-
36
- test("a piece that simply appears ends the wait that began before it", async (t) => {
37
- const { store, root } = storeInATempRoot();
38
- t.after(() => {
39
- store.dropAll("the test is over");
40
- rmSync(root, { recursive: true, force: true });
41
- });
42
-
43
- const dir = store.directoryFor(KEY);
44
- // The wait begins on a segment nobody has written yet — the cold case, and
45
- // the only one the deadline used to decide.
46
- const waited = store.waitFor(KEY, 7, BACKSTOP_MS);
47
-
48
- // What the `hls` muxer does: a temporary name of its own, renamed into place
49
- // when the piece is whole. Nothing is told; the directory simply moves.
50
- const temp = path.join(dir, "segment-00007.mp4.tmp");
51
- writeFileSync(temp, Buffer.alloc(64));
52
- renameSync(temp, path.join(dir, fmp4Format.segmentFileName(7)));
53
-
54
- assert.equal(await waited, true, "the piece was on disk and the wait did not end");
55
- assert.ok(store.pathOf(KEY, 7), "and the store must be able to hand it over");
56
- });
57
-
58
- test("clearing up after a run is not its pieces arriving", async (t) => {
59
- const { store, root } = storeInATempRoot();
60
- t.after(() => {
61
- store.dropAll("the test is over");
62
- rmSync(root, { recursive: true, force: true });
63
- });
64
-
65
- const dir = store.directoryFor(KEY);
66
- const unfinished = path.join(dir, fmp4Format.makingFileNameTemplate("3").replace("%05d", "00009"));
67
- writeFileSync(unfinished, Buffer.alloc(64));
68
-
69
- let ended = false;
70
- // 200 ms is a grace before asserting that something did NOT happen, which can
71
- // only ever pass too easily — never a measurement.
72
- const waited = store.waitFor(KEY, 9, 200).then((value) => {
73
- ended = value;
74
- return value;
75
- });
76
- rmSync(unfinished, { force: true });
77
-
78
- assert.equal(await waited, false, "a directory that moved because a file went holds nothing new");
79
- assert.equal(ended, false);
80
- });
81
-
82
- test("the writer that does report a closure is unaffected", async (t) => {
83
- const { store, root } = storeInATempRoot();
84
- t.after(() => {
85
- store.dropAll("the test is over");
86
- rmSync(root, { recursive: true, force: true });
87
- });
88
-
89
- const dir = store.directoryFor(KEY);
90
- const making = fmp4Format.makingFileNameTemplate("4").replace("%05d", "00004");
91
- writeFileSync(path.join(dir, making), Buffer.alloc(64));
92
-
93
- const waited = store.waitFor(KEY, 4, BACKSTOP_MS);
94
- assert.equal(store.publish(KEY, making, fmp4Format), fmp4Format.segmentFileName(4));
95
- assert.equal(await waited, true);
96
- });
1
+ /**
2
+ * @file A piece is closed by one of two writers, and both must say so.
3
+ *
4
+ * The `segment` muxer writes under a working name and reports the closure on a
5
+ * channel of its own, which reaches `SegmentStore.publish`. The `hls` muxer
6
+ * writes through a temporary name of its own (`+temp_file`) and reports
7
+ * nothing: the file simply appears under the name it is served as. That branch
8
+ * is taken by every re-encoded output cut on the even grid — an ordinary
9
+ * quality step — and for it a wait could only end on its deadline, so a segment
10
+ * lying finished on disk was answered 503 after the whole hold.
11
+ */
12
+
13
+ import test from "node:test";
14
+ import assert from "node:assert/strict";
15
+ import { mkdtempSync, renameSync, rmSync, writeFileSync } from "node:fs";
16
+ import os from "node:os";
17
+ import path from "node:path";
18
+ import { SegmentStore } from "../services/storage/segment-store/SegmentStore.js";
19
+ import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
20
+
21
+ const KEY = "torrent:abc:fmt=fmp4:grid=uniform@0:video-only:v=libx264/854x480";
22
+
23
+ /** A deadline is the backstop of these waits, never what they measure. */
24
+ const BACKSTOP_MS = 10_000;
25
+
26
+ /**
27
+ * @returns {{ store: SegmentStore, root: string }}
28
+ */
29
+ function storeInATempRoot() {
30
+ const root = mkdtempSync(path.join(os.tmpdir(), "segment-closed-"));
31
+ const store = new SegmentStore({ root, logger: { info: () => {}, warn: () => {} } });
32
+ store.useFormat(KEY, fmp4Format);
33
+ return { store, root };
34
+ }
35
+
36
+ test("a piece that simply appears ends the wait that began before it", async (t) => {
37
+ const { store, root } = storeInATempRoot();
38
+ t.after(() => {
39
+ store.dropAll("the test is over");
40
+ rmSync(root, { recursive: true, force: true });
41
+ });
42
+
43
+ const dir = store.directoryFor(KEY);
44
+ // The wait begins on a segment nobody has written yet — the cold case, and
45
+ // the only one the deadline used to decide.
46
+ const waited = store.waitFor(KEY, 7, BACKSTOP_MS);
47
+
48
+ // What the `hls` muxer does: a temporary name of its own, renamed into place
49
+ // when the piece is whole. Nothing is told; the directory simply moves.
50
+ const temp = path.join(dir, "segment-00007.mp4.tmp");
51
+ writeFileSync(temp, Buffer.alloc(64));
52
+ renameSync(temp, path.join(dir, fmp4Format.segmentFileName(7)));
53
+
54
+ assert.equal(await waited, true, "the piece was on disk and the wait did not end");
55
+ assert.ok(store.pathOf(KEY, 7), "and the store must be able to hand it over");
56
+ });
57
+
58
+ test("clearing up after a run is not its pieces arriving", async (t) => {
59
+ const { store, root } = storeInATempRoot();
60
+ t.after(() => {
61
+ store.dropAll("the test is over");
62
+ rmSync(root, { recursive: true, force: true });
63
+ });
64
+
65
+ const dir = store.directoryFor(KEY);
66
+ const unfinished = path.join(dir, fmp4Format.makingFileNameTemplate("3").replace("%05d", "00009"));
67
+ writeFileSync(unfinished, Buffer.alloc(64));
68
+
69
+ let ended = false;
70
+ // 200 ms is a grace before asserting that something did NOT happen, which can
71
+ // only ever pass too easily — never a measurement.
72
+ const waited = store.waitFor(KEY, 9, 200).then((value) => {
73
+ ended = value;
74
+ return value;
75
+ });
76
+ rmSync(unfinished, { force: true });
77
+
78
+ assert.equal(await waited, false, "a directory that moved because a file went holds nothing new");
79
+ assert.equal(ended, false);
80
+ });
81
+
82
+ test("the writer that does report a closure is unaffected", async (t) => {
83
+ const { store, root } = storeInATempRoot();
84
+ t.after(() => {
85
+ store.dropAll("the test is over");
86
+ rmSync(root, { recursive: true, force: true });
87
+ });
88
+
89
+ const dir = store.directoryFor(KEY);
90
+ const making = fmp4Format.makingFileNameTemplate("4").replace("%05d", "00004");
91
+ writeFileSync(path.join(dir, making), Buffer.alloc(64));
92
+
93
+ const waited = store.waitFor(KEY, 4, BACKSTOP_MS);
94
+ assert.equal(store.publish(KEY, making, fmp4Format), fmp4Format.segmentFileName(4));
95
+ assert.equal(await waited, true);
96
+ });
97
+
98
+ test("a directory removed from outside and made again is watched again", async (t) => {
99
+ const { store, root } = storeInATempRoot();
100
+ t.after(() => {
101
+ store.dropAll("the test is over");
102
+ rmSync(root, { recursive: true, force: true });
103
+ });
104
+
105
+ // Removed by something other than the store, so its watch was not stopped
106
+ // first. The watch held on that path watches a directory that is gone.
107
+ rmSync(store.directoryFor(KEY), { recursive: true, force: true });
108
+ const dir = store.directoryFor(KEY);
109
+ const waited = store.waitFor(KEY, 3, BACKSTOP_MS);
110
+
111
+ writeFileSync(path.join(dir, fmp4Format.segmentFileName(3)), Buffer.alloc(64));
112
+
113
+ assert.equal(await waited, true, "the piece arrived in the new directory and the wait did not end");
114
+ });
@@ -1,221 +1,222 @@
1
- /**
2
- * @file A piece short of a track is judged against ITS OWN header.
3
- *
4
- * The question "does this piece carry every track" has two possible answers and
5
- * they call for opposite actions, which is why asking it against the session's
6
- * cached header alone was not enough: a piece that disagrees with that header
7
- * may be short, or the header may be the odd one out. A self-contained piece
8
- * carries a `moov` of its own, so it can be compared with itself, and that
9
- * comparison tells the two apart without asking anything about encoders.
10
- *
11
- * WHAT THIS REPLACED. The action used to be chosen by "is any run of this
12
- * session alive?" — a question about the SESSION, asked about a fact of the
13
- * BYTES. A run anywhere in the film meant "still being written; waiting for
14
- * it", and nothing re-judged or re-made the piece afterwards, so one
15
- * misjudgement became permanent.
16
- *
17
- * Field 2026-09-14: `segment-00033.mp4` of a copied picture, already served
18
- * whole to one viewer at 5 099 421 bytes, was refused to a second viewer for
19
- * five minutes — four holds of sixty seconds each, then 503 — while the run in
20
- * force was at #192 and beyond. That viewer's picture stood still 196.2 s and
21
- * then a further 56.2 s.
22
- *
23
- * The run question is also unanswerable and does not need asking: a piece being
24
- * written is called `making-<tag>-NNNNN.mp4` and takes its served name by a
25
- * rename, which is atomic. A file under its served name is never being written.
26
- */
27
-
28
- import test from "node:test";
29
- import assert from "node:assert/strict";
30
- import { access, writeFile } from "node:fs/promises";
31
- import path from "node:path";
32
- import { SourceFile } from "../services/media/SourceFile.js";
33
- import { Timeline } from "../services/encode/output/Timeline.js";
34
- import { managerWithOwnStore } from "./helpers/manager.js";
35
- import { startRunOn } from "./helpers/encode-run.js";
36
- import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
37
- import { outputSpec } from "./helpers/output-spec.js";
38
-
39
- const SESSION_ID = "3333333344445555";
40
- const OUTPUT_KEY = "own-header:fmt=fmp4:grid=kf@0:video-only:v=0/copy";
41
- const MOVIE_TIMESCALE = 1000;
42
-
43
- /**
44
- * @param {string} type
45
- * @param {Buffer} body
46
- * @returns {Buffer}
47
- */
48
- function box(type, body) {
49
- const head = Buffer.alloc(8);
50
- head.writeUInt32BE(8 + body.length, 0);
51
- head.write(type, 4, "latin1");
52
- return Buffer.concat([head, body]);
53
- }
54
-
55
- /**
56
- * @param {number} trackId
57
- * @returns {Buffer}
58
- */
59
- function trak(trackId) {
60
- const tkhdBody = Buffer.alloc(84);
61
- tkhdBody.writeUInt32BE(trackId, 12);
62
- const mdhdBody = Buffer.alloc(20);
63
- mdhdBody.writeUInt32BE(90_000, 12);
64
- return box("trak", Buffer.concat([
65
- box("tkhd", tkhdBody),
66
- box("mdia", box("mdhd", mdhdBody))
67
- ]));
68
- }
69
-
70
- /**
71
- * A header declaring `tracks` tracks.
72
- *
73
- * @param {number} tracks
74
- * @returns {Buffer}
75
- */
76
- function moovOf(tracks) {
77
- const mvhdBody = Buffer.alloc(100);
78
- mvhdBody.writeUInt32BE(MOVIE_TIMESCALE, 12);
79
- const traks = [];
80
- for (let id = 1; id <= tracks; id += 1) {
81
- traks.push(trak(id));
82
- }
83
- return box("moov", Buffer.concat([box("mvhd", mvhdBody), ...traks]));
84
- }
85
-
86
- /**
87
- * @param {number} trackId
88
- * @returns {Buffer}
89
- */
90
- function traf(trackId) {
91
- const tfhdBody = Buffer.alloc(8);
92
- tfhdBody.writeUInt32BE(trackId, 4);
93
- const tfdtBody = Buffer.alloc(12);
94
- tfdtBody.writeUInt8(1, 0);
95
- tfdtBody.writeBigUInt64BE(0n, 4);
96
- return box("traf", Buffer.concat([box("tfhd", tfhdBody), box("tfdt", tfdtBody)]));
97
- }
98
-
99
- /**
100
- * A piece shaped like the `segment` muxer's output.
101
- *
102
- * @param {{ declares: number, carries: number }} shape - How many tracks its own
103
- * header declares, and how many its fragments actually carry. The two differ
104
- * only in a piece a run closed short.
105
- * @returns {Buffer}
106
- */
107
- function selfContainedPiece({ declares, carries }) {
108
- const trafs = [];
109
- for (let id = 1; id <= carries; id += 1) {
110
- trafs.push(traf(id));
111
- }
112
- return Buffer.concat([
113
- box("ftyp", Buffer.alloc(16, 0)),
114
- moovOf(declares),
115
- box("moof", Buffer.concat(trafs)),
116
- box("mdat", Buffer.alloc(64, 0x5a)),
117
- box("mfra", Buffer.alloc(24, 0))
118
- ]);
119
- }
120
-
121
- /**
122
- * A manager with one session whose first two pieces are already on disk.
123
- *
124
- * @param {{ piece: Buffer, sessionHeader: Buffer }} params - The piece written
125
- * for every number, and what the session has cached as its header.
126
- * @returns {Promise<{ manager: object, dirPath: string }>}
127
- */
128
- async function managerHolding({ piece, sessionHeader }) {
129
- const { manager } = managerWithOwnStore();
130
- manager.segmentStore.useFormat(OUTPUT_KEY, fmp4Format);
131
- const dirPath = manager.segmentStore.directoryFor(OUTPUT_KEY);
132
- await writeFile(path.join(dirPath, "segment-00000.mp4"), piece);
133
- await writeFile(path.join(dirPath, "segment-00001.mp4"), piece);
134
-
135
- const session = {
136
- id: SESSION_ID,
137
- spec: outputSpec({ transcodeVideo: false }),
138
- outputKey: OUTPUT_KEY,
139
- dirPath,
140
- timeline: new Timeline({ boundaries: [0, 12.5, 25, 37.5, 50, 62.5], cutGrid: "uniform" }),
141
- state: "ready",
142
- file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" })
143
- .learn({ durationSeconds: 62.5 }),
144
- get inputFile() { return this.file; },
145
- get audioFile() { return this.file; },
146
- lastAloneSpeed: 2,
147
- startedAt: Date.now(),
148
- lastAccessedAt: Date.now(),
149
- runs: new Set(),
150
- lastError: "",
151
- segmentCount: 5,
152
- segmentFormat: fmp4Format,
153
- useSyntheticPlaylist: true,
154
- playlistText: "#EXTM3U\n",
155
- initBytes: sessionHeader,
156
- waitEpoch: 0
157
- };
158
- manager.outputs.set(SESSION_ID, session);
159
- // No plan runs here: this file is about the path that answers a request.
160
- manager.encodeRuns.planEncodersNow = () => {};
161
- manager.encodeRuns.planEncodersSoon = () => {};
162
- // A LIVE RUN, deliberately. Under the rule this replaced, a live run anywhere
163
- // was what turned a refusal into a permanent one — so both cases below are
164
- // asked with one running, which is the state the field failure happened in.
165
- startRunOn(session, { from: 0, usesExplicitCuts: true, speedX: 2 });
166
- return { manager, dirPath };
167
- }
168
-
169
- test("a piece that agrees with its own header is served, whatever the session's header says", async () => {
170
- // Two tracks declared, two carried: whole by its own account. The session's
171
- // cached header declares three — the state that cannot be told from a short
172
- // piece without asking the piece itself.
173
- const piece = selfContainedPiece({ declares: 2, carries: 2 });
174
- const { manager, dirPath } = await managerHolding({
175
- piece,
176
- sessionHeader: moovOf(3)
177
- });
178
-
179
- const answer = await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
180
-
181
- assert.equal(answer.kind, "file", "the piece is whole and must reach the viewer");
182
- await access(path.join(dirPath, "segment-00000.mp4"));
183
- });
184
-
185
- test("a piece that disagrees with its own header is removed, so it can be made again", async () => {
186
- // Two tracks declared, one carried: closed short by a run that was killed
187
- // mid-piece, which is what this check exists to catch.
188
- const piece = selfContainedPiece({ declares: 2, carries: 1 });
189
- const { manager, dirPath } = await managerHolding({
190
- piece,
191
- sessionHeader: moovOf(2)
192
- });
193
-
194
- const answer = await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
195
-
196
- assert.equal(answer.kind, "warming-up", "a short piece is not servable");
197
- await assert.rejects(
198
- () => access(path.join(dirPath, "segment-00000.mp4")),
199
- /ENOENT/,
200
- "and it is removed, or nothing will ever make it again"
201
- );
202
- });
203
-
204
- test("a live run elsewhere in the film does not make a short piece permanent", async () => {
205
- const piece = selfContainedPiece({ declares: 2, carries: 1 });
206
- const { manager, dirPath } = await managerHolding({
207
- piece,
208
- sessionHeader: moovOf(2)
209
- });
210
- // The field state exactly: the run in force is hundreds of segments ahead of
211
- // the piece being asked for.
212
- startRunOn(manager.outputs.get(SESSION_ID), { from: 192, usesExplicitCuts: true, speedX: 2 });
213
-
214
- await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
215
-
216
- await assert.rejects(
217
- () => access(path.join(dirPath, "segment-00000.mp4")),
218
- /ENOENT/,
219
- "a run a thousand segments away says nothing about these bytes"
220
- );
221
- });
1
+ /**
2
+ * @file A piece short of a track is judged against ITS OWN header.
3
+ *
4
+ * The question "does this piece carry every track" has two possible answers and
5
+ * they call for opposite actions, which is why asking it against the session's
6
+ * cached header alone was not enough: a piece that disagrees with that header
7
+ * may be short, or the header may be the odd one out. A self-contained piece
8
+ * carries a `moov` of its own, so it can be compared with itself, and that
9
+ * comparison tells the two apart without asking anything about encoders.
10
+ *
11
+ * WHAT THIS REPLACED. The action used to be chosen by "is any run of this
12
+ * session alive?" — a question about the SESSION, asked about a fact of the
13
+ * BYTES. A run anywhere in the film meant "still being written; waiting for
14
+ * it", and nothing re-judged or re-made the piece afterwards, so one
15
+ * misjudgement became permanent.
16
+ *
17
+ * Field 2026-09-14: `segment-00033.mp4` of a copied picture, already served
18
+ * whole to one viewer at 5 099 421 bytes, was refused to a second viewer for
19
+ * five minutes — four holds of sixty seconds each, then 503 — while the run in
20
+ * force was at #192 and beyond. That viewer's picture stood still 196.2 s and
21
+ * then a further 56.2 s.
22
+ *
23
+ * The run question is also unanswerable and does not need asking: a piece being
24
+ * written is called `making-<tag>-NNNNN.mp4` and takes its served name by a
25
+ * rename, which is atomic. A file under its served name is never being written.
26
+ */
27
+
28
+ import test from "node:test";
29
+ import assert from "node:assert/strict";
30
+ import { access, writeFile } from "node:fs/promises";
31
+ import path from "node:path";
32
+ import { SourceFile } from "../services/media/SourceFile.js";
33
+ import { Timeline } from "../services/encode/output/Timeline.js";
34
+ import { managerWithOwnStore } from "./helpers/manager.js";
35
+ import { startRunOn } from "./helpers/encode-run.js";
36
+ import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
37
+ import { outputSpec } from "./helpers/output-spec.js";
38
+
39
+ const SESSION_ID = "3333333344445555";
40
+ const OUTPUT_KEY = "own-header:fmt=fmp4:grid=kf@0:video-only:v=0/copy";
41
+ const MOVIE_TIMESCALE = 1000;
42
+
43
+ /**
44
+ * @param {string} type
45
+ * @param {Buffer} body
46
+ * @returns {Buffer}
47
+ */
48
+ function box(type, body) {
49
+ const head = Buffer.alloc(8);
50
+ head.writeUInt32BE(8 + body.length, 0);
51
+ head.write(type, 4, "latin1");
52
+ return Buffer.concat([head, body]);
53
+ }
54
+
55
+ /**
56
+ * @param {number} trackId
57
+ * @returns {Buffer}
58
+ */
59
+ function trak(trackId) {
60
+ const tkhdBody = Buffer.alloc(84);
61
+ tkhdBody.writeUInt32BE(trackId, 12);
62
+ const mdhdBody = Buffer.alloc(20);
63
+ mdhdBody.writeUInt32BE(90_000, 12);
64
+ return box("trak", Buffer.concat([
65
+ box("tkhd", tkhdBody),
66
+ box("mdia", box("mdhd", mdhdBody))
67
+ ]));
68
+ }
69
+
70
+ /**
71
+ * A header declaring `tracks` tracks.
72
+ *
73
+ * @param {number} tracks
74
+ * @returns {Buffer}
75
+ */
76
+ function moovOf(tracks) {
77
+ const mvhdBody = Buffer.alloc(100);
78
+ mvhdBody.writeUInt32BE(MOVIE_TIMESCALE, 12);
79
+ const traks = [];
80
+ for (let id = 1; id <= tracks; id += 1) {
81
+ traks.push(trak(id));
82
+ }
83
+ return box("moov", Buffer.concat([box("mvhd", mvhdBody), ...traks]));
84
+ }
85
+
86
+ /**
87
+ * @param {number} trackId
88
+ * @returns {Buffer}
89
+ */
90
+ function traf(trackId) {
91
+ const tfhdBody = Buffer.alloc(8);
92
+ tfhdBody.writeUInt32BE(trackId, 4);
93
+ const tfdtBody = Buffer.alloc(12);
94
+ tfdtBody.writeUInt8(1, 0);
95
+ tfdtBody.writeBigUInt64BE(0n, 4);
96
+ return box("traf", Buffer.concat([box("tfhd", tfhdBody), box("tfdt", tfdtBody)]));
97
+ }
98
+
99
+ /**
100
+ * A piece shaped like the `segment` muxer's output.
101
+ *
102
+ * @param {{ declares: number, carries: number }} shape - How many tracks its own
103
+ * header declares, and how many its fragments actually carry. The two differ
104
+ * only in a piece a run closed short.
105
+ * @returns {Buffer}
106
+ */
107
+ function selfContainedPiece({ declares, carries }) {
108
+ const trafs = [];
109
+ for (let id = 1; id <= carries; id += 1) {
110
+ trafs.push(traf(id));
111
+ }
112
+ return Buffer.concat([
113
+ box("ftyp", Buffer.alloc(16, 0)),
114
+ moovOf(declares),
115
+ box("moof", Buffer.concat(trafs)),
116
+ box("mdat", Buffer.alloc(64, 0x5a)),
117
+ box("mfra", Buffer.alloc(24, 0))
118
+ ]);
119
+ }
120
+
121
+ /**
122
+ * A manager with one session whose first two pieces are already on disk.
123
+ *
124
+ * @param {{ piece: Buffer, sessionHeader: Buffer }} params - The piece written
125
+ * for every number, and what the session has cached as its header.
126
+ * @returns {Promise<{ manager: object, dirPath: string }>}
127
+ */
128
+ async function managerHolding({ piece, sessionHeader }) {
129
+ const { manager } = managerWithOwnStore();
130
+ manager.segmentStore.useFormat(OUTPUT_KEY, fmp4Format);
131
+ const dirPath = manager.segmentStore.directoryFor(OUTPUT_KEY);
132
+ await writeFile(path.join(dirPath, "segment-00000.mp4"), piece);
133
+ await writeFile(path.join(dirPath, "segment-00001.mp4"), piece);
134
+
135
+ const session = {
136
+ id: SESSION_ID,
137
+ spec: outputSpec({ transcodeVideo: false }),
138
+ outputKey: OUTPUT_KEY,
139
+ dirPath,
140
+ timeline: new Timeline({ boundaries: [0, 12.5, 25, 37.5, 50, 62.5], cutGrid: "uniform" }),
141
+ state: "ready",
142
+ file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" })
143
+ .learn({ durationSeconds: 62.5 }),
144
+ get inputFile() { return this.file; },
145
+ get audioFile() { return this.file; },
146
+ lastAloneSpeed: 2,
147
+ startedAt: Date.now(),
148
+ lastAccessedAt: Date.now(),
149
+ runs: new Set(),
150
+ lastError: "",
151
+ segmentCount: 5,
152
+ segmentFormat: fmp4Format,
153
+ useSyntheticPlaylist: true,
154
+ playlistText: "#EXTM3U\n",
155
+ waitEpoch: 0
156
+ };
157
+ manager.outputs.set(SESSION_ID, session);
158
+ // The header this output serves is the store's, one owner for it.
159
+ manager.segmentStore.keepInit(OUTPUT_KEY, sessionHeader);
160
+ // No plan runs here: this file is about the path that answers a request.
161
+ manager.encodeRuns.planEncodersNow = () => {};
162
+ manager.encodeRuns.planEncodersSoon = () => {};
163
+ // A LIVE RUN, deliberately. Under the rule this replaced, a live run anywhere
164
+ // was what turned a refusal into a permanent one — so both cases below are
165
+ // asked with one running, which is the state the field failure happened in.
166
+ startRunOn(session, { from: 0, usesExplicitCuts: true, speedX: 2 });
167
+ return { manager, dirPath };
168
+ }
169
+
170
+ test("a piece that agrees with its own header is served, whatever the session's header says", async () => {
171
+ // Two tracks declared, two carried: whole by its own account. The session's
172
+ // cached header declares three — the state that cannot be told from a short
173
+ // piece without asking the piece itself.
174
+ const piece = selfContainedPiece({ declares: 2, carries: 2 });
175
+ const { manager, dirPath } = await managerHolding({
176
+ piece,
177
+ sessionHeader: moovOf(3)
178
+ });
179
+
180
+ const answer = await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
181
+
182
+ assert.equal(answer.kind, "file", "the piece is whole and must reach the viewer");
183
+ await access(path.join(dirPath, "segment-00000.mp4"));
184
+ });
185
+
186
+ test("a piece that disagrees with its own header is removed, so it can be made again", async () => {
187
+ // Two tracks declared, one carried: closed short by a run that was killed
188
+ // mid-piece, which is what this check exists to catch.
189
+ const piece = selfContainedPiece({ declares: 2, carries: 1 });
190
+ const { manager, dirPath } = await managerHolding({
191
+ piece,
192
+ sessionHeader: moovOf(2)
193
+ });
194
+
195
+ const answer = await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
196
+
197
+ assert.equal(answer.kind, "warming-up", "a short piece is not servable");
198
+ await assert.rejects(
199
+ () => access(path.join(dirPath, "segment-00000.mp4")),
200
+ /ENOENT/,
201
+ "and it is removed, or nothing will ever make it again"
202
+ );
203
+ });
204
+
205
+ test("a live run elsewhere in the film does not make a short piece permanent", async () => {
206
+ const piece = selfContainedPiece({ declares: 2, carries: 1 });
207
+ const { manager, dirPath } = await managerHolding({
208
+ piece,
209
+ sessionHeader: moovOf(2)
210
+ });
211
+ // The field state exactly: the run in force is hundreds of segments ahead of
212
+ // the piece being asked for.
213
+ startRunOn(manager.outputs.get(SESSION_ID), { from: 192, usesExplicitCuts: true, speedX: 2 });
214
+
215
+ await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
216
+
217
+ await assert.rejects(
218
+ () => access(path.join(dirPath, "segment-00000.mp4")),
219
+ /ENOENT/,
220
+ "a run a thousand segments away says nothing about these bytes"
221
+ );
222
+ });