@torrent-tv/proxy 2.86.0 → 2.87.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.
Files changed (385) hide show
  1. package/CHANGELOG.md +2142 -1915
  2. package/CLAUDE.md +322 -197
  3. package/assets/calibration/NOTICE.md +2 -2
  4. package/bin/cli.js +684 -616
  5. package/biome.json +409 -233
  6. package/docs/browser-proxy-contract.md +94 -0
  7. package/docs/container-architecture.md +218 -213
  8. package/docs/download-architecture.md +7 -7
  9. package/docs/encode-architecture.md +287 -11
  10. package/docs/encode-run-state.md +2 -2
  11. package/docs/logs.md +97 -97
  12. package/docs/{disk-architecture.md → storage-architecture.md} +107 -1
  13. package/openspec/changes/adaptive-bitrate/design.md +2 -2
  14. package/openspec/changes/adaptive-bitrate/proposal.md +1 -1
  15. package/openspec/changes/adaptive-bitrate/tasks.md +1 -1
  16. package/openspec/changes/chunked-request-bodies/design.md +1 -1
  17. package/openspec/changes/chunked-request-bodies/proposal.md +1 -1
  18. package/openspec/changes/chunked-request-bodies/tasks.md +1 -1
  19. package/openspec/changes/cold-start/design.md +1 -1
  20. package/openspec/changes/disk-cap/proposal.md +1 -1
  21. package/openspec/changes/proxy-observability/proposal.md +2 -2
  22. package/openspec/changes/track-selection/proposal.md +1 -1
  23. package/openspec/changes/transcode-quality/proposal.md +1 -1
  24. package/package.json +2 -2
  25. package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
  26. package/research/double-spawn-2026-09-10.md +171 -171
  27. package/routes/api/playback-plan/post.js +2 -2
  28. package/routes/api/sources/files/get.js +1 -1
  29. package/routes/api/sources/stats/get.js +1 -1
  30. package/routes/api/sources/warm/post.js +10 -3
  31. package/routes/api/subtitles/get.js +230 -230
  32. package/routes/api/transcode-sessions/fragment-far/post.js +3 -3
  33. package/routes/api/transcode-sessions/net-report/post.js +129 -94
  34. package/routes/api/transcode-sessions/post.js +160 -139
  35. package/routes/api/transcode-sessions/progress/get.js +3 -3
  36. package/routes/api/transcode-sessions/release/post.js +3 -3
  37. package/routes/api/transcode-sessions/seek/post.js +12 -3
  38. package/routes/stream/get.js +2 -2
  39. package/routes/transcode/audio-file/get.js +15 -6
  40. package/routes/transcode/audio-warm/get.js +9 -6
  41. package/routes/transcode/session-file/get.js +52 -268
  42. package/routes/transcode/variant-file/get.js +46 -7
  43. package/routes/transcode/variant-warm/get.js +19 -6
  44. package/scripts/render-run-graph.js +1 -1
  45. package/server.js +720 -465
  46. package/services/encode/CoverageMap.js +428 -428
  47. package/services/encode/CushionReport.js +345 -0
  48. package/services/encode/EncodeAdmission.js +332 -0
  49. package/services/{orchestrators → encode}/EncodeOrchestrator.js +307 -29
  50. package/services/encode/EncodeRun.js +16 -9
  51. package/services/encode/EncodeRuns.js +1229 -0
  52. package/services/encode/Encoder.js +18 -2
  53. package/services/encode/EncoderSelection.js +38 -0
  54. package/services/encode/LocalObservations.js +359 -0
  55. package/services/encode/NvencEncoder.js +5 -0
  56. package/services/encode/OutputOpening.js +755 -0
  57. package/services/encode/OutputTimes.js +579 -0
  58. package/services/encode/Renditions.js +2162 -0
  59. package/services/encode/RunProgress.js +84 -0
  60. package/services/encode/SoftwareEncoder.js +12 -7
  61. package/services/encode/args.js +402 -186
  62. package/services/encode/calibration.js +361 -0
  63. package/services/encode/contention.js +128 -128
  64. package/services/encode/encode-run-state.js +16 -20
  65. package/services/encode/fingerprint.js +322 -0
  66. package/services/{hwaccel.js → encode/hwaccel.js} +82 -42
  67. package/services/{learned-median.js → encode/learned-median.js} +11 -0
  68. package/services/encode/output/EncodedOutput.js +57 -0
  69. package/services/encode/output/Output.js +131 -0
  70. package/services/{output/LiveOutputs.js → encode/output/OutputCatalog.js} +141 -40
  71. package/services/encode/output/OutputSpec.js +390 -0
  72. package/services/{output → encode/output}/Timeline.js +12 -37
  73. package/services/encode/output/index.js +20 -0
  74. package/services/encode/output/ladder.js +32 -0
  75. package/services/{output → encode/output}/playlists.js +3 -0
  76. package/services/encode/output-key-format.js +21 -0
  77. package/services/encode/quality/EncodeCost.js +1083 -0
  78. package/services/encode/quality/HostLoad.js +525 -0
  79. package/services/encode/quality/HostTimings.js +249 -0
  80. package/services/encode/quality/OutputQualityState.js +28 -0
  81. package/services/encode/quality/QualityController.js +935 -0
  82. package/services/encode/quality/QualityOffer.js +416 -0
  83. package/services/encode/quality/drain-threat.js +47 -0
  84. package/services/encode/quality/link-budget.js +254 -0
  85. package/services/encode/quality/output-format.js +266 -0
  86. package/services/encode/quality/serving-output.js +216 -0
  87. package/services/encode/quality/visible-rung.js +57 -0
  88. package/services/encode/run-command.js +23 -20
  89. package/services/encode/run-inputs.js +42 -0
  90. package/services/{segment-formats → encode/segment-formats}/fmp4.js +63 -0
  91. package/services/{segment-formats → encode/segment-formats}/index.js +3 -3
  92. package/services/encode/segment-formats/init-compat.js +324 -0
  93. package/services/{segment-formats → encode/segment-formats}/mp4-boxes.js +8 -3
  94. package/services/encode/throughput.js +97 -0
  95. package/services/{orchestrators → media}/ContainerOrchestrator.js +45 -7
  96. package/services/media/KeyframeTables.js +316 -0
  97. package/services/{source → media}/SourceFile.js +7 -55
  98. package/services/{torrent-worker/subtitle-cues.js → media/SubtitleCues.js} +103 -132
  99. package/services/media/SubtitleOrchestrator.js +167 -0
  100. package/services/{audio-inventory.js → media/audio-inventory.js} +7 -1
  101. package/services/{container → media/container}/AviContainer.js +266 -266
  102. package/services/{container → media/container}/Container.js +596 -601
  103. package/services/media/container/ContainerFactory.js +56 -0
  104. package/services/media/container/KeyframeTable.js +272 -0
  105. package/services/{container → media/container}/MatroskaContainer.js +1485 -1485
  106. package/services/{container → media/container}/Mp4Container.js +1244 -1244
  107. package/services/{container → media/container}/SubtitleFileContainer.js +322 -322
  108. package/services/media/keyframe-probe.js +157 -0
  109. package/services/media/media-info-probe.js +93 -0
  110. package/services/{playback-planner.js → media/playback-planner.js} +82 -39
  111. package/services/{tracks → media/tracks}/AudioTrack.js +131 -131
  112. package/services/{tracks → media/tracks}/language-detect.js +174 -174
  113. package/services/server/OutputLifecycle.js +418 -0
  114. package/services/server/SegmentServing.js +1146 -0
  115. package/services/server/ViewerRequests.js +522 -0
  116. package/services/server/controllers/PlaybackController.js +36 -0
  117. package/services/{controllers → server/controllers}/SubtitleController.js +17 -33
  118. package/services/server/http-responses.js +60 -0
  119. package/services/server/transcode-session-files.js +325 -0
  120. package/services/server/wire-outputs.js +983 -0
  121. package/services/storage/Diagnostics.js +202 -0
  122. package/services/storage/MachineBudget.js +235 -0
  123. package/services/{piece-store → storage}/allowance.js +107 -107
  124. package/services/{core-dumps.js → storage/core-dumps.js} +1 -1
  125. package/services/{files → storage/files}/CompletedFiles.js +116 -1
  126. package/services/{disk → storage}/free.js +33 -0
  127. package/services/storage/machine-memory.js +56 -0
  128. package/services/{memory-report.js → storage/memory-report.js} +575 -596
  129. package/services/{piece-store → storage/piece-store}/piece-disk-store.js +0 -7
  130. package/services/{piece-store → storage/piece-store}/shared-piece-store.js +28 -21
  131. package/services/{encode → storage/segment-store}/SegmentStore.js +395 -8
  132. package/services/storage/wire.js +175 -0
  133. package/services/{download → torrent/download}/SwarmSelection.js +2 -2
  134. package/services/{download → torrent/download}/registry.js +2 -2
  135. package/services/torrent/files.js +1 -1
  136. package/services/torrent/piece-store-of.js +29 -0
  137. package/services/{torrent-pool.js → torrent/torrent-pool.js} +26 -119
  138. package/services/{torrent-worker → torrent/worker}/background-fill.js +2 -2
  139. package/services/{torrent-worker → torrent/worker}/channel.js +271 -250
  140. package/services/{torrent-worker → torrent/worker}/client.js +77 -58
  141. package/services/torrent/worker/held-bytes.js +155 -0
  142. package/services/{torrent-worker → torrent/worker}/install-webrtc-shim.js +1 -1
  143. package/services/{torrent-worker → torrent/worker}/piece-reader.js +21 -30
  144. package/services/{torrent-worker → torrent/worker}/pool-adapter.js +101 -137
  145. package/services/{torrent-worker → torrent/worker}/protocol.js +26 -30
  146. package/services/torrent/worker/resume-warm.js +107 -0
  147. package/services/{torrent-worker → torrent/worker}/webrtc-shim.js +2 -2
  148. package/services/{torrent-worker → torrent/worker}/worker.js +68 -159
  149. package/services/{data-channel-handler.js → transport/data-channel-handler.js} +1356 -1356
  150. package/services/{delivery-probe.js → transport/delivery-probe.js} +842 -842
  151. package/services/{health-collector.js → transport/health-collector.js} +6 -35
  152. package/services/{packet-witness.js → transport/packet-witness.js} +808 -784
  153. package/services/{port-mapper.js → transport/port-mapper.js} +1 -1
  154. package/services/{usrsctp-state.js → transport/usrsctp-state.js} +1 -1
  155. package/services/{webrtc-manager.js → transport/webrtc-manager.js} +1 -1
  156. package/services/viewer/Assignments.js +340 -0
  157. package/services/{priority → viewer}/PriorityMap.js +390 -390
  158. package/services/{priority → viewer}/PriorityOrchestrator.js +325 -320
  159. package/services/viewer/Viewer.js +657 -483
  160. package/services/viewer/Viewers.js +368 -257
  161. package/services/viewer/active-output.js +37 -54
  162. package/services/viewer/audio-start.js +4 -3
  163. package/services/viewer/buffer-trend.js +81 -0
  164. package/services/viewer/choices.js +647 -0
  165. package/services/viewer/positions.js +126 -120
  166. package/services/viewer/report-intake.js +46 -45
  167. package/test/asset-paths.test.js +28 -0
  168. package/test/audio-inventory.test.js +3 -3
  169. package/test/audio-track-bitrate.test.js +40 -0
  170. package/test/auto-quality-step.test.js +707 -515
  171. package/test/available-share.test.js +1 -1
  172. package/test/background-fill.test.js +1 -1
  173. package/test/client-log-file.test.js +110 -110
  174. package/test/completed-files.test.js +1 -1
  175. package/test/concurrent-cost.test.js +13 -3
  176. package/test/container-media-info.test.js +3 -3
  177. package/test/core-dumps.test.js +1 -1
  178. package/test/coverage-follows-the-disk.test.js +46 -9
  179. package/test/cue-push-carries-what-the-walk-found.test.js +58 -0
  180. package/test/cut-times-timeline.test.js +2 -2
  181. package/test/cuts-follow-published-grid.test.js +9 -8
  182. package/test/data-channel-frame.test.js +1 -1
  183. package/test/dead-torrent-handle.test.js +1 -1
  184. package/test/declared-rates.test.js +2 -2
  185. package/test/declared-tracks.test.js +2 -2
  186. package/test/decode-cost-fit.test.js +1 -1
  187. package/test/decode-cost.test.js +34 -25
  188. package/test/delivery-probe.test.js +1 -1
  189. package/test/demand-register.test.js +4 -4
  190. package/test/dht-bootstrap.test.js +1 -1
  191. package/test/drain-threat.test.js +65 -0
  192. package/test/duplicate-add.test.js +1 -1
  193. package/test/encode-admission.test.js +146 -0
  194. package/test/encode-cost.test.js +199 -10
  195. package/test/encode-orchestrator.test.js +39 -1
  196. package/test/encode-plan-viewers.test.js +2 -2
  197. package/test/encode-run.test.js +2 -2
  198. package/test/encode-slope.test.js +1 -1
  199. package/test/encoded-output.test.js +39 -0
  200. package/test/encoder-readings.test.js +1 -1
  201. package/test/every-claimant-is-told-its-share.test.js +152 -0
  202. package/test/failed-encoder-still-serves-what-exists.test.js +354 -0
  203. package/test/failed-start-breaker.test.js +81 -30
  204. package/test/fastest-wires.test.js +1 -1
  205. package/test/ffmpeg-stream-counts.test.js +1 -1
  206. package/test/file-edges.test.js +3 -3
  207. package/test/fmp4-self-contained.test.js +1 -1
  208. package/test/fmp4-timestamps.test.js +1 -1
  209. package/test/fmp4-track-completeness.test.js +1 -1
  210. package/test/format-keyed-outputs.test.js +78 -0
  211. package/test/health-metrics.test.js +3 -2
  212. package/test/held-request-width.test.js +22 -23
  213. package/test/helpers/encode-run.js +136 -136
  214. package/test/helpers/held-file.js +29 -0
  215. package/test/helpers/manager.js +57 -0
  216. package/test/helpers/output-spec.js +63 -0
  217. package/test/host-load.test.js +1 -1
  218. package/test/index-accuracy-is-about-the-file.test.js +128 -0
  219. package/test/init-compat.test.js +219 -0
  220. package/test/input-lost-quiets-the-plan.test.js +1 -1
  221. package/test/input-unavailable.test.js +1 -1
  222. package/test/keeping-period.test.js +4 -4
  223. package/test/keyframe-index-accuracy.test.js +97 -45
  224. package/test/keyframe-table-budget.test.js +28 -33
  225. package/test/keyframes-belong-to-the-file.test.js +174 -57
  226. package/test/learned-median.test.js +1 -1
  227. package/test/letting-go-asks-who-is-watching.test.js +191 -0
  228. package/test/limit-row-by-area.test.js +60 -0
  229. package/test/link-budget.test.js +111 -0
  230. package/test/link-probe.test.js +56 -56
  231. package/test/local-encoding-data.test.js +273 -0
  232. package/test/look-ahead-coverage.test.js +1 -1
  233. package/test/manager-surface.test.js +294 -0
  234. package/test/matroska-blocks.test.js +0 -0
  235. package/test/matroska-cues-track.test.js +1 -1
  236. package/test/memory-budget.test.js +2 -2
  237. package/test/mp4-composition-times.test.js +0 -0
  238. package/test/mp4-subtitles.test.js +1 -1
  239. package/test/one-authority.test.js +45 -41
  240. package/test/one-budget.test.js +149 -0
  241. package/test/one-output-one-session.test.js +204 -0
  242. package/test/{disk-space.test.js → one-resource-divided.test.js} +30 -10
  243. package/test/one-serving-output-per-height.test.js +458 -0
  244. package/test/orchestrator-wired.test.js +46 -45
  245. package/test/output-catalog.test.js +39 -0
  246. package/test/output-shape.test.js +4 -51
  247. package/test/output-spec.test.js +251 -157
  248. package/test/output-speed.test.js +17 -15
  249. package/test/outputs-being-prepared.test.js +40 -0
  250. package/test/packet-witness-ring.test.js +2 -2
  251. package/test/packet-witness.test.js +1 -1
  252. package/test/piece-disk-store.test.js +2 -2
  253. package/test/piece-from-whole-file.test.js +1 -1
  254. package/test/piece-lru.test.js +1 -1
  255. package/test/piece-reader.test.js +2 -2
  256. package/test/piece-store-eviction.test.js +1 -1
  257. package/test/piece-store-never-refuses.test.js +1 -1
  258. package/test/piece-store-reservations.test.js +1 -1
  259. package/test/piece-store-slow-disk.test.js +1 -1
  260. package/test/piece-tail.test.js +1 -1
  261. package/test/plan-host-timings.test.js +1 -1
  262. package/test/plan-is-steady.test.js +225 -225
  263. package/test/priority-map-download.test.js +4 -4
  264. package/test/priority-map-emptied.test.js +204 -205
  265. package/test/priority-map-per-output.test.js +215 -209
  266. package/test/priority-map.test.js +1 -1
  267. package/test/probe-wedge-certainty.test.js +1 -1
  268. package/test/produced-copy-choice.test.js +15 -60
  269. package/test/quality-offer.test.js +162 -0
  270. package/test/quality-variants.test.js +1229 -1167
  271. package/test/rate-control-in-the-output.test.js +180 -0
  272. package/test/read-survives-withdrawal.test.js +2 -2
  273. package/test/read-window.test.js +10 -3
  274. package/test/resume-warm.test.js +1 -1
  275. package/test/run-inputs.test.js +80 -0
  276. package/test/run-position-follows-published-grid.test.js +13 -11
  277. package/test/run-progress.test.js +65 -0
  278. package/test/same-height-switch.test.js +664 -0
  279. package/test/seek-frees-the-viewers-requests.test.js +217 -0
  280. package/test/seek-landing.test.js +13 -13
  281. package/test/seek-target-not-superseded.test.js +28 -29
  282. package/test/segment-closed-on-both-branches.test.js +96 -0
  283. package/test/segment-cut-times.test.js +1 -1
  284. package/test/segment-hold-supersede.test.js +28 -35
  285. package/test/segment-judged-by-its-own-header.test.js +221 -0
  286. package/test/segment-serve-wiring.test.js +130 -39
  287. package/test/segment-store-eviction.test.js +22 -7
  288. package/test/segment-store.test.js +98 -14
  289. package/test/segment-true-start.test.js +1 -1
  290. package/test/segments-are-shared.test.js +17 -16
  291. package/test/send-chunk-bytes.test.js +1 -1
  292. package/test/serving-existing-output.test.js +102 -0
  293. package/test/serving-output.test.js +102 -0
  294. package/test/shared-piece-store.test.js +1 -1
  295. package/test/source-file.test.js +1 -1
  296. package/test/stale-request-after-seek.test.js +17 -21
  297. package/test/startup-readings.test.js +10 -5
  298. package/test/subtitle-cue-framing.test.js +5 -5
  299. package/test/subtitle-cue-source.test.js +36 -38
  300. package/test/subtitle-cue-walk.test.js +11 -10
  301. package/test/subtitle-defaults.test.js +1 -1
  302. package/test/subtitle-language.test.js +4 -4
  303. package/test/subtitle-track-numbering.test.js +5 -4
  304. package/test/supply-margin.test.js +1 -1
  305. package/test/swarm-follows-readers.test.js +3 -3
  306. package/test/swarm-reach.test.js +1 -1
  307. package/test/swarm-selection.test.js +3 -3
  308. package/test/tail-duplication.test.js +1 -1
  309. package/test/the-channel-has-both-ends.test.js +91 -0
  310. package/test/the-torrent-thread-serves-bytes.test.js +107 -0
  311. package/test/thread-pool.test.js +3 -3
  312. package/test/timeline.test.js +1 -12
  313. package/test/torrent-cost.test.js +1 -1
  314. package/test/tracks-begin-together.test.js +38 -24
  315. package/test/tunnel-renewal.test.js +1 -1
  316. package/test/two-viewers-one-picture.test.js +120 -87
  317. package/test/upload-hurry.test.js +3 -3
  318. package/test/usrsctp-state.test.js +1 -1
  319. package/test/video-facts.test.js +1 -1
  320. package/test/viewer-assignments.test.js +143 -0
  321. package/test/viewer-outputs.test.js +60 -52
  322. package/test/viewer-presence.test.js +22 -25
  323. package/test/viewer-requests-admission.test.js +89 -0
  324. package/test/viewer-states.test.js +189 -202
  325. package/test/viewer-subtitle-subscription.test.js +1 -1
  326. package/test/viewer.test.js +139 -136
  327. package/test/viewing-generations.test.js +390 -0
  328. package/test/visible-rung.test.js +37 -0
  329. package/test/wait-ledger.test.js +1 -1
  330. package/test/webrtc-shim.test.js +3 -3
  331. package/test/wedge-certainty.test.js +2 -2
  332. package/test/withdraw-piece-claim.test.js +2 -2
  333. package/test/worker-channel.test.js +1 -31
  334. package/utils/client-log-file.js +151 -151
  335. package/scripts/read-container-index.mjs +0 -108
  336. package/services/container/ContainerFactory.js +0 -122
  337. package/services/controllers/PlaybackController.js +0 -33
  338. package/services/disk/DiskSpace.js +0 -150
  339. package/services/disk/wire.js +0 -61
  340. package/services/hls-session-manager.js +0 -9608
  341. package/services/orchestrators/SubtitleOrchestrator.js +0 -160
  342. package/services/output/Output.js +0 -94
  343. package/services/output/OutputSpec.js +0 -202
  344. package/services/output/index.js +0 -1
  345. package/services/output/ladder.js +0 -26
  346. package/services/produced-index.js +0 -222
  347. package/services/quality/EncodeCost.js +0 -555
  348. package/services/torrent-worker/container-tracks.js +0 -414
  349. package/services/viewer/link-readings.js +0 -56
  350. package/test/produced-index.test.js +0 -142
  351. /package/{services → bin}/thread-pool.js +0 -0
  352. /package/services/{available-share.js → encode/available-share.js} +0 -0
  353. /package/services/{decode-cost-fit.js → encode/decode-cost-fit.js} +0 -0
  354. /package/services/{encoder-readings.js → encode/encoder-readings.js} +0 -0
  355. /package/services/{host-load.js → encode/host-load.js} +0 -0
  356. /package/services/{output → encode/output}/cut-grid.js +0 -0
  357. /package/services/{output → encode/output}/rates.js +0 -0
  358. /package/services/{segment-formats → encode/segment-formats}/mpegts.js +0 -0
  359. /package/services/{torrent-cost.js → encode/torrent-cost.js} +0 -0
  360. /package/services/{container → media/container}/ebml-reader.js +0 -0
  361. /package/services/{ffmpeg-banner.js → media/ffmpeg-banner.js} +0 -0
  362. /package/services/{tracks → media/tracks}/ContainerTrack.js +0 -0
  363. /package/services/{tracks → media/tracks}/ImageSubtitleTrack.js +0 -0
  364. /package/services/{tracks → media/tracks}/SubtitleTrack.js +0 -0
  365. /package/services/{tracks → media/tracks}/TextSubtitleTrack.js +0 -0
  366. /package/services/{tracks → media/tracks}/VideoTrack.js +0 -0
  367. /package/services/{tracks → media/tracks}/index.js +0 -0
  368. /package/services/{files → storage/files}/piece-from-whole-file.js +0 -0
  369. /package/services/{disk → storage}/keep.js +0 -0
  370. /package/services/{piece-store → storage/piece-store}/piece-lru.js +0 -0
  371. /package/services/{disk → storage}/returns.js +0 -0
  372. /package/services/{demand → torrent/demand}/DemandRegister.js +0 -0
  373. /package/services/{demand → torrent/demand}/Urgency.js +0 -0
  374. /package/services/{demand → torrent/demand}/Window.js +0 -0
  375. /package/services/{demand → torrent/demand}/index.js +0 -0
  376. /package/services/{demand → torrent/demand}/pieces.js +0 -0
  377. /package/services/{download → torrent/download}/withdraw-claim.js +0 -0
  378. /package/services/{supply-margin.js → torrent/supply-margin.js} +0 -0
  379. /package/services/{torrent-source-key.js → torrent/torrent-source-key.js} +0 -0
  380. /package/services/{torrent-worker → torrent/worker}/fastest-wires.js +0 -0
  381. /package/services/{torrent-worker → torrent/worker}/handle-state.js +0 -0
  382. /package/services/{nat-classifier.js → transport/nat-classifier.js} +0 -0
  383. /package/services/{registry-api.js → transport/registry-api.js} +0 -0
  384. /package/services/{tunnel-client.js → transport/tunnel-client.js} +0 -0
  385. /package/services/{priority → viewer}/WaitLedger.js +0 -0
