@torrent-tv/proxy 2.86.0 → 2.87.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/CHANGELOG.md +2142 -1915
  2. package/CLAUDE.md +322 -197
  3. package/assets/calibration/NOTICE.md +2 -2
  4. package/bin/cli.js +684 -616
  5. package/biome.json +409 -233
  6. package/docs/browser-proxy-contract.md +94 -0
  7. package/docs/container-architecture.md +218 -213
  8. package/docs/download-architecture.md +7 -7
  9. package/docs/encode-architecture.md +287 -11
  10. package/docs/encode-run-state.md +2 -2
  11. package/docs/logs.md +97 -97
  12. package/docs/{disk-architecture.md → storage-architecture.md} +107 -1
  13. package/openspec/changes/adaptive-bitrate/design.md +2 -2
  14. package/openspec/changes/adaptive-bitrate/proposal.md +1 -1
  15. package/openspec/changes/adaptive-bitrate/tasks.md +1 -1
  16. package/openspec/changes/chunked-request-bodies/design.md +1 -1
  17. package/openspec/changes/chunked-request-bodies/proposal.md +1 -1
  18. package/openspec/changes/chunked-request-bodies/tasks.md +1 -1
  19. package/openspec/changes/cold-start/design.md +1 -1
  20. package/openspec/changes/disk-cap/proposal.md +1 -1
  21. package/openspec/changes/proxy-observability/proposal.md +2 -2
  22. package/openspec/changes/track-selection/proposal.md +1 -1
  23. package/openspec/changes/transcode-quality/proposal.md +1 -1
  24. package/package.json +2 -2
  25. package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
  26. package/research/double-spawn-2026-09-10.md +171 -171
  27. package/routes/api/playback-plan/post.js +2 -2
  28. package/routes/api/sources/files/get.js +1 -1
  29. package/routes/api/sources/stats/get.js +1 -1
  30. package/routes/api/sources/warm/post.js +10 -3
  31. package/routes/api/subtitles/get.js +230 -230
  32. package/routes/api/transcode-sessions/fragment-far/post.js +3 -3
  33. package/routes/api/transcode-sessions/net-report/post.js +129 -94
  34. package/routes/api/transcode-sessions/post.js +160 -139
  35. package/routes/api/transcode-sessions/progress/get.js +3 -3
  36. package/routes/api/transcode-sessions/release/post.js +3 -3
  37. package/routes/api/transcode-sessions/seek/post.js +12 -3
  38. package/routes/stream/get.js +2 -2
  39. package/routes/transcode/audio-file/get.js +15 -6
  40. package/routes/transcode/audio-warm/get.js +9 -6
  41. package/routes/transcode/session-file/get.js +52 -268
  42. package/routes/transcode/variant-file/get.js +46 -7
  43. package/routes/transcode/variant-warm/get.js +19 -6
  44. package/scripts/render-run-graph.js +1 -1
  45. package/server.js +720 -465
  46. package/services/encode/CoverageMap.js +428 -428
  47. package/services/encode/CushionReport.js +345 -0
  48. package/services/encode/EncodeAdmission.js +332 -0
  49. package/services/{orchestrators → encode}/EncodeOrchestrator.js +307 -29
  50. package/services/encode/EncodeRun.js +16 -9
  51. package/services/encode/EncodeRuns.js +1229 -0
  52. package/services/encode/Encoder.js +18 -2
  53. package/services/encode/EncoderSelection.js +38 -0
  54. package/services/encode/LocalObservations.js +359 -0
  55. package/services/encode/NvencEncoder.js +5 -0
  56. package/services/encode/OutputOpening.js +755 -0
  57. package/services/encode/OutputTimes.js +579 -0
  58. package/services/encode/Renditions.js +2162 -0
  59. package/services/encode/RunProgress.js +84 -0
  60. package/services/encode/SoftwareEncoder.js +12 -7
  61. package/services/encode/args.js +402 -186
  62. package/services/encode/calibration.js +361 -0
  63. package/services/encode/contention.js +128 -128
  64. package/services/encode/encode-run-state.js +16 -20
  65. package/services/encode/fingerprint.js +322 -0
  66. package/services/{hwaccel.js → encode/hwaccel.js} +82 -42
  67. package/services/{learned-median.js → encode/learned-median.js} +11 -0
  68. package/services/encode/output/EncodedOutput.js +57 -0
  69. package/services/encode/output/Output.js +131 -0
  70. package/services/{output/LiveOutputs.js → encode/output/OutputCatalog.js} +141 -40
  71. package/services/encode/output/OutputSpec.js +390 -0
  72. package/services/{output → encode/output}/Timeline.js +12 -37
  73. package/services/encode/output/index.js +20 -0
  74. package/services/encode/output/ladder.js +32 -0
  75. package/services/{output → encode/output}/playlists.js +3 -0
  76. package/services/encode/output-key-format.js +21 -0
  77. package/services/encode/quality/EncodeCost.js +1083 -0
  78. package/services/encode/quality/HostLoad.js +525 -0
  79. package/services/encode/quality/HostTimings.js +249 -0
  80. package/services/encode/quality/OutputQualityState.js +28 -0
  81. package/services/encode/quality/QualityController.js +935 -0
  82. package/services/encode/quality/QualityOffer.js +416 -0
  83. package/services/encode/quality/drain-threat.js +47 -0
  84. package/services/encode/quality/link-budget.js +254 -0
  85. package/services/encode/quality/output-format.js +266 -0
  86. package/services/encode/quality/serving-output.js +216 -0
  87. package/services/encode/quality/visible-rung.js +57 -0
  88. package/services/encode/run-command.js +23 -20
  89. package/services/encode/run-inputs.js +42 -0
  90. package/services/{segment-formats → encode/segment-formats}/fmp4.js +63 -0
  91. package/services/{segment-formats → encode/segment-formats}/index.js +3 -3
  92. package/services/encode/segment-formats/init-compat.js +324 -0
  93. package/services/{segment-formats → encode/segment-formats}/mp4-boxes.js +8 -3
  94. package/services/encode/throughput.js +97 -0
  95. package/services/{orchestrators → media}/ContainerOrchestrator.js +45 -7
  96. package/services/media/KeyframeTables.js +316 -0
  97. package/services/{source → media}/SourceFile.js +7 -55
  98. package/services/{torrent-worker/subtitle-cues.js → media/SubtitleCues.js} +103 -132
  99. package/services/media/SubtitleOrchestrator.js +167 -0
  100. package/services/{audio-inventory.js → media/audio-inventory.js} +7 -1
  101. package/services/{container → media/container}/AviContainer.js +266 -266
  102. package/services/{container → media/container}/Container.js +596 -601
  103. package/services/media/container/ContainerFactory.js +56 -0
  104. package/services/media/container/KeyframeTable.js +272 -0
  105. package/services/{container → media/container}/MatroskaContainer.js +1485 -1485
  106. package/services/{container → media/container}/Mp4Container.js +1244 -1244
  107. package/services/{container → media/container}/SubtitleFileContainer.js +322 -322
  108. package/services/media/keyframe-probe.js +157 -0
  109. package/services/media/media-info-probe.js +93 -0
  110. package/services/{playback-planner.js → media/playback-planner.js} +82 -39
  111. package/services/{tracks → media/tracks}/AudioTrack.js +131 -131
  112. package/services/{tracks → media/tracks}/language-detect.js +174 -174
  113. package/services/server/OutputLifecycle.js +418 -0
  114. package/services/server/SegmentServing.js +1146 -0
  115. package/services/server/ViewerRequests.js +522 -0
  116. package/services/server/controllers/PlaybackController.js +36 -0
  117. package/services/{controllers → server/controllers}/SubtitleController.js +17 -33
  118. package/services/server/http-responses.js +60 -0
  119. package/services/server/transcode-session-files.js +325 -0
  120. package/services/server/wire-outputs.js +983 -0
  121. package/services/storage/Diagnostics.js +202 -0
  122. package/services/storage/MachineBudget.js +235 -0
  123. package/services/{piece-store → storage}/allowance.js +107 -107
  124. package/services/{core-dumps.js → storage/core-dumps.js} +1 -1
  125. package/services/{files → storage/files}/CompletedFiles.js +116 -1
  126. package/services/{disk → storage}/free.js +33 -0
  127. package/services/storage/machine-memory.js +56 -0
  128. package/services/{memory-report.js → storage/memory-report.js} +575 -596
  129. package/services/{piece-store → storage/piece-store}/piece-disk-store.js +0 -7
  130. package/services/{piece-store → storage/piece-store}/shared-piece-store.js +28 -21
  131. package/services/{encode → storage/segment-store}/SegmentStore.js +395 -8
  132. package/services/storage/wire.js +175 -0
  133. package/services/{download → torrent/download}/SwarmSelection.js +2 -2
  134. package/services/{download → torrent/download}/registry.js +2 -2
  135. package/services/torrent/files.js +1 -1
  136. package/services/torrent/piece-store-of.js +29 -0
  137. package/services/{torrent-pool.js → torrent/torrent-pool.js} +26 -119
  138. package/services/{torrent-worker → torrent/worker}/background-fill.js +2 -2
  139. package/services/{torrent-worker → torrent/worker}/channel.js +271 -250
  140. package/services/{torrent-worker → torrent/worker}/client.js +77 -58
  141. package/services/torrent/worker/held-bytes.js +155 -0
  142. package/services/{torrent-worker → torrent/worker}/install-webrtc-shim.js +1 -1
  143. package/services/{torrent-worker → torrent/worker}/piece-reader.js +21 -30
  144. package/services/{torrent-worker → torrent/worker}/pool-adapter.js +101 -137
  145. package/services/{torrent-worker → torrent/worker}/protocol.js +26 -30
  146. package/services/torrent/worker/resume-warm.js +107 -0
  147. package/services/{torrent-worker → torrent/worker}/webrtc-shim.js +2 -2
  148. package/services/{torrent-worker → torrent/worker}/worker.js +68 -159
  149. package/services/{data-channel-handler.js → transport/data-channel-handler.js} +1356 -1356
  150. package/services/{delivery-probe.js → transport/delivery-probe.js} +842 -842
  151. package/services/{health-collector.js → transport/health-collector.js} +6 -35
  152. package/services/{packet-witness.js → transport/packet-witness.js} +808 -784
  153. package/services/{port-mapper.js → transport/port-mapper.js} +1 -1
  154. package/services/{usrsctp-state.js → transport/usrsctp-state.js} +1 -1
  155. package/services/{webrtc-manager.js → transport/webrtc-manager.js} +1 -1
  156. package/services/viewer/Assignments.js +340 -0
  157. package/services/{priority → viewer}/PriorityMap.js +390 -390
  158. package/services/{priority → viewer}/PriorityOrchestrator.js +325 -320
  159. package/services/viewer/Viewer.js +657 -483
  160. package/services/viewer/Viewers.js +368 -257
  161. package/services/viewer/active-output.js +37 -54
  162. package/services/viewer/audio-start.js +4 -3
  163. package/services/viewer/buffer-trend.js +81 -0
  164. package/services/viewer/choices.js +647 -0
  165. package/services/viewer/positions.js +126 -120
  166. package/services/viewer/report-intake.js +46 -45
  167. package/test/asset-paths.test.js +28 -0
  168. package/test/audio-inventory.test.js +3 -3
  169. package/test/audio-track-bitrate.test.js +40 -0
  170. package/test/auto-quality-step.test.js +707 -515
  171. package/test/available-share.test.js +1 -1
  172. package/test/background-fill.test.js +1 -1
  173. package/test/client-log-file.test.js +110 -110
  174. package/test/completed-files.test.js +1 -1
  175. package/test/concurrent-cost.test.js +13 -3
  176. package/test/container-media-info.test.js +3 -3
  177. package/test/core-dumps.test.js +1 -1
  178. package/test/coverage-follows-the-disk.test.js +46 -9
  179. package/test/cue-push-carries-what-the-walk-found.test.js +58 -0
  180. package/test/cut-times-timeline.test.js +2 -2
  181. package/test/cuts-follow-published-grid.test.js +9 -8
  182. package/test/data-channel-frame.test.js +1 -1
  183. package/test/dead-torrent-handle.test.js +1 -1
  184. package/test/declared-rates.test.js +2 -2
  185. package/test/declared-tracks.test.js +2 -2
  186. package/test/decode-cost-fit.test.js +1 -1
  187. package/test/decode-cost.test.js +34 -25
  188. package/test/delivery-probe.test.js +1 -1
  189. package/test/demand-register.test.js +4 -4
  190. package/test/dht-bootstrap.test.js +1 -1
  191. package/test/drain-threat.test.js +65 -0
  192. package/test/duplicate-add.test.js +1 -1
  193. package/test/encode-admission.test.js +146 -0
  194. package/test/encode-cost.test.js +199 -10
  195. package/test/encode-orchestrator.test.js +39 -1
  196. package/test/encode-plan-viewers.test.js +2 -2
  197. package/test/encode-run.test.js +2 -2
  198. package/test/encode-slope.test.js +1 -1
  199. package/test/encoded-output.test.js +39 -0
  200. package/test/encoder-readings.test.js +1 -1
  201. package/test/every-claimant-is-told-its-share.test.js +152 -0
  202. package/test/failed-encoder-still-serves-what-exists.test.js +354 -0
  203. package/test/failed-start-breaker.test.js +81 -30
  204. package/test/fastest-wires.test.js +1 -1
  205. package/test/ffmpeg-stream-counts.test.js +1 -1
  206. package/test/file-edges.test.js +3 -3
  207. package/test/fmp4-self-contained.test.js +1 -1
  208. package/test/fmp4-timestamps.test.js +1 -1
  209. package/test/fmp4-track-completeness.test.js +1 -1
  210. package/test/format-keyed-outputs.test.js +78 -0
  211. package/test/health-metrics.test.js +3 -2
  212. package/test/held-request-width.test.js +22 -23
  213. package/test/helpers/encode-run.js +136 -136
  214. package/test/helpers/held-file.js +29 -0
  215. package/test/helpers/manager.js +57 -0
  216. package/test/helpers/output-spec.js +63 -0
  217. package/test/host-load.test.js +1 -1
  218. package/test/index-accuracy-is-about-the-file.test.js +128 -0
  219. package/test/init-compat.test.js +219 -0
  220. package/test/input-lost-quiets-the-plan.test.js +1 -1
  221. package/test/input-unavailable.test.js +1 -1
  222. package/test/keeping-period.test.js +4 -4
  223. package/test/keyframe-index-accuracy.test.js +97 -45
  224. package/test/keyframe-table-budget.test.js +28 -33
  225. package/test/keyframes-belong-to-the-file.test.js +174 -57
  226. package/test/learned-median.test.js +1 -1
  227. package/test/letting-go-asks-who-is-watching.test.js +191 -0
  228. package/test/limit-row-by-area.test.js +60 -0
  229. package/test/link-budget.test.js +111 -0
  230. package/test/link-probe.test.js +56 -56
  231. package/test/local-encoding-data.test.js +273 -0
  232. package/test/look-ahead-coverage.test.js +1 -1
  233. package/test/manager-surface.test.js +294 -0
  234. package/test/matroska-blocks.test.js +0 -0
  235. package/test/matroska-cues-track.test.js +1 -1
  236. package/test/memory-budget.test.js +2 -2
  237. package/test/mp4-composition-times.test.js +0 -0
  238. package/test/mp4-subtitles.test.js +1 -1
  239. package/test/one-authority.test.js +45 -41
  240. package/test/one-budget.test.js +149 -0
  241. package/test/one-output-one-session.test.js +204 -0
  242. package/test/{disk-space.test.js → one-resource-divided.test.js} +30 -10
  243. package/test/one-serving-output-per-height.test.js +458 -0
  244. package/test/orchestrator-wired.test.js +46 -45
  245. package/test/output-catalog.test.js +39 -0
  246. package/test/output-shape.test.js +4 -51
  247. package/test/output-spec.test.js +251 -157
  248. package/test/output-speed.test.js +17 -15
  249. package/test/outputs-being-prepared.test.js +40 -0
  250. package/test/packet-witness-ring.test.js +2 -2
  251. package/test/packet-witness.test.js +1 -1
  252. package/test/piece-disk-store.test.js +2 -2
  253. package/test/piece-from-whole-file.test.js +1 -1
  254. package/test/piece-lru.test.js +1 -1
  255. package/test/piece-reader.test.js +2 -2
  256. package/test/piece-store-eviction.test.js +1 -1
  257. package/test/piece-store-never-refuses.test.js +1 -1
  258. package/test/piece-store-reservations.test.js +1 -1
  259. package/test/piece-store-slow-disk.test.js +1 -1
  260. package/test/piece-tail.test.js +1 -1
  261. package/test/plan-host-timings.test.js +1 -1
  262. package/test/plan-is-steady.test.js +225 -225
  263. package/test/priority-map-download.test.js +4 -4
  264. package/test/priority-map-emptied.test.js +204 -205
  265. package/test/priority-map-per-output.test.js +215 -209
  266. package/test/priority-map.test.js +1 -1
  267. package/test/probe-wedge-certainty.test.js +1 -1
  268. package/test/produced-copy-choice.test.js +15 -60
  269. package/test/quality-offer.test.js +162 -0
  270. package/test/quality-variants.test.js +1229 -1167
  271. package/test/rate-control-in-the-output.test.js +180 -0
  272. package/test/read-survives-withdrawal.test.js +2 -2
  273. package/test/read-window.test.js +10 -3
  274. package/test/resume-warm.test.js +1 -1
  275. package/test/run-inputs.test.js +80 -0
  276. package/test/run-position-follows-published-grid.test.js +13 -11
  277. package/test/run-progress.test.js +65 -0
  278. package/test/same-height-switch.test.js +664 -0
  279. package/test/seek-frees-the-viewers-requests.test.js +217 -0
  280. package/test/seek-landing.test.js +13 -13
  281. package/test/seek-target-not-superseded.test.js +28 -29
  282. package/test/segment-closed-on-both-branches.test.js +96 -0
  283. package/test/segment-cut-times.test.js +1 -1
  284. package/test/segment-hold-supersede.test.js +28 -35
  285. package/test/segment-judged-by-its-own-header.test.js +221 -0
  286. package/test/segment-serve-wiring.test.js +130 -39
  287. package/test/segment-store-eviction.test.js +22 -7
  288. package/test/segment-store.test.js +98 -14
  289. package/test/segment-true-start.test.js +1 -1
  290. package/test/segments-are-shared.test.js +17 -16
  291. package/test/send-chunk-bytes.test.js +1 -1
  292. package/test/serving-existing-output.test.js +102 -0
  293. package/test/serving-output.test.js +102 -0
  294. package/test/shared-piece-store.test.js +1 -1
  295. package/test/source-file.test.js +1 -1
  296. package/test/stale-request-after-seek.test.js +17 -21
  297. package/test/startup-readings.test.js +10 -5
  298. package/test/subtitle-cue-framing.test.js +5 -5
  299. package/test/subtitle-cue-source.test.js +36 -38
  300. package/test/subtitle-cue-walk.test.js +11 -10
  301. package/test/subtitle-defaults.test.js +1 -1
  302. package/test/subtitle-language.test.js +4 -4
  303. package/test/subtitle-track-numbering.test.js +5 -4
  304. package/test/supply-margin.test.js +1 -1
  305. package/test/swarm-follows-readers.test.js +3 -3
  306. package/test/swarm-reach.test.js +1 -1
  307. package/test/swarm-selection.test.js +3 -3
  308. package/test/tail-duplication.test.js +1 -1
  309. package/test/the-channel-has-both-ends.test.js +91 -0
  310. package/test/the-torrent-thread-serves-bytes.test.js +107 -0
  311. package/test/thread-pool.test.js +3 -3
  312. package/test/timeline.test.js +1 -12
  313. package/test/torrent-cost.test.js +1 -1
  314. package/test/tracks-begin-together.test.js +38 -24
  315. package/test/tunnel-renewal.test.js +1 -1
  316. package/test/two-viewers-one-picture.test.js +120 -87
  317. package/test/upload-hurry.test.js +3 -3
  318. package/test/usrsctp-state.test.js +1 -1
  319. package/test/video-facts.test.js +1 -1
  320. package/test/viewer-assignments.test.js +143 -0
  321. package/test/viewer-outputs.test.js +60 -52
  322. package/test/viewer-presence.test.js +22 -25
  323. package/test/viewer-requests-admission.test.js +89 -0
  324. package/test/viewer-states.test.js +189 -202
  325. package/test/viewer-subtitle-subscription.test.js +1 -1
  326. package/test/viewer.test.js +139 -136
  327. package/test/viewing-generations.test.js +390 -0
  328. package/test/visible-rung.test.js +37 -0
  329. package/test/wait-ledger.test.js +1 -1
  330. package/test/webrtc-shim.test.js +3 -3
  331. package/test/wedge-certainty.test.js +2 -2
  332. package/test/withdraw-piece-claim.test.js +2 -2
  333. package/test/worker-channel.test.js +1 -31
  334. package/utils/client-log-file.js +151 -151
  335. package/scripts/read-container-index.mjs +0 -108
  336. package/services/container/ContainerFactory.js +0 -122
  337. package/services/controllers/PlaybackController.js +0 -33
  338. package/services/disk/DiskSpace.js +0 -150
  339. package/services/disk/wire.js +0 -61
  340. package/services/hls-session-manager.js +0 -9608
  341. package/services/orchestrators/SubtitleOrchestrator.js +0 -160
  342. package/services/output/Output.js +0 -94
  343. package/services/output/OutputSpec.js +0 -202
  344. package/services/output/index.js +0 -1
  345. package/services/output/ladder.js +0 -26
  346. package/services/produced-index.js +0 -222
  347. package/services/quality/EncodeCost.js +0 -555
  348. package/services/torrent-worker/container-tracks.js +0 -414
  349. package/services/viewer/link-readings.js +0 -56
  350. package/test/produced-index.test.js +0 -142
  351. /package/{services → bin}/thread-pool.js +0 -0
  352. /package/services/{available-share.js → encode/available-share.js} +0 -0
  353. /package/services/{decode-cost-fit.js → encode/decode-cost-fit.js} +0 -0
  354. /package/services/{encoder-readings.js → encode/encoder-readings.js} +0 -0
  355. /package/services/{host-load.js → encode/host-load.js} +0 -0
  356. /package/services/{output → encode/output}/cut-grid.js +0 -0
  357. /package/services/{output → encode/output}/rates.js +0 -0
  358. /package/services/{segment-formats → encode/segment-formats}/mpegts.js +0 -0
  359. /package/services/{torrent-cost.js → encode/torrent-cost.js} +0 -0
  360. /package/services/{container → media/container}/ebml-reader.js +0 -0
  361. /package/services/{ffmpeg-banner.js → media/ffmpeg-banner.js} +0 -0
  362. /package/services/{tracks → media/tracks}/ContainerTrack.js +0 -0
  363. /package/services/{tracks → media/tracks}/ImageSubtitleTrack.js +0 -0
  364. /package/services/{tracks → media/tracks}/SubtitleTrack.js +0 -0
  365. /package/services/{tracks → media/tracks}/TextSubtitleTrack.js +0 -0
  366. /package/services/{tracks → media/tracks}/VideoTrack.js +0 -0
  367. /package/services/{tracks → media/tracks}/index.js +0 -0
  368. /package/services/{files → storage/files}/piece-from-whole-file.js +0 -0
  369. /package/services/{disk → storage}/keep.js +0 -0
  370. /package/services/{piece-store → storage/piece-store}/piece-lru.js +0 -0
  371. /package/services/{disk → storage}/returns.js +0 -0
  372. /package/services/{demand → torrent/demand}/DemandRegister.js +0 -0
  373. /package/services/{demand → torrent/demand}/Urgency.js +0 -0
  374. /package/services/{demand → torrent/demand}/Window.js +0 -0
  375. /package/services/{demand → torrent/demand}/index.js +0 -0
  376. /package/services/{demand → torrent/demand}/pieces.js +0 -0
  377. /package/services/{download → torrent/download}/withdraw-claim.js +0 -0
  378. /package/services/{supply-margin.js → torrent/supply-margin.js} +0 -0
  379. /package/services/{torrent-source-key.js → torrent/torrent-source-key.js} +0 -0
  380. /package/services/{torrent-worker → torrent/worker}/fastest-wires.js +0 -0
  381. /package/services/{torrent-worker → torrent/worker}/handle-state.js +0 -0
  382. /package/services/{nat-classifier.js → transport/nat-classifier.js} +0 -0
  383. /package/services/{registry-api.js → transport/registry-api.js} +0 -0
  384. /package/services/{tunnel-client.js → transport/tunnel-client.js} +0 -0
  385. /package/services/{priority → viewer}/WaitLedger.js +0 -0
