@torrent-tv/proxy 2.85.1 → 2.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/CHANGELOG.md +2138 -1909
  2. package/CLAUDE.md +318 -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/container-architecture.md +218 -213
  7. package/docs/download-architecture.md +7 -7
  8. package/docs/encode-architecture.md +287 -11
  9. package/docs/encode-run-state.md +2 -2
  10. package/docs/logs.md +97 -97
  11. package/docs/{disk-architecture.md → storage-architecture.md} +107 -1
  12. package/openspec/changes/adaptive-bitrate/design.md +2 -2
  13. package/openspec/changes/adaptive-bitrate/proposal.md +1 -1
  14. package/openspec/changes/adaptive-bitrate/tasks.md +1 -1
  15. package/openspec/changes/chunked-request-bodies/design.md +1 -1
  16. package/openspec/changes/chunked-request-bodies/proposal.md +1 -1
  17. package/openspec/changes/chunked-request-bodies/tasks.md +1 -1
  18. package/openspec/changes/cold-start/design.md +1 -1
  19. package/openspec/changes/disk-cap/proposal.md +1 -1
  20. package/openspec/changes/proxy-observability/proposal.md +2 -2
  21. package/openspec/changes/track-selection/proposal.md +1 -1
  22. package/openspec/changes/transcode-quality/proposal.md +1 -1
  23. package/package.json +2 -2
  24. package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
  25. package/research/double-spawn-2026-09-10.md +171 -171
  26. package/routes/api/playback-plan/post.js +2 -2
  27. package/routes/api/sources/files/get.js +1 -1
  28. package/routes/api/sources/stats/get.js +1 -1
  29. package/routes/api/sources/warm/post.js +10 -3
  30. package/routes/api/subtitles/get.js +230 -230
  31. package/routes/api/transcode-sessions/fragment-far/post.js +3 -3
  32. package/routes/api/transcode-sessions/net-report/post.js +129 -94
  33. package/routes/api/transcode-sessions/post.js +160 -139
  34. package/routes/api/transcode-sessions/progress/get.js +3 -3
  35. package/routes/api/transcode-sessions/release/post.js +3 -3
  36. package/routes/api/transcode-sessions/seek/post.js +12 -3
  37. package/routes/stream/get.js +2 -2
  38. package/routes/transcode/audio-file/get.js +15 -6
  39. package/routes/transcode/audio-warm/get.js +8 -5
  40. package/routes/transcode/session-file/get.js +429 -268
  41. package/routes/transcode/variant-file/get.js +47 -7
  42. package/routes/transcode/variant-warm/get.js +18 -6
  43. package/scripts/render-run-graph.js +1 -1
  44. package/server.js +720 -465
  45. package/services/encode/CoverageMap.js +428 -428
  46. package/services/encode/CushionReport.js +345 -0
  47. package/services/encode/EncodeAdmission.js +332 -0
  48. package/services/{orchestrators → encode}/EncodeOrchestrator.js +285 -17
  49. package/services/encode/EncodeRun.js +16 -9
  50. package/services/encode/EncodeRuns.js +1219 -0
  51. package/services/encode/Encoder.js +18 -2
  52. package/services/encode/EncoderSelection.js +38 -0
  53. package/services/encode/LocalObservations.js +359 -0
  54. package/services/encode/NvencEncoder.js +5 -0
  55. package/services/encode/OutputOpening.js +745 -0
  56. package/services/encode/OutputTimes.js +579 -0
  57. package/services/encode/Renditions.js +2162 -0
  58. package/services/encode/RunProgress.js +84 -0
  59. package/services/encode/SoftwareEncoder.js +12 -7
  60. package/services/encode/args.js +402 -186
  61. package/services/encode/calibration.js +361 -0
  62. package/services/encode/contention.js +128 -128
  63. package/services/encode/encode-run-state.js +16 -20
  64. package/services/encode/fingerprint.js +322 -0
  65. package/services/{hwaccel.js → encode/hwaccel.js} +82 -42
  66. package/services/{learned-median.js → encode/learned-median.js} +11 -0
  67. package/services/encode/output/EncodedOutput.js +57 -0
  68. package/services/encode/output/Output.js +131 -0
  69. package/services/{output/LiveOutputs.js → encode/output/OutputCatalog.js} +141 -40
  70. package/services/encode/output/OutputSpec.js +390 -0
  71. package/services/{output → encode/output}/Timeline.js +12 -37
  72. package/services/encode/output/index.js +20 -0
  73. package/services/encode/output/ladder.js +32 -0
  74. package/services/{output → encode/output}/playlists.js +3 -0
  75. package/services/encode/output-key-format.js +21 -0
  76. package/services/encode/quality/EncodeCost.js +1083 -0
  77. package/services/encode/quality/HostLoad.js +525 -0
  78. package/services/encode/quality/HostTimings.js +249 -0
  79. package/services/encode/quality/OutputQualityState.js +28 -0
  80. package/services/encode/quality/QualityController.js +931 -0
  81. package/services/encode/quality/QualityOffer.js +416 -0
  82. package/services/encode/quality/drain-threat.js +47 -0
  83. package/services/encode/quality/link-budget.js +254 -0
  84. package/services/encode/quality/output-format.js +266 -0
  85. package/services/encode/quality/serving-output.js +216 -0
  86. package/services/encode/quality/visible-rung.js +57 -0
  87. package/services/encode/run-command.js +23 -20
  88. package/services/encode/run-inputs.js +42 -0
  89. package/services/{segment-formats → encode/segment-formats}/fmp4.js +63 -0
  90. package/services/{segment-formats → encode/segment-formats}/index.js +3 -3
  91. package/services/encode/segment-formats/init-compat.js +324 -0
  92. package/services/{segment-formats → encode/segment-formats}/mp4-boxes.js +8 -3
  93. package/services/encode/throughput.js +97 -0
  94. package/services/{orchestrators → media}/ContainerOrchestrator.js +45 -7
  95. package/services/media/KeyframeTables.js +316 -0
  96. package/services/{source → media}/SourceFile.js +7 -55
  97. package/services/{torrent-worker/subtitle-cues.js → media/SubtitleCues.js} +103 -132
  98. package/services/media/SubtitleOrchestrator.js +167 -0
  99. package/services/{audio-inventory.js → media/audio-inventory.js} +7 -1
  100. package/services/{container → media/container}/AviContainer.js +266 -266
  101. package/services/{container → media/container}/Container.js +596 -601
  102. package/services/media/container/ContainerFactory.js +56 -0
  103. package/services/media/container/KeyframeTable.js +272 -0
  104. package/services/{container → media/container}/MatroskaContainer.js +1485 -1485
  105. package/services/{container → media/container}/Mp4Container.js +1244 -1244
  106. package/services/{container → media/container}/SubtitleFileContainer.js +322 -322
  107. package/services/media/keyframe-probe.js +157 -0
  108. package/services/media/media-info-probe.js +93 -0
  109. package/services/{playback-planner.js → media/playback-planner.js} +82 -39
  110. package/services/{tracks → media/tracks}/AudioTrack.js +131 -131
  111. package/services/{tracks → media/tracks}/language-detect.js +174 -174
  112. package/services/server/OutputLifecycle.js +418 -0
  113. package/services/server/SegmentServing.js +1146 -0
  114. package/services/server/ViewerRequests.js +522 -0
  115. package/services/server/controllers/PlaybackController.js +36 -0
  116. package/services/{controllers → server/controllers}/SubtitleController.js +17 -33
  117. package/services/server/wire-outputs.js +958 -0
  118. package/services/storage/Diagnostics.js +202 -0
  119. package/services/storage/MachineBudget.js +235 -0
  120. package/services/{piece-store → storage}/allowance.js +107 -107
  121. package/services/{core-dumps.js → storage/core-dumps.js} +1 -1
  122. package/services/{files → storage/files}/CompletedFiles.js +116 -1
  123. package/services/{disk → storage}/free.js +33 -0
  124. package/services/storage/machine-memory.js +56 -0
  125. package/services/{memory-report.js → storage/memory-report.js} +575 -596
  126. package/services/{piece-store → storage/piece-store}/piece-disk-store.js +0 -7
  127. package/services/{piece-store → storage/piece-store}/shared-piece-store.js +28 -21
  128. package/services/{encode → storage/segment-store}/SegmentStore.js +395 -8
  129. package/services/storage/wire.js +175 -0
  130. package/services/{download → torrent/download}/SwarmSelection.js +2 -2
  131. package/services/{download → torrent/download}/registry.js +2 -2
  132. package/services/torrent/files.js +1 -1
  133. package/services/torrent/piece-store-of.js +29 -0
  134. package/services/{torrent-pool.js → torrent/torrent-pool.js} +26 -119
  135. package/services/{torrent-worker → torrent/worker}/background-fill.js +2 -2
  136. package/services/{torrent-worker → torrent/worker}/channel.js +271 -250
  137. package/services/{torrent-worker → torrent/worker}/client.js +77 -58
  138. package/services/torrent/worker/held-bytes.js +155 -0
  139. package/services/{torrent-worker → torrent/worker}/install-webrtc-shim.js +1 -1
  140. package/services/{torrent-worker → torrent/worker}/piece-reader.js +21 -30
  141. package/services/{torrent-worker → torrent/worker}/pool-adapter.js +101 -137
  142. package/services/{torrent-worker → torrent/worker}/protocol.js +26 -30
  143. package/services/torrent/worker/resume-warm.js +107 -0
  144. package/services/{torrent-worker → torrent/worker}/webrtc-shim.js +2 -2
  145. package/services/{torrent-worker → torrent/worker}/worker.js +68 -159
  146. package/services/{data-channel-handler.js → transport/data-channel-handler.js} +1356 -1356
  147. package/services/{delivery-probe.js → transport/delivery-probe.js} +842 -842
  148. package/services/{health-collector.js → transport/health-collector.js} +6 -35
  149. package/services/{packet-witness.js → transport/packet-witness.js} +808 -784
  150. package/services/{port-mapper.js → transport/port-mapper.js} +1 -1
  151. package/services/{usrsctp-state.js → transport/usrsctp-state.js} +1 -1
  152. package/services/{webrtc-manager.js → transport/webrtc-manager.js} +1 -1
  153. package/services/viewer/Assignments.js +340 -0
  154. package/services/{priority → viewer}/PriorityMap.js +390 -390
  155. package/services/{priority → viewer}/PriorityOrchestrator.js +325 -320
  156. package/services/viewer/Viewer.js +657 -489
  157. package/services/viewer/Viewers.js +368 -257
  158. package/services/viewer/active-output.js +37 -54
  159. package/services/viewer/audio-start.js +46 -0
  160. package/services/viewer/buffer-trend.js +81 -0
  161. package/services/viewer/choices.js +647 -0
  162. package/services/viewer/positions.js +126 -120
  163. package/services/viewer/report-intake.js +46 -44
  164. package/test/asset-paths.test.js +28 -0
  165. package/test/audio-inventory.test.js +3 -3
  166. package/test/audio-track-bitrate.test.js +40 -0
  167. package/test/auto-quality-step.test.js +707 -518
  168. package/test/available-share.test.js +1 -1
  169. package/test/background-fill.test.js +1 -1
  170. package/test/client-log-file.test.js +110 -110
  171. package/test/completed-files.test.js +1 -1
  172. package/test/concurrent-cost.test.js +13 -3
  173. package/test/container-media-info.test.js +3 -3
  174. package/test/core-dumps.test.js +1 -1
  175. package/test/coverage-follows-the-disk.test.js +40 -8
  176. package/test/cue-push-carries-what-the-walk-found.test.js +58 -0
  177. package/test/cut-times-timeline.test.js +2 -2
  178. package/test/cuts-follow-published-grid.test.js +9 -8
  179. package/test/data-channel-frame.test.js +1 -1
  180. package/test/dead-torrent-handle.test.js +1 -1
  181. package/test/declared-rates.test.js +2 -2
  182. package/test/declared-tracks.test.js +2 -2
  183. package/test/decode-cost-fit.test.js +1 -1
  184. package/test/decode-cost.test.js +34 -25
  185. package/test/delivery-probe.test.js +1 -1
  186. package/test/demand-register.test.js +4 -4
  187. package/test/dht-bootstrap.test.js +1 -1
  188. package/test/drain-threat.test.js +65 -0
  189. package/test/duplicate-add.test.js +1 -1
  190. package/test/encode-admission.test.js +146 -0
  191. package/test/encode-cost.test.js +199 -10
  192. package/test/encode-orchestrator.test.js +39 -1
  193. package/test/encode-plan-viewers.test.js +2 -2
  194. package/test/encode-run.test.js +2 -2
  195. package/test/encode-slope.test.js +1 -1
  196. package/test/encoded-output.test.js +39 -0
  197. package/test/encoder-readings.test.js +1 -1
  198. package/test/every-claimant-is-told-its-share.test.js +152 -0
  199. package/test/failed-encoder-still-serves-what-exists.test.js +354 -0
  200. package/test/failed-start-breaker.test.js +81 -30
  201. package/test/fastest-wires.test.js +1 -1
  202. package/test/ffmpeg-stream-counts.test.js +1 -1
  203. package/test/file-edges.test.js +3 -3
  204. package/test/fmp4-self-contained.test.js +1 -1
  205. package/test/fmp4-timestamps.test.js +1 -1
  206. package/test/fmp4-track-completeness.test.js +1 -1
  207. package/test/format-keyed-outputs.test.js +78 -0
  208. package/test/health-metrics.test.js +3 -2
  209. package/test/held-request-width.test.js +22 -23
  210. package/test/helpers/encode-run.js +136 -136
  211. package/test/helpers/held-file.js +29 -0
  212. package/test/helpers/manager.js +57 -0
  213. package/test/helpers/output-spec.js +63 -0
  214. package/test/host-load.test.js +1 -1
  215. package/test/index-accuracy-is-about-the-file.test.js +128 -0
  216. package/test/init-compat.test.js +219 -0
  217. package/test/input-lost-quiets-the-plan.test.js +1 -1
  218. package/test/input-unavailable.test.js +1 -1
  219. package/test/keeping-period.test.js +4 -4
  220. package/test/keyframe-index-accuracy.test.js +97 -45
  221. package/test/keyframe-table-budget.test.js +28 -33
  222. package/test/keyframes-belong-to-the-file.test.js +174 -57
  223. package/test/learned-median.test.js +1 -1
  224. package/test/letting-go-asks-who-is-watching.test.js +191 -0
  225. package/test/limit-row-by-area.test.js +60 -0
  226. package/test/link-budget.test.js +111 -0
  227. package/test/link-probe.test.js +56 -56
  228. package/test/local-encoding-data.test.js +273 -0
  229. package/test/look-ahead-coverage.test.js +1 -1
  230. package/test/manager-surface.test.js +294 -0
  231. package/test/matroska-blocks.test.js +0 -0
  232. package/test/matroska-cues-track.test.js +1 -1
  233. package/test/memory-budget.test.js +2 -2
  234. package/test/mp4-composition-times.test.js +0 -0
  235. package/test/mp4-subtitles.test.js +1 -1
  236. package/test/one-authority.test.js +45 -41
  237. package/test/one-budget.test.js +149 -0
  238. package/test/one-output-one-session.test.js +204 -0
  239. package/test/{disk-space.test.js → one-resource-divided.test.js} +30 -10
  240. package/test/one-serving-output-per-height.test.js +458 -0
  241. package/test/orchestrator-wired.test.js +46 -45
  242. package/test/output-catalog.test.js +39 -0
  243. package/test/output-shape.test.js +4 -51
  244. package/test/output-spec.test.js +251 -157
  245. package/test/output-speed.test.js +17 -15
  246. package/test/outputs-being-prepared.test.js +40 -0
  247. package/test/packet-witness-ring.test.js +2 -2
  248. package/test/packet-witness.test.js +1 -1
  249. package/test/piece-disk-store.test.js +2 -2
  250. package/test/piece-from-whole-file.test.js +1 -1
  251. package/test/piece-lru.test.js +1 -1
  252. package/test/piece-reader.test.js +2 -2
  253. package/test/piece-store-eviction.test.js +1 -1
  254. package/test/piece-store-never-refuses.test.js +1 -1
  255. package/test/piece-store-reservations.test.js +1 -1
  256. package/test/piece-store-slow-disk.test.js +1 -1
  257. package/test/piece-tail.test.js +1 -1
  258. package/test/plan-host-timings.test.js +1 -1
  259. package/test/plan-is-steady.test.js +225 -225
  260. package/test/priority-map-download.test.js +4 -4
  261. package/test/priority-map-emptied.test.js +204 -205
  262. package/test/priority-map-per-output.test.js +215 -209
  263. package/test/priority-map.test.js +1 -1
  264. package/test/probe-wedge-certainty.test.js +1 -1
  265. package/test/produced-copy-choice.test.js +15 -60
  266. package/test/quality-offer.test.js +162 -0
  267. package/test/quality-variants.test.js +1229 -1167
  268. package/test/rate-control-in-the-output.test.js +180 -0
  269. package/test/read-survives-withdrawal.test.js +2 -2
  270. package/test/read-window.test.js +10 -3
  271. package/test/resume-warm.test.js +1 -1
  272. package/test/run-inputs.test.js +80 -0
  273. package/test/run-position-follows-published-grid.test.js +13 -11
  274. package/test/run-progress.test.js +65 -0
  275. package/test/same-height-switch.test.js +664 -0
  276. package/test/seek-frees-the-viewers-requests.test.js +217 -0
  277. package/test/seek-landing.test.js +13 -13
  278. package/test/seek-target-not-superseded.test.js +28 -29
  279. package/test/segment-closed-on-both-branches.test.js +96 -0
  280. package/test/segment-cut-times.test.js +1 -1
  281. package/test/segment-hold-supersede.test.js +28 -35
  282. package/test/segment-judged-by-its-own-header.test.js +221 -0
  283. package/test/segment-serve-wiring.test.js +130 -39
  284. package/test/segment-store-eviction.test.js +22 -7
  285. package/test/segment-store.test.js +98 -14
  286. package/test/segment-true-start.test.js +1 -1
  287. package/test/segments-are-shared.test.js +17 -16
  288. package/test/send-chunk-bytes.test.js +1 -1
  289. package/test/serving-existing-output.test.js +102 -0
  290. package/test/serving-output.test.js +102 -0
  291. package/test/shared-piece-store.test.js +1 -1
  292. package/test/source-file.test.js +1 -1
  293. package/test/stale-request-after-seek.test.js +17 -21
  294. package/test/startup-readings.test.js +10 -5
  295. package/test/subtitle-cue-framing.test.js +5 -5
  296. package/test/subtitle-cue-source.test.js +36 -38
  297. package/test/subtitle-cue-walk.test.js +11 -10
  298. package/test/subtitle-defaults.test.js +1 -1
  299. package/test/subtitle-language.test.js +4 -4
  300. package/test/subtitle-track-numbering.test.js +5 -4
  301. package/test/supply-margin.test.js +1 -1
  302. package/test/swarm-follows-readers.test.js +3 -3
  303. package/test/swarm-reach.test.js +1 -1
  304. package/test/swarm-selection.test.js +3 -3
  305. package/test/tail-duplication.test.js +1 -1
  306. package/test/the-channel-has-both-ends.test.js +91 -0
  307. package/test/the-torrent-thread-serves-bytes.test.js +107 -0
  308. package/test/thread-pool.test.js +3 -3
  309. package/test/timeline.test.js +1 -12
  310. package/test/torrent-cost.test.js +1 -1
  311. package/test/tracks-begin-together.test.js +38 -24
  312. package/test/tunnel-renewal.test.js +1 -1
  313. package/test/two-viewers-one-picture.test.js +120 -87
  314. package/test/upload-hurry.test.js +3 -3
  315. package/test/usrsctp-state.test.js +1 -1
  316. package/test/video-facts.test.js +1 -1
  317. package/test/viewer-assignments.test.js +143 -0
  318. package/test/viewer-outputs.test.js +60 -52
  319. package/test/viewer-presence.test.js +22 -25
  320. package/test/viewer-requests-admission.test.js +89 -0
  321. package/test/viewer-states.test.js +189 -172
  322. package/test/viewer-subtitle-subscription.test.js +1 -1
  323. package/test/viewer.test.js +139 -136
  324. package/test/viewing-generations.test.js +390 -0
  325. package/test/visible-rung.test.js +37 -0
  326. package/test/wait-ledger.test.js +1 -1
  327. package/test/webrtc-shim.test.js +3 -3
  328. package/test/wedge-certainty.test.js +2 -2
  329. package/test/withdraw-piece-claim.test.js +2 -2
  330. package/test/worker-channel.test.js +1 -31
  331. package/utils/client-log-file.js +151 -151
  332. package/scripts/read-container-index.mjs +0 -108
  333. package/services/container/ContainerFactory.js +0 -122
  334. package/services/controllers/PlaybackController.js +0 -33
  335. package/services/disk/DiskSpace.js +0 -150
  336. package/services/disk/wire.js +0 -61
  337. package/services/hls-session-manager.js +0 -9689
  338. package/services/orchestrators/SubtitleOrchestrator.js +0 -160
  339. package/services/output/Output.js +0 -94
  340. package/services/output/OutputSpec.js +0 -202
  341. package/services/output/index.js +0 -1
  342. package/services/output/ladder.js +0 -26
  343. package/services/produced-index.js +0 -222
  344. package/services/quality/EncodeCost.js +0 -555
  345. package/services/torrent-worker/container-tracks.js +0 -414
  346. package/test/produced-index.test.js +0 -142
  347. /package/{services → bin}/thread-pool.js +0 -0
  348. /package/services/{available-share.js → encode/available-share.js} +0 -0
  349. /package/services/{decode-cost-fit.js → encode/decode-cost-fit.js} +0 -0
  350. /package/services/{encoder-readings.js → encode/encoder-readings.js} +0 -0
  351. /package/services/{host-load.js → encode/host-load.js} +0 -0
  352. /package/services/{output → encode/output}/cut-grid.js +0 -0
  353. /package/services/{output → encode/output}/rates.js +0 -0
  354. /package/services/{segment-formats → encode/segment-formats}/mpegts.js +0 -0
  355. /package/services/{torrent-cost.js → encode/torrent-cost.js} +0 -0
  356. /package/services/{container → media/container}/ebml-reader.js +0 -0
  357. /package/services/{ffmpeg-banner.js → media/ffmpeg-banner.js} +0 -0
  358. /package/services/{tracks → media/tracks}/ContainerTrack.js +0 -0
  359. /package/services/{tracks → media/tracks}/ImageSubtitleTrack.js +0 -0
  360. /package/services/{tracks → media/tracks}/SubtitleTrack.js +0 -0
  361. /package/services/{tracks → media/tracks}/TextSubtitleTrack.js +0 -0
  362. /package/services/{tracks → media/tracks}/VideoTrack.js +0 -0
  363. /package/services/{tracks → media/tracks}/index.js +0 -0
  364. /package/services/{files → storage/files}/piece-from-whole-file.js +0 -0
  365. /package/services/{disk → storage}/keep.js +0 -0
  366. /package/services/{piece-store → storage/piece-store}/piece-lru.js +0 -0
  367. /package/services/{disk → storage}/returns.js +0 -0
  368. /package/services/{demand → torrent/demand}/DemandRegister.js +0 -0
  369. /package/services/{demand → torrent/demand}/Urgency.js +0 -0
  370. /package/services/{demand → torrent/demand}/Window.js +0 -0
  371. /package/services/{demand → torrent/demand}/index.js +0 -0
  372. /package/services/{demand → torrent/demand}/pieces.js +0 -0
  373. /package/services/{download → torrent/download}/withdraw-claim.js +0 -0
  374. /package/services/{supply-margin.js → torrent/supply-margin.js} +0 -0
  375. /package/services/{torrent-source-key.js → torrent/torrent-source-key.js} +0 -0
  376. /package/services/{torrent-worker → torrent/worker}/fastest-wires.js +0 -0
  377. /package/services/{torrent-worker → torrent/worker}/handle-state.js +0 -0
  378. /package/services/{nat-classifier.js → transport/nat-classifier.js} +0 -0
  379. /package/services/{registry-api.js → transport/registry-api.js} +0 -0
  380. /package/services/{tunnel-client.js → transport/tunnel-client.js} +0 -0
  381. /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
  });