@torrent-tv/proxy 2.89.7 → 2.89.8

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 (497) hide show
  1. package/.claude/commands/opsx/apply.md +155 -155
  2. package/.claude/commands/opsx/archive.md +160 -160
  3. package/.claude/commands/opsx/explore.md +174 -174
  4. package/.claude/commands/opsx/propose.md +109 -109
  5. package/.claude/commands/opsx/sync.md +143 -143
  6. package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
  7. package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
  8. package/.claude/skills/openspec-explore/SKILL.md +289 -289
  9. package/.claude/skills/openspec-propose/SKILL.md +113 -113
  10. package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
  11. package/.dockerignore +5 -5
  12. package/AGENTS.md +358 -338
  13. package/CHANGELOG.md +2340 -2335
  14. package/Dockerfile +30 -30
  15. package/LICENSE +16 -16
  16. package/README.md +369 -369
  17. package/assets/calibration/NOTICE.md +100 -100
  18. package/assets/calibration/audio/NOTICE.md +19 -19
  19. package/assets/calibration/audio/manifest.json +98 -98
  20. package/assets/diagnostics/sctpstate.gdb +31 -31
  21. package/bin/cli.js +684 -684
  22. package/bin/thread-pool.js +30 -30
  23. package/biome.json +412 -412
  24. package/docs/browser-proxy-contract.md +155 -155
  25. package/docs/container-architecture.md +273 -273
  26. package/docs/download-architecture.md +261 -261
  27. package/docs/encode-architecture.md +765 -765
  28. package/docs/encode-run-state.md +96 -96
  29. package/docs/logs.md +97 -97
  30. package/docs/playback-start-forecast.md +113 -113
  31. package/docs/socks5-egress-research.md +114 -114
  32. package/docs/storage-architecture.md +273 -273
  33. package/knip.json +14 -14
  34. package/openspec/changes/adaptive-bitrate/design.md +92 -92
  35. package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
  36. package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
  37. package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
  38. package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
  39. package/openspec/changes/binary-distribution/proposal.md +53 -53
  40. package/openspec/changes/chunked-request-bodies/design.md +81 -81
  41. package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
  42. package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
  43. package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
  44. package/openspec/changes/cold-start/design.md +98 -98
  45. package/openspec/changes/cold-start/proposal.md +62 -62
  46. package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
  47. package/openspec/changes/cold-start/tasks.md +48 -48
  48. package/openspec/changes/disk-cap/.openspec.yaml +2 -2
  49. package/openspec/changes/disk-cap/proposal.md +37 -37
  50. package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
  51. package/openspec/changes/disk-cap/tasks.md +19 -19
  52. package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
  53. package/openspec/changes/proxy-observability/design.md +38 -38
  54. package/openspec/changes/proxy-observability/proposal.md +49 -49
  55. package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
  56. package/openspec/changes/proxy-observability/tasks.md +21 -21
  57. package/openspec/changes/seek-debounce/design.md +118 -118
  58. package/openspec/changes/seek-debounce/proposal.md +59 -59
  59. package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
  60. package/openspec/changes/seek-debounce/tasks.md +33 -33
  61. package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
  62. package/openspec/changes/subtitle-language/proposal.md +50 -50
  63. package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
  64. package/openspec/changes/subtitle-language/tasks.md +26 -26
  65. package/openspec/changes/track-selection/.openspec.yaml +2 -2
  66. package/openspec/changes/track-selection/design.md +47 -47
  67. package/openspec/changes/track-selection/proposal.md +45 -45
  68. package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
  69. package/openspec/changes/track-selection/tasks.md +20 -20
  70. package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
  71. package/openspec/changes/transcode-quality/proposal.md +51 -51
  72. package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
  73. package/openspec/changes/transcode-quality/tasks.md +63 -63
  74. package/openspec/config.yaml +38 -38
  75. package/package.json +51 -50
  76. package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
  77. package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
  78. package/research/double-spawn-2026-09-10.md +171 -171
  79. package/research/handover-2026-09-13.md +209 -209
  80. package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
  81. package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
  82. package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
  83. package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
  84. package/routes/api/client-logs/post.js +86 -86
  85. package/routes/api/delivery-sink/get.js +72 -72
  86. package/routes/api/link-probe/get.js +51 -51
  87. package/routes/api/playback-plan/audio-tracks/post.js +29 -29
  88. package/routes/api/playback-plan/post.js +53 -53
  89. package/routes/api/sources/files/get.js +106 -106
  90. package/routes/api/sources/post.js +36 -36
  91. package/routes/api/sources/stats/get.js +132 -132
  92. package/routes/api/sources/warm/post.js +217 -217
  93. package/routes/api/subtitles/get.js +230 -230
  94. package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
  95. package/routes/api/transcode-sessions/net-report/post.js +138 -138
  96. package/routes/api/transcode-sessions/post.js +178 -178
  97. package/routes/api/transcode-sessions/progress/get.js +27 -27
  98. package/routes/api/transcode-sessions/release/post.js +41 -41
  99. package/routes/api/transcode-sessions/seek/post.js +63 -63
  100. package/routes/health/get.js +13 -13
  101. package/routes/healthz/get.js +13 -13
  102. package/routes/stream/get.js +416 -416
  103. package/routes/transcode/audio-file/get.js +64 -64
  104. package/routes/transcode/audio-warm/get.js +95 -95
  105. package/routes/transcode/session-file/get.js +52 -52
  106. package/routes/transcode/variant-file/get.js +95 -95
  107. package/routes/transcode/variant-warm/get.js +84 -84
  108. package/scripts/build-audio-samples.mjs +31 -31
  109. package/scripts/render-run-graph.js +173 -173
  110. package/server.js +748 -748
  111. package/services/encode/CoverageMap.js +428 -428
  112. package/services/encode/CushionReport.js +345 -345
  113. package/services/encode/EncodeAdmission.js +376 -376
  114. package/services/encode/EncodeOrchestrator.js +1192 -1192
  115. package/services/encode/EncodePlan.js +1368 -1368
  116. package/services/encode/EncodeRun.js +36 -12
  117. package/services/encode/Encoder.js +142 -142
  118. package/services/encode/EncoderSelection.js +38 -38
  119. package/services/encode/LocalObservations.js +359 -359
  120. package/services/encode/NvencEncoder.js +50 -50
  121. package/services/encode/OutputOpening.js +784 -784
  122. package/services/encode/OutputTimes.js +579 -579
  123. package/services/encode/QsvEncoder.js +58 -58
  124. package/services/encode/Renditions.js +2283 -2283
  125. package/services/encode/RunClock.js +87 -87
  126. package/services/encode/RunProgress.js +79 -79
  127. package/services/encode/SegmentDemand.js +95 -95
  128. package/services/encode/SoftwareEncoder.js +121 -121
  129. package/services/encode/V4l2m2mEncoder.js +53 -53
  130. package/services/encode/VaapiEncoder.js +66 -66
  131. package/services/encode/args.js +418 -418
  132. package/services/encode/audio-calibration.js +67 -67
  133. package/services/encode/audio-work-rate.js +15 -15
  134. package/services/encode/available-share.js +89 -89
  135. package/services/encode/calibration.js +361 -361
  136. package/services/encode/contention.js +128 -128
  137. package/services/encode/decode-cost-fit.js +363 -363
  138. package/services/encode/encode-exit.js +116 -116
  139. package/services/encode/encode-run-state.js +551 -551
  140. package/services/encode/encoder-readings.js +43 -43
  141. package/services/encode/fingerprint.js +322 -322
  142. package/services/encode/host-load.js +174 -174
  143. package/services/encode/hwaccel.js +1894 -1894
  144. package/services/encode/index.js +9 -9
  145. package/services/encode/learned-median.js +90 -90
  146. package/services/encode/output/EncodedOutput.js +57 -57
  147. package/services/encode/output/Output.js +131 -131
  148. package/services/encode/output/OutputCatalog.js +414 -414
  149. package/services/encode/output/OutputSpec.js +390 -390
  150. package/services/encode/output/Timeline.js +308 -308
  151. package/services/encode/output/cut-grid.js +107 -107
  152. package/services/encode/output/index.js +24 -24
  153. package/services/encode/output/ladder.js +32 -32
  154. package/services/encode/output/playlists.js +284 -284
  155. package/services/encode/output/rates.js +125 -125
  156. package/services/encode/output-key-format.js +21 -21
  157. package/services/encode/quality/EncodeCost.js +1043 -1043
  158. package/services/encode/quality/HostLoad.js +525 -525
  159. package/services/encode/quality/HostTimings.js +249 -249
  160. package/services/encode/quality/OutputQualityState.js +24 -24
  161. package/services/encode/quality/QualityController.js +758 -758
  162. package/services/encode/quality/QualityOffer.js +416 -416
  163. package/services/encode/quality/drain-threat.js +47 -47
  164. package/services/encode/quality/link-budget.js +337 -337
  165. package/services/encode/quality/output-format.js +319 -319
  166. package/services/encode/quality/serving-output.js +216 -216
  167. package/services/encode/quality/visible-rung.js +57 -57
  168. package/services/encode/run-budget.js +97 -97
  169. package/services/encode/run-command.js +757 -757
  170. package/services/encode/run-costs.js +155 -155
  171. package/services/encode/run-inputs.js +53 -53
  172. package/services/encode/segment-formats/fmp4.js +476 -476
  173. package/services/encode/segment-formats/init-compat.js +324 -324
  174. package/services/encode/segment-formats/mpegts.js +134 -134
  175. package/services/encode/start-stop-cost.js +178 -178
  176. package/services/encode/throughput.js +97 -97
  177. package/services/encode/torrent-cost.js +88 -88
  178. package/services/identity/installation-id.js +38 -38
  179. package/services/media/ContainerOrchestrator.js +202 -202
  180. package/services/media/KeyframeTables.js +356 -356
  181. package/services/media/SourceFile.js +298 -298
  182. package/services/media/SubtitleCues.js +592 -592
  183. package/services/media/SubtitleOrchestrator.js +167 -167
  184. package/services/media/audio-inventory.js +299 -299
  185. package/services/media/container/AviContainer.js +268 -268
  186. package/services/media/container/Container.js +632 -632
  187. package/services/media/container/ContainerFactory.js +38 -38
  188. package/services/media/container/KeyframeTable.js +272 -272
  189. package/services/media/container/MatroskaContainer.js +1031 -1031
  190. package/services/media/container/Mp4Container.js +1354 -1354
  191. package/services/media/container/SubtitleFileContainer.js +339 -339
  192. package/services/media/container/ebml-reader.js +159 -159
  193. package/services/media/container/ebml-stream.js +174 -174
  194. package/services/media/container/matroska-clusters.js +1165 -1165
  195. package/services/media/container/unavailable.js +77 -77
  196. package/services/media/ffmpeg-banner.js +260 -260
  197. package/services/media/keyframe-probe.js +157 -157
  198. package/services/media/media-info-probe.js +93 -93
  199. package/services/media/playback-planner.js +931 -931
  200. package/services/media/tracks/AudioTrack.js +143 -143
  201. package/services/media/tracks/ContainerTrack.js +84 -84
  202. package/services/media/tracks/ImageSubtitleTrack.js +19 -19
  203. package/services/media/tracks/SubtitleTrack.js +38 -38
  204. package/services/media/tracks/TextSubtitleTrack.js +295 -295
  205. package/services/media/tracks/VideoTrack.js +33 -33
  206. package/services/media/tracks/audio-bound.js +233 -233
  207. package/services/media/tracks/index.js +15 -15
  208. package/services/media/tracks/language-detect.js +174 -174
  209. package/services/server/OutputLifecycle.js +418 -418
  210. package/services/server/SegmentServing.js +1152 -1152
  211. package/services/server/ViewerRequests.js +785 -785
  212. package/services/server/WaitLedger.js +142 -142
  213. package/services/server/controllers/PlaybackController.js +40 -40
  214. package/services/server/controllers/SubtitleController.js +109 -109
  215. package/services/server/http-responses.js +60 -60
  216. package/services/server/transcode-session-files.js +325 -325
  217. package/services/server/wire-outputs.js +1029 -1029
  218. package/services/storage/Diagnostics.js +202 -202
  219. package/services/storage/MachineBudget.js +235 -235
  220. package/services/storage/allowance.js +107 -107
  221. package/services/storage/core-dumps.js +106 -106
  222. package/services/storage/files/CompletedFiles.js +391 -391
  223. package/services/storage/files/piece-from-whole-file.js +118 -118
  224. package/services/storage/free.js +71 -71
  225. package/services/storage/keep.js +48 -48
  226. package/services/storage/machine-memory.js +56 -56
  227. package/services/storage/memory-report.js +575 -575
  228. package/services/storage/piece-store/piece-disk-store.js +539 -539
  229. package/services/storage/piece-store/piece-lru.js +495 -495
  230. package/services/storage/piece-store/shared-piece-store.js +1937 -1937
  231. package/services/storage/returns.js +103 -103
  232. package/services/storage/segment-store/SegmentStore.js +1294 -1294
  233. package/services/storage/wire.js +175 -175
  234. package/services/torrent/Contents.js +382 -382
  235. package/services/torrent/demand/DemandRegister.js +202 -202
  236. package/services/torrent/demand/Urgency.js +137 -137
  237. package/services/torrent/demand/Window.js +118 -118
  238. package/services/torrent/demand/index.js +8 -8
  239. package/services/torrent/demand/pieces.js +140 -140
  240. package/services/torrent/download/SwarmSelection.js +386 -386
  241. package/services/torrent/download/registry.js +102 -102
  242. package/services/torrent/download/withdraw-claim.js +80 -80
  243. package/services/torrent/episode-naming.js +182 -182
  244. package/services/torrent/files.js +455 -455
  245. package/services/torrent/naming.js +619 -619
  246. package/services/torrent/piece-store-of.js +29 -29
  247. package/services/torrent/supply-margin.js +264 -264
  248. package/services/torrent/torrent-pool.js +2688 -2688
  249. package/services/torrent/worker/background-fill.js +213 -213
  250. package/services/torrent/worker/channel.js +271 -271
  251. package/services/torrent/worker/client.js +836 -836
  252. package/services/torrent/worker/fastest-wires.js +342 -342
  253. package/services/torrent/worker/handle-state.js +23 -23
  254. package/services/torrent/worker/held-bytes.js +231 -231
  255. package/services/torrent/worker/install-webrtc-shim.js +32 -32
  256. package/services/torrent/worker/piece-reader.js +1078 -1078
  257. package/services/torrent/worker/pool-adapter.js +371 -371
  258. package/services/torrent/worker/protocol.js +184 -184
  259. package/services/torrent/worker/resume-warm.js +107 -107
  260. package/services/torrent/worker/webrtc-shim.js +219 -219
  261. package/services/torrent/worker/worker.js +1137 -1137
  262. package/services/transport/data-channel-handler.js +1373 -1373
  263. package/services/transport/delivery-probe.js +842 -842
  264. package/services/transport/health-collector.js +53 -53
  265. package/services/transport/nat-classifier.js +231 -231
  266. package/services/transport/packet-witness.js +808 -808
  267. package/services/transport/port-mapper.js +235 -235
  268. package/services/transport/registry-api.js +81 -81
  269. package/services/transport/tunnel-client.js +447 -447
  270. package/services/transport/usrsctp-state.js +175 -175
  271. package/services/transport/webrtc-manager.js +484 -484
  272. package/services/viewer/Assignments.js +340 -340
  273. package/services/viewer/PriorityMap.js +421 -421
  274. package/services/viewer/PriorityOrchestrator.js +325 -325
  275. package/services/viewer/Viewer.js +691 -691
  276. package/services/viewer/Viewers.js +368 -368
  277. package/services/viewer/active-output.js +37 -37
  278. package/services/viewer/audio-start.js +46 -46
  279. package/services/viewer/buffer-trend.js +81 -81
  280. package/services/viewer/choices.js +647 -647
  281. package/services/viewer/media-time.js +355 -355
  282. package/services/viewer/playback-readiness.js +635 -635
  283. package/services/viewer/positions.js +126 -126
  284. package/services/viewer/report-intake.js +46 -46
  285. package/store/source-registry.js +71 -71
  286. package/test/asset-paths.test.js +28 -28
  287. package/test/audio-buffer-continuity.test.js +77 -77
  288. package/test/audio-calibration.test.js +23 -23
  289. package/test/audio-inventory.test.js +212 -212
  290. package/test/audio-track-bitrate.test.js +40 -40
  291. package/test/auto-quality-step.test.js +762 -762
  292. package/test/available-share.test.js +76 -76
  293. package/test/background-fill.test.js +190 -190
  294. package/test/client-log-file.test.js +110 -110
  295. package/test/coalesce.test.js +39 -39
  296. package/test/completed-files.test.js +115 -115
  297. package/test/concurrent-cost.test.js +148 -148
  298. package/test/container-media-info.test.js +228 -228
  299. package/test/contention.test.js +76 -76
  300. package/test/core-dumps.test.js +57 -57
  301. package/test/coverage-follows-the-disk.test.js +233 -228
  302. package/test/coverage-map.test.js +195 -195
  303. package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
  304. package/test/cut-times-timeline.test.js +65 -65
  305. package/test/cuts-follow-published-grid.test.js +109 -109
  306. package/test/data-channel-frame.test.js +63 -63
  307. package/test/data-channel-viewer-lifecycle.test.js +92 -92
  308. package/test/dead-torrent-handle.test.js +52 -52
  309. package/test/declared-rates.test.js +116 -116
  310. package/test/declared-tracks.test.js +43 -43
  311. package/test/decode-cost-fit.test.js +202 -202
  312. package/test/decode-cost.test.js +475 -475
  313. package/test/delivery-probe.test.js +393 -393
  314. package/test/demand-register.test.js +206 -206
  315. package/test/dht-bootstrap.test.js +72 -72
  316. package/test/drain-threat.test.js +65 -65
  317. package/test/duplicate-add.test.js +48 -48
  318. package/test/encode-admission.test.js +146 -146
  319. package/test/encode-cost.test.js +275 -275
  320. package/test/encode-exit.test.js +111 -111
  321. package/test/encode-orchestrator.test.js +426 -426
  322. package/test/encode-plan-viewers.test.js +725 -725
  323. package/test/encode-plan.test.js +545 -545
  324. package/test/encode-run-state.test.js +314 -314
  325. package/test/encode-run.test.js +20 -0
  326. package/test/encode-slope.test.js +46 -46
  327. package/test/encoded-output.test.js +39 -39
  328. package/test/encoder-kinds.test.js +122 -122
  329. package/test/encoder-readings.test.js +68 -68
  330. package/test/episode-naming.test.js +131 -131
  331. package/test/every-claimant-is-told-its-share.test.js +152 -152
  332. package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
  333. package/test/failed-start-breaker.test.js +202 -202
  334. package/test/fastest-wires.test.js +143 -143
  335. package/test/ffmpeg-stream-counts.test.js +78 -78
  336. package/test/file-edges.test.js +191 -191
  337. package/test/fixtures/audio-frame-boundary.json +1 -1
  338. package/test/fmp4-self-contained.test.js +80 -80
  339. package/test/fmp4-timestamps.test.js +140 -140
  340. package/test/fmp4-track-completeness.test.js +41 -41
  341. package/test/format-keyed-outputs.test.js +78 -78
  342. package/test/health-metrics.test.js +36 -38
  343. package/test/held-read-ownership.test.js +77 -77
  344. package/test/held-request-width.test.js +157 -157
  345. package/test/helpers/encode-run.js +248 -240
  346. package/test/helpers/held-file.js +77 -77
  347. package/test/helpers/manager.js +62 -62
  348. package/test/helpers/matroska-file.js +296 -296
  349. package/test/helpers/output-spec.js +63 -63
  350. package/test/host-load.test.js +68 -68
  351. package/test/index-accuracy-is-about-the-file.test.js +128 -128
  352. package/test/init-compat.test.js +219 -219
  353. package/test/input-lost-quiets-the-plan.test.js +261 -261
  354. package/test/input-unavailable.test.js +48 -48
  355. package/test/installation-id.test.js +39 -39
  356. package/test/keeping-period.test.js +83 -83
  357. package/test/keyframe-index-accuracy.test.js +188 -188
  358. package/test/keyframe-table-budget.test.js +123 -123
  359. package/test/keyframes-belong-to-the-file.test.js +249 -249
  360. package/test/learned-median.test.js +37 -37
  361. package/test/letting-go-asks-who-is-watching.test.js +191 -191
  362. package/test/limit-row-by-area.test.js +60 -60
  363. package/test/link-budget.test.js +111 -111
  364. package/test/link-probe.test.js +56 -56
  365. package/test/local-encoding-data.test.js +273 -273
  366. package/test/logger-repeats.test.js +134 -134
  367. package/test/look-ahead-coverage.test.js +56 -56
  368. package/test/manager-surface.test.js +291 -291
  369. package/test/matroska-cluster-discovery.test.js +323 -323
  370. package/test/matroska-cues-track.test.js +192 -192
  371. package/test/media-time.test.js +62 -62
  372. package/test/memory-budget.test.js +390 -390
  373. package/test/move-cost.test.js +216 -216
  374. package/test/mp4-subtitles.test.js +173 -173
  375. package/test/one-authority.test.js +285 -285
  376. package/test/one-budget.test.js +149 -149
  377. package/test/one-output-one-session.test.js +273 -273
  378. package/test/one-resource-divided.test.js +185 -185
  379. package/test/one-serving-output-per-height.test.js +458 -458
  380. package/test/orchestrator-wired.test.js +207 -207
  381. package/test/output-catalog.test.js +39 -39
  382. package/test/output-shape.test.js +21 -21
  383. package/test/output-spec.test.js +251 -251
  384. package/test/output-speed.test.js +92 -87
  385. package/test/outputs-being-prepared.test.js +40 -40
  386. package/test/packet-witness-ring.test.js +236 -236
  387. package/test/packet-witness.test.js +148 -148
  388. package/test/piece-disk-store.test.js +401 -401
  389. package/test/piece-from-whole-file.test.js +129 -129
  390. package/test/piece-lru.test.js +319 -319
  391. package/test/piece-reader.test.js +162 -162
  392. package/test/piece-selection-offset.test.js +85 -85
  393. package/test/piece-store-eviction.test.js +595 -595
  394. package/test/piece-store-never-refuses.test.js +153 -153
  395. package/test/piece-store-reservations.test.js +403 -403
  396. package/test/piece-store-slow-disk.test.js +159 -159
  397. package/test/piece-tail.test.js +97 -97
  398. package/test/plan-host-timings.test.js +68 -68
  399. package/test/plan-is-steady.test.js +225 -225
  400. package/test/playback-readiness.test.js +518 -518
  401. package/test/playback-start-regressions.test.js +101 -101
  402. package/test/priority-map-download.test.js +174 -174
  403. package/test/priority-map-emptied.test.js +204 -204
  404. package/test/priority-map-per-output.test.js +215 -215
  405. package/test/priority-map.test.js +163 -163
  406. package/test/probe-wedge-certainty.test.js +60 -60
  407. package/test/produced-copy-choice.test.js +213 -213
  408. package/test/published-timeline.test.js +83 -83
  409. package/test/quality-offer.test.js +160 -160
  410. package/test/quality-variants.test.js +1274 -1270
  411. package/test/rate-control-in-the-output.test.js +181 -180
  412. package/test/read-survives-withdrawal.test.js +164 -164
  413. package/test/read-window.test.js +365 -365
  414. package/test/reads-not-yet-arrived.test.js +218 -218
  415. package/test/resume-warm.test.js +39 -39
  416. package/test/run-graph-drift.test.js +37 -37
  417. package/test/run-inputs.test.js +80 -80
  418. package/test/run-intervals.test.js +100 -100
  419. package/test/run-position-follows-published-grid.test.js +89 -89
  420. package/test/run-progress.test.js +64 -64
  421. package/test/same-height-switch.test.js +663 -663
  422. package/test/seek-frees-the-viewers-requests.test.js +217 -217
  423. package/test/seek-landing.test.js +109 -109
  424. package/test/seek-target-not-superseded.test.js +195 -195
  425. package/test/segment-closed-on-both-branches.test.js +114 -114
  426. package/test/segment-cut-times.test.js +63 -63
  427. package/test/segment-demand.test.js +80 -80
  428. package/test/segment-hold-supersede.test.js +91 -91
  429. package/test/segment-judged-by-its-own-header.test.js +222 -222
  430. package/test/segment-serve-wiring.test.js +481 -481
  431. package/test/segment-store-eviction.test.js +247 -247
  432. package/test/segment-store.test.js +352 -352
  433. package/test/segment-true-start.test.js +86 -86
  434. package/test/segments-are-shared.test.js +176 -176
  435. package/test/send-chunk-bytes.test.js +86 -86
  436. package/test/serving-existing-output.test.js +111 -111
  437. package/test/serving-output.test.js +102 -102
  438. package/test/shared-piece-store.test.js +310 -310
  439. package/test/sidecar-naming.test.js +142 -142
  440. package/test/soundtrack-rate-bound.test.js +289 -289
  441. package/test/source-file.test.js +133 -133
  442. package/test/source-files-route.test.js +113 -113
  443. package/test/source-registry.test.js +18 -18
  444. package/test/split-a-stretch.test.js +139 -139
  445. package/test/stale-request-after-seek.test.js +158 -158
  446. package/test/startup-readings.test.js +124 -124
  447. package/test/stream-route.test.js +237 -237
  448. package/test/subtitle-cue-framing.test.js +200 -200
  449. package/test/subtitle-cue-source.test.js +102 -102
  450. package/test/subtitle-cue-walk.test.js +385 -385
  451. package/test/subtitle-cursor.test.js +63 -63
  452. package/test/subtitle-defaults.test.js +118 -118
  453. package/test/subtitle-language.test.js +252 -252
  454. package/test/subtitle-metadata.test.js +15 -15
  455. package/test/subtitle-plan-waits.test.js +125 -125
  456. package/test/subtitle-track-numbering.test.js +391 -391
  457. package/test/supply-margin.test.js +190 -190
  458. package/test/swarm-follows-readers.test.js +174 -174
  459. package/test/swarm-reach.test.js +130 -130
  460. package/test/swarm-selection.test.js +220 -220
  461. package/test/tail-duplication.test.js +167 -167
  462. package/test/the-channel-has-both-ends.test.js +91 -91
  463. package/test/the-torrent-thread-serves-bytes.test.js +107 -107
  464. package/test/thread-pool.test.js +53 -53
  465. package/test/timeline.test.js +84 -84
  466. package/test/torrent-contents.test.js +235 -235
  467. package/test/torrent-cost.test.js +71 -71
  468. package/test/torrent-files.test.js +221 -221
  469. package/test/torrent-naming.test.js +255 -255
  470. package/test/tracks-begin-together.test.js +209 -209
  471. package/test/tunnel-renewal.test.js +167 -167
  472. package/test/two-viewers-one-picture.test.js +508 -508
  473. package/test/upload-hurry.test.js +186 -186
  474. package/test/usrsctp-state.test.js +131 -131
  475. package/test/video-facts.test.js +102 -102
  476. package/test/viewer-assignments.test.js +143 -143
  477. package/test/viewer-outputs.test.js +285 -285
  478. package/test/viewer-presence.test.js +116 -116
  479. package/test/viewer-requests-admission.test.js +92 -92
  480. package/test/viewer-starts-on-map.test.js +27 -27
  481. package/test/viewer-states.test.js +192 -189
  482. package/test/viewer-subtitle-subscription.test.js +96 -96
  483. package/test/viewer.test.js +139 -139
  484. package/test/viewing-generations.test.js +390 -390
  485. package/test/visible-rung.test.js +37 -37
  486. package/test/wait-ledger.test.js +100 -100
  487. package/test/webrtc-shim.test.js +149 -149
  488. package/test/wedge-certainty.test.js +131 -131
  489. package/test/whole-file-held-read.test.js +30 -30
  490. package/test/withdraw-piece-claim.test.js +212 -212
  491. package/test/worker-channel.test.js +158 -158
  492. package/utils/client-log-file.js +151 -151
  493. package/utils/coalesce.js +41 -41
  494. package/utils/logger.js +312 -312
  495. package/utils/parse-range.js +25 -25
  496. package/utils/perf.js +59 -59
  497. package/utils/torrent-source-key.js +38 -38
