@torrent-tv/proxy 2.78.0 → 2.79.0

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.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @file One place decides which encoders exist.
3
+ *
4
+ * Not a check of behaviour but of shape, and it is here because the shape is
5
+ * what failed. Three separate places used to decide where an encoder should
6
+ * work and whether it should go on living, and they disagreed on every pass:
7
+ * measured in the field on 2026-09-05, 684 starts and 660 stops in 482 seconds,
8
+ * of which 294 were one place killing what another had just decided to keep,
9
+ * while the viewer's own segment went unmade for 32.3 seconds.
10
+ *
11
+ * Every rule below is one that was broken then. A reader who needs to add a
12
+ * fourth place should read this file first and then not.
13
+ */
14
+
15
+ import test from "node:test";
16
+ import assert from "node:assert/strict";
17
+ import { readFileSync } from "node:fs";
18
+ import path from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
22
+
23
+ /**
24
+ * @param {string} relative
25
+ * @returns {string}
26
+ */
27
+ function source(relative) {
28
+ return readFileSync(path.join(HERE, "..", relative), "utf8");
29
+ }
30
+
31
+ /**
32
+ * Lines of code, without comments or blanks: a rule about what the code does
33
+ * must not be answered by what a comment says about it.
34
+ *
35
+ * @param {string} text
36
+ * @returns {string[]}
37
+ */
38
+ function statements(text) {
39
+ return text
40
+ .split("\n")
41
+ .map((line) => line.trim())
42
+ .filter((line) => line.length > 0 && !line.startsWith("//") && !line.startsWith("*") && !line.startsWith("/*"));
43
+ }
44
+
45
+ test("an encoder is stopped for scheduling reasons in exactly one place", () => {
46
+ // The orchestrator decides; nobody else may. What is left in the session
47
+ // manager is teardown — the session is going away and its encoders with it —
48
+ // which is not a decision about which encoders should exist.
49
+ const orchestrator = statements(source("services/orchestrators/EncodeOrchestrator.js"));
50
+ const stopsInOrchestrator = orchestrator.filter((line) => line.includes("run.stop("));
51
+ assert.equal(stopsInOrchestrator.length, 1, "the orchestrator stops runs in one place");
52
+
53
+ const manager = statements(source("services/hls-session-manager.js"));
54
+ const stopsInManager = manager.filter((line) => line.includes(".stop("));
55
+ assert.equal(
56
+ stopsInManager.length,
57
+ 2,
58
+ "the session manager stops runs only when a session is torn down: " +
59
+ stopsInManager.join(" / ")
60
+ );
61
+ });
62
+
63
+ test("nothing outside the encoding layer starts an encoder", () => {
64
+ // A run is built in one place. Two places building them is how a start came
65
+ // to kill what the plan had decided to keep — the killing lived in the
66
+ // building.
67
+ const manager = statements(source("services/hls-session-manager.js"));
68
+ const builds = manager.filter((line) => line.includes("new EncodeRun("));
69
+ assert.equal(builds.length, 1, "one place builds an encoder");
70
+ });
71
+
72
+ test("starting an encoder stops nothing", () => {
73
+ // The rule that broke it: the start path looked for a live run whose own
74
+ // start was not below the new one's and killed it. It is not enough that the
75
+ // line is gone — the words it was written with must not come back.
76
+ const manager = source("services/hls-session-manager.js");
77
+ assert.equal(
78
+ manager.includes("previousRun"),
79
+ false,
80
+ "there is no such thing as the previous run: a session holds several"
81
+ );
82
+ assert.equal(manager.includes("a new run is taking its place"), false);
83
+ });
84
+
85
+ test("a seek moves the viewer and nothing else", () => {
86
+ // It used to do eleven things and write the position into five places. What
87
+ // follows from a viewer moving is the map's business, and the orchestrators
88
+ // read the map.
89
+ const manager = source("services/hls-session-manager.js");
90
+ const seek = manager.slice(
91
+ manager.indexOf("requestSeek(sessionId, positionSeconds"),
92
+ manager.indexOf("requestSeek(sessionId, positionSeconds") + 2000
93
+ );
94
+ const body = seek.slice(0, seek.indexOf("\n }\n"));
95
+ assert.equal(body.includes("#startEncodeRun"), false, "a seek starts no encoder");
96
+ assert.equal(body.includes("setTimeout"), false, "and waits for nothing");
97
+ });
98
+
99
+ test("how far an encoder may work is answered once", () => {
100
+ // The plan computes the stretch and it reaches ffmpeg. A second computation
101
+ // somewhere else is what made the first one pointless: it was passed and then
102
+ // dropped by a parameter list that did not name it.
103
+ const orchestrator = source("services/orchestrators/EncodeOrchestrator.js");
104
+ assert.ok(orchestrator.includes("makeRun({ address, from, to })"), "the stretch is handed over");
105
+ });
@@ -8,7 +8,7 @@
8
8
 