@@ -25,21 +25,17 @@ import "./install-webrtc-shim.js";
25
25
  import { parentPort, workerData } from "node:worker_threads";
26
26
  import { createSendStream } from "./channel.js";
27
27
  import { readFragments, supplyFiguresFor } from "./piece-reader.js";
28
- import { cuesHeldFor, declaredSubtitleTracksOf, subtitleTracksOf, warmSubtitleCues } from "./subtitle-cues.js";
29
28
  import {
30
- CONTAINER_HEAD_BYTES,
31
- containerKeyframesOf,
32
- containerMediaInfoOf,
33
- containerTracksOf,
34
29
  warmResumePosition
35
- } from "./container-tracks.js";
30
+ } from "./resume-warm.js";
36
31
  import { fillFileInBackground } from "./background-fill.js";
37
32
  import { demandFor } from "../download/registry.js";
38
- import { CompletedFiles, completedFilesRoot } from "../files/CompletedFiles.js";
39
- import { pieceFromWholeFiles, pieceIsInWholeFiles } from "../files/piece-from-whole-file.js";
33
+ import { heldRangesOf, readHeldBytes } from "./held-bytes.js";
34
+ import { CompletedFiles, completedFilesRoot } from "../../storage/files/CompletedFiles.js";
35
+ import { pieceFromWholeFiles, pieceIsInWholeFiles } from "../../storage/files/piece-from-whole-file.js";
40
36
  import { Command, Event } from "./protocol.js";
