@torrent-tv/proxy 2.89.16 → 2.89.18

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 (299) hide show
  1. package/README.md +14 -3
  2. package/package.json +10 -2
  3. package/routes/api/client-logs/post.js +9 -2
  4. package/server.js +0 -11
  5. package/.claude/commands/opsx/apply.md +0 -155
  6. package/.claude/commands/opsx/archive.md +0 -160
  7. package/.claude/commands/opsx/explore.md +0 -174
  8. package/.claude/commands/opsx/propose.md +0 -109
  9. package/.claude/commands/opsx/sync.md +0 -143
  10. package/.claude/skills/openspec-apply-change/SKILL.md +0 -159
  11. package/.claude/skills/openspec-archive-change/SKILL.md +0 -117
  12. package/.claude/skills/openspec-explore/SKILL.md +0 -289
  13. package/.claude/skills/openspec-propose/SKILL.md +0 -113
  14. package/.claude/skills/openspec-sync-specs/SKILL.md +0 -147
  15. package/.dockerignore +0 -5
  16. package/AGENTS.md +0 -363
  17. package/CHANGELOG.md +0 -2373
  18. package/Dockerfile +0 -30
  19. package/biome.json +0 -412
  20. package/corpus.tmp.json +0 -1
  21. package/docs/browser-proxy-contract.md +0 -155
  22. package/docs/container-architecture.md +0 -273
  23. package/docs/download-architecture.md +0 -261
  24. package/docs/encode-architecture.md +0 -765
  25. package/docs/encode-run-state.md +0 -96
  26. package/docs/logs.md +0 -138
  27. package/docs/playback-start-forecast.md +0 -113
  28. package/docs/socks5-egress-research.md +0 -114
  29. package/docs/storage-architecture.md +0 -273
  30. package/knip.json +0 -14
  31. package/main2.cpuprofile +0 -1
  32. package/openspec/changes/adaptive-bitrate/.openspec.yaml +0 -0
  33. package/openspec/changes/adaptive-bitrate/design.md +0 -92
  34. package/openspec/changes/adaptive-bitrate/proposal.md +0 -56
  35. package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +0 -51
  36. package/openspec/changes/adaptive-bitrate/tasks.md +0 -40
  37. package/openspec/changes/binary-distribution/.openspec.yaml +0 -2
  38. package/openspec/changes/binary-distribution/proposal.md +0 -53
  39. package/openspec/changes/chunked-request-bodies/design.md +0 -81
  40. package/openspec/changes/chunked-request-bodies/proposal.md +0 -53
  41. package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +0 -30
  42. package/openspec/changes/chunked-request-bodies/tasks.md +0 -40
  43. package/openspec/changes/cold-start/design.md +0 -98
  44. package/openspec/changes/cold-start/proposal.md +0 -62
  45. package/openspec/changes/cold-start/specs/cold-start/spec.md +0 -47
  46. package/openspec/changes/cold-start/tasks.md +0 -48
  47. package/openspec/changes/disk-cap/.openspec.yaml +0 -2
  48. package/openspec/changes/disk-cap/proposal.md +0 -37
  49. package/openspec/changes/disk-cap/specs/disk-cap/spec.md +0 -25
  50. package/openspec/changes/disk-cap/tasks.md +0 -19
  51. package/openspec/changes/proxy-observability/.openspec.yaml +0 -2
  52. package/openspec/changes/proxy-observability/design.md +0 -38
  53. package/openspec/changes/proxy-observability/proposal.md +0 -49
  54. package/openspec/changes/proxy-observability/specs/observability/spec.md +0 -33
  55. package/openspec/changes/proxy-observability/tasks.md +0 -21
  56. package/openspec/changes/seek-debounce/.openspec.yaml +0 -0
  57. package/openspec/changes/seek-debounce/design.md +0 -118
  58. package/openspec/changes/seek-debounce/proposal.md +0 -59
  59. package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +0 -41
  60. package/openspec/changes/seek-debounce/tasks.md +0 -33
  61. package/openspec/changes/subtitle-language/.openspec.yaml +0 -2
  62. package/openspec/changes/subtitle-language/proposal.md +0 -50
  63. package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +0 -34
  64. package/openspec/changes/subtitle-language/tasks.md +0 -26
  65. package/openspec/changes/track-selection/.openspec.yaml +0 -2
  66. package/openspec/changes/track-selection/design.md +0 -47
  67. package/openspec/changes/track-selection/proposal.md +0 -45
  68. package/openspec/changes/track-selection/specs/track-selection/spec.md +0 -41
  69. package/openspec/changes/track-selection/tasks.md +0 -20
  70. package/openspec/changes/transcode-quality/.openspec.yaml +0 -2
  71. package/openspec/changes/transcode-quality/proposal.md +0 -51
  72. package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +0 -139
  73. package/openspec/changes/transcode-quality/tasks.md +0 -63
  74. package/openspec/config.yaml +0 -38
  75. package/public/.well-known/appspecific/com.chrome.devtools.json +0 -1
  76. package/research/delivery-wedge-stand-ladder-2026-09-13.md +0 -329
  77. package/research/double-spawn-2026-09-10.md +0 -171
  78. package/research/handover-2026-09-13.md +0 -209
  79. package/research/handover-reader-claims-removal-2026-09-12.md +0 -166
  80. package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +0 -175
  81. package/research/preset-benchmark-size-scaling-2026-08-22.md +0 -55
  82. package/research/priority-map-is-the-truth-2026-09-12.md +0 -225
  83. package/research/valgrind-replica-2026-08-27/logs.tar.gz +0 -0
  84. package/scripts/build-audio-samples.mjs +0 -31
  85. package/scripts/render-run-graph.js +0 -173
  86. package/test/asset-paths.test.js +0 -28
  87. package/test/audio-buffer-continuity.test.js +0 -77
  88. package/test/audio-calibration.test.js +0 -23
  89. package/test/audio-inventory.test.js +0 -212
  90. package/test/audio-track-bitrate.test.js +0 -40
  91. package/test/audio-warm-recorded.test.js +0 -63
  92. package/test/auto-quality-step.test.js +0 -762
  93. package/test/available-share.test.js +0 -76
  94. package/test/background-fill.test.js +0 -190
  95. package/test/client-log-file.test.js +0 -126
  96. package/test/coalesce.test.js +0 -39
  97. package/test/completed-files.test.js +0 -115
  98. package/test/concurrent-cost.test.js +0 -148
  99. package/test/container-media-info.test.js +0 -228
  100. package/test/contention.test.js +0 -76
  101. package/test/core-dumps.test.js +0 -57
  102. package/test/coverage-follows-the-disk.test.js +0 -233
  103. package/test/coverage-map.test.js +0 -195
  104. package/test/cue-push-carries-what-the-walk-found.test.js +0 -58
  105. package/test/cut-times-timeline.test.js +0 -65
  106. package/test/cuts-follow-published-grid.test.js +0 -109
  107. package/test/data-channel-frame.test.js +0 -63
  108. package/test/data-channel-viewer-lifecycle.test.js +0 -193
  109. package/test/dead-torrent-handle.test.js +0 -52
  110. package/test/declared-rates.test.js +0 -116
  111. package/test/declared-tracks.test.js +0 -43
  112. package/test/decode-cost-fit.test.js +0 -202
  113. package/test/decode-cost.test.js +0 -475
  114. package/test/delivery-probe-field.test.js +0 -173
  115. package/test/delivery-probe.test.js +0 -382
  116. package/test/demand-register.test.js +0 -206
  117. package/test/dht-bootstrap.test.js +0 -72
  118. package/test/drain-threat.test.js +0 -65
  119. package/test/duplicate-add.test.js +0 -48
  120. package/test/encode-admission.test.js +0 -170
  121. package/test/encode-cost.test.js +0 -275
  122. package/test/encode-exit.test.js +0 -111
  123. package/test/encode-orchestrator.test.js +0 -426
  124. package/test/encode-plan-viewers.test.js +0 -725
  125. package/test/encode-plan.test.js +0 -545
  126. package/test/encode-run-state.test.js +0 -314
  127. package/test/encode-run.test.js +0 -248
  128. package/test/encode-slope.test.js +0 -46
  129. package/test/encoded-output.test.js +0 -39
  130. package/test/encoder-kinds.test.js +0 -122
  131. package/test/encoder-readings.test.js +0 -68
  132. package/test/episode-naming.test.js +0 -131
  133. package/test/every-claimant-is-told-its-share.test.js +0 -152
  134. package/test/failed-encoder-still-serves-what-exists.test.js +0 -354
  135. package/test/failed-start-breaker.test.js +0 -202
  136. package/test/fastest-wires.test.js +0 -143
  137. package/test/ffmpeg-stream-counts.test.js +0 -101
  138. package/test/file-edges.test.js +0 -191
  139. package/test/files-in-use.test.js +0 -34
  140. package/test/fixtures/audio-frame-boundary.json +0 -1
  141. package/test/fixtures/refused-audio-20261002.json +0 -10
  142. package/test/fmp4-presentation-clock.test.js +0 -148
  143. package/test/fmp4-self-contained.test.js +0 -80
  144. package/test/fmp4-timestamps.test.js +0 -140
  145. package/test/fmp4-track-completeness.test.js +0 -41
  146. package/test/format-keyed-outputs.test.js +0 -78
  147. package/test/health-metrics.test.js +0 -36
  148. package/test/held-read-ownership.test.js +0 -77
  149. package/test/held-request-width.test.js +0 -157
  150. package/test/helpers/encode-run.js +0 -248
  151. package/test/helpers/held-file.js +0 -77
  152. package/test/helpers/manager.js +0 -62
  153. package/test/helpers/matroska-file.js +0 -296
  154. package/test/helpers/output-spec.js +0 -63
  155. package/test/host-load.test.js +0 -68
  156. package/test/index-accuracy-is-about-the-file.test.js +0 -128
  157. package/test/init-compat.test.js +0 -219
  158. package/test/input-lost-quiets-the-plan.test.js +0 -261
  159. package/test/input-unavailable.test.js +0 -48
  160. package/test/installation-id.test.js +0 -39
  161. package/test/keeping-period.test.js +0 -83
  162. package/test/keyframe-index-accuracy.test.js +0 -188
  163. package/test/keyframe-table-budget.test.js +0 -123
  164. package/test/keyframes-belong-to-the-file.test.js +0 -249
  165. package/test/learned-median.test.js +0 -37
  166. package/test/letting-go-asks-who-is-watching.test.js +0 -191
  167. package/test/limit-row-by-area.test.js +0 -60
  168. package/test/link-budget.test.js +0 -111
  169. package/test/link-probe.test.js +0 -56
  170. package/test/local-encoding-data.test.js +0 -273
  171. package/test/logger-repeats.test.js +0 -134
  172. package/test/look-ahead-coverage.test.js +0 -56
  173. package/test/manager-surface.test.js +0 -291
  174. package/test/matroska-blocks.test.js +0 -0
  175. package/test/matroska-cluster-discovery.test.js +0 -323
  176. package/test/matroska-cues-track.test.js +0 -192
  177. package/test/media-time.test.js +0 -62
  178. package/test/memory-budget.test.js +0 -390
  179. package/test/move-cost.test.js +0 -216
  180. package/test/mp4-composition-times.test.js +0 -0
  181. package/test/mp4-subtitles.test.js +0 -173
  182. package/test/one-authority.test.js +0 -285
  183. package/test/one-budget.test.js +0 -149
  184. package/test/one-output-one-session.test.js +0 -273
  185. package/test/one-resource-divided.test.js +0 -185
  186. package/test/one-serving-output-per-height.test.js +0 -458
  187. package/test/orchestrator-wired.test.js +0 -207
  188. package/test/output-catalog.test.js +0 -39
  189. package/test/output-shape.test.js +0 -21
  190. package/test/output-spec.test.js +0 -251
  191. package/test/output-speed.test.js +0 -92
  192. package/test/outputs-being-prepared.test.js +0 -40
  193. package/test/packet-witness-ring.test.js +0 -236
  194. package/test/packet-witness.test.js +0 -148
  195. package/test/piece-completeness.test.js +0 -78
  196. package/test/piece-disk-store.test.js +0 -401
  197. package/test/piece-from-whole-file.test.js +0 -129
  198. package/test/piece-lru.test.js +0 -319
  199. package/test/piece-reader.test.js +0 -162
  200. package/test/piece-selection-offset.test.js +0 -85
  201. package/test/piece-store-eviction.test.js +0 -595
  202. package/test/piece-store-never-refuses.test.js +0 -153
  203. package/test/piece-store-reservations.test.js +0 -403
  204. package/test/piece-store-slow-disk.test.js +0 -159
  205. package/test/piece-tail.test.js +0 -97
  206. package/test/plan-host-timings.test.js +0 -68
  207. package/test/plan-is-steady.test.js +0 -225
  208. package/test/playback-readiness.test.js +0 -518
  209. package/test/playback-start-regressions.test.js +0 -101
  210. package/test/priority-map-download.test.js +0 -174
  211. package/test/priority-map-emptied.test.js +0 -204
  212. package/test/priority-map-per-output.test.js +0 -215
  213. package/test/priority-map.test.js +0 -163
  214. package/test/probe-wedge-certainty.test.js +0 -60
  215. package/test/produced-copy-choice.test.js +0 -213
  216. package/test/published-timeline.test.js +0 -83
  217. package/test/quality-offer.test.js +0 -160
  218. package/test/quality-variants.test.js +0 -1274
  219. package/test/rate-control-in-the-output.test.js +0 -181
  220. package/test/read-survives-withdrawal.test.js +0 -164
  221. package/test/read-window.test.js +0 -365
  222. package/test/reads-not-yet-arrived.test.js +0 -218
  223. package/test/resume-warm.test.js +0 -39
  224. package/test/run-graph-drift.test.js +0 -37
  225. package/test/run-inputs.test.js +0 -80
  226. package/test/run-intervals.test.js +0 -100
  227. package/test/run-position-follows-published-grid.test.js +0 -89
  228. package/test/run-progress.test.js +0 -64
  229. package/test/same-height-switch.test.js +0 -663
  230. package/test/seek-frees-the-viewers-requests.test.js +0 -217
  231. package/test/seek-landing.test.js +0 -109
  232. package/test/seek-target-not-superseded.test.js +0 -195
  233. package/test/segment-closed-on-both-branches.test.js +0 -114
  234. package/test/segment-cut-times.test.js +0 -63
  235. package/test/segment-demand.test.js +0 -80
  236. package/test/segment-hold-supersede.test.js +0 -91
  237. package/test/segment-judged-by-its-own-header.test.js +0 -222
  238. package/test/segment-serve-wiring.test.js +0 -481
  239. package/test/segment-store-eviction.test.js +0 -247
  240. package/test/segment-store.test.js +0 -352
  241. package/test/segment-true-start.test.js +0 -86
  242. package/test/segments-are-shared.test.js +0 -176
  243. package/test/send-chunk-bytes.test.js +0 -86
  244. package/test/serving-existing-output.test.js +0 -111
  245. package/test/serving-output.test.js +0 -102
  246. package/test/shared-piece-store.test.js +0 -310
  247. package/test/sidecar-naming.test.js +0 -142
  248. package/test/soundtrack-rate-bound.test.js +0 -289
  249. package/test/source-file.test.js +0 -133
  250. package/test/source-files-route.test.js +0 -113
  251. package/test/source-registry.test.js +0 -18
  252. package/test/split-a-stretch.test.js +0 -139
  253. package/test/stale-request-after-seek.test.js +0 -158
  254. package/test/startup-readings.test.js +0 -124
  255. package/test/stream-route.test.js +0 -237
  256. package/test/subtitle-cue-framing.test.js +0 -200
  257. package/test/subtitle-cue-source.test.js +0 -102
  258. package/test/subtitle-cue-walk.test.js +0 -385
  259. package/test/subtitle-cursor.test.js +0 -63
  260. package/test/subtitle-defaults.test.js +0 -118
  261. package/test/subtitle-language.test.js +0 -252
  262. package/test/subtitle-metadata.test.js +0 -15
  263. package/test/subtitle-plan-waits.test.js +0 -125
  264. package/test/subtitle-track-numbering.test.js +0 -391
  265. package/test/supply-margin.test.js +0 -190
  266. package/test/swarm-follows-readers.test.js +0 -174
  267. package/test/swarm-reach.test.js +0 -130
  268. package/test/swarm-selection.test.js +0 -220
  269. package/test/tail-duplication.test.js +0 -167
  270. package/test/the-channel-has-both-ends.test.js +0 -91
  271. package/test/the-torrent-thread-serves-bytes.test.js +0 -107
  272. package/test/thread-pool.test.js +0 -53
  273. package/test/timeline.test.js +0 -84
  274. package/test/torrent-contents.test.js +0 -235
  275. package/test/torrent-cost.test.js +0 -71
  276. package/test/torrent-files.test.js +0 -221
  277. package/test/torrent-naming.test.js +0 -255
  278. package/test/tracks-begin-together.test.js +0 -209
  279. package/test/tunnel-renewal.test.js +0 -167
  280. package/test/two-viewers-one-picture.test.js +0 -508
  281. package/test/upload-hurry.test.js +0 -186
  282. package/test/usrsctp-state.test.js +0 -131
  283. package/test/video-facts.test.js +0 -102
  284. package/test/viewer-assignments.test.js +0 -143
  285. package/test/viewer-outputs.test.js +0 -285
  286. package/test/viewer-presence.test.js +0 -116
  287. package/test/viewer-requests-admission.test.js +0 -92
  288. package/test/viewer-starts-on-map.test.js +0 -27
  289. package/test/viewer-states.test.js +0 -192
  290. package/test/viewer-subtitle-subscription.test.js +0 -96
  291. package/test/viewer.test.js +0 -139
  292. package/test/viewing-generations.test.js +0 -390
  293. package/test/visible-rung.test.js +0 -37
  294. package/test/wait-ledger.test.js +0 -100
  295. package/test/webrtc-shim.test.js +0 -149
  296. package/test/wedge-certainty.test.js +0 -131
  297. package/test/whole-file-held-read.test.js +0 -30
  298. package/test/withdraw-piece-claim.test.js +0 -212
  299. package/test/worker-channel.test.js +0 -158
