@torrent-tv/proxy 2.86.0 → 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 (382) hide show
  1. package/CHANGELOG.md +2138 -1915
  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 -483
  157. package/services/viewer/Viewers.js +368 -257
  158. package/services/viewer/active-output.js +37 -54
  159. package/services/viewer/audio-start.js +4 -3
  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 -45
  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 -515
  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 -202
  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 -9608
  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/services/viewer/link-readings.js +0 -56
  347. package/test/produced-index.test.js +0 -142
  348. /package/{services → bin}/thread-pool.js +0 -0
  349. /package/services/{available-share.js → encode/available-share.js} +0 -0
  350. /package/services/{decode-cost-fit.js → encode/decode-cost-fit.js} +0 -0
  351. /package/services/{encoder-readings.js → encode/encoder-readings.js} +0 -0
  352. /package/services/{host-load.js → encode/host-load.js} +0 -0
  353. /package/services/{output → encode/output}/cut-grid.js +0 -0
  354. /package/services/{output → encode/output}/rates.js +0 -0
  355. /package/services/{segment-formats → encode/segment-formats}/mpegts.js +0 -0
  356. /package/services/{torrent-cost.js → encode/torrent-cost.js} +0 -0
  357. /package/services/{container → media/container}/ebml-reader.js +0 -0
  358. /package/services/{ffmpeg-banner.js → media/ffmpeg-banner.js} +0 -0
  359. /package/services/{tracks → media/tracks}/ContainerTrack.js +0 -0
  360. /package/services/{tracks → media/tracks}/ImageSubtitleTrack.js +0 -0
  361. /package/services/{tracks → media/tracks}/SubtitleTrack.js +0 -0
  362. /package/services/{tracks → media/tracks}/TextSubtitleTrack.js +0 -0
  363. /package/services/{tracks → media/tracks}/VideoTrack.js +0 -0
  364. /package/services/{tracks → media/tracks}/index.js +0 -0
  365. /package/services/{files → storage/files}/piece-from-whole-file.js +0 -0
  366. /package/services/{disk → storage}/keep.js +0 -0
  367. /package/services/{piece-store → storage/piece-store}/piece-lru.js +0 -0
  368. /package/services/{disk → storage}/returns.js +0 -0
  369. /package/services/{demand → torrent/demand}/DemandRegister.js +0 -0
  370. /package/services/{demand → torrent/demand}/Urgency.js +0 -0
  371. /package/services/{demand → torrent/demand}/Window.js +0 -0
  372. /package/services/{demand → torrent/demand}/index.js +0 -0
  373. /package/services/{demand → torrent/demand}/pieces.js +0 -0
  374. /package/services/{download → torrent/download}/withdraw-claim.js +0 -0
  375. /package/services/{supply-margin.js → torrent/supply-margin.js} +0 -0
  376. /package/services/{torrent-source-key.js → torrent/torrent-source-key.js} +0 -0
  377. /package/services/{torrent-worker → torrent/worker}/fastest-wires.js +0 -0
  378. /package/services/{torrent-worker → torrent/worker}/handle-state.js +0 -0
  379. /package/services/{nat-classifier.js → transport/nat-classifier.js} +0 -0
  380. /package/services/{registry-api.js → transport/registry-api.js} +0 -0
  381. /package/services/{tunnel-client.js → transport/tunnel-client.js} +0 -0
  382. /package/services/{priority → viewer}/WaitLedger.js +0 -0
@@ -9,7 +9,7 @@
9
9
  import assert from "node:assert/strict";
10
10
  import test from "node:test";
11
11
 
12
- import { availableShareFrom, correctForAvailability } from "../services/available-share.js";
12
+ import { availableShareFrom, correctForAvailability } from "../services/encode/available-share.js";
13
13
 