41
- import { startMemoryReport, WORKER_MEMORY_SAMPLE_MS } from "../memory-report.js";
42
- import { forwardLogsTo, logger } from "../../utils/logger.js";
37
+ import { startMemoryReport, WORKER_MEMORY_SAMPLE_MS } from "../../storage/memory-report.js";
38
+ import { forwardLogsTo, logger } from "../../../utils/logger.js";
43
39
 
44
40
  /**
45
41
  * Everything this thread logs goes to the main thread, which owns the file.
@@ -70,8 +66,8 @@ forwardLogsTo((level, message) => {
70
66
  // the hook above had a chance to register. Verified the hard way: with a static
71
67
  // import the process still aborted, and the stack named the genuine polyfill.
72
68
  const { TorrentPool, resolveDhtBootstrap } = await import("../torrent-pool.js");
73
- const { collectStoreStats, findSharedStore, machineReserveBytes, pieceBufferCollection, reviseSpillBudgets, reviseStoreBudgets } =
74
- await import("../piece-store/shared-piece-store.js");
69
+ const { SharedPieceStore, collectStoreStats, findSharedStore, machineReserveBytes, memoryClaim, pieceBufferCollection, reviseSpillBudgets, reviseStoreBudgets } =
70
+ await import("../../storage/piece-store/shared-piece-store.js");
75
71
 
76
72
  // Resolved before the client exists, because the client builds its DHT in its
77
73
  // own constructor and the addresses have to be in hand by then. Awaiting here
@@ -80,9 +76,9 @@ const { collectStoreStats, findSharedStore, machineReserveBytes, pieceBufferColl
80
76
  const dhtBootstrap = await resolveDhtBootstrap();
81
77
 
82
78
  const pool = new TorrentPool({
83
- maxDiskBytes: workerData?.maxDiskBytes,
84
79
  memoryBytes: workerData?.memoryBytes,
85
- dhtBootstrap
80
+ dhtBootstrap,
81
+ pieceStore: SharedPieceStore
86
82
  });
87
83
 
88
84
  /** Torrents by sourceKey — the main thread names them, this thread owns them. */