9
9
  import test from "node:test";
10
10
  import assert from "node:assert/strict";
11
- import { mapForViewer, mergeMaps, inWorkingOrder } from "../services/encode/DemandMap.js";
11
+ import { mapForViewer, mergeMaps, inWorkingOrder } from "../services/priority/PriorityMap.js";
12
12
 
13
13
  test("a viewer's own position is the most urgent thing there is", () => {
14
14
  const map = mapForViewer({
@@ -25,23 +25,60 @@ test("a viewer's own position is the most urgent thing there is", () => {
25
25
  }
26
26
  });
27
27
 
28
- test("a machine that cannot keep up must have more ready before the viewer sets off", () => {
29
- // 900s of film in front. At 0.25x the encoder loses three seconds of film per
30
- // second played, so 675s must exist first, or the viewer stalls partway.
31
- const slow = mapForViewer({ atSeconds: 100, durationSeconds: 1000, allowanceSeconds: 0, encodeSpeedX: 0.25 });
32
- const fast = mapForViewer({ atSeconds: 100, durationSeconds: 1000, allowanceSeconds: 0, encodeSpeedX: 2 });
28
+ test("a machine that cannot keep up needs more encoders, not a longer first zone", () => {
29
+ // An encoder starting where the viewer stands stays ahead of them for
30
+ // `held x s / (1 - s)`, so the SLOWER the machine the shorter that is — and
31
+ // the film is held by more encoders rather than by one working further.
32
+ const slow = mapForViewer({
33
+ atSeconds: 0,
34
+ durationSeconds: 1000,
35
+ allowanceSeconds: 10,
36
+ encodeSpeedX: 0.25
37
+ });
38
+ const quicker = mapForViewer({
39
+ atSeconds: 0,
40
+ durationSeconds: 1000,
41
+ allowanceSeconds: 10,
42
+ encodeSpeedX: 0.5
43
+ });
33
44
 
34
- assert.equal(slow[0].to, 775, "100 + 900 x 0.75");
35
45
  assert.ok(
36
- slow[0].to - slow[0].from > fast[0].to - fast[0].from,
37
- "the slower the machine, the more of the film must be made in advance"
46
+ slow[0].to - slow[0].from < quicker[0].to - quicker[0].from,
47
+ "the slower machine holds the viewer for less"
38
48
  );
49
+ assert.ok(slow.length > quicker.length, "so more encoders are needed to hold the film");
50
+ });
51
+
52
+ test("the first zone is exactly what one encoder can hold", () => {
53
+ // `held x s / (1 - s)`: ten seconds held at half speed is ten seconds held,
54
+ // and the encoder is caught exactly there. A longer first zone would stall
55
+ // the viewer inside it; a shorter one would start the next encoder nearer,
56
+ // where its own bound is tighter.
57
+ const map = mapForViewer({
58
+ atSeconds: 0,
59
+ durationSeconds: 100,
60
+ allowanceSeconds: 10,
61
+ encodeSpeedX: 0.5
62
+ });
63
+
64
+ assert.equal(map[0].to, 10);
65
+ assert.equal(map[1].to, 30, "the next holds three times as long, because they arrive later");
66
+ assert.equal(map[2].to, 70);
39
67
  });
40
68
 
41
- test("the allowance is added to the shortfall, not chosen instead of it", () => {
42
- const map = mapForViewer({ atSeconds: 0, durationSeconds: 100, allowanceSeconds: 10, encodeSpeedX: 0.5 });
69
+ test("a viewer holding nothing is told the whole of it is urgent", () => {
70
+ // With nothing held, no partition holds them: the first encoder stays ahead
71
+ // for zero seconds. Slicing the film into equal slivers would pretend
72
+ // otherwise.
73
+ const map = mapForViewer({
74
+ atSeconds: 0,
75
+ durationSeconds: 1000,
76
+ allowanceSeconds: 0,
77
+ encodeSpeedX: 0.25
78
+ });
43
79
 
44
- assert.equal(map[0].to, 60, "50s of shortfall plus 10s of measured allowance");
80
+ assert.equal(map.length, 1);
81
+ assert.equal(map[0].to, 1000);
45
82
  });
46
83
 
47
84
  test("the rest of the track is still wanted, and wanted last", () => {