14
14
  test("only the work nobody has been charged for is subtracted", () => {
15
15
  // The field reading: the box is saturated, our encoder has more than half of
@@ -11,7 +11,7 @@
11
11
  import assert from "node:assert/strict";
12
12
  import test from "node:test";
13
13
  import { EventEmitter } from "node:events";
14
- import { fillFileInBackground, fillIsRunning } from "../services/torrent-worker/background-fill.js";
14
+ import { fillFileInBackground, fillIsRunning } from "../services/torrent/worker/background-fill.js";
15
15
 
16
16
  /**
17
17
  * A file whose reads resolve when the test says so, recording what was asked
@@ -1,110 +1,110 @@
1
- /**
2
- * @file The browser's log, written beside the proxy's.
3
- *
4
- * What these hold is the naming, because the name is the whole point: the two
5
- * halves of a session have to join without guessing, and a torrent name is
6
- * arbitrary bytes chosen by a stranger that is about to become a path.
7
- */
8
-
9
- import test from "node:test";
10
- import assert from "node:assert/strict";
11
- import { mkdtempSync, readdirSync, readFileSync } from "node:fs";
12
- import { tmpdir } from "node:os";
13
- import { join } from "node:path";
14
-
15
- import { createClientLogFiles } from "../utils/client-log-file.js";
16
-
17
- /** A directory with a proxy log in it, which is what the sink is given. */
18
- function aDirectory() {
19
- const dir = mkdtempSync(join(tmpdir(), "ttv-client-log-"));
20
- return { dir, proxyLog: join(dir, "proxy.log") };
21
- }
22
-
23
- const aSession = (over = {}) => ({
24
- sessionId: "abcd1234",
25
- startedAt: "2026-09-13T16:05:24.123Z",
26
- torrentName: "Reacher.S04E07.1080p.rus.LostFilm.TV.mkv",
27
- infoHash: "94dda59fdf252447d6ca5a8775dab2d11a832da5",
28
- ...over
29
- });
30
-
31
- test("the file lands beside the proxy log, named by when it started and what is watched", async () => {
32
- const { dir, proxyLog } = aDirectory();
33
- const logs = createClientLogFiles(proxyLog);
34
- logs.write(aSession(), ["first line"]);
35
- await logs.close();
36
-
37
- const [name] = readdirSync(dir).filter((f) => f.startsWith("client-"));
38
- assert.ok(name, "a client log file was written");
39
- // The stamp is UTC and sorts; the session id joins it to the proxy's lines;
40
- // the film says which one it is without opening the file.
41
- assert.match(name, /^client-20260913-160524-abcd1234-Reacher\.S04E07\.1080p\.rus\.LostFilm\.TV\.mkv-94dda59f\.log$/);
42
- assert.equal(readFileSync(join(dir, name), "utf8"), "first line\n");
43
- });
44
-
45
- test("lines from one session go on appending to the one file", async () => {
46
- const { dir, proxyLog } = aDirectory();
47
- const logs = createClientLogFiles(proxyLog);
48
- logs.write(aSession(), ["one", "two"]);
49
- logs.write(aSession(), ["three"]);
50
- await logs.close();
51
-
52
- const names = readdirSync(dir).filter((f) => f.startsWith("client-"));
53
- assert.equal(names.length, 1);
54
- assert.equal(readFileSync(join(dir, names[0]), "utf8"), "one\ntwo\nthree\n");
55
- });
56
-
57
- test("a session that has not chosen a film yet still gets its own file", async () => {
58
- // The page opening, a proxy being chosen, a connection failing — all before
59
- // any torrent exists, and all worth keeping.
60
- const { dir, proxyLog } = aDirectory();
61
- const logs = createClientLogFiles(proxyLog);
62
- logs.write(aSession({ torrentName: "", infoHash: "" }), ["before any film"]);
63
- await logs.close();
64
-
65
- const [name] = readdirSync(dir).filter((f) => f.startsWith("client-"));
66
- assert.match(name, /-no-torrent-yet\.log$/);
67
- });
68
-
69
- test("the file keeps its name when the film arrives mid-session", async () => {
70
- // Keyed by the session, not by the name: the earlier lines belong to the same
71
- // viewing and must not start a second file.
72
- const { dir, proxyLog } = aDirectory();
73
- const logs = createClientLogFiles(proxyLog);
74
- logs.write(aSession({ torrentName: "", infoHash: "" }), ["before"]);
75
- logs.write(aSession(), ["after"]);
76
- await logs.close();
77
-
78
- const names = readdirSync(dir).filter((f) => f.startsWith("client-"));
79
- assert.equal(names.length, 1);
80
- assert.match(names[0], /-no-torrent-yet\.log$/);
81
- assert.equal(readFileSync(join(dir, names[0]), "utf8"), "before\nafter\n");
82
- });
83
-
84
- test("a hostile torrent name cannot escape the directory", async () => {
85
- const { dir, proxyLog } = aDirectory();
86
- const logs = createClientLogFiles(proxyLog);
87
- logs.write(aSession({ torrentName: "../../etc/passwd" }), ["x"]);
88
- await logs.close();
89
-
90
- const names = readdirSync(dir).filter((f) => f.startsWith("client-"));
91
- assert.equal(names.length, 1);
92
- assert.ok(!names[0].includes(".."), `no traversal in ${names[0]}`);
93
- assert.ok(!names[0].includes("/"), `no separator in ${names[0]}`);
94
- });
95
-
96
- test("two sessions of one film are two files", async () => {
97
- const { dir, proxyLog } = aDirectory();
98
- const logs = createClientLogFiles(proxyLog);
99
- logs.write(aSession({ sessionId: "aaaa1111" }), ["one viewer"]);
100
- logs.write(aSession({ sessionId: "bbbb2222", startedAt: "2026-09-13T16:07:11.000Z" }), ["another"]);
101
- await logs.close();
102
-
103
- assert.equal(readdirSync(dir).filter((f) => f.startsWith("client-")).length, 2);
104
- });
105
-
106
- test("no proxy log path means nothing is written and nothing throws", async () => {
107
- const logs = createClientLogFiles("");
108
- logs.write(aSession(), ["dropped"]);
109
- await logs.close();
110
- });
1
+ /**
2
+ * @file The browser's log, written beside the proxy's.
3
+ *
4
+ * What these hold is the naming, because the name is the whole point: the two
5
+ * halves of a session have to join without guessing, and a torrent name is
6
+ * arbitrary bytes chosen by a stranger that is about to become a path.
7
+ */
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { mkdtempSync, readdirSync, readFileSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+
15
+ import { createClientLogFiles } from "../utils/client-log-file.js";
16
+
17
+ /** A directory with a proxy log in it, which is what the sink is given. */
18
+ function aDirectory() {
19
+ const dir = mkdtempSync(join(tmpdir(), "ttv-client-log-"));
20
+ return { dir, proxyLog: join(dir, "proxy.log") };
21
+ }
22
+
23
+ const aSession = (over = {}) => ({
24
+ sessionId: "abcd1234",
25
+ startedAt: "2026-09-13T16:05:24.123Z",
26
+ torrentName: "Reacher.S04E07.1080p.rus.LostFilm.TV.mkv",
27
+ infoHash: "94dda59fdf252447d6ca5a8775dab2d11a832da5",
28
+ ...over
29
+ });
30
+
31
+ test("the file lands beside the proxy log, named by when it started and what is watched", async () => {
32
+ const { dir, proxyLog } = aDirectory();
33
+ const logs = createClientLogFiles(proxyLog);
34
+ logs.write(aSession(), ["first line"]);
35
+ await logs.close();
36
+
37
+ const [name] = readdirSync(dir).filter((f) => f.startsWith("client-"));
38
+ assert.ok(name, "a client log file was written");
39
+ // The stamp is UTC and sorts; the session id joins it to the proxy's lines;
40
+ // the film says which one it is without opening the file.
41
+ assert.match(name, /^client-20260913-160524-abcd1234-Reacher\.S04E07\.1080p\.rus\.LostFilm\.TV\.mkv-94dda59f\.log$/);
42
+ assert.equal(readFileSync(join(dir, name), "utf8"), "first line\n");
43
+ });
44
+
45
+ test("lines from one session go on appending to the one file", async () => {
46
+ const { dir, proxyLog } = aDirectory();
47
+ const logs = createClientLogFiles(proxyLog);
48
+ logs.write(aSession(), ["one", "two"]);
49
+ logs.write(aSession(), ["three"]);
50
+ await logs.close();
51
+
52
+ const names = readdirSync(dir).filter((f) => f.startsWith("client-"));
53
+ assert.equal(names.length, 1);
54
+ assert.equal(readFileSync(join(dir, names[0]), "utf8"), "one\ntwo\nthree\n");
55
+ });
56
+
57
+ test("a session that has not chosen a film yet still gets its own file", async () => {
58
+ // The page opening, a proxy being chosen, a connection failing — all before
59
+ // any torrent exists, and all worth keeping.
60
+ const { dir, proxyLog } = aDirectory();
61
+ const logs = createClientLogFiles(proxyLog);
62
+ logs.write(aSession({ torrentName: "", infoHash: "" }), ["before any film"]);
63
+ await logs.close();
64
+
65
+ const [name] = readdirSync(dir).filter((f) => f.startsWith("client-"));
66
+ assert.match(name, /-no-torrent-yet\.log$/);
67
+ });
68
+
69
+ test("the file keeps its name when the film arrives mid-session", async () => {
70
+ // Keyed by the session, not by the name: the earlier lines belong to the same
71
+ // viewing and must not start a second file.
72
+ const { dir, proxyLog } = aDirectory();
73
+ const logs = createClientLogFiles(proxyLog);
74
+ logs.write(aSession({ torrentName: "", infoHash: "" }), ["before"]);
75
+ logs.write(aSession(), ["after"]);
76
+ await logs.close();
77
+
78
+ const names = readdirSync(dir).filter((f) => f.startsWith("client-"));
79
+ assert.equal(names.length, 1);
80
+ assert.match(names[0], /-no-torrent-yet\.log$/);
81
+ assert.equal(readFileSync(join(dir, names[0]), "utf8"), "before\nafter\n");
82
+ });
83
+
84
+ test("a hostile torrent name cannot escape the directory", async () => {
85
+ const { dir, proxyLog } = aDirectory();
86
+ const logs = createClientLogFiles(proxyLog);
87
+ logs.write(aSession({ torrentName: "../../etc/passwd" }), ["x"]);
88
+ await logs.close();
89
+
90
+ const names = readdirSync(dir).filter((f) => f.startsWith("client-"));
91
+ assert.equal(names.length, 1);
92
+ assert.ok(!names[0].includes(".."), `no traversal in ${names[0]}`);
93
+ assert.ok(!names[0].includes("/"), `no separator in ${names[0]}`);
94
+ });
95
+
96
+ test("two sessions of one film are two files", async () => {
97
+ const { dir, proxyLog } = aDirectory();
98
+ const logs = createClientLogFiles(proxyLog);
99
+ logs.write(aSession({ sessionId: "aaaa1111" }), ["one viewer"]);
100
+ logs.write(aSession({ sessionId: "bbbb2222", startedAt: "2026-09-13T16:07:11.000Z" }), ["another"]);
101
+ await logs.close();
102
+
103
+ assert.equal(readdirSync(dir).filter((f) => f.startsWith("client-")).length, 2);
104
+ });
105
+
106
+ test("no proxy log path means nothing is written and nothing throws", async () => {
107
+ const logs = createClientLogFiles("");
108
+ logs.write(aSession(), ["dropped"]);
109
+ await logs.close();
110
+ });
@@ -13,7 +13,7 @@ import fs from "node:fs/promises";
13
13
  import os from "node:os";
14
14
  import path from "node:path";
15
15
  import { Readable } from "node:stream";
16
- import { CompletedFiles } from "../services/files/CompletedFiles.js";
16
+ import { CompletedFiles } from "../services/storage/files/CompletedFiles.js";
17
17
 
18
18
  const INFO_HASH = "abcdef0123456789abcdef0123456789abcdef01";
19
19
 
@@ -11,7 +11,7 @@
11
11
  import assert from "node:assert/strict";
12
12
  import test from "node:test";
13
13
 
14
- import { canSustainOutput } from "../services/hwaccel.js";
14
+ import { canSustainOutput } from "../services/encode/hwaccel.js";
15
15
 
16
16
  // A host that encodes 640x360 at 24 fps about eleven times over, and decodes
17
17
  // 1080p24 at 2.6x — the addon host's own figures.
@@ -59,9 +59,9 @@ test("nothing else running leaves the answer exactly as it was", () => {
59
59
  assert.equal(withZero.speed, without.speed);
60
60
  });
61
61
 
62
- test("a host with nothing measured still refuses nothing", () => {
62
+ test("a wiring made without the startup calibration judges nothing", () => {
63
63
  const answer = canSustainOutput({
64
- benchmark: [],
64
+ benchmark: null,
65
65
  outputPixelsPerSec: RUNG_240P,
66
66
  concurrentCostSec: 0.125
67
67
  });
@@ -69,6 +69,16 @@ test("a host with nothing measured still refuses nothing", () => {
69
69
  assert.equal(answer.speed, null);
70
70
  });
71
71
 
72
+ test("a host calibrated with no mode qualified refuses a re-encode (roadmap item 97, step 14)", () => {
73
+ const answer = canSustainOutput({
74
+ benchmark: [],
75
+ outputPixelsPerSec: RUNG_240P,
76
+ concurrentCostSec: 0.125
77
+ });
78
+ assert.equal(answer.sustainable, false, "unknown capacity is not capacity");
79
+ assert.equal(answer.speed, null);
80
+ });
81
+
72
82
  test("a cost heavy enough to fill the machine puts a rung below realtime", () => {
73
83
  const answer = canSustainOutput({
74
84
  benchmark: BENCHMARK,
@@ -17,9 +17,9 @@
17
17
  import assert from "node:assert/strict";
18
18
  import test from "node:test";
19
19
 
20
- import { MatroskaContainer } from "../services/container/MatroskaContainer.js";
21
- import { Mp4Container } from "../services/container/Mp4Container.js";
22
- import { AviContainer } from "../services/container/AviContainer.js";
20
+ import { MatroskaContainer } from "../services/media/container/MatroskaContainer.js";
21
+ import { Mp4Container } from "../services/media/container/Mp4Container.js";
22
+ import { AviContainer } from "../services/media/container/AviContainer.js";
23
23
 
24
24
  /**
25
25
  * An EBML element: its id bytes, a four-byte size, then the payload.
@@ -9,7 +9,7 @@
9
9
  import assert from "node:assert/strict";
10
10
  import test from "node:test";
11
11
 
12
- import { dumpsToRemove, isCoreDump } from "../services/core-dumps.js";
12
+ import { dumpsToRemove, isCoreDump } from "../services/storage/core-dumps.js";
13
13
 
14
14
  const dumps = [
15
15
  { name: "core.WorkerThread.81.1787176646", writtenAt: 1787176646000 },
@@ -29,10 +29,10 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
29
29
  import os from "node:os";
30
30
  import path from "node:path";
31
31
  import { EncodeRun } from "../services/encode/EncodeRun.js";
32
- import { SegmentStore } from "../services/encode/SegmentStore.js";
32
+ import { SegmentStore } from "../services/storage/segment-store/SegmentStore.js";
33
33
  import { SoftwareEncoder } from "../services/encode/SoftwareEncoder.js";
34
- import { EncodeOrchestrator } from "../services/orchestrators/EncodeOrchestrator.js";
35
- import { fmp4Format } from "../services/segment-formats/fmp4.js";
34
+ import { EncodeOrchestrator } from "../services/encode/EncodeOrchestrator.js";
35
+ import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
36
36
 
37
37
  const PICTURE = "torrent:abc:fmt=fmp4:grid=kf@0:video-only:v=0/copy";
38
38
  const SEGMENTS = 482;
@@ -114,7 +114,15 @@ function theWholeFilmIsOnDisk(store, dir) {
114
114
 
115
115
  test("a film that is entirely made needs no encoder", (t) => {
116
116
  const { made, store, dir, root } = orchestratorWithAStore();
117
- t.after(() => rmSync(root, { recursive: true, force: true }));
117
+ t.after(() => {
118
+ // THE STORE LETS ITS DIRECTORIES GO, and only then is the root removed. It
119
+ // holds a watch on each of them — that is how a piece closed by a muxer
120
+ // with no channel of its own becomes an event — and a directory with an
121
+ // open watch on it cannot be removed on Windows: `rmSync` retries until it
122
+ // gives up, and the check reads as a hang.
123
+ store.dropAll("the check is over");
124
+ rmSync(root, { recursive: true, force: true });
125
+ });
118
126
 
119
127
  theWholeFilmIsOnDisk(store, dir);
120
128
  aViewerAtTheStart(made);
@@ -128,7 +136,15 @@ test("a film that is entirely made needs no encoder", (t) => {
128
136
 
129
137
  test("segments the store has lost stop being ready, and an encoder is placed again", (t) => {
130
138
  const { made, store, dir, root } = orchestratorWithAStore();
131
- t.after(() => rmSync(root, { recursive: true, force: true }));
139
+ t.after(() => {
140
+ // THE STORE LETS ITS DIRECTORIES GO, and only then is the root removed. It
141
+ // holds a watch on each of them — that is how a piece closed by a muxer
142
+ // with no channel of its own becomes an event — and a directory with an
143
+ // open watch on it cannot be removed on Windows: `rmSync` retries until it
144
+ // gives up, and the check reads as a hang.
145
+ store.dropAll("the check is over");
146
+ rmSync(root, { recursive: true, force: true });
147
+ });
132
148
 
133
149
  theWholeFilmIsOnDisk(store, dir);
134
150
  aViewerAtTheStart(made);
@@ -151,7 +167,15 @@ test("segments the store has lost stop being ready, and an encoder is placed aga
151
167
 
152
168
  test("a piece discarded with the run that had it open is not ready either", (t) => {
153
169
  const { made, store, dir, root } = orchestratorWithAStore();
154
- t.after(() => rmSync(root, { recursive: true, force: true }));
170
+ t.after(() => {
171
+ // THE STORE LETS ITS DIRECTORIES GO, and only then is the root removed. It
172
+ // holds a watch on each of them — that is how a piece closed by a muxer
173
+ // with no channel of its own becomes an event — and a directory with an
174
+ // open watch on it cannot be removed on Windows: `rmSync` retries until it
175
+ // gives up, and the check reads as a hang.
176
+ store.dropAll("the check is over");
177
+ rmSync(root, { recursive: true, force: true });
178
+ });
155
179
 
156
180
  theWholeFilmIsOnDisk(store, dir);
157
181
  aViewerAtTheStart(made);
@@ -169,8 +193,16 @@ test("a piece discarded with the run that had it open is not ready either", (t)
169
193
  });
170
194
 
171
195
  test("nobody making what somebody waits for is said out loud", (t) => {
172
- const { made, root, lines } = orchestratorWithAStore();
173
- t.after(() => rmSync(root, { recursive: true, force: true }));
196
+ const { made, store, root, lines } = orchestratorWithAStore();
197
+ t.after(() => {
198
+ // THE STORE LETS ITS DIRECTORIES GO, and only then is the root removed. It
199
+ // holds a watch on each of them — that is how a piece closed by a muxer
200
+ // with no channel of its own becomes an event — and a directory with an
201
+ // open watch on it cannot be removed on Windows: `rmSync` retries until it
202
+ // gives up, and the check reads as a hang.
203
+ store.dropAll("the check is over");
204
+ rmSync(root, { recursive: true, force: true });
205
+ });
174
206
 
175
207
  // A HOST THAT CANNOT BUILD AN ENCODER. Whatever the reason — and there are
176
208
  // several — the state it leaves is the one that cost two sessions on
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @file What the subtitle walk found reaches the viewer whole.
3
+ *
4
+ * It did not. The walk has produced `detectedLanguage` on every push since
5
+ * 2.69.0 — the language read from the CUES rather than from a container that
6
+ * often states none — and the whole of the "label moves when the answer
7
+ * arrives" behaviour rests on it. It never arrived: the fact was copied by hand
8
+ * twice on its way out, and each copy listed the fields it knew about. The
9
+ * worker posted `trackIndex, cues, language, cursor`; the main thread's client
10
+ * rebuilt the same four from the message. `detectedLanguage` was in neither
11
+ * list, so the browser was told nothing and the label it shows for an untagged
12
+ * track has never moved.
13
+ *
14
+ * It is not a typo to fix in two places — it is the shape. A fact copied field
15
+ * by field loses whatever the copier did not know about, and says nothing when
16
+ * it does. The walk's own result travels now, so a field added to it arrives
17
+ * without anybody being told to forward it.
18
+ *
19
+ * (Both copies are gone anyway: the walk runs on the thread that pushes.)
20
+ */
21
+
22
+ import test from "node:test";
23
+ import assert from "node:assert/strict";
24
+ import { readFileSync } from "node:fs";
25
+ import path from "node:path";
26
+ import { fileURLToPath } from "node:url";
27
+
28
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
29
+
30
+ /**
31
+ * @param {string} relative
32
+ * @returns {string}
33
+ */
34
+ function source(relative) {
35
+ return readFileSync(path.join(ROOT, relative), "utf8");
36
+ }
37
+
38
+ test("the walk states the language it read from the cues", () => {
39
+ // The producer. Without this the rest of the check is about nothing.
40
+ assert.match(source("services/media/SubtitleCues.js"), /detectedLanguage: detectLanguage\(/);
41
+ });
42
+
43
+ test("the consumer asks for it", () => {
44
+ // The other end has always destructured it, which is what made the loss
45
+ // silent: a field nobody forwards is `undefined` and reads as "not detected".
46
+ assert.match(
47
+ source("services/transport/data-channel-handler.js"),
48
+ /function publishSubtitleCues\(\{[^}]*detectedLanguage[^}]*\}\)/
49
+ );
50
+ });
51
+
52
+ test("the push forwards the whole of what the walk found, rather than a list of fields", () => {
53
+ // The one line that closes it. A list of names here is how the fact was lost
54
+ // twice, and a list cannot say that it is incomplete.
55
+ const wiring = source("server.js");
56
+
57
+ assert.match(wiring, /onSubtitleCues\?\.\(\{ sourceKey, fileIndex, \.\.\.entry \}\)/);
58
+ });
@@ -13,10 +13,10 @@
13
13
  */
14
14
 
15
15
  import assert from "node:assert/strict";
16
- import { Timeline } from "../services/output/Timeline.js";
16
+ import { Timeline } from "../services/encode/output/Timeline.js";
17
17
  import test from "node:test";
18
18
 
19
- import { onKeyframeGridFor } from "../services/hls-session-manager.js";
19
+ import { onKeyframeGridFor } from "../services/encode/run-command.js";
20
20
 
21
21
  test("a copied picture works on the source's timeline", () => {
22
22
  assert.equal(
@@ -15,11 +15,12 @@
15
15
  */
16
16
 
17
17
  import assert from "node:assert/strict";
18
- import { computeCutGrid } from "../services/output/cut-grid.js";
19
- import { Timeline } from "../services/output/Timeline.js";
18
+ import { computeCutGrid } from "../services/encode/output/cut-grid.js";
19
+ import { Timeline } from "../services/encode/output/Timeline.js";
20
20
  import test from "node:test";
21
21
 
22
- import { HlsSessionManager, segmentCutTimesFrom } from "../services/hls-session-manager.js";
22
+ import { wireOutputs } from "../services/server/wire-outputs.js";
23
+ import { segmentCutTimesFrom } from "../services/encode/run-command.js";
23
24
 
24
25
  /** What the playlist in the player's hands says. */
25
26
  const PUBLISHED = [0, 4.004, 8.008, 12.012, 16.016, 20.02];
@@ -29,10 +30,10 @@ const CORRECTED = [0, 4.004, 6.006, 12.012, 14.014, 20.02];
29
30
  /**
30
31
  * A session that has published one grid and since corrected another.
31
32
  *
32
- * @returns {{ manager: HlsSessionManager, session: object }}
33
+ * @returns {{ manager: object, session: object }}
33
34
  */
34
35
  function sessionWithDriftedGrid() {
35
- const manager = new HlsSessionManager({
36
+ const manager = wireOutputs({
36
37
  enabled: true,
37
38
  ffmpegBin: "ffmpeg",
38
39
  localBindHost: "127.0.0.1",
@@ -47,7 +48,7 @@ function sessionWithDriftedGrid() {
47
48
 
48
49
  test("the cut list is the one the playlist was written from", () => {
49
50
  const { manager, session } = sessionWithDriftedGrid();
50
- const grid = manager.publishedGridFor(session);
51
+ const grid = manager.outputTimes.publishedGridFor(session);
51
52
  assert.deepEqual(grid, PUBLISHED);
52
53
  // Interior cuts of a run starting at #1, rebased on the run's own start
53
54
  // (4.004 s), and stopping before the last entry, which is the file's end.
@@ -60,7 +61,7 @@ test("the cut list is the one the playlist was written from", () => {
60
61
  test("a corrected grid does not move the cuts of a session already being read", () => {
61
62
  const { manager, session } = sessionWithDriftedGrid();
62
63
  const fromCorrected = segmentCutTimesFrom(session.timeline.boundaries, 1);
63
- const fromPublished = segmentCutTimesFrom(manager.publishedGridFor(session), 1);
64
+ const fromPublished = segmentCutTimesFrom(manager.outputTimes.publishedGridFor(session), 1);
64
65
  assert.notDeepEqual(fromCorrected, fromPublished);
65
66
  // The gap the field measured: the corrected table would have cut #2 two
66
67
  // seconds early, which is four times what a player bridges.
@@ -70,7 +71,7 @@ test("a corrected grid does not move the cuts of a session already being read",
70
71
  test("a session that published no grid falls back to the live one", () => {
71
72
  const { manager } = sessionWithDriftedGrid();
72
73
  const session = { id: "no-playlist", timeline: new Timeline({ boundaries: [...CORRECTED], published: [], cutGrid: "uniform" }) };
73
- assert.deepEqual(manager.publishedGridFor(session), CORRECTED);
74
+ assert.deepEqual(manager.outputTimes.publishedGridFor(session), CORRECTED);
74
75
  });
75
76
 
76
77
  test("no segment is left shorter than a segment at the end of the film", () => {
@@ -10,7 +10,7 @@
10
10
 
11
11
  import test from "node:test";
12
12
  import assert from "node:assert/strict";
13
- import { encodeFrame } from "../services/data-channel-handler.js";
13
+ import { encodeFrame } from "../services/transport/data-channel-handler.js";
14
14
 
15
15
  const requestId = Buffer.from("abc123", "ascii");
16
16
 
@@ -19,7 +19,7 @@
19
19
 
20
20
  import test from "node:test";
21
21
  import assert from "node:assert/strict";
22
- import { isUsableTorrentHandle } from "../services/torrent-worker/handle-state.js";
22
+ import { isUsableTorrentHandle } from "../services/torrent/worker/handle-state.js";
23
23
 
24
24
  test("a live torrent is usable", () => {
25
25
  assert.equal(isUsableTorrentHandle({ destroyed: false, files: [{ name: "a.mkv" }] }), true);
@@ -10,8 +10,8 @@
10
10
 
11
11
  import test from "node:test";
12
12
  import assert from "node:assert/strict";
13
- import { declaredRates } from "../services/output/rates.js";
14
- import { bitrateFor } from "../services/output/playlists.js";
13
+ import { declaredRates } from "../services/encode/output/rates.js";
14
+ import { bitrateFor } from "../services/encode/output/playlists.js";
15
15
 
16
16
  // The field file: 2 806 246 976 bytes over 20:18, cut into 291 pieces.
17
17
  const FIELD = { fileLength: 2_806_246_976, durationSeconds: 1218.73 };
@@ -16,8 +16,8 @@ import test from "node:test";
16
16
  import assert from "node:assert/strict";
17
17
  import { readFileSync } from "node:fs";
18
18
 
19
- const planner = readFileSync(new URL("../services/playback-planner.js", import.meta.url), "utf8");
20
- const sessions = readFileSync(new URL("../services/hls-session-manager.js", import.meta.url), "utf8");
19
+ const planner = readFileSync(new URL("../services/media/playback-planner.js", import.meta.url), "utf8");
20
+ const sessions = readFileSync(new URL("../services/encode/Renditions.js", import.meta.url), "utf8");
21
21
 
22
22
  test("every field the declaration reads is a field the cache stores", () => {
23
23
  // What `declaredTracks` reads off the cached media info.
@@ -11,7 +11,7 @@
11
11
  import assert from "node:assert/strict";
12
12
  import test from "node:test";
13
13
 
14
- import { decodeCostOf, fitDecodeCost } from "../services/decode-cost-fit.js";
14
+ import { decodeCostOf, fitDecodeCost } from "../services/encode/decode-cost-fit.js";
15
15
 
16
16
  const FPS = 24;
17
17