@@ -406,13 +402,6 @@ async function runCommand(command, params, id) {
406
402
  return { downloaded, uploaded };
407
403
  }
408
404
 
409
- case Command.SUBTITLE_TRACKS: {
410
- const torrent = await requireTorrent(params.sourceKey);
411
- return {
412
- tracks: await subtitleTracksOf(torrent, params.fileIndex, params.sourceKey),
413
- declared: await declaredSubtitleTracksOf(torrent, params.fileIndex, params.sourceKey)
414
- };
415
- }
416
405
 
417
406
  case Command.FILL_FILE: {
418
407
  const torrent = await requireTorrent(params.sourceKey);
@@ -421,58 +410,6 @@ async function runCommand(command, params, id) {
421
410
  };
422
411
  }
423
412
 
424
- case Command.CONTAINER_TRACKS: {
425
- const torrent = await requireTorrent(params.sourceKey);
426
- return {
427
- tracks: await containerTracksOf(torrent, params.fileIndex, params.sourceKey, {
428
- // The header of a file nobody is playing has usually not arrived at
429
- // all — a sidecar soundtrack is asked about before anyone has chosen
430
- // it. Its head is a few hundred kilobytes, and without them there is
431
- // nothing to read.
432
- prefetchEdges: () =>
433
- pool.prefetchFileEdges(torrent, params.fileIndex, {
434
- headBytes: CONTAINER_HEAD_BYTES,
435
- tailBytes: 0,
436
- timeoutMs: 60_000
437
- })
438
- })
439
- };
440
- }
441
-
442
- case Command.CONTAINER_MEDIA_INFO: {
443
- const torrent = await requireTorrent(params.sourceKey);
444
- return {
445
- info: await containerMediaInfoOf(torrent, params.fileIndex, params.sourceKey, {
446
- // Same reason as the track table above: the file this is asked about
447
- // is often one nobody has played yet, so its head has to be fetched
448
- // before there is anything to read.
449
- prefetchEdges: () =>
450
- pool.prefetchFileEdges(torrent, params.fileIndex, {
451
- headBytes: CONTAINER_HEAD_BYTES,
452
- tailBytes: 0,
453
- timeoutMs: 60_000
454
- })
455
- })
456
- };
457
- }
458
-
459
- case Command.CONTAINER_KEYFRAMES: {
460
- const torrent = await requireTorrent(params.sourceKey);
461
- return {
462
- index: await containerKeyframesOf(torrent, params.fileIndex, params.sourceKey, {
463
- // Both edges this time. A Matroska file's Cues sit at the END, behind
464
- // a SeekHead in the head, so a read that has neither waits for the
465
- // swarm twice over.
466
- prefetchEdges: () =>
467
- pool.prefetchFileEdges(torrent, params.fileIndex, {
468
- headBytes: CONTAINER_HEAD_BYTES,
469
- tailBytes: CONTAINER_HEAD_BYTES,
470
- timeoutMs: 60_000
471
- })
472
- })
473
- };
474
- }
475
-
476
413
  case Command.WARM_POSITION: {
477
414
  const torrent = await requireTorrent(params.sourceKey);
478
415
  return {
@@ -482,12 +419,9 @@ async function runCommand(command, params, id) {
482
419
  params.sourceKey,
483
420
  params.positionSeconds,
484
421
  {
485
- prefetchEdges: () =>
486
- pool.prefetchFileEdges(torrent, params.fileIndex, {
487
- headBytes: CONTAINER_HEAD_BYTES,
488
- tailBytes: 0,
489
- timeoutMs: 60_000
490
- }),
422
+ // Told by the main thread, which is where what a file states about
423
+ // itself is read.
424
+ durationSeconds: params.durationSeconds,
491
425
  fetchRegion: (start, bytes) =>
492
426
  pool.prefetchFileRegion(torrent, params.fileIndex, start, bytes)
493
427
  }
@@ -495,18 +429,6 @@ async function runCommand(command, params, id) {
495
429
  };
496
430
  }
497
431
 
498
- case Command.SUBTITLE_CUES: {
499
- const torrent = await requireTorrent(params.sourceKey);
500
- const held = await cuesHeldFor(torrent, params.fileIndex, params.sourceKey, params.trackNumber);
501
- return {
502
- cues: held.cues,
503
- coveredClusters: held.coveredClusters,
504
- indexedClusters: held.indexedClusters,
505
- codecId: held.track?.codecId ?? "",
506
- codecPrivate: held.track?.codecPrivate ?? "",
507
- language: held.track?.language ?? ""
508
- };
509
- }
510
432
 
511
433
  case Command.FILE_STATS: {
512
434
  const torrent = await requireTorrent(params.sourceKey);
@@ -577,6 +499,17 @@ async function runCommand(command, params, id) {
577
499
  return true;
578
500
  }
579
501
 
502
+ case Command.HELD_RANGES: {
503
+ const torrent = await requireTorrent(params.sourceKey);
504
+ return { ranges: heldRangesOf(torrent, params.fileIndex) };
505
+ }
506
+
507
+ case Command.READ_HELD: {
508
+ const torrent = await requireTorrent(params.sourceKey);
509
+ const bytes = await readHeldBytes(torrent, params.fileIndex, params.start, params.end, logger);
510
+ return { bytes };
511
+ }
512
+
580
513
  case Command.SPILL_ALLOWANCE: {
581
514
  // The disk has one owner and it is on the main thread, where the segments
582
515
  // are. What arrives is this thread’s whole share; the stores divide it
@@ -584,6 +517,21 @@ async function runCommand(command, params, id) {
584
517
  return reviseSpillBudgets(Number(params.bytes));
585
518
  }
586
519
 
520
+ case Command.MEMORY_ALLOWANCE: {
521
+ // Memory has one owner, on the main thread, and it is the same owner the
522
+ // disk has — the two resources trade, so they cannot be divided apart.
523
+ reviseStoreBudgets(Number(params.bytes));
524
+ return memoryClaim();
525
+ }
526
+
527
+ case Command.WHOLE_FILES_ALLOWANCE: {
528
+ // Whole files are held on this thread and the disk has one owner, on the
529
+ // other. What arrives is their share; what goes back is what they hold
530
+ // after it, which is what the owner divides by next time.
531
+ const after = await completedFiles.allow(Number(params.bytes));
532
+ return after.bytes;
533
+ }
534
+
587
535
  case Command.DESTROY_ALL: {
588
536
  torrentsByKey.clear();
589
537
  sourceRecipes.clear();
@@ -834,68 +782,30 @@ function describePieceBuffers() {
834
782
  }
835
783
 
836
784
  /**
837
- * Walk subtitle cues for every actively-read file of one torrent, and PUSH
838
- * whatever came out new to the main thread — which is what makes a browser's
839
- * copy current without it having asked.
785
+ * Say that pieces of the actively-read files of one torrent have arrived.
786
+ *
787
+ * It used to WALK them — reading each file's new subtitle clusters and pushing
788
+ * the cues it found. That put the cue reading, and with it a container parse,
789
+ * in the thread that owns the swarm; the walk is on the main thread now and
790
+ * this announces the one fact only this thread can know.
791
+ *
792
+ * Which files: the ones anything is stated for — a viewer's own picture and
793
+ * soundtrack through the priority map, and the ends of a file that is open. It
794
+ * replaced a count of readers, which said the same thing by keeping a second
795
+ * copy of it.
840
796
  *
841
797
  * @param {string} sourceKey
842
798
  * @param {object} torrent
843
799
  * @returns {void}
844
800
  */
845
- function warmActiveFiles(sourceKey, torrent) {
846
- // The files anything is stated for — a viewer's own picture and soundtrack
847
- // through the priority map, and the ends of a file that is open. It replaces
848
- // a count of readers, which said the same thing by keeping a second copy of
849
- // it.
850
- for (const fileIndex of demandFor(torrent).register.files()) {
851
- const key = `${sourceKey}:${fileIndex}`;
852
- // A trigger that arrives while the previous pass is still walking is
853
- // dropped, not queued. `verified` fires per piece, so on a fast download
854
- // these arrive many times a second; the walk is serialized per file anyway,
855
- // and a queue of identical passes would only postpone the one that has
856
- // something new to find.
857
- if (warmupInFlight.has(key)) {
858
- continue;
859
- }
860
- warmupInFlight.add(key);
861
- warmSubtitleCues(torrent, fileIndex, sourceKey)
862
- .then((fresh) => {
863
- for (const entry of fresh) {
864
- const span = entry.spanStartSeconds === null
865
- ? "empty"
866
- : `${entry.spanStartSeconds.toFixed(1)}-${entry.spanEndSeconds.toFixed(1)}s`;
867
- log(
868
- `subtitle push ${sourceKey.slice(0, 8)}:${fileIndex} track ${entry.trackIndex}: ` +
869
- `${entry.cues.length} new cue(s) covering ${span}, ` +
870
- `clusters walked ${entry.walkedClusters}/${entry.indexedClusters}, cursor ${entry.cursor}, ` +
871
- "posting to main thread"
872
- );
873
- parentPort.postMessage({
874
- type: Event.SUBTITLE_CUES_READY,
875
- sourceKey,
876
- fileIndex,
877
- trackIndex: entry.trackIndex,
878
- cues: entry.cues,
879
- language: entry.language,
880
- cursor: entry.cursor
881
- });
882
- }
883
- })
884
- .catch((error) => {
885
- log(`subtitle warmup ${sourceKey}:${fileIndex} failed: ${error instanceof Error ? error.message : error}`);
886
- })
887
- .finally(() => {
888
- warmupInFlight.delete(key);
889
- });
801
+ function announceArrivals(sourceKey, torrent) {
802
+ const fileIndexes = [...demandFor(torrent).register.files()];
803
+ if (fileIndexes.length === 0) {
804
+ return;
890
805
  }
806
+ parentPort.postMessage({ type: Event.PIECES_ARRIVED, sourceKey, fileIndexes });
891
807
  }
892
808
 
893
- /**
894
- * Files whose warmup pass has not finished yet, by `sourceKey:fileIndex`.
895
- *
896
- * @type {Set<string>}
897
- */
898
- const warmupInFlight = new Set();
899
809
 
900
810
  /**
901
811
  * Torrents already wired to warm their subtitle cues the moment a piece
@@ -903,7 +813,7 @@ const warmupInFlight = new Set();
903
813
  *
904
814
  * @type {WeakSet<object>}
905
815
  */
906
- const subtitleWarmupWired = new WeakSet();
816
+ const arrivalsWired = new WeakSet();
907
817
 
908
818
  /**
909
819
  * A piece becoming readable is the actual event a cue can be pulled from —
@@ -918,29 +828,28 @@ const subtitleWarmupWired = new WeakSet();
918
828
  * @param {object} torrent
919
829
  * @returns {void}
920
830
  */
921
- function ensureSubtitleWarmupWired(sourceKey, torrent) {
922
- if (subtitleWarmupWired.has(torrent)) {
831
+ function ensureArrivalsWired(sourceKey, torrent) {
832
+ if (arrivalsWired.has(torrent)) {
923
833
  return;
924
834
  }
925
- subtitleWarmupWired.add(torrent);
926
- torrent.on("verified", () => warmActiveFiles(sourceKey, torrent));
835
+ arrivalsWired.add(torrent);
836
+ torrent.on("verified", () => announceArrivals(sourceKey, torrent));
927
837
  }
928
838
 
929
839
  /**
930
- * How often an actively-read file's subtitle cues are walked ahead of being
931
- * asked for, as a fallback beside the per-piece `verified` listener above —
932
- * catches a listener attached after pieces already verified, and anything the
933
- * event path might otherwise miss. Cheap once caught up (`warmSubtitleCues`
934
- * skips clusters it has already read).
840
+ * How often arrivals are announced regardless, as a fallback beside the
841
+ * per-piece `verified` listener above — it catches a listener attached after
842
+ * pieces had already verified, and anything the event path might otherwise
843
+ * miss. Cheap: the walk it wakes skips clusters it has already read.
935
844
  */
936
- const SUBTITLE_WARMUP_INTERVAL_MS = 3_000;
845
+ const ARRIVAL_ANNOUNCE_INTERVAL_MS = 3_000;
937
846
 
938
847
  setInterval(() => {
939
848
  for (const [sourceKey, torrent] of pool.torrents) {
940
- ensureSubtitleWarmupWired(sourceKey, torrent);
941
- warmActiveFiles(sourceKey, torrent);
849
+ ensureArrivalsWired(sourceKey, torrent);
850
+ announceArrivals(sourceKey, torrent);
942
851
  }
943
- }, SUBTITLE_WARMUP_INTERVAL_MS).unref();
852
+ }, ARRIVAL_ANNOUNCE_INTERVAL_MS).unref();
944
853
 
945
854
  /**
946
855
  * Files this proxy has downloaded whole. One directory, two readers of it: this