@torrent-tv/proxy 2.80.9 → 2.80.10

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,153 +1,176 @@
1
- /**
2
- * @file Picture and sound must begin their runs at the same real instant.
3
- *
4
- * The two branches are asked for the same time and land in different places: a
5
- * copied picture may only begin at a real keyframe and may not begin before the
6
- * time asked for, so it moves FORWARD to the next one; a soundtrack has no
7
- * keyframes and begins exactly where asked. Measured 2026-08-17, the difference
8
- * was 0.58-2.96 s on one file, and the viewer got sound with no new picture for
9
- * as long as it lasted.
10
- *
11
- * The picture's true start is measured from the piece it produced. This pins
12
- * that the measurement is carried to the other members of the family.
13
- */
14
-
15
- import assert from "node:assert/strict";
16
- import { SourceFile } from "../services/source/SourceFile.js";
17
- import { startRunOn } from "./helpers/encode-run.js";
18
- import { Timeline } from "../services/output/Timeline.js";
19
- import test from "node:test";
20
-
21
- import { HlsSessionManager } from "../services/hls-session-manager.js";
22
- import { ENCODE_RUN_STATE, INITIAL_RUN_STATE } from "../services/encode/encode-run-state.js";
23
-
24
- const BOUNDARIES = [0, 4, 8, 12, 16, 20];
25
-
26
- /**
27
- * A film's family: the picture, and a soundtrack rendition of it. Both runs
28
- * begin at boundary #2, which the container's table puts at 8 s.
29
- *
30
- * @returns {{ manager: HlsSessionManager, picture: object, sound: object }}
31
- */
32
- function familyAtBoundaryTwo() {
33
- // ONE table for the film. Where it is cut is a fact about the FILE, so the
34
- // picture and its soundtrack hold the same array rather than a copy each.
35
- const boundaries = [...BOUNDARIES];
36
- const manager = new HlsSessionManager({
37
- enabled: true,
38
- ffmpegBin: "ffmpeg",
39
- localBindHost: "127.0.0.1",
40
- localPort: 9090
41
- });
42
- const picture = {
43
- id: "picture",
44
- state: "ready",
45
- timeline: new Timeline({ boundaries: boundaries, cutGrid: "uniform" }),
46
- file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "film.mkv" }),
47
- runs: new Set(),
48
- pendingRun: null,
49
- indexCheck: null
50
- };
51
- // The soundtrack of that picture: the same file, published on its own. Found
52
- // by what it is, since no list of ids names it any more.
53
- const sound = {
54
- id: "sound",
55
- state: "ready",
56
- audioOnly: true,
57
- baseSessionId: "picture",
58
- timeline: new Timeline({ boundaries: boundaries, cutGrid: "uniform" }),
59
- file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "film.mkv" }),
60
- runs: new Set(),
61
- pendingRun: null,
62
- indexCheck: null
63
- };
64
- startRunOn(picture, { from: 2 });
65
- startRunOn(sound, { from: 2 });
66
- manager.sessionsById.set("picture", picture);
67
- manager.sessionsById.set("sound", sound);
68
-
69
- return { manager, picture, sound };
70
- }
71
-
72
- test("a soundtrack follows the picture to the instant the picture really began", () => {
73
- const { manager, picture, sound } = familyAtBoundaryTwo();
74
- const runBefore = [...sound.runs][0];
75
-
76
- manager.correctBoundaryFromSegment(picture, 2, 10.5);
77
-
78
- assert.deepEqual(
79
- picture.timeline.boundaries,
80
- [0, 4, 10.5, 12, 16, 20],
81
- "the family's table must hold what the file itself said"
82
- );
83
- assert.equal(
84
- sound.timeline.boundaries[2],
85
- 10.5,
86
- "and every member's table with it — one film, one table, nothing to keep in step"
87
- );
88
- // A NEW run, not a seek. A seek decides by index, finds the soundtrack
89
- // already begins at #2 and answers "already within the running encode"
90
- // true about the index, false about the instant. The first version of this
91
- // fix did exactly that and moved nothing.
92
- //
93
- // The attempt is the evidence because it is what a run start claims before
94
- // it awaits anything: the assertion then holds without the test needing a
95
- // filesystem, a process, or a guess about how many ticks to wait for one.
96
- assert.ok(
97
- sound.pendingRun,
98
- "the soundtrack's run must be started again, at the corrected time"
99
- );
100
- assert.equal(sound.pendingRun.startIndex, 2);
101
- assert.equal([...sound.runs][0], runBefore, "and the run in force is only replaced once one is built");
102
- });
103
-
104
- test("a correction the table already holds moves nobody", () => {
105
- const { manager, picture, sound } = familyAtBoundaryTwo();
106
- const before = [...sound.timeline.boundaries];
107
- // Within the tolerance: the reading agrees with the table, so there is
108
- // nothing to correct and nothing to move. This is what makes the repositioning
109
- // converge instead of repeating on every produced segment.
110
- manager.correctBoundaryFromSegment(picture, 2, 8.1);
111
- assert.deepEqual(sound.timeline.boundaries, before);
112
- });
113
-
114
- test("a member that is not running is left alone", () => {
115
- const { manager, picture, sound } = familyAtBoundaryTwo();
116
- // A rung the viewer switched away from has no process. Moving it would start
117
- // an encoder for nobody — the failure that put three ffmpeg runs on one file.
118
- const soundRun = [...sound.runs][0];
119
- soundRun.stop("the viewer switched away");
120
- // A stopped run is no longer live, so nothing of this session begins at #2
121
- // any more which is what "left alone" means here.
122
- manager.correctBoundaryFromSegment(picture, 2, 10.5);
123
- assert.equal(sound.pendingRun, null, "a stopped member is not started again for nobody");
124
- assert.equal(soundRun.from, 2, "a stopped member keeps its place and its silence");
125
- assert.equal(soundRun.state, ENCODE_RUN_STATE.STOPPED);
126
- assert.notEqual(INITIAL_RUN_STATE, ENCODE_RUN_STATE.STOPPED);
127
- });
128
-
129
- test("a soundtrack does not move the grid the picture is cut on", () => {
130
- const { manager, picture, sound } = familyAtBoundaryTwo();
131
- const pictureBefore = [...picture.timeline.boundaries];
132
- const soundBefore = [...sound.timeline.boundaries];
133
-
134
- // The sound reports where IT began, which is where it was asked to begin, to
135
- // within one audio frame. The picture's own boundary is a keyframe of the
136
- // file and can be seconds away from that — both readings correct, about
137
- // different things.
138
- //
139
- // Field 2026-08-20: boundary #521 of one film was corrected 2086.084
140
- // 2084.082 by the picture and 2084.082 2086.033 by the sound 1.6 s later,
141
- // 1.951 s apart, each overwriting the other for as long as the film ran. The
142
- // table never converged, so the guard that stops a correction the table
143
- // already holds never fired.
144
- manager.correctBoundaryFromSegment(sound, 2, 10.5);
145
-
146
- assert.deepEqual(
147
- picture.timeline.boundaries,
148
- pictureBefore,
149
- "the grid is the picture's cut list and a soundtrack may not move it"
150
- );
151
- assert.deepEqual(sound.timeline.boundaries, soundBefore);
152
- assert.equal(picture.pendingRun, null, "and nothing is restarted on a soundtrack's say-so");
153
- });
1
+ /**
2
+ * @file Picture and sound must begin their runs at the same real instant.
3
+ *
4
+ * The two branches are asked for the same time and land in different places: a
5
+ * copied picture may only begin at a real keyframe and may not begin before the
6
+ * time asked for, so it moves FORWARD to the next one; a soundtrack has no
7
+ * keyframes and begins exactly where asked. Measured 2026-08-17, the difference
8
+ * was 0.58-2.96 s on one file, and the viewer got sound with no new picture for
9
+ * as long as it lasted.
10
+ *
11
+ * The picture's true start is measured from the piece it produced. This pins
12
+ * that the measurement is carried to the other members of the family.
13
+ */
14
+
15
+ import assert from "node:assert/strict";
16
+ import { SourceFile } from "../services/source/SourceFile.js";
17
+ import { startRunOn } from "./helpers/encode-run.js";
18
+ import { Timeline } from "../services/output/Timeline.js";
19
+ import test from "node:test";
20
+
21
+ import { HlsSessionManager } from "../services/hls-session-manager.js";
22
+ import { fmp4Format } from "../services/segment-formats/fmp4.js";
23
+ import { ENCODE_RUN_STATE, INITIAL_RUN_STATE } from "../services/encode/encode-run-state.js";
24
+
25
+ const BOUNDARIES = [0, 4, 8, 12, 16, 20];
26
+
27
+ /**
28
+ * A film's family: the picture, and a soundtrack rendition of it. Both runs
29
+ * begin at boundary #2, which the container's table puts at 8 s.
30
+ *
31
+ * @returns {{ manager: HlsSessionManager, picture: object, sound: object }}
32
+ */
33
+ function familyAtBoundaryTwo() {
34
+ // ONE table for the film. Where it is cut is a fact about the FILE, so the
35
+ // picture and its soundtrack hold the same array rather than a copy each.
36
+ const boundaries = [...BOUNDARIES];
37
+ const manager = new HlsSessionManager({
38
+ enabled: true,
39
+ ffmpegBin: "ffmpeg",
40
+ localBindHost: "127.0.0.1",
41
+ localPort: 9090
42
+ });
43
+ const picture = {
44
+ id: "picture",
45
+ state: "ready",
46
+ timeline: new Timeline({ boundaries: boundaries, cutGrid: "uniform" }),
47
+ file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "film.mkv" }),
48
+ // An ordinary session reads its own file, and its sound is inside it. The
49
+ // three differ only for a soundtrack shipped as a file of its own — and the
50
+ // command is built from all three, so a fixture that states only the first
51
+ // describes a session the product cannot make.
52
+ get inputFile() { return this.file; },
53
+ get audioFile() { return this.file; },
54
+ // How its pieces are packaged. The command asks the format where to cut and
55
+ // what to name the files, so a session without one is not a session.
56
+ segmentFormat: fmp4Format,
57
+ // Where its encoder has got to, which a run writes into as it works.
58
+ progress: { processedSeconds: 0 },
59
+ runs: new Set(),
60
+ pendingRun: null,
61
+ indexCheck: null
62
+ };
63
+ // The soundtrack of that picture: the same file, published on its own. Found
64
+ // by what it is, since no list of ids names it any more.
65
+ const sound = {
66
+ id: "sound",
67
+ state: "ready",
68
+ audioOnly: true,
69
+ baseSessionId: "picture",
70
+ timeline: new Timeline({ boundaries: boundaries, cutGrid: "uniform" }),
71
+ file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "film.mkv" }),
72
+ // An ordinary session reads its own file, and its sound is inside it. The
73
+ // three differ only for a soundtrack shipped as a file of its own — and the
74
+ // command is built from all three, so a fixture that states only the first
75
+ // describes a session the product cannot make.
76
+ get inputFile() { return this.file; },
77
+ get audioFile() { return this.file; },
78
+ // How its pieces are packaged. The command asks the format where to cut and
79
+ // what to name the files, so a session without one is not a session.
80
+ segmentFormat: fmp4Format,
81
+ // Where its encoder has got to, which a run writes into as it works.
82
+ progress: { processedSeconds: 0 },
83
+ runs: new Set(),
84
+ pendingRun: null,
85
+ indexCheck: null
86
+ };
87
+ startRunOn(picture, { from: 2 });
88
+ startRunOn(sound, { from: 2 });
89
+ manager.sessionsById.set("picture", picture);
90
+ manager.sessionsById.set("sound", sound);
91
+
92
+ return { manager, picture, sound };
93
+ }
94
+
95
+ test("a soundtrack follows the picture to the instant the picture really began", () => {
96
+ const { manager, picture, sound } = familyAtBoundaryTwo();
97
+ const runBefore = [...sound.runs][0];
98
+
99
+ manager.correctBoundaryFromSegment(picture, 2, 10.5);
100
+
101
+ assert.deepEqual(
102
+ picture.timeline.boundaries,
103
+ [0, 4, 10.5, 12, 16, 20],
104
+ "the family's table must hold what the file itself said"
105
+ );
106
+ assert.equal(
107
+ sound.timeline.boundaries[2],
108
+ 10.5,
109
+ "and every member's table with it one film, one table, nothing to keep in step"
110
+ );
111
+ // A NEW run, not a seek. A seek decides by index, finds the soundtrack
112
+ // already begins at #2 and answers "already within the running encode" —
113
+ // true about the index, false about the instant. The first version of this
114
+ // fix did exactly that and moved nothing.
115
+ //
116
+ // The attempt is the evidence because it is what a run start claims before
117
+ // it awaits anything: the assertion then holds without the test needing a
118
+ // filesystem, a process, or a guess about how many ticks to wait for one.
119
+ assert.ok(
120
+ sound.pendingRun,
121
+ "the soundtrack's run must be started again, at the corrected time"
122
+ );
123
+ assert.equal(sound.pendingRun.startIndex, 2);
124
+ assert.equal([...sound.runs][0], runBefore, "and the run in force is only replaced once one is built");
125
+ });
126
+
127
+ test("a correction the table already holds moves nobody", () => {
128
+ const { manager, picture, sound } = familyAtBoundaryTwo();
129
+ const before = [...sound.timeline.boundaries];
130
+ // Within the tolerance: the reading agrees with the table, so there is
131
+ // nothing to correct and nothing to move. This is what makes the repositioning
132
+ // converge instead of repeating on every produced segment.
133
+ manager.correctBoundaryFromSegment(picture, 2, 8.1);
134
+ assert.deepEqual(sound.timeline.boundaries, before);
135
+ });
136
+
137
+ test("a member that is not running is left alone", () => {
138
+ const { manager, picture, sound } = familyAtBoundaryTwo();
139
+ // A rung the viewer switched away from has no process. Moving it would start
140
+ // an encoder for nobody — the failure that put three ffmpeg runs on one file.
141
+ const soundRun = [...sound.runs][0];
142
+ soundRun.stop("the viewer switched away");
143
+ // A stopped run is no longer live, so nothing of this session begins at #2
144
+ // any more — which is what "left alone" means here.
145
+ manager.correctBoundaryFromSegment(picture, 2, 10.5);
146
+ assert.equal(sound.pendingRun, null, "a stopped member is not started again for nobody");
147
+ assert.equal(soundRun.from, 2, "a stopped member keeps its place and its silence");
148
+ assert.equal(soundRun.state, ENCODE_RUN_STATE.STOPPED);
149
+ assert.notEqual(INITIAL_RUN_STATE, ENCODE_RUN_STATE.STOPPED);
150
+ });
151
+
152
+ test("a soundtrack does not move the grid the picture is cut on", () => {
153
+ const { manager, picture, sound } = familyAtBoundaryTwo();
154
+ const pictureBefore = [...picture.timeline.boundaries];
155
+ const soundBefore = [...sound.timeline.boundaries];
156
+
157
+ // The sound reports where IT began, which is where it was asked to begin, to
158
+ // within one audio frame. The picture's own boundary is a keyframe of the
159
+ // file and can be seconds away from that — both readings correct, about
160
+ // different things.
161
+ //
162
+ // Field 2026-08-20: boundary #521 of one film was corrected 2086.084 →
163
+ // 2084.082 by the picture and 2084.082 → 2086.033 by the sound 1.6 s later,
164
+ // 1.951 s apart, each overwriting the other for as long as the film ran. The
165
+ // table never converged, so the guard that stops a correction the table
166
+ // already holds never fired.
167
+ manager.correctBoundaryFromSegment(sound, 2, 10.5);
168
+
169
+ assert.deepEqual(
170
+ picture.timeline.boundaries,
171
+ pictureBefore,
172
+ "the grid is the picture's cut list and a soundtrack may not move it"
173
+ );
174
+ assert.deepEqual(sound.timeline.boundaries, soundBefore);
175
+ assert.equal(picture.pendingRun, null, "and nothing is restarted on a soundtrack's say-so");
176
+ });