@torrent-tv/proxy 2.75.0 → 2.76.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/CHANGELOG.md +1509 -1461
- package/CLAUDE.md +11 -1
- package/biome.json +182 -1
- package/docs/container-architecture.md +2 -1
- package/docs/encode-run-state.md +1 -1
- package/knip.json +14 -0
- package/package.json +1 -1
- package/routes/api/sources/warm/post.js +1 -1
- package/routes/api/transcode-sessions/post.js +185 -185
- package/routes/api/transcode-sessions/progress/get.js +5 -1
- package/routes/transcode/audio-file/get.js +11 -1
- package/routes/transcode/audio-warm/get.js +11 -1
- package/routes/transcode/session-file/get.js +1 -1
- package/routes/transcode/variant-file/get.js +10 -1
- package/scripts/render-run-graph.js +2 -2
- package/server.js +25 -0
- package/services/audio-inventory.js +206 -201
- package/services/container/AviContainer.js +1 -1
- package/services/container/Container.js +33 -1
- package/services/container/MatroskaContainer.js +1 -1
- package/services/container/Mp4Container.js +1 -1
- package/services/container/SubtitleFileContainer.js +0 -1
- package/services/controllers/SubtitleController.js +128 -128
- package/services/demand/index.js +7 -10
- package/services/download/registry.js +0 -14
- package/services/encode/CoverageMap.js +281 -0
- package/services/encode/EncodePlan.js +255 -0
- package/services/encode/EncodeRun.js +587 -0
- package/services/encode/Encoder.js +84 -0
- package/services/encode/NvencEncoder.js +45 -0
- package/services/encode/QsvEncoder.js +47 -0
- package/services/encode/SegmentDemand.js +0 -0
- package/services/encode/SegmentStore.js +529 -0
- package/services/encode/SoftwareEncoder.js +111 -0
- package/services/encode/V4l2m2mEncoder.js +53 -0
- package/services/encode/VaapiEncoder.js +53 -0
- package/services/encode/args.js +200 -0
- package/services/{encode-exit.js → encode/encode-exit.js} +17 -0
- package/services/encode/index.js +9 -0
- package/services/encode/run-command.js +647 -0
- package/services/hls-session-manager.js +11100 -10711
- package/services/hwaccel.js +1688 -1992
- package/services/orchestrators/EncodeOrchestrator.js +359 -0
- package/services/output/LiveOutputs.js +213 -0
- package/services/output/Output.js +94 -0
- package/services/output/OutputSpec.js +195 -0
- package/services/output/Timeline.js +220 -0
- package/services/output/index.js +1 -0
- package/services/output/ladder.js +26 -0
- package/services/playback-planner.js +806 -775
- package/services/produced-index.js +222 -300
- package/services/source/SourceFile.js +346 -0
- package/services/{sidecar-files.js → torrent/files.js} +107 -11
- package/services/torrent/naming.js +619 -0
- package/services/torrent-worker/client.js +10 -0
- package/services/torrent-worker/container-tracks.js +71 -43
- package/services/torrent-worker/pool-adapter.js +18 -0
- package/services/torrent-worker/protocol.js +7 -0
- package/services/torrent-worker/subtitle-cues.js +549 -549
- package/services/torrent-worker/worker.js +18 -0
- package/services/tracks/TextSubtitleTrack.js +287 -287
- package/services/tracks/index.js +15 -14
- package/services/viewer/Viewer.js +145 -0
- package/services/viewer/Viewers.js +124 -0
- package/test/auto-quality-step.test.js +508 -506
- package/test/behind-head-repair.test.js +17 -7
- package/test/coverage-map.test.js +153 -0
- package/test/cut-times-timeline.test.js +6 -5
- package/test/cuts-follow-published-grid.test.js +4 -4
- package/test/decode-cost.test.js +31 -12
- package/test/encode-exit.test.js +1 -1
- package/test/encode-orchestrator.test.js +196 -0
- package/test/encode-plan.test.js +245 -0
- package/test/encode-run-state.test.js +2 -2
- package/test/encode-run.test.js +168 -0
- package/test/encoder-kinds.test.js +122 -0
- package/test/held-request-width.test.js +9 -3
- package/test/helpers/encode-run.js +128 -0
- package/test/keyframe-index-accuracy.test.js +19 -12
- package/test/keyframes-belong-to-the-file.test.js +132 -0
- package/test/orchestrator-wired.test.js +164 -0
- package/test/output-shape.test.js +68 -0
- package/test/output-spec.test.js +157 -0
- package/test/produced-copy-choice.test.js +58 -92
- package/test/produced-index.test.js +142 -188
- package/test/quality-variants.test.js +1079 -1075
- package/test/run-graph-drift.test.js +1 -1
- package/test/run-intervals.test.js +329 -0
- package/test/run-position-follows-published-grid.test.js +4 -4
- package/test/seek-landing.test.js +8 -8
- package/test/seek-target-not-superseded.test.js +21 -9
- package/test/segment-demand.test.js +82 -0
- package/test/segment-serve-wiring.test.js +47 -52
- package/test/segment-store.test.js +187 -0
- package/test/segments-are-shared.test.js +175 -0
- package/test/sidecar-naming.test.js +142 -0
- package/test/source-file.test.js +133 -0
- package/test/stale-request-after-seek.test.js +18 -12
- package/test/subtitle-language.test.js +252 -252
- package/test/timeline.test.js +95 -0
- package/test/{sidecar-files.test.js → torrent-files.test.js} +44 -1
- package/test/torrent-naming.test.js +255 -0
- package/test/tracks-begin-together.test.js +44 -32
- package/test/two-viewers-one-picture.test.js +347 -0
- package/test/viewer-outputs.test.js +273 -0
- package/test/viewer.test.js +91 -0
- package/utils/perf.js +1 -63
- package/services/container/index.js +0 -6
- package/services/controllers/index.js +0 -2
- package/services/download/index.js +0 -8
- package/services/orchestrators/index.js +0 -2
- /package/services/{encode-run-state.js → encode/encode-run-state.js} +0 -0
- /package/services/{language-detect.js → tracks/language-detect.js} +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file One object per person watching, and one deletion to forget them.
|
|
3
|
+
*
|
|
4
|
+
* It was six maps hung on the session, each keyed by consumer id, and the
|
|
5
|
+
* forgetting was already wrong: releasing a consumer emptied none of them, so a
|
|
6
|
+
* viewer who had left went on counting as wanting their soundtrack until their
|
|
7
|
+
* head expired, and their entries stayed for the life of the session.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { SourceFile } from "../services/source/SourceFile.js";
|
|
13
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
14
|
+
import os from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { HlsSessionManager } from "../services/hls-session-manager.js";
|
|
17
|
+
import { Viewer, viewerOf, viewersOf } from "../services/viewer/Viewer.js";
|
|
18
|
+
|
|
19
|
+
const SESSION_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
|
|
20
|
+
|
|
21
|
+
test("a viewer nobody has heard from is not watching", () => {
|
|
22
|
+
const viewer = new Viewer("someone");
|
|
23
|
+
const now = 1_000_000;
|
|
24
|
+
|
|
25
|
+
assert.equal(viewer.isLive(now, 60_000), false, "no head at all: they have never asked");
|
|
26
|
+
viewer.head = { segment: 4, seconds: 16, at: now - 10_000 };
|
|
27
|
+
assert.equal(viewer.isLive(now, 60_000), true);
|
|
28
|
+
viewer.head = { segment: 4, seconds: 16, at: now - 120_000 };
|
|
29
|
+
assert.equal(viewer.isLive(now, 60_000), false, "the tab is gone and nothing released it");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("a stated position outranks the segment they last asked for", () => {
|
|
33
|
+
const viewer = new Viewer("someone");
|
|
34
|
+
assert.equal(viewer.positionSeconds(), null);
|
|
35
|
+
|
|
36
|
+
viewer.head = { segment: 10, seconds: 40, at: 1 };
|
|
37
|
+
assert.equal(viewer.positionSeconds(), 40, "where their player is reading");
|
|
38
|
+
|
|
39
|
+
viewer.head = { segment: 10, seconds: 40, at: 1, seeked: 900 };
|
|
40
|
+
assert.equal(viewer.positionSeconds(), 900, "a seek is the viewer saying where they are");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("asking for a viewer twice is the same viewer", () => {
|
|
44
|
+
const session = {};
|
|
45
|
+
const first = viewerOf(session, "one");
|
|
46
|
+
first.audio = { trackIndex: 3, transcode: false };
|
|
47
|
+
|
|
48
|
+
assert.equal(viewerOf(session, "one"), first);
|
|
49
|
+
assert.equal(viewerOf(session, "one").audio.trackIndex, 3);
|
|
50
|
+
assert.notEqual(viewerOf(session, "two"), first);
|
|
51
|
+
assert.equal(viewersOf(session).size, 2);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("releasing a consumer forgets everything that was true of them alone", async (t) => {
|
|
55
|
+
const dirPath = await mkdtemp(path.join(os.tmpdir(), "viewer-"));
|
|
56
|
+
t.after(async () => {
|
|
57
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
const manager = new HlsSessionManager({
|
|
60
|
+
enabled: true,
|
|
61
|
+
ffmpegBin: "ffmpeg",
|
|
62
|
+
localBindHost: "127.0.0.1",
|
|
63
|
+
localPort: 9090
|
|
64
|
+
});
|
|
65
|
+
const session = {
|
|
66
|
+
id: SESSION_ID,
|
|
67
|
+
dirPath,
|
|
68
|
+
state: "live",
|
|
69
|
+
file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" }),
|
|
70
|
+
// An ordinary session reads its own file, and its sound is inside it. The
|
|
71
|
+
// three differ only for a soundtrack shipped as a file of its own.
|
|
72
|
+
get inputFile() { return this.file; },
|
|
73
|
+
get audioFile() { return this.file; },
|
|
74
|
+
consumers: new Set(["staying", "leaving"]),
|
|
75
|
+
viewers: new Map(),
|
|
76
|
+
lastAccessedAt: Date.now()
|
|
77
|
+
};
|
|
78
|
+
manager.sessionsById.set(SESSION_ID, session);
|
|
79
|
+
|
|
80
|
+
const leaving = viewerOf(session, "leaving");
|
|
81
|
+
leaving.audio = { trackIndex: 2, transcode: true };
|
|
82
|
+
leaving.activeVariantId = "some-variant";
|
|
83
|
+
leaving.head = { segment: 40, seconds: 160, at: Date.now() };
|
|
84
|
+
viewerOf(session, "staying").audio = { trackIndex: 0, transcode: false };
|
|
85
|
+
|
|
86
|
+
await manager.releaseSessionConsumer(SESSION_ID, "leaving", "the tab was closed");
|
|
87
|
+
|
|
88
|
+
assert.equal(session.viewers.has("leaving"), false, "one deletion, not six");
|
|
89
|
+
assert.equal(session.viewers.has("staying"), true, "and it takes nobody else with it");
|
|
90
|
+
assert.equal(session.consumers.size, 1);
|
|
91
|
+
});
|
package/utils/perf.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* somewhere specific.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { monitorEventLoopDelay
|
|
24
|
+
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
25
25
|
|
|
26
26
|
const NANOSECONDS_PER_MILLISECOND = 1e6;
|
|
27
27
|
// Sampling interval for the loop-delay histogram. 20 ms is fine enough to catch
|
|
@@ -57,65 +57,3 @@ export function eventLoopDelay() {
|
|
|
57
57
|
export function resetEventLoopDelay() {
|
|
58
58
|
loopDelay.reset();
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Accumulates how long the parts of one operation took.
|
|
63
|
-
*
|
|
64
|
-
* Usage: `mark()` after each step; `summary()` renders `step=12.3ms` pairs in
|
|
65
|
-
* the order they were marked.
|
|
66
|
-
*/
|
|
67
|
-
export class OperationTimer {
|
|
68
|
-
#startedAt;
|
|
69
|
-
#lastMarkAt;
|
|
70
|
-
#marks;
|
|
71
|
-
|
|
72
|
-
constructor() {
|
|
73
|
-
this.#startedAt = performance.now();
|
|
74
|
-
this.#lastMarkAt = this.#startedAt;
|
|
75
|
-
this.#marks = [];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Record the time since the previous mark under `name`.
|
|
80
|
-
*
|
|
81
|
-
* @param {string} name
|
|
82
|
-
* @returns {number} Milliseconds since the previous mark.
|
|
83
|
-
*/
|
|
84
|
-
mark(name) {
|
|
85
|
-
const now = performance.now();
|
|
86
|
-
const elapsed = now - this.#lastMarkAt;
|
|
87
|
-
this.#lastMarkAt = now;
|
|
88
|
-
this.#marks.push([name, elapsed]);
|
|
89
|
-
return elapsed;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Add a figure measured elsewhere (a running total, a count) so it appears in
|
|
94
|
-
* the same line as the timings.
|
|
95
|
-
*
|
|
96
|
-
* @param {string} name
|
|
97
|
-
* @param {number} value
|
|
98
|
-
* @returns {void}
|
|
99
|
-
*/
|
|
100
|
-
add(name, value) {
|
|
101
|
-
this.#marks.push([name, value]);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Total elapsed time since construction, in milliseconds.
|
|
106
|
-
*
|
|
107
|
-
* @returns {number}
|
|
108
|
-
*/
|
|
109
|
-
totalMs() {
|
|
110
|
-
return performance.now() - this.#startedAt;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* All marks as `name=12.3ms` pairs, in order.
|
|
115
|
-
*
|
|
116
|
-
* @returns {string}
|
|
117
|
-
*/
|
|
118
|
-
summary() {
|
|
119
|
-
return this.#marks.map(([name, value]) => `${name}=${value.toFixed(1)}ms`).join(" ");
|
|
120
|
-
}
|
|
121
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { Container } from "./Container.js";
|
|
2
|
-
export { MatroskaContainer } from "./MatroskaContainer.js";
|
|
3
|
-
export { Mp4Container } from "./Mp4Container.js";
|
|
4
|
-
export { AviContainer } from "./AviContainer.js";
|
|
5
|
-
export { SubtitleFileContainer, SUBTITLE_FILE_EXTENSIONS } from "./SubtitleFileContainer.js";
|
|
6
|
-
export { ContainerFactory } from "./ContainerFactory.js";
|
|
File without changes
|
|
File without changes
|