@@ -29,11 +29,12 @@ import assert from "node:assert/strict";
29
29
  import { mkdtempSync, rmSync } from "node:fs";
30
30
  import os from "node:os";
31
31
  import path from "node:path";
32
- import { HlsSessionManager } from "../services/hls-session-manager.js";
33
- import { SourceFile } from "../services/source/SourceFile.js";
32
+ import { wireOutputs } from "../services/server/wire-outputs.js";
33
+ import { SourceFile } from "../services/media/SourceFile.js";
34
34
  import { ENCODE_EXIT } from "../services/encode/encode-exit.js";
35
+ import { outputSpec } from "./helpers/output-spec.js";
35
36
 
36
- const SESSION_ID = "cccccccc-0000-4000-8000-000000000001";
37
+ const SESSION_ID = "cccccccc00004000";
37
38
 
38
39
  /**
39
40
  * A session with one run in it, shaped as the manager expects.
@@ -45,19 +46,13 @@ function sessionWithARun(dirPath) {
45
46
  const run = { from: 0, to: -1, argsDescribed: "ffmpeg …", state: "ENDED_FAILED" };
46
47
  const session = {
47
48
  id: SESSION_ID,
49
+ spec: outputSpec({ transcodeVideo: false }),
48
50
  dirPath,
49
51
  state: "live",
50
52
  file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" }),
51
53
  get inputFile() { return this.file; },
52
54
  get audioFile() { return this.file; },
53
55
  outputKey: "output-under-test",
54
- consumers: new Set(["someone"]),
55
- viewers: new Map(),
56
- runs: new Set([run]),
57
- transcodeVideo: false,
58
- failedStartAt: -1,
59
- failedStartCount: 0,
60
- lastError: "",
61
56
  lastRequestedSegment: 0,
62
57
  progress: { updatedAt: 0, processedSeconds: 0, totalSeconds: 100, startPositionSeconds: 0 },
63
58
  lastAccessedAt: Date.now()
@@ -87,65 +82,121 @@ function failedFast(run, from) {
87
82
  test("a run that ended is still recognised as the session's own", (t) => {
88
83
  const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
89
84
  t.after(() => rmSync(dirPath, { recursive: true, force: true }));
90
- const manager = new HlsSessionManager({
85
+ const manager = wireOutputs({
91
86
  enabled: true,
92
87
  ffmpegBin: "ffmpeg",
93
88
  localBindHost: "127.0.0.1",
94
89
  localPort: 9090
95
90
  });
96
91
  const { session, run } = sessionWithARun(dirPath);
97
- manager.sessionsById.set(SESSION_ID, session);
92
+ manager.outputs.set(SESSION_ID, session);
93
+ manager.encodeOrchestrator.adopt(session.outputKey, run);
98
94
 
99
- manager.noteRunEnded(session, run, failedFast(run, 0));
95
+ manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
100
96
 
101
97
  // If the identity were read after the removal, nothing here would have been
102
98
  // written: the handler would have returned at its second line.
103
- assert.equal(session.failedStartCount, 1, "the failure was counted");
104
- assert.equal(session.failedStartAt, 0, "at the position it happened");
105
- assert.ok(session.lastError.length > 0, "and the session knows what went wrong");
99
+ assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 1, "the failure was counted");
100
+ assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).at, 0, "at the position it happened");
101
+ assert.ok(manager.encodeOrchestrator.errorOf(session.outputKey).length > 0, "and the encoding owner knows what went wrong");
102
+ assert.equal(
103
+ manager.encodeOrchestrator.stateOf(session.outputKey),
104
+ "ENDED_FAILED",
105
+ "the ending remains observable after the ended run leaves the registry"
106
+ );
106
107
  });
107
108
 
108
109
  test("the count runs at segment 0, which is where a first start happens", (t) => {
109
110
  const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
110
111
  t.after(() => rmSync(dirPath, { recursive: true, force: true }));
111
- const manager = new HlsSessionManager({
112
+ const manager = wireOutputs({
112
113
  enabled: true,
113
114
  ffmpegBin: "ffmpeg",
114
115
  localBindHost: "127.0.0.1",
115
116
  localPort: 9090
116
117
  });
117
118
  const { session } = sessionWithARun(dirPath);
118
- manager.sessionsById.set(SESSION_ID, session);
119
+ manager.outputs.set(SESSION_ID, session);
119
120
 
120
121
  for (let attempt = 1; attempt <= 3; attempt += 1) {
121
122
  const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
122
- session.runs.add(run);
123
- manager.noteRunEnded(session, run, failedFast(run, 0));
124
- assert.equal(session.failedStartCount, attempt);
123
+ manager.encodeOrchestrator.adopt(session.outputKey, run);
124
+ manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
125
+ assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, attempt);
126
+ assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), attempt < 3);
125
127
  }
128
+ assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 1), true, "another position has its own budget");
126
129
  });
127
130
 
128
131
  test("real work resets the count, so a transient failure is not permanent", (t) => {
129
132
  const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
130
133
  t.after(() => rmSync(dirPath, { recursive: true, force: true }));
131
- const manager = new HlsSessionManager({
134
+ const manager = wireOutputs({
132
135
  enabled: true,
133
136
  ffmpegBin: "ffmpeg",
134
137
  localBindHost: "127.0.0.1",
135
138
  localPort: 9090
136
139
  });
137
140
  const { session } = sessionWithARun(dirPath);
138
- manager.sessionsById.set(SESSION_ID, session);
141
+ manager.outputs.set(SESSION_ID, session);
139
142
 
140
143
  const quick = { from: 0, to: -1 };
141
- session.runs.add(quick);
142
- manager.noteRunEnded(session, quick, failedFast(quick, 0));
143
- assert.equal(session.failedStartCount, 1);
144
+ manager.encodeOrchestrator.adopt(session.outputKey, quick);
145
+ manager.encodeRuns.noteRunEnded(session, quick, failedFast(quick, 0));
146
+ assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 1);
144
147
 
145
148
  const lived = { from: 0, to: -1 };
146
- session.runs.add(lived);
147
- manager.noteRunEnded(session, lived, { ...failedFast(lived, 0), livedMs: 30_000 });
149
+ manager.encodeOrchestrator.adopt(session.outputKey, lived);
150
+ manager.encodeRuns.noteRunEnded(session, lived, { ...failedFast(lived, 0), livedMs: 30_000 });
148
151
 
149
- assert.equal(session.failedStartCount, 0, "a run that did real work is not a failing start");
150
- assert.equal(session.failedStartAt, -1);
152
+ assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 0, "a run that did real work is not a failing start");
153
+ assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).at, -1);
154
+ });
155
+
156
+ test("a failure belongs to the output it happened on, not to the next output of the same address", async (t) => {
157
+ const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
158
+ t.after(() => rmSync(dirPath, { recursive: true, force: true }));
159
+ const manager = wireOutputs({
160
+ enabled: true,
161
+ ffmpegBin: "ffmpeg",
162
+ localBindHost: "127.0.0.1",
163
+ localPort: 9090
164
+ });
165
+ t.after(() => manager.lifecycle.disposeAll());
166
+ const { session } = sessionWithARun(dirPath);
167
+ manager.outputs.set(SESSION_ID, session);
168
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
169
+ const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
170
+ manager.encodeOrchestrator.adopt(session.outputKey, run);
171
+ manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
172
+ }
173
+ assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), false, "the first output is blocked");
174
+
175
+ await manager.lifecycle.disposeSession(SESSION_ID);
176
+
177
+ // The same parameters opened again: the name follows from the key, so the
178
+ // new output has the same address. What failed on the disposed one is not a
179
+ // fact about this one — a viewer arriving is new information.
180
+ assert.equal(manager.encodeOrchestrator.stateOf(session.outputKey), "IDLE", "no ending is inherited");
181
+ assert.equal(manager.encodeOrchestrator.errorOf(session.outputKey), "", "no error is inherited");
182
+ assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), true, "no blocked position is inherited");
183
+ });
184
+
185
+ test("a run ending after its output was disposed leaves nothing behind for the next output", (t) => {
186
+ const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
187
+ t.after(() => rmSync(dirPath, { recursive: true, force: true }));
188
+ const manager = wireOutputs({
189
+ enabled: true,
190
+ ffmpegBin: "ffmpeg",
191
+ localBindHost: "127.0.0.1",
192
+ localPort: 9090
193
+ });
194
+ const { session, run } = sessionWithARun(dirPath);
195
+ manager.encodeOrchestrator.adopt(session.outputKey, run);
196
+ // Disposal removed the output from the registry; its process ends later.
197
+ manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
198
+
199
+ assert.equal(manager.encodeOrchestrator.stateOf(session.outputKey), "IDLE");
200
+ assert.equal(manager.encodeOrchestrator.errorOf(session.outputKey), "");
201
+ assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 0);
151
202
  });
@@ -10,7 +10,7 @@
10
10
  import assert from "node:assert/strict";
11
11
  import test from "node:test";
12
12
 
13
- import { askFastestWiresFor, canPlaceRequests, wiresForPiece } from "../services/torrent-worker/fastest-wires.js";
13
+ import { askFastestWiresFor, canPlaceRequests, wiresForPiece } from "../services/torrent/worker/fastest-wires.js";
14
14
 
15
15
  /**
16
16
  * @param {{ speed: number, has?: boolean, choking?: boolean, destroyed?: boolean }} options
@@ -11,7 +11,7 @@
11
11
  import assert from "node:assert/strict";
12
12
  import test from "node:test";
13
13
 
14
- import { parseFfmpegStreamCounts } from "../services/ffmpeg-banner.js";
14
+ import { parseFfmpegStreamCounts } from "../services/media/ffmpeg-banner.js";
15
15
 
16
16
  const FIELD_BANNER = `
17
17
  Input #0, matroska,webm, from 'http://127.0.0.1:9090/stream?sourceKey=b3f08efc':
@@ -15,9 +15,9 @@
15
15
 
16
16
  import test from "node:test";
17
17
  import assert from "node:assert/strict";
18
- import { TorrentPool, stateFileEdges, withdrawFileEdges } from "../services/torrent-pool.js";
19
- import { demandFor, forgetTorrent } from "../services/download/registry.js";
20
- import { Urgency } from "../services/demand/index.js";
18
+ import { TorrentPool, stateFileEdges, withdrawFileEdges } from "../services/torrent/torrent-pool.js";
19
+ import { demandFor, forgetTorrent } from "../services/torrent/download/registry.js";
20
+ import { Urgency } from "../services/torrent/demand/index.js";
21
21
 
22
22
  /** A torrent of two files, of which the second is the film. */