@@ -1,155 +1,155 @@
1
- # Browser–proxy playback contract
2
-
3
- This document records the HTTP contract used by the browser playback code and
4
- implemented by this proxy. The same paths and payloads pass through either the
5
- direct HTTP transport or the WebRTC data channel. It is a description of the
6
- current code, not a generated schema or a version negotiation protocol.
7
-
8
- ## Source and file discovery
9
-
10
- | Request | Required input | Successful answer |
11
- |---|---|---|
12
- | `POST /api/sources` | `{ sourceType, source }` | `{ sourceKey }` identifies the registered source. |
13
- | `GET /api/sources/:sourceKey/files?maxWaitMs=N` | Registered `sourceKey`; optional wait budget | While magnet metadata is unavailable: `{ pending: true }`. When ready: `{ name, infoHash, files, items, shape }`. `files` carries `fileIndex`, `name`, `relativePath`, `length`, and `kind`; `items` maps each video `fileIndex` to audio, subtitle, and image file indices and carries `episode` — what the name states in the release's own numbering (`season`, `episodes`, `part`, `special`, `showHint`, `titleHint`) or `null`. `shape` is `single`, `series` or `undetermined` (2.88.0); an older proxy sends neither field. |
14
- | `GET /api/sources/:sourceKey/stats?fileIndex=N` | Registered source; optional file index | Current peer, transfer, header, and file progress readings. A reading may not contain every measurement; absent or `null` measurements are unknown. |
15
- | `POST /api/sources/:sourceKey/warm` | Optional `{ fileIndex, positionSeconds }`; an empty object warms source metadata and the first video | `{ started, swarm, edges, sidecars }` reports which preparation started. Work continues in the background. |
16
- | `GET /api/link-probe?bytes=N` | Positive byte count | A no-store binary body of zero bytes, capped at 2 MiB. The browser measures completed transfer time; an unavailable route leaves link speed unknown. |
17
-
18
- `pending: true` means the requested metadata or plan is not ready yet. The
19
- browser polls again. It does not mean that the torrent has no video or that the
20
- request failed. A non-pending file-list answer uses the proxy's file indices;
21
- the browser must keep those indices when opening a file.
22
-
23
- ## Playback plan and transcode session
24
-
25
- `POST /api/playback-plan` takes `{ sourceKey, fileIndex, userAgent }`. Its answer
26
- contains `mode`, `directUrl`, codec and container names, duration and source
27
- dimensions, track inventories, paired sidecar files, `offeredHeights`,
28
- `audioTracksPending`, and `pending`. The browser uses the codec fields to make
29
- the direct-versus-transcode decision itself; `mode` is advisory.
30
-
31
- When `audioTracksPending` is `true`, one or more sidecar headers were still
32
- unavailable. The browser may call `POST /api/playback-plan/audio-tracks` with
33
- `{ sourceKey, fileIndex }`; the answer contains the current `audioTracks` and
34
- `pending`. A resolved header updates metadata on the already-published first
35
- track for that sidecar. It never changes track indices or the number of HLS
36
- renditions in the active plan.
37
-
38
- Each `audioTracks` entry may carry `bitrateKbps`, the average rate the file
39
- states for the track, and `peakKbps`, the most the track's codec configuration
40
- allows it to carry (AAC-LC with 1024-sample frames, and AC-3; any other
41
- configuration has none). Both may be `null`. The browser does not need them;
42
- the proxy uses them to decide how the track is produced. A late header keeps a
43
- known value and withdraws `peakKbps` only when it contradicts the configuration
44
- it was computed from.
45
-
46
- `sidecarSubtitles` lists subtitle files the proxy paired with this video. Each
47
- entry carries the torrent `fileIndex` and the proxy's filename-derived language
48
- and release metadata. The browser uses these entries to fetch subtitle files
49
- and to identify a remembered choice. `sidecarImages` is returned beside it but
50
- is not needed for playback. Older proxies and plans made with transcoding
51
- disabled may omit these fields; the browser treats a missing or non-array list
52
- as empty.
53
-
54
- The browser maps a missing `mode` to `direct`, uses its registered `/stream`
55
- URL when `directUrl` is absent, maps missing codec/container names to empty
56
- strings, maps missing duration or dimensions to `0`, and maps missing audio or
57
- subtitle track arrays to `[]`. It maps missing or non-object `offeredHeights` to
58
- `null`; for an object value, missing or invalid `copy` and `transcode` lists
59
- become empty arrays. `null` means no offer was supplied, while empty lists
60
- explicitly offer no heights for their respective playback branch. Only
61
- `pending: true` asks the browser to poll again.
62
-
63
- `POST /api/transcode-sessions` requires `sourceKey`, `fileIndex`, and
64
- `consumerId`. The browser also sends the requested audio/video transcode flags,
65
- the visible picture size, whether audio is requested as separate renditions,
66
- the start position, selected audio track, and segment format when known. The
67
- answer contains `sessionId`, `playlistPath`, `offeredHeights`, declared `tracks`,
68
- `soundtrack`, and `lookaheadSeconds`. `masterPath` and `variantHeight` are
69
- present only when the session has quality variants.
70
-
71
- `transcodeAudio` states whether the browser can play the selected track as it
72
- is. The proxy decides the actual mode: it copies a track the browser plays
73
- when the track has a `peakKbps` or a `bitrateKbps`, and otherwise re-encodes it
74
- to stereo AAC at 128 kbit/s, including a track the browser could play.
75
- `soundtrack` is `{ trackIndex, transcode }`, the track this viewer is sent and
76
- whether it is re-encoded. Older proxies omit it.
77
-
78
- The browser interprets `409` with `outcome: "output-unavailable"` as a refusal
79
- for this viewer's link and `outcome: "no-capacity"` as a proxy capacity
80
- refusal. Other non-success statuses remain errors; an error body only adds
81
- display detail and is not required for status handling. For
82
- `output-unavailable`, `figures.verdict` is `"no safe bound"` when a part of the
83
- load has no figure; `figures.videoClass` and `figures.audioClass` are
84
- `"unknown"` for that part, and `figures.soundtracks` is the number of
85
- soundtracks the viewer can choose from.
86
-
87
- ## Session control and media files
88
-
89
- | Request | Meaning |
90
- |---|---|
91
- | `POST /api/transcode-sessions/:id/seek` | `{ positionSeconds, consumerId, generation }` reports the viewer's seek. A successful answer is `204`; the proxy handles the seek server side. |
92
- | `GET /api/transcode-sessions/:id/progress?consumer=:id` | Reads current session progress and also keeps the viewer's session alive. `playbackReadiness` is the proxy's versioned forecast used to release the startup wait; missing or unsupported versions do not authorize playback. An unreadable or failed answer does not imply that playback has ended. |
93
- | `POST /api/transcode-sessions/:id/net-report` | `{ consumerId, bufferedAheadSec, ... }` reports the viewer's buffer and optional state/link measurements. `consumerId` and non-negative `bufferedAheadSec` are required. `linkMbps` is the smoothed estimate used by the quality budget; `linkSampleMbps` and `linkSampleAt` carry the newest raw delivery measurement and its sample time for the readiness forecast. `bufferLimitSeconds` states the browser's accepted forward-buffer ceiling. Missing optional values do not reject the report or erase the last known value. Missing `playing` becomes `false`; missing `waiting` is derived from the playback and buffer state. Missing `positionSeconds` leaves the position unchanged, missing `qualityMode` leaves the prior mode, `onScreen` defaults to `true`, and `inPictureInPicture` defaults to `false`. Success is `204`. |
94
- | `POST /api/transcode-sessions/:id/fragment-far` | Diagnostic fragment and buffer positions; does not change encoding. Best effort, success `204`. |
95
- | `POST /api/transcode-sessions/:id/release` | `{ consumerId, reason }` releases this viewer's session assignment. |
96
- | `GET /transcode/:id/:fileName` | HLS playlist, init, or media segment. A file that is still being produced is answered with retryable `503`, not `202`. |
97
- | `GET /transcode/:id/v/:height/warm` and `/transcode/:id/a/:track/warm` | Prepare the requested video height or audio track at a position. `204` means ready. Audio warm-up `404` means the proxy does not support this operation for that session. Audio warm-up takes an optional `transcode=0\|1`: whether the browser cannot play THAT track's codec as it is. The proxy decides the track's mode by the rule used when a file is opened; without the parameter it assumes the browser needs what it needed for its current track. |
98
- | `GET /stream?sourceKey=…&fileIndex=N` | Direct file bytes with HTTP range support. |
99
-
100
- ### Playback readiness forecast
101
-
102
- The progress response may include `playbackReadiness`:
103
-
104
- | Field | Meaning |
105
- |---|---|
106
- | `version` | Forecast contract version. The browser releases its startup wait only for version `1` with `ready: true`. |
107
- | `ready` | The measured service model meets the remaining presentation deadlines and real client tracks cover the current position. Browser capacity is not an admission condition. |
108
- | `delaySeconds` | When `ready` is false, the predicted minimum delay from now to a safe start; `null` means the current measurements do not support a finite forecast. |
109
- | `measuredAt`, `predictedStartAt` | Proxy-clock observation and conditional start time, in milliseconds. The UI counts down using its own elapsed clock, without comparing clocks across machines. |
110
- | `operations` | Required video/audio operations, their source/output codecs, current speed and processed position. |
111
- | `preparedUntilSeconds` | Continuous proxy media predicted to be available at the forecast start. |
112
- | `bufferedSeconds` | Client buffer reported to the proxy. |
113
- | `reserveSeconds` | Maximum interruption reserve across the required video and selected audio outputs, derived from each output's measured supply waits and next segment duration. |
114
- | `neededSeconds` | Reserve shortfall at the current client buffer. |
115
- | `reason` | Machine-readable result of the forecast. |
116
- | `preparedSegments` | Number of already-produced output segments available across required tracks. |
117
- | `bufferedAtStartSeconds` | Forecast playable buffer at the proposed start time, when a finite delay is available. |
118
-
119
- The forecast considers the video output and the selected audio output when audio
120
- is delivered separately. A mixed output can depend on more than one source
121
- file. Repeated source files are counted once; distinct files keep separate
122
- remaining-work estimates and share the measured download rate of their torrent.
123
- The browser reports buffer state and delivery measurements, but does not
124
- calculate or override readiness.
125
-
126
- Service work is the integral of a nonnegative measured-rate forecast. The latest
127
- rate relaxes over the observed measurement span toward the lower of its latest
128
- value and the recent mean; the measurement history covers the output look-ahead.
129
- A positive measured rate cannot become permanently zero from an old declining
130
- linear fit. A measured zero remains zero until a new measurement arrives.
131
- The reported loader limit is bounded below by the browser buffer already held.
132
- During startup waiting the browser refreshes stale delivery measurements through
133
- the existing link probe and reports their age to avoid clock-offset errors.
134
-
135
- ## Subtitles
136
-
137
- `GET /api/subtitles` requires `sourceKey` and `fileIndex`. Without `trackIndex`
138
- it reads a paired sidecar file. With `trackIndex`, it reads cues from an
139
- embedded text track and may take `consumerId` to subscribe that viewer to
140
- subsequent cue pushes. `since` asks for cues after a previously received
141
- cursor.
142
-
143
- `200` returns WebVTT. Embedded cue answers may include
144
- `X-Subtitle-Cursor`, `X-Subtitle-Covered-Clusters`, and
145
- `X-Subtitle-Indexed-Clusters`; language detection may include
146
- `X-Subtitle-Language` and `X-Subtitle-Language-Name`. While an embedded track
147
- is being extracted, the response is `202` with `{ pending: true }`; the browser
148
- waits and repeats the request. A missing cursor or language header means that
149
- the browser has no value to preserve or display yet. A failed sidecar or
150
- embedded-track request skips that track without failing video playback.
151
-
152
- Subtitle cue pushes are delivered through the established proxy connection,
153
- not by a separate browser polling route. After reconnect, the browser repeats
154
- the embedded-track request with `since` to restore the subscription and receive
155
- missed cues.
1
+ # Browser–proxy playback contract
2
+
3
+ This document records the HTTP contract used by the browser playback code and
4
+ implemented by this proxy. The same paths and payloads pass through either the
5
+ direct HTTP transport or the WebRTC data channel. It is a description of the
6
+ current code, not a generated schema or a version negotiation protocol.
7
+
8
+ ## Source and file discovery
9
+
10
+ | Request | Required input | Successful answer |
11
+ |---|---|---|
12
+ | `POST /api/sources` | `{ sourceType, source }` | `{ sourceKey }` identifies the registered source. |
13
+ | `GET /api/sources/:sourceKey/files?maxWaitMs=N` | Registered `sourceKey`; optional wait budget | While magnet metadata is unavailable: `{ pending: true }`. When ready: `{ name, infoHash, files, items, shape }`. `files` carries `fileIndex`, `name`, `relativePath`, `length`, and `kind`; `items` maps each video `fileIndex` to audio, subtitle, and image file indices and carries `episode` — what the name states in the release's own numbering (`season`, `episodes`, `part`, `special`, `showHint`, `titleHint`) or `null`. `shape` is `single`, `series` or `undetermined` (2.88.0); an older proxy sends neither field. |
14
+ | `GET /api/sources/:sourceKey/stats?fileIndex=N` | Registered source; optional file index | Current peer, transfer, header, and file progress readings. A reading may not contain every measurement; absent or `null` measurements are unknown. |
15
+ | `POST /api/sources/:sourceKey/warm` | Optional `{ fileIndex, positionSeconds }`; an empty object warms source metadata and the first video | `{ started, swarm, edges, sidecars }` reports which preparation started. Work continues in the background. |
16
+ | `GET /api/link-probe?bytes=N` | Positive byte count | A no-store binary body of zero bytes, capped at 2 MiB. The browser measures completed transfer time; an unavailable route leaves link speed unknown. |
17
+
18
+ `pending: true` means the requested metadata or plan is not ready yet. The
19
+ browser polls again. It does not mean that the torrent has no video or that the
20
+ request failed. A non-pending file-list answer uses the proxy's file indices;
21
+ the browser must keep those indices when opening a file.
22
+
23
+ ## Playback plan and transcode session
24
+
25
+ `POST /api/playback-plan` takes `{ sourceKey, fileIndex, userAgent }`. Its answer
26
+ contains `mode`, `directUrl`, codec and container names, duration and source
27
+ dimensions, track inventories, paired sidecar files, `offeredHeights`,
28
+ `audioTracksPending`, and `pending`. The browser uses the codec fields to make
29
+ the direct-versus-transcode decision itself; `mode` is advisory.
30
+
31
+ When `audioTracksPending` is `true`, one or more sidecar headers were still
32
+ unavailable. The browser may call `POST /api/playback-plan/audio-tracks` with
33
+ `{ sourceKey, fileIndex }`; the answer contains the current `audioTracks` and
34
+ `pending`. A resolved header updates metadata on the already-published first
35
+ track for that sidecar. It never changes track indices or the number of HLS
36
+ renditions in the active plan.
37
+
38
+ Each `audioTracks` entry may carry `bitrateKbps`, the average rate the file
39
+ states for the track, and `peakKbps`, the most the track's codec configuration
40
+ allows it to carry (AAC-LC with 1024-sample frames, and AC-3; any other
41
+ configuration has none). Both may be `null`. The browser does not need them;
42
+ the proxy uses them to decide how the track is produced. A late header keeps a
43
+ known value and withdraws `peakKbps` only when it contradicts the configuration
44
+ it was computed from.
45
+
46
+ `sidecarSubtitles` lists subtitle files the proxy paired with this video. Each
47
+ entry carries the torrent `fileIndex` and the proxy's filename-derived language
48
+ and release metadata. The browser uses these entries to fetch subtitle files
49
+ and to identify a remembered choice. `sidecarImages` is returned beside it but
50
+ is not needed for playback. Older proxies and plans made with transcoding
51
+ disabled may omit these fields; the browser treats a missing or non-array list
52
+ as empty.
53
+
54
+ The browser maps a missing `mode` to `direct`, uses its registered `/stream`
55
+ URL when `directUrl` is absent, maps missing codec/container names to empty
56
+ strings, maps missing duration or dimensions to `0`, and maps missing audio or
57
+ subtitle track arrays to `[]`. It maps missing or non-object `offeredHeights` to
58
+ `null`; for an object value, missing or invalid `copy` and `transcode` lists
59
+ become empty arrays. `null` means no offer was supplied, while empty lists
60
+ explicitly offer no heights for their respective playback branch. Only
61
+ `pending: true` asks the browser to poll again.
62
+
63
+ `POST /api/transcode-sessions` requires `sourceKey`, `fileIndex`, and
64
+ `consumerId`. The browser also sends the requested audio/video transcode flags,
65
+ the visible picture size, whether audio is requested as separate renditions,
66
+ the start position, selected audio track, and segment format when known. The
67
+ answer contains `sessionId`, `playlistPath`, `offeredHeights`, declared `tracks`,
68
+ `soundtrack`, and `lookaheadSeconds`. `masterPath` and `variantHeight` are
69
+ present only when the session has quality variants.
70
+
71
+ `transcodeAudio` states whether the browser can play the selected track as it
72
+ is. The proxy decides the actual mode: it copies a track the browser plays
73
+ when the track has a `peakKbps` or a `bitrateKbps`, and otherwise re-encodes it
74
+ to stereo AAC at 128 kbit/s, including a track the browser could play.
75
+ `soundtrack` is `{ trackIndex, transcode }`, the track this viewer is sent and
76
+ whether it is re-encoded. Older proxies omit it.
77
+
78
+ The browser interprets `409` with `outcome: "output-unavailable"` as a refusal
79
+ for this viewer's link and `outcome: "no-capacity"` as a proxy capacity
80
+ refusal. Other non-success statuses remain errors; an error body only adds
81
+ display detail and is not required for status handling. For
82
+ `output-unavailable`, `figures.verdict` is `"no safe bound"` when a part of the
83
+ load has no figure; `figures.videoClass` and `figures.audioClass` are
84
+ `"unknown"` for that part, and `figures.soundtracks` is the number of
85
+ soundtracks the viewer can choose from.
86
+
87
+ ## Session control and media files
88
+
89
+ | Request | Meaning |
90
+ |---|---|
91
+ | `POST /api/transcode-sessions/:id/seek` | `{ positionSeconds, consumerId, generation }` reports the viewer's seek. A successful answer is `204`; the proxy handles the seek server side. |
92
+ | `GET /api/transcode-sessions/:id/progress?consumer=:id` | Reads current session progress and also keeps the viewer's session alive. `playbackReadiness` is the proxy's versioned forecast used to release the startup wait; missing or unsupported versions do not authorize playback. An unreadable or failed answer does not imply that playback has ended. |
93
+ | `POST /api/transcode-sessions/:id/net-report` | `{ consumerId, bufferedAheadSec, ... }` reports the viewer's buffer and optional state/link measurements. `consumerId` and non-negative `bufferedAheadSec` are required. `linkMbps` is the smoothed estimate used by the quality budget; `linkSampleMbps` and `linkSampleAt` carry the newest raw delivery measurement and its sample time for the readiness forecast. `bufferLimitSeconds` states the browser's accepted forward-buffer ceiling. Missing optional values do not reject the report or erase the last known value. Missing `playing` becomes `false`; missing `waiting` is derived from the playback and buffer state. Missing `positionSeconds` leaves the position unchanged, missing `qualityMode` leaves the prior mode, `onScreen` defaults to `true`, and `inPictureInPicture` defaults to `false`. Success is `204`. |
94
+ | `POST /api/transcode-sessions/:id/fragment-far` | Diagnostic fragment and buffer positions; does not change encoding. Best effort, success `204`. |
95
+ | `POST /api/transcode-sessions/:id/release` | `{ consumerId, reason }` releases this viewer's session assignment. |
96
+ | `GET /transcode/:id/:fileName` | HLS playlist, init, or media segment. A file that is still being produced is answered with retryable `503`, not `202`. |
97
+ | `GET /transcode/:id/v/:height/warm` and `/transcode/:id/a/:track/warm` | Prepare the requested video height or audio track at a position. `204` means ready. Audio warm-up `404` means the proxy does not support this operation for that session. Audio warm-up takes an optional `transcode=0\|1`: whether the browser cannot play THAT track's codec as it is. The proxy decides the track's mode by the rule used when a file is opened; without the parameter it assumes the browser needs what it needed for its current track. |
98
+ | `GET /stream?sourceKey=…&fileIndex=N` | Direct file bytes with HTTP range support. |
99
+
100
+ ### Playback readiness forecast
101
+
102
+ The progress response may include `playbackReadiness`:
103
+
104
+ | Field | Meaning |
105
+ |---|---|
106
+ | `version` | Forecast contract version. The browser releases its startup wait only for version `1` with `ready: true`. |
107
+ | `ready` | The measured service model meets the remaining presentation deadlines and real client tracks cover the current position. Browser capacity is not an admission condition. |
108
+ | `delaySeconds` | When `ready` is false, the predicted minimum delay from now to a safe start; `null` means the current measurements do not support a finite forecast. |
109
+ | `measuredAt`, `predictedStartAt` | Proxy-clock observation and conditional start time, in milliseconds. The UI counts down using its own elapsed clock, without comparing clocks across machines. |
110
+ | `operations` | Required video/audio operations, their source/output codecs, current speed and processed position. |
111
+ | `preparedUntilSeconds` | Continuous proxy media predicted to be available at the forecast start. |
112
+ | `bufferedSeconds` | Client buffer reported to the proxy. |
113
+ | `reserveSeconds` | Maximum interruption reserve across the required video and selected audio outputs, derived from each output's measured supply waits and next segment duration. |
114
+ | `neededSeconds` | Reserve shortfall at the current client buffer. |
115
+ | `reason` | Machine-readable result of the forecast. |
116
+ | `preparedSegments` | Number of already-produced output segments available across required tracks. |
117
+ | `bufferedAtStartSeconds` | Forecast playable buffer at the proposed start time, when a finite delay is available. |
118
+
119
+ The forecast considers the video output and the selected audio output when audio
120
+ is delivered separately. A mixed output can depend on more than one source
121
+ file. Repeated source files are counted once; distinct files keep separate
122
+ remaining-work estimates and share the measured download rate of their torrent.
123
+ The browser reports buffer state and delivery measurements, but does not
124
+ calculate or override readiness.
125
+
126
+ Service work is the integral of a nonnegative measured-rate forecast. The latest
127
+ rate relaxes over the observed measurement span toward the lower of its latest
128
+ value and the recent mean; the measurement history covers the output look-ahead.
129
+ A positive measured rate cannot become permanently zero from an old declining
130
+ linear fit. A measured zero remains zero until a new measurement arrives.
131
+ The reported loader limit is bounded below by the browser buffer already held.
132
+ During startup waiting the browser refreshes stale delivery measurements through
133
+ the existing link probe and reports their age to avoid clock-offset errors.
134
+
135
+ ## Subtitles
136
+
137
+ `GET /api/subtitles` requires `sourceKey` and `fileIndex`. Without `trackIndex`
138
+ it reads a paired sidecar file. With `trackIndex`, it reads cues from an
139
+ embedded text track and may take `consumerId` to subscribe that viewer to
140
+ subsequent cue pushes. `since` asks for cues after a previously received
141
+ cursor.
142
+
143
+ `200` returns WebVTT. Embedded cue answers may include
144
+ `X-Subtitle-Cursor`, `X-Subtitle-Covered-Clusters`, and
145
+ `X-Subtitle-Indexed-Clusters`; language detection may include
146
+ `X-Subtitle-Language` and `X-Subtitle-Language-Name`. While an embedded track
147
+ is being extracted, the response is `202` with `{ pending: true }`; the browser
148
+ waits and repeats the request. A missing cursor or language header means that
149
+ the browser has no value to preserve or display yet. A failed sidecar or
150
+ embedded-track request skips that track without failing video playback.
151
+
152
+ Subtitle cue pushes are delivered through the established proxy connection,
153
+ not by a separate browser polling route. After reconnect, the browser repeats
154
+ the embedded-track request with `since` to restore the subscription and receive
155
+ missed cues.