@@ -1,176 +0,0 @@
1
- /**
2
- * @file Two sessions of one output write into one place, and each serves what
3
- * the other has made.
4
- *
5
- * This is what the whole address change is for. Measured 2026-09-03: two
6
- * viewers of one copied file got two sessions with byte-identical output —
7
- * `segment-00000.mp4` of 4141899 bytes, twice — and neither could see the
8
- * other's work, because the directory was named after the session's own random
9
- * id (`research/two-viewers-one-file-2026-09-03.md`).
10
- *
11
- * The start position is deliberately NOT part of the address: segment 42 covers
12
- * the same span whoever began where, so two viewers who opened the same film at
13
- * different places produce interchangeable segments and must share them.
14
- */
15
-
16
- import test from "node:test";
17
- import assert from "node:assert/strict";
18
- import { SourceFile } from "../services/media/SourceFile.js";
19
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
20
- import os from "node:os";
21
- import path from "node:path";
22
- import { wireOutputs } from "../services/server/wire-outputs.js";
23
- import { SegmentStore } from "../services/storage/segment-store/SegmentStore.js";
24
- import { Timeline } from "../services/encode/output/Timeline.js";
25
- import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
26
- import { outputSpec } from "./helpers/output-spec.js";
27
-
28
- const OUTPUT_KEY = "torrent:abc:fmt=fmp4:grid=kf@0:video-only:v=0/copy";
29
-
30
- /**
31
- * @returns {{ manager: object, store: SegmentStore, root: string }}
32
- */
33
- function managerOverATempStore() {
34
- const root = mkdtempSync(path.join(os.tmpdir(), "shared-segments-"));
35
- const store = new SegmentStore({ root });
36
- const manager = wireOutputs({
37
- enabled: true,
38
- ffmpegBin: "ffmpeg",
39
- localBindHost: "127.0.0.1",
40
- localPort: 9090,
41
- segmentStore: store
42
- });
43
- return { manager, store, root };
44
- }
45
-
46
- /**
47
- * A session shaped like a live one, sharing an output with any other built
48
- * from the same key.
49
- *
50
- * @param {{ id: string, dirPath: string, outputKey: string }} params
51
- * @returns {object}
52
- */
53
- function sessionOn({ id, dirPath, outputKey }) {
54
- return {
55
- id,
56
- spec: outputSpec({ transcodeVideo: false }),
57
- outputKey,
58
- dirPath,
59
- state: "ready",
60
- file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" }),
61
- // An ordinary session reads its own file, and its sound is inside it. The
62
- // three differ only for a soundtrack shipped as a file of its own.
63
- get inputFile() { return this.file; },
64
- get audioFile() { return this.file; },
65
- segmentFormat: fmp4Format,
66
- // Where the file is cut, which every live session holds.
67
- timeline: new Timeline({ boundaries: [0, 4, 8], cutGrid: "keyframe" }),
68
- lastAccessedAt: Date.now()
69
- };
70
- }
71
-
72
- test("a second session of the same output is given the same directory", (t) => {
73
- const { store, root } = managerOverATempStore();
74
- t.after(() => rmSync(root, { recursive: true, force: true }));
75
-
76
- // Two viewers who opened one film ten minutes apart. Their sessions differ —
77
- // the start position is still what keeps their encoders apart — but what they
78
- // produce is the same material.
79
- assert.equal(store.pathFor(OUTPUT_KEY), store.pathFor(OUTPUT_KEY));
80
- });
81
-
82
- test("each session serves the segments the other one's encoder made", (t) => {
83
- const { manager, store, root } = managerOverATempStore();
84
- t.after(() => rmSync(root, { recursive: true, force: true }));
85
-
86
- const dirPath = store.directoryFor(OUTPUT_KEY);
87
- store.useFormat(OUTPUT_KEY, fmp4Format);
88
-
89
- // Two runs writing into the ONE directory this output has. They are kept
90
- // apart by the stretches they were given, not by a directory each, so neither
91
- // can reach the other's numbers and there is nothing to separate.
92
- writeFileSync(path.join(dirPath, "segment-00000.mp4"), Buffer.alloc(32, 1));
93
- writeFileSync(path.join(dirPath, "segment-00100.mp4"), Buffer.alloc(32, 2));
94
-
95
- const viewerOne = sessionOn({ id: "s-one", dirPath, outputKey: OUTPUT_KEY });
96
- const viewerTwo = sessionOn({ id: "s-two", dirPath, outputKey: OUTPUT_KEY });
97
- manager.outputs.set(viewerOne.id, viewerOne);
98
- manager.outputs.set(viewerTwo.id, viewerTwo);
99
-
100
- const sorted = (numbers) => [...numbers].sort((left, right) => left - right);
101
- const heldByOne = sorted(manager.serving.producedSegmentNumbers(viewerOne));
102
- const heldByTwo = sorted(manager.serving.producedSegmentNumbers(viewerTwo));
103
-
104
- assert.deepEqual(heldByOne, [0, 100]);
105
- assert.deepEqual(heldByTwo, [0, 100], "neither viewer's session is the owner of either run");
106
- });
107
-
108
- test("a session leaving does not take the segments with it", async (t) => {
109
- const { manager, store, root } = managerOverATempStore();
110
- t.after(() => rmSync(root, { recursive: true, force: true }));
111
-
112
- const dirPath = store.directoryFor(OUTPUT_KEY);
113
- store.useFormat(OUTPUT_KEY, fmp4Format);
114
- writeFileSync(path.join(dirPath, "segment-00000.mp4"), Buffer.alloc(32, 1));
115
-
116
- const viewerOne = sessionOn({ id: "s-one", dirPath, outputKey: OUTPUT_KEY });
117
- const viewerTwo = sessionOn({ id: "s-two", dirPath, outputKey: OUTPUT_KEY });
118
- manager.outputs.set(viewerOne.id, viewerOne);
119
- manager.outputs.set(viewerTwo.id, viewerTwo);
120
-
121
- await manager.lifecycle.disposeSession(viewerOne.id);
122
- await manager.lifecycle.disposeSession(viewerTwo.id);
123
-
124
- // Both sessions are gone and the material is still here. That is the whole
125
- // decoupling: a session ending says nothing about whether anybody will ask
126
- // for these segments again — the viewer who left may come back, and a viewer
127
- // who never had a session here may open the same film in a minute.
128
- assert.notEqual(store.pathOf(OUTPUT_KEY, 0), null);
129
- });
130
-
131
- test("what nobody has read for long enough goes, and time is the only judge", (t) => {
132
- const root = mkdtempSync(path.join(os.tmpdir(), "shared-segments-"));
133
- t.after(() => rmSync(root, { recursive: true, force: true }));
134
- let clock = 1_000_000;
135
- const store = new SegmentStore({ root, now: () => clock });
136
-
137
- const dirPath = store.directoryFor(OUTPUT_KEY);
138
- store.useFormat(OUTPUT_KEY, fmp4Format);
139
- writeFileSync(path.join(dirPath, "segment-00000.mp4"), Buffer.alloc(32, 1));
140
-
141
- clock += 60_000;
142
- assert.deepEqual(
143
- store.enforce({ idleMs: 3_600_000, maxBytes: 1e9 }),
144
- { droppedIdle: 0, droppedForRoom: 0, segmentsRemoved: 0, bytes: 32 }
145
- );
146
-
147
- clock += 7_200_000;
148
- const swept = store.enforce({ idleMs: 3_600_000, maxBytes: 1e9 });
149
- assert.equal(swept.droppedIdle, 1);
150
- assert.equal(store.pathOf(OUTPUT_KEY, 0), null);
151
- });
152
-
153
- test("over the allowance, what was read longest ago goes first", (t) => {
154
- const root = mkdtempSync(path.join(os.tmpdir(), "shared-segments-"));
155
- t.after(() => rmSync(root, { recursive: true, force: true }));
156
- let clock = 1_000_000;
157
- const store = new SegmentStore({ root, now: () => clock });
158
-
159
- const older = OUTPUT_KEY;
160
- const newer = `${OUTPUT_KEY}:second`;
161
- for (const key of [older, newer]) {
162
- const dir = store.directoryFor(key);
163
- store.useFormat(key, fmp4Format);
164
- writeFileSync(path.join(dir, "segment-00000.mp4"), Buffer.alloc(1000, 1));
165
- clock += 1000;
166
- }
167
- // Reading the newer one again is what puts the older one at the front of the
168
- // queue: the judge is the last REQUEST, not when the file was written.
169
- clock += 1000;
170
- store.pathOf(newer, 0);
171
-
172
- const swept = store.enforce({ idleMs: 3_600_000, maxBytes: 1500 });
173
- assert.equal(swept.droppedForRoom, 1);
174
- assert.equal(store.pathOf(older, 0), null);
175
- assert.notEqual(store.pathOf(newer, 0), null);
176
- });
@@ -1,86 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { bodySender } from "../services/transport/data-channel-handler.js";
4
-
5
- /**
6
- * Drives the sender over a body arriving in the given read sizes, and answers
7
- * both what reached the far end and what was written — so "the body is
8
- * unchanged" is asserted against the body rather than against a length.
9
- *
10
- * @param {number} sizeBytes
11
- * @param {number[]} readSizes - The sizes the body read hands over, in order.
12
- * @returns {{ sent: Buffer[], counted: number, written: Buffer }}
13
- */
14
- function sendBody(sizeBytes, readSizes) {
15
- /** @type {Buffer[]} */
16
- const sent = [];
17
- const sender = bodySender((bytes) => sent.push(Buffer.from(bytes)), sizeBytes);
18
- /** @type {Buffer[]} */
19
- const written = [];
20
- let counted = 0;
21
- let next = 0;
22
- for (const size of readSizes) {
23
- const block = Buffer.alloc(size);
24
- for (let index = 0; index < size; index += 1) {
25
- block[index] = next & 0xff;
26
- next += 1;
27
- }
28
- written.push(block);
29
- counted += sender.push(block);
30
- }
31
- counted += sender.flush();
32
- return { sent, counted, written: Buffer.concat(written) };
33
- }
34
-
35
- test("with no size chosen, one body read is one message", () => {
36
- const { sent, counted } = sendBody(0, [1000, 2000, 3]);
37
- assert.deepEqual(
38
- sent.map((message) => message.length),
39
- [1000, 2000, 3]
40
- );
41
- assert.equal(counted, 3);
42
- });
43
-
44
- test("a smaller size cuts a body read into messages of exactly that size", () => {
45
- const { sent, counted } = sendBody(400, [1000]);
46
- assert.deepEqual(
47
- sent.map((message) => message.length),
48
- [400, 400, 200]
49
- );
50
- assert.equal(counted, 3);
51
- });
52
-
53
- test("a larger size joins body reads until it is filled", () => {
54
- const { sent } = sendBody(2500, [1000, 1000, 1000, 1000]);
55
- assert.deepEqual(
56
- sent.map((message) => message.length),
57
- [2500, 1500]
58
- );
59
- });
60
-
61
- test("every byte arrives once, in order, whatever the size", () => {
62
- for (const size of [0, 1, 7, 400, 2500, 999_999]) {
63
- const { sent, written } = sendBody(size, [1000, 3, 5000, 17]);
64
- assert.deepEqual(Buffer.concat(sent), written, `size ${size} changed the body`);
65
- }
66
- });
67
-
68
- test("a size larger than the whole body still sends it, once, on flush", () => {
69
- const { sent, written, counted } = sendBody(10_000, [100, 200]);
70
- assert.equal(sent.length, 1);
71
- assert.equal(counted, 1);
72
- assert.deepEqual(sent[0], written);
73
- });
74
-
75
- test("the count is of messages, which is what a reading is attributed to", () => {
76
- // 163 messages for 10 657 210 bytes is the field reading of 2026-09-12; at
77
- // 16 KB the same body is 651 of them, and that difference is the measurement.
78
- const { counted } = sendBody(16 * 1024, [10_657_210]);
79
- assert.equal(counted, Math.ceil(10_657_210 / (16 * 1024)));
80
- });
81
-
82
- test("nothing is sent for an empty body, and flush has nothing to release", () => {
83
- const { sent, counted } = sendBody(400, []);
84
- assert.deepEqual(sent, []);
85
- assert.equal(counted, 0);
86
- });
@@ -1,111 +0,0 @@
1
- /**
2
- * @file An output already made serves a viewer when the rules decided with the
3
- * user 2026-09-16 allow it, through the paths a real request takes.
4
- */
5
-
6
- import test from "node:test";
7
- import assert from "node:assert/strict";
8
- import { writeFileSync } from "node:fs";
9
- import path from "node:path";
10
- import { managerWithOwnStore } from "./helpers/manager.js";
11
- import { outputSpec } from "./helpers/output-spec.js";
12
- import { SourceFile } from "../services/media/SourceFile.js";
13
- import { Timeline } from "../services/encode/output/Timeline.js";
14
- import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
15
- import { Output } from "../services/encode/output/Output.js";
16
- import { qualityStateOf } from "../services/encode/quality/OutputQualityState.js";
17
-
18
- const TORRENT = "torrent:11f0929918e2b5aa2e5b71ecdbe5c0f1a4bbf7d1";
19
-
20
- function hostWithA1080pOutput(t) {
21
- const { manager, store, cleanup } = managerWithOwnStore({
22
- startupWaitMs: 0,
23
- videoEncoder: { kind: "vaapi", name: "h264_vaapi", inputArgs: [] }
24
- });
25
- t.after(async () => {
26
- await manager.lifecycle.disposeAll();
27
- cleanup();
28
- });
29
- manager.encodeRuns.planEncodersNow = () => {};
30
- manager.encodeRuns.planEncodersSoon = () => {};
31
- manager.getCachedMediaInfo = () => ({ durationSeconds: 400, width: 1920, height: 1080, fps: 24 });
32
- // The soundtrack the plan listed, at the rate the file states: a copy of it
33
- // has a figure, so it stays a copy.
34
- manager.getCachedAudioTracks = () => [{ index: 0, fileIndex: 0, sourceTrackIndex: 0, bitrateKbps: 128 }];
35
- const spec = outputSpec({
36
- sourceKey: TORRENT,
37
- transcodeVideo: true,
38
- transcodeAudio: false,
39
- encoder: "h264_vaapi",
40
- width: 1920,
41
- height: 1080,
42
- fps: 24,
43
- cutGrid: "uniform"
44
- });
45
- const key = spec.toKey();
46
- store.useFormat(key, fmp4Format);
47
- // The piece at the start of the film is made.
48
- writeFileSync(path.join(store.directoryFor(key), fmp4Format.segmentFileName(0)), Buffer.alloc(100));
49
- const file = new SourceFile({ sourceKey: TORRENT, fileIndex: 0, name: "film.mkv" }).learn({ width: 1920, height: 1080, durationSeconds: 400 });
50
- const made = {
51
- id: spec.toName(),
52
- spec,
53
- get outputKey() { return this.spec.toKey(); },
54
- file,
55
- timeline: new Timeline({ boundaries: Array.from({ length: 101 }, (_, index) => index * 4), cutGrid: "uniform" }),
56
- output: new Output({ encodeWidth: 1920, encodeHeight: 1080, outputFps: 24 }),
57
- segmentFormat: fmp4Format,
58
- useSyntheticPlaylist: true
59
- };
60
- manager.outputs.set(made.id, made);
61
- // WHAT ITS ENCODER WAS SEEN DOING. Its first piece is made, so an encoder ran
62
- // on it here; a viewer put on it now needs one again, and a place for one is
63
- // asked by what one costs — with no measured cost the proxy refuses (roadmap
64
- // item 97, step 14).
65
- qualityStateOf(made).lastAloneSpeed = 2;
66
- return { manager, made };
67
- }
68
-
69
- function request(over) {
70
- return {
71
- sourceKey: TORRENT,
72
- fileIndex: 0,
73
- fileName: "film.mkv",
74
- transcodeVideo: true,
75
- transcodeAudio: false,
76
- segmentFormatId: "fmp4",
77
- ...over
78
- };
79
- }
80
-
81
- test("the automatic choice is served by a better output whose piece at the viewer's position is made", async (t) => {
82
- const { manager, made } = hostWithA1080pOutput(t);
83
- const answered = await manager.viewerRequests.createOrGetSession(
84
- request({ consumerId: "auto-viewer", targetWidth: 1280, targetHeight: 720 })
85
- );
86
- assert.equal(answered, made);
87
- });
88
-
89
- test("a size picked by hand is not served by another size", async (t) => {
90
- const { manager, made } = hostWithA1080pOutput(t);
91
- let answered = null;
92
- try {
93
- answered = await manager.viewerRequests.createOrGetSession(
94
- request({ consumerId: "picker", targetWidth: 1280, targetHeight: 720, exactSize: true })
95
- );
96
- } catch {
97
- answered = null;
98
- }
99
- assert.notEqual(answered, made);
100
- });
101
-
102
- test("a step asked for by an automatic viewer and served by the picture leaves the picture a picture", async (t) => {
103
- const { manager, made } = hostWithA1080pOutput(t);
104
- manager.viewers.of(made, "auto-viewer").qualityMode = "auto";
105
-
106
- const variant = await manager.renditions.resolveVariantSession(made.id, 720, 0, "auto-viewer");
107
-
108
- assert.equal(variant, made, "the picture already made at 1080p serves the 720p request");
109
- assert.notEqual(made.isStep, true, "and is not turned into a step of itself");
110
- assert.ok(manager.viewers.forOutput(made).has("auto-viewer"));
111
- });
@@ -1,102 +0,0 @@
1
- /**
2
- * @file Which existing output serves a viewer: the rules decided with the user
3
- * 2026-09-16, with the viewer's own link asked in every mode and on every
4
- * branch (roadmap item 97, step 11).
5
- */
6
-
7
- import test from "node:test";
8
- import assert from "node:assert/strict";
9
- import { chooseServingOutput } from "../services/encode/quality/serving-output.js";
10
- import { linkCouldCarry, loadOf, PEAK_CLASS } from "../services/encode/quality/link-budget.js";
11
-
12
- const WANTED = { width: 1824, height: 1026 };
13
- const NEXT_LOWER = 1280 * 720;
14
-
15
- /**
16
- * @param {string} key
17
- * @param {number} width
18
- * @param {number} height
19
- * @param {{ mbps?: number | null, peakClass?: string, readyHere?: boolean }} [over]
20
- */
21
- function candidate(key, width, height, { mbps = 5, peakClass = PEAK_CLASS.KNOWN, readyHere = true } = {}) {
22
- return { key, width, height, readyHere, load: loadOf({ mbps, peakClass: mbps === null ? PEAK_CLASS.UNKNOWN : peakClass }, null) };
23
- }
24
-
25
- /**
26
- * @param {object} over
27
- * @param {number | null} [linkMbps] - What this viewer's link measured.
28
- */
29
- function choose(over, linkMbps = null) {
30
- return chooseServingOutput({
31
- mode: "auto",
32
- wanted: WANTED,
33
- nextLowerArea: NEXT_LOWER,
34
- atRisk: false,
35
- judge: (one) => linkCouldCarry(linkMbps, one.load),
36
- candidates: [],
37
- ...over
38
- })?.key ?? null;
39
- }
40
-
41
- test("the same quality or higher is served, the nearest of them", () => {
42
- assert.equal(choose({ candidates: [candidate("2160", 3840, 2160), candidate("1080", 1920, 1080)] }), "1080");
43
- });
44
-
45
- test("higher quality is not served over a link it does not fit", () => {
46
- assert.equal(choose({ candidates: [candidate("1080", 1920, 1080, { mbps: 12 })] }, 10), null);
47
- });
48
-
49
- test("a little lower — above the next rung down — is served, and only if the link admits it", () => {
50
- assert.equal(choose({ candidates: [candidate("1000", 1776, 1000)] }), "1000");
51
- assert.equal(
52
- choose({ candidates: [candidate("1000", 1776, 1000, { mbps: 12 })] }, 10),
53
- null,
54
- "a smaller picture proves nothing by itself: its own load is asked"
55
- );
56
- });
57
-
58
- test("the next rung down or lower is served only when making the wanted format risks a wait, and only if it fits", () => {
59
- const lower = [candidate("720", 1280, 720), candidate("480", 854, 480)];
60
- assert.equal(choose({ candidates: lower }), null);
61
- assert.equal(choose({ candidates: lower, atRisk: true }), "720");
62
- assert.equal(
63
- choose({ candidates: [candidate("720", 1280, 720, { mbps: 12 }), candidate("480", 854, 480, { mbps: 3 })], atRisk: true }, 10),
64
- "480",
65
- "the larger one does not fit this link, so it is not taken"
66
- );
67
- });
68
-
69
- test("an output without the piece at the viewer's position spares them nothing", () => {
70
- assert.equal(choose({ candidates: [candidate("1080", 1920, 1080, { readyHere: false })] }), null);
71
- });
72
-
73
- test("a size chosen by hand is served only exactly, and only if the link admits it", () => {
74
- const candidates = [candidate("1080", 1920, 1080), candidate("exact", 1824, 1026)];
75
- assert.equal(choose({ mode: "manual", candidates }), "exact");
76
- assert.equal(choose({ mode: "manual", candidates: [candidate("1080", 1920, 1080)], atRisk: true }), null);
77
- assert.equal(
78
- choose({ mode: "manual", candidates: [candidate("exact", 1824, 1026, { mbps: 12 })] }, 10),
79
- null,
80
- "the size they picked, but more than their link carries: not handed over"
81
- );
82
- });
83
-
84
- test("of two outputs of the size picked by hand, the higher limit the link admits is served", () => {
85
- const candidates = [candidate("2800", 1824, 1026, { mbps: 3.64 }), candidate("1400", 1824, 1026, { mbps: 1.82 })];
86
- assert.equal(choose({ mode: "manual", candidates }, 10), "2800");
87
- assert.equal(choose({ mode: "manual", candidates }, 3), "1400", "the link admits only the lower limit");
88
- });
89
-
90
- test("a bound is taken before an average, even when the average's limit is higher", () => {
91
- const candidates = [
92
- candidate("estimated", 1824, 1026, { mbps: 6, peakClass: PEAK_CLASS.ESTIMATED }),
93
- candidate("known", 1824, 1026, { mbps: 4, peakClass: PEAK_CLASS.KNOWN })
94
- ];
95
- assert.equal(choose({ mode: "manual", candidates }, 10), "known");
96
- });
97
-
98
- test("an output with no bound is not served against a measured link, and is while nothing measured it", () => {
99
- const candidates = [candidate("hardware", 1824, 1026, { mbps: null })];
100
- assert.equal(choose({ mode: "manual", candidates }, 50), null);
101
- assert.equal(choose({ mode: "manual", candidates }, null), "hardware");
102
- });