23
23
  function torrentOf({ length = 1_000_000 } = {}) {
@@ -11,7 +11,7 @@
11
11
 
12
12
  import test from "node:test";
13
13
  import assert from "node:assert/strict";
14
- import { fmp4Format } from "../services/segment-formats/fmp4.js";
14
+ import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
15
15
 
16
16
  /**
17
17
  * A piece shaped like the muxer's output. Sizes are what matters, not contents.
@@ -10,7 +10,7 @@
10
10
 
11
11
  import test from "node:test";
12
12
  import assert from "node:assert/strict";
13
- import { stampSegmentStartTime } from "../services/segment-formats/mp4-boxes.js";
13
+ import { stampSegmentStartTime } from "../services/encode/segment-formats/mp4-boxes.js";
14
14
 
15
15
  /**
16
16
  * @param {string} type
@@ -12,7 +12,7 @@
12
12
 
13
13
  import test from "node:test";
14
14
  import assert from "node:assert/strict";
15
- import { fmp4Format } from "../services/segment-formats/fmp4.js";
15
+ import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
16
16
  // Build a minimal init declaring two tracks, and fragments declaring one/two.
17
17
  function box(type, body) {
18
18
  const b = Buffer.alloc(8 + body.length);
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @file An output is named by the format it produces, and nothing may put
3
+ * pieces of another format under that name.
4
+ *
5
+ * Two ways that could still happen once the name became the format: a
6
+ * directory left by an earlier version, whose key named the box a viewer asked
7
+ * for and so cannot say what format is inside; and a hardware encoder failing,
8
+ * after which the proxy encodes with software while outputs named by the failed
9
+ * encoder are still being watched.
10
+ */
11
+
12
+ import test from "node:test";
13
+ import assert from "node:assert/strict";
14
+ import { existsSync, writeFileSync } from "node:fs";
15
+ import path from "node:path";
16
+ import { managerWithOwnStore } from "./helpers/manager.js";
17
+ import { outputSpec } from "./helpers/output-spec.js";
18
+ import { ENCODE_EXIT } from "../services/encode/encode-exit.js";
19
+ import { SourceFile } from "../services/media/SourceFile.js";
20
+
21
+ test("a directory whose key names an asked-for box is not adopted, and one naming a format is", (t) => {
22
+ const { store, root, manager, cleanup } = managerWithOwnStore();
23
+ t.after(cleanup);
24
+ const oldKey = "torrent:abc:fmt=fmp4:grid=even@0:video-only:v=0/enc:1280x720:budget";
25
+ const newKey = outputSpec({ sourceKey: "torrent:abc", transcodeVideo: true, width: 1280, height: 720, audioSeparate: true }).toKey();
26
+ const oldDir = store.directoryFor(oldKey);
27
+ const newDir = store.directoryFor(newKey);
28
+ writeFileSync(path.join(oldDir, "segment-00000.mp4"), Buffer.alloc(10));
29
+ writeFileSync(path.join(newDir, "segment-00000.mp4"), Buffer.alloc(10));
30
+
31
+ const result = manager.lifecycle.adoptSegmentsLeftBehind();
32
+
33
+ assert.equal(result.adopted, 1);
34
+ assert.equal(result.dropped, 1);
35
+ assert.equal(existsSync(oldDir), false, "the old directory goes");
36
+ assert.equal(existsSync(newDir), true, "the named format stays");
37
+ assert.ok(root);
38
+ });
39
+
40
+ test("when a hardware encoder fails, the outputs it named are closed rather than continued by software", async (t) => {
41
+ const { manager, cleanup } = managerWithOwnStore({
42
+ videoEncoder: { kind: "vaapi", name: "h264_vaapi", inputArgs: [] }
43
+ });
44
+ t.after(async () => {
45
+ await manager.lifecycle.disposeAll();
46
+ cleanup();
47
+ });
48
+ manager.encodeRuns.planEncodersNow = () => {};
49
+ manager.encodeRuns.planEncodersSoon = () => {};
50
+ const file = new SourceFile({ sourceKey: "torrent:abc", fileIndex: 0, name: "film.mkv" });
51
+ const record = (id, encoder) => {
52
+ const spec = outputSpec({ sourceKey: "torrent:abc", transcodeVideo: true, width: 1280, height: 720, encoder });
53
+ const output = { id, spec, get outputKey() { return this.spec.toKey(); }, file };
54
+ manager.outputs.set(id, output);
55
+ return output;
56
+ };
57
+ const failing = record("aaaaaaaa00000001", "h264_vaapi");
58
+ const software = record("aaaaaaaa00000002", "libx264");
59
+ const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
60
+ manager.encodeOrchestrator.adopt(failing.outputKey, run);
61
+
62
+ manager.encodeRuns.noteRunEnded(failing, run, {
63
+ address: failing.outputKey,
64
+ run,
65
+ ending: ENCODE_EXIT.FAILED,
66
+ from: 0,
67
+ to: -1,
68
+ livedMs: 30_000,
69
+ because: "vaapi failed",
70
+ lastError: "vaapi failed",
71
+ producedCount: 3
72
+ });
73
+ await new Promise((resolve) => setImmediate(resolve));
74
+
75
+ assert.equal(manager.videoEncoder.kind, "software");
76
+ assert.equal(manager.outputs.has(failing.id), false, "an output of the failed encoder is closed");
77
+ assert.equal(manager.outputs.has(software.id), true, "an output of another encoder is untouched");
78
+ });
@@ -6,7 +6,8 @@ import test from "node:test";
6
6
  import assert from "node:assert/strict";
7
7
  import os from "node:os";
8
8
 
9
- import { availableMemoryBytes, collectHealthMetrics } from "../services/health-collector.js";
9
+ import { collectHealthMetrics } from "../services/transport/health-collector.js";
10
+ import { availableMemoryBytes } from "../services/storage/machine-memory.js";
10
11
 
11
12
  test("free memory is what could be given out, not what is idle this instant", () => {
12
13
  const available = availableMemoryBytes();
@@ -27,7 +28,7 @@ test("free memory is what could be given out, not what is idle this instant", ()
27
28
  });
28
29
 
29
30
  test("the health report is three bounded numbers", () => {
30
- const metrics = collectHealthMetrics();
31
+ const metrics = collectHealthMetrics({ availableMemoryBytes });
31
32
  assert.ok(metrics.cpuLoad >= 0);
32
33
  assert.ok(metrics.memFree > 0 && metrics.memFree <= 1);
33
34
  assert.ok(Number.isInteger(metrics.uptime) && metrics.uptime >= 0);
@@ -11,23 +11,22 @@
11
11
 
12
12
  import test from "node:test";
13
13
  import assert from "node:assert/strict";
14
- import { Timeline } from "../services/output/Timeline.js";
14
+ import { Timeline } from "../services/encode/output/Timeline.js";
15
15
  import { mkdtemp, rm } from "node:fs/promises";
16
16
  import os from "node:os";
17
17
  import path from "node:path";
18
- import { HlsSessionManager } from "../services/hls-session-manager.js";
19
- import { fmp4Format } from "../services/segment-formats/fmp4.js";
20
- import { viewerOf } from "../services/viewer/Viewer.js";
18
+ import { wireOutputs } from "../services/server/wire-outputs.js";
19
+ import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
21
20
 
22
- const SESSION_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
21
+ const SESSION_ID = "aaaaaaaabbbbcccc";
23
22
  const SEGMENT_SECONDS = 4;
24
23
 
25
24
  /**
26
- * @returns {Promise<{ manager: HlsSessionManager, dirPath: string }>}
25
+ * @returns {Promise<{ manager: object, dirPath: string }>}
27
26
  */
28
27
  async function managerWithSession() {
29
28
  const dirPath = await mkdtemp(path.join(os.tmpdir(), "held-request-"));
30
- const manager = new HlsSessionManager({
29
+ const manager = wireOutputs({
31
30
  enabled: true,
32
31
  ffmpegBin: "ffmpeg",
33
32
  localBindHost: "127.0.0.1",
@@ -47,10 +46,10 @@ async function managerWithSession() {
47
46
  segmentCount: 200,
48
47
  useSyntheticPlaylist: true
49
48
  };
50
- manager.sessionsById.set(SESSION_ID, session);
49
+ manager.outputs.set(SESSION_ID, session);
51
50
  // The viewer is at segment #25, and says so themselves.
52
- viewerOf(session, "").moveTo(25 * SEGMENT_SECONDS);
53
- viewerOf(session, "").playing = false;
51
+ manager.viewers.of(session, "viewer").moveTo(25 * SEGMENT_SECONDS);
52
+ manager.viewers.of(session, "viewer").playing = false;
54
53
  return { manager, dirPath };
55
54
  }
56
55
 
@@ -70,9 +69,9 @@ test("the width is the encoder's own look-ahead, in segments", async (t) => {
70
69
 
71
70
  const width = Math.ceil(manager.lookaheadSeconds / manager.segmentDurationSec);
72
71
  assert.equal(width, 30, "120 s of look-ahead over 4 s segments");
73
- assert.equal(manager.requestStillWanted(SESSION_ID, segment(25 + width)), true, "the far edge");
72
+ assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(25 + width)), true, "the far edge");
74
73
  assert.equal(
75
- manager.requestStillWanted(SESSION_ID, segment(25 + width + 1)),
74
+ manager.serving.requestStillWanted(SESSION_ID, segment(25 + width + 1)),
76
75
  false,
77
76
  "past everything the encoder is allowed to have produced"
78
77
  );
@@ -87,7 +86,7 @@ test("a request the old eight-segment width would have refused is kept", async (
87
86
  // #34 is nine segments ahead of the viewer — inside a 120 s cushion and
88
87
  // outside the eight the width used to be. This is the request a browser
89
88
  // holding the whole cushion makes constantly.
90
- assert.equal(manager.requestStillWanted(SESSION_ID, segment(34)), true);
89
+ assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(34)), true);
91
90
  });
92
91
 
93
92
  test("a segment behind the viewer is still released", async (t) => {
@@ -96,7 +95,7 @@ test("a segment behind the viewer is still released", async (t) => {
96
95
  await rm(dirPath, { recursive: true, force: true });
97
96
  });
98
97
 
99
- assert.equal(manager.requestStillWanted(SESSION_ID, segment(24)), false);
98
+ assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(24)), false);
100
99
  });
101
100
 
102
101
  test("a seek by one viewer does not release the request held for another", async (t) => {
@@ -108,8 +107,8 @@ test("a seek by one viewer does not release the request held for another", async
108
107
  // One is at segment #25, the other far ahead at #150 — inside the fixture's
109
108
  // own 200-segment timeline, since a position past the end of the grid is
110
109
  // clamped to it and would prove nothing about the width.
111
- manager.requestSeek(SESSION_ID, 100, "behind");
112
- manager.requestSeek(SESSION_ID, 600, "ahead");
110
+ manager.viewerRequests.requestSeek(SESSION_ID, 100, "behind");
111
+ manager.viewerRequests.requestSeek(SESSION_ID, 600, "ahead");
113
112
 
114
113
  // A SEEK DOES ONE THING: it puts the viewer where they now are. It used to
115
114
  // write that position into five places, this session's shared field among
@@ -120,12 +119,12 @@ test("a seek by one viewer does not release the request held for another", async
120
119
  assert.equal(manager.viewers.get("ahead").positionSeconds(), 600);
121
120
 
122
121
  assert.equal(
123
- manager.requestStillWanted(SESSION_ID, segment(26), "behind"),
122
+ manager.serving.requestStillWanted(SESSION_ID, segment(26), "behind"),
124
123
  true,
125
124
  "the segment the viewer behind is waiting for is still theirs to wait for"
126
125
  );
127
126
  assert.equal(
128
- manager.requestStillWanted(SESSION_ID, segment(26), "ahead"),
127
+ manager.serving.requestStillWanted(SESSION_ID, segment(26), "ahead"),
129
128
  false,
130
129
  "and for the one in front it is a place they have left"
131
130
  );
@@ -140,9 +139,9 @@ test("a viewer's own head moves with their seek", async (t) => {
140
139
  // Their last request was at #25; they jump to 600 s, which is #150. The
141
140
  // segment at the target must be wanted — refusing it there is the freeze of
142
141
  // 2026-08-18.
143
- manager.requestSeek(SESSION_ID, 600, "viewer");
144
- assert.equal(manager.requestStillWanted(SESSION_ID, segment(150), "viewer"), true);
145
- assert.equal(manager.requestStillWanted(SESSION_ID, segment(25), "viewer"), false);
142
+ manager.viewerRequests.requestSeek(SESSION_ID, 600, "viewer");
143
+ assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(150), "viewer"), true);
144
+ assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(25), "viewer"), false);
146
145
  });
147
146
 
148
147
  test("a viewer nobody can name is judged against the one shared position", async (t) => {
@@ -153,6 +152,6 @@ test("a viewer nobody can name is judged against the one shared position", async
153
152
 
154
153
  // A plain HTTP transport builds its own URLs and carries no id. The old
155
154
  // behaviour is what remains, which for a single viewer is the same thing.
156
- assert.equal(manager.requestStillWanted(SESSION_ID, segment(26), ""), true);
157
- assert.equal(manager.requestStillWanted(SESSION_ID, segment(24), ""), false);
155
+ assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(26), ""), true);
156
+ assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(24), ""), false);
158
157
  });