@torrent-tv/proxy 2.89.6 → 2.89.7

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 (436) 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 +338 -338
  13. package/CHANGELOG.md +5 -0
  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/diagnostics/sctpstate.gdb +31 -31
  19. package/bin/cli.js +684 -684
  20. package/bin/thread-pool.js +30 -30
  21. package/docs/download-architecture.md +261 -261
  22. package/docs/encode-architecture.md +765 -765
  23. package/docs/encode-run-state.md +96 -96
  24. package/docs/logs.md +97 -97
  25. package/docs/playback-start-forecast.md +17 -2
  26. package/docs/socks5-egress-research.md +114 -114
  27. package/docs/storage-architecture.md +273 -273
  28. package/knip.json +14 -14
  29. package/openspec/changes/adaptive-bitrate/design.md +92 -92
  30. package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
  31. package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
  32. package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
  33. package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
  34. package/openspec/changes/binary-distribution/proposal.md +53 -53
  35. package/openspec/changes/chunked-request-bodies/design.md +81 -81
  36. package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
  37. package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
  38. package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
  39. package/openspec/changes/cold-start/design.md +98 -98
  40. package/openspec/changes/cold-start/proposal.md +62 -62
  41. package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
  42. package/openspec/changes/cold-start/tasks.md +48 -48
  43. package/openspec/changes/disk-cap/.openspec.yaml +2 -2
  44. package/openspec/changes/disk-cap/proposal.md +37 -37
  45. package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
  46. package/openspec/changes/disk-cap/tasks.md +19 -19
  47. package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
  48. package/openspec/changes/proxy-observability/design.md +38 -38
  49. package/openspec/changes/proxy-observability/proposal.md +49 -49
  50. package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
  51. package/openspec/changes/proxy-observability/tasks.md +21 -21
  52. package/openspec/changes/seek-debounce/design.md +118 -118
  53. package/openspec/changes/seek-debounce/proposal.md +59 -59
  54. package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
  55. package/openspec/changes/seek-debounce/tasks.md +33 -33
  56. package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
  57. package/openspec/changes/subtitle-language/proposal.md +50 -50
  58. package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
  59. package/openspec/changes/subtitle-language/tasks.md +26 -26
  60. package/openspec/changes/track-selection/.openspec.yaml +2 -2
  61. package/openspec/changes/track-selection/design.md +47 -47
  62. package/openspec/changes/track-selection/proposal.md +45 -45
  63. package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
  64. package/openspec/changes/track-selection/tasks.md +20 -20
  65. package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
  66. package/openspec/changes/transcode-quality/proposal.md +51 -51
  67. package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
  68. package/openspec/changes/transcode-quality/tasks.md +63 -63
  69. package/openspec/config.yaml +38 -38
  70. package/package.json +1 -1
  71. package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
  72. package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
  73. package/research/double-spawn-2026-09-10.md +171 -171
  74. package/research/handover-2026-09-13.md +209 -209
  75. package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
  76. package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
  77. package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
  78. package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
  79. package/routes/api/client-logs/post.js +86 -86
  80. package/routes/api/delivery-sink/get.js +72 -72
  81. package/routes/api/link-probe/get.js +51 -51
  82. package/routes/api/playback-plan/audio-tracks/post.js +29 -29
  83. package/routes/api/playback-plan/post.js +53 -53
  84. package/routes/api/sources/post.js +36 -36
  85. package/routes/api/sources/stats/get.js +132 -132
  86. package/routes/api/sources/warm/post.js +217 -217
  87. package/routes/api/subtitles/get.js +230 -230
  88. package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
  89. package/routes/api/transcode-sessions/net-report/post.js +138 -138
  90. package/routes/api/transcode-sessions/post.js +178 -178
  91. package/routes/api/transcode-sessions/progress/get.js +27 -27
  92. package/routes/api/transcode-sessions/release/post.js +41 -41
  93. package/routes/api/transcode-sessions/seek/post.js +63 -63
  94. package/routes/health/get.js +13 -13
  95. package/routes/healthz/get.js +13 -13
  96. package/routes/stream/get.js +416 -416
  97. package/routes/transcode/audio-file/get.js +64 -64
  98. package/routes/transcode/audio-warm/get.js +95 -95
  99. package/routes/transcode/session-file/get.js +52 -52
  100. package/routes/transcode/variant-file/get.js +95 -95
  101. package/routes/transcode/variant-warm/get.js +84 -84
  102. package/scripts/render-run-graph.js +173 -173
  103. package/server.js +748 -748
  104. package/services/encode/CoverageMap.js +428 -428
  105. package/services/encode/CushionReport.js +345 -345
  106. package/services/encode/EncodeAdmission.js +376 -376
  107. package/services/encode/EncodeOrchestrator.js +1192 -1192
  108. package/services/encode/EncodePlan.js +1368 -1368
  109. package/services/encode/EncodeRun.js +32 -4
  110. package/services/encode/EncodeRuns.js +19 -7
  111. package/services/encode/Encoder.js +142 -142
  112. package/services/encode/EncoderSelection.js +38 -38
  113. package/services/encode/LocalObservations.js +359 -359
  114. package/services/encode/NvencEncoder.js +50 -50
  115. package/services/encode/OutputTimes.js +579 -579
  116. package/services/encode/QsvEncoder.js +58 -58
  117. package/services/encode/Renditions.js +2283 -2283
  118. package/services/encode/RunClock.js +87 -87
  119. package/services/encode/RunProgress.js +79 -79
  120. package/services/encode/SegmentDemand.js +95 -95
  121. package/services/encode/SoftwareEncoder.js +121 -121
  122. package/services/encode/V4l2m2mEncoder.js +53 -53
  123. package/services/encode/VaapiEncoder.js +66 -66
  124. package/services/encode/args.js +418 -418
  125. package/services/encode/available-share.js +89 -89
  126. package/services/encode/calibration.js +361 -361
  127. package/services/encode/contention.js +128 -128
  128. package/services/encode/decode-cost-fit.js +363 -363
  129. package/services/encode/encode-exit.js +116 -114
  130. package/services/encode/encode-run-state.js +551 -551
  131. package/services/encode/encoder-readings.js +43 -43
  132. package/services/encode/fingerprint.js +322 -322
  133. package/services/encode/host-load.js +174 -174
  134. package/services/encode/index.js +9 -9
  135. package/services/encode/learned-median.js +90 -90
  136. package/services/encode/output/EncodedOutput.js +57 -57
  137. package/services/encode/output/Output.js +131 -131
  138. package/services/encode/output/OutputCatalog.js +414 -414
  139. package/services/encode/output/OutputSpec.js +390 -390
  140. package/services/encode/output/Timeline.js +308 -308
  141. package/services/encode/output/cut-grid.js +107 -107
  142. package/services/encode/output/ladder.js +32 -32
  143. package/services/encode/output/playlists.js +284 -284
  144. package/services/encode/output/rates.js +125 -125
  145. package/services/encode/output-key-format.js +21 -21
  146. package/services/encode/piece-completeness.js +60 -2
  147. package/services/encode/quality/EncodeCost.js +1043 -1043
  148. package/services/encode/quality/HostLoad.js +525 -525
  149. package/services/encode/quality/HostTimings.js +249 -249
  150. package/services/encode/quality/OutputQualityState.js +24 -24
  151. package/services/encode/quality/QualityController.js +758 -758
  152. package/services/encode/quality/QualityOffer.js +416 -416
  153. package/services/encode/quality/drain-threat.js +47 -47
  154. package/services/encode/quality/link-budget.js +337 -337
  155. package/services/encode/quality/output-format.js +319 -319
  156. package/services/encode/quality/serving-output.js +216 -216
  157. package/services/encode/quality/visible-rung.js +57 -57
  158. package/services/encode/run-budget.js +97 -97
  159. package/services/encode/run-command.js +3 -1
  160. package/services/encode/run-costs.js +155 -155
  161. package/services/encode/run-inputs.js +53 -53
  162. package/services/encode/segment-formats/fmp4.js +476 -476
  163. package/services/encode/segment-formats/index.js +1 -0
  164. package/services/encode/segment-formats/init-compat.js +324 -324
  165. package/services/encode/segment-formats/mp4-boxes.js +8 -1
  166. package/services/encode/segment-formats/mpegts.js +134 -134
  167. package/services/encode/start-stop-cost.js +178 -178
  168. package/services/encode/throughput.js +97 -97
  169. package/services/encode/torrent-cost.js +88 -88
  170. package/services/identity/installation-id.js +38 -38
  171. package/services/media/SourceFile.js +298 -298
  172. package/services/media/SubtitleOrchestrator.js +167 -167
  173. package/services/media/container/KeyframeTable.js +272 -272
  174. package/services/media/container/ebml-reader.js +159 -159
  175. package/services/media/ffmpeg-banner.js +260 -260
  176. package/services/media/keyframe-probe.js +157 -157
  177. package/services/media/media-info-probe.js +93 -93
  178. package/services/media/playback-planner.js +931 -931
  179. package/services/media/tracks/AudioTrack.js +143 -143
  180. package/services/media/tracks/ImageSubtitleTrack.js +19 -19
  181. package/services/media/tracks/SubtitleTrack.js +38 -38
  182. package/services/media/tracks/VideoTrack.js +33 -33
  183. package/services/media/tracks/audio-bound.js +233 -233
  184. package/services/media/tracks/index.js +15 -15
  185. package/services/media/tracks/language-detect.js +174 -174
  186. package/services/server/OutputLifecycle.js +418 -418
  187. package/services/server/SegmentServing.js +1152 -1152
  188. package/services/server/ViewerRequests.js +785 -785
  189. package/services/server/WaitLedger.js +142 -142
  190. package/services/server/controllers/PlaybackController.js +40 -40
  191. package/services/server/controllers/SubtitleController.js +109 -109
  192. package/services/server/http-responses.js +60 -60
  193. package/services/server/transcode-session-files.js +325 -325
  194. package/services/server/wire-outputs.js +1029 -1029
  195. package/services/storage/Diagnostics.js +202 -202
  196. package/services/storage/MachineBudget.js +235 -235
  197. package/services/storage/allowance.js +107 -107
  198. package/services/storage/core-dumps.js +106 -106
  199. package/services/storage/files/CompletedFiles.js +391 -391
  200. package/services/storage/files/piece-from-whole-file.js +118 -118
  201. package/services/storage/free.js +71 -71
  202. package/services/storage/keep.js +48 -48
  203. package/services/storage/machine-memory.js +56 -56
  204. package/services/storage/memory-report.js +575 -575
  205. package/services/storage/piece-store/piece-disk-store.js +539 -539
  206. package/services/storage/piece-store/piece-lru.js +495 -495
  207. package/services/storage/returns.js +103 -103
  208. package/services/storage/wire.js +175 -175
  209. package/services/torrent/demand/DemandRegister.js +202 -202
  210. package/services/torrent/demand/Urgency.js +137 -137
  211. package/services/torrent/demand/Window.js +118 -118
  212. package/services/torrent/demand/index.js +8 -8
  213. package/services/torrent/demand/pieces.js +140 -140
  214. package/services/torrent/download/SwarmSelection.js +386 -386
  215. package/services/torrent/download/registry.js +102 -102
  216. package/services/torrent/download/withdraw-claim.js +80 -80
  217. package/services/torrent/files.js +455 -455
  218. package/services/torrent/naming.js +619 -619
  219. package/services/torrent/piece-store-of.js +29 -29
  220. package/services/torrent/supply-margin.js +264 -264
  221. package/services/torrent/worker/background-fill.js +213 -213
  222. package/services/torrent/worker/channel.js +271 -271
  223. package/services/torrent/worker/fastest-wires.js +342 -342
  224. package/services/torrent/worker/handle-state.js +23 -23
  225. package/services/torrent/worker/install-webrtc-shim.js +32 -32
  226. package/services/torrent/worker/piece-reader.js +1078 -1078
  227. package/services/torrent/worker/pool-adapter.js +371 -371
  228. package/services/torrent/worker/protocol.js +184 -184
  229. package/services/torrent/worker/resume-warm.js +107 -107
  230. package/services/torrent/worker/webrtc-shim.js +219 -219
  231. package/services/transport/delivery-probe.js +842 -842
  232. package/services/transport/health-collector.js +53 -53
  233. package/services/transport/nat-classifier.js +231 -231
  234. package/services/transport/packet-witness.js +808 -808
  235. package/services/transport/port-mapper.js +235 -235
  236. package/services/transport/registry-api.js +81 -81
  237. package/services/transport/tunnel-client.js +447 -447
  238. package/services/transport/usrsctp-state.js +175 -175
  239. package/services/transport/webrtc-manager.js +484 -484
  240. package/services/viewer/Assignments.js +340 -340
  241. package/services/viewer/PriorityOrchestrator.js +325 -325
  242. package/services/viewer/Viewer.js +691 -691
  243. package/services/viewer/Viewers.js +368 -368
  244. package/services/viewer/active-output.js +37 -37
  245. package/services/viewer/audio-start.js +46 -46
  246. package/services/viewer/buffer-trend.js +81 -81
  247. package/services/viewer/choices.js +647 -647
  248. package/services/viewer/media-time.js +355 -355
  249. package/services/viewer/playback-readiness.js +635 -635
  250. package/services/viewer/positions.js +126 -126
  251. package/services/viewer/report-intake.js +46 -46
  252. package/store/source-registry.js +71 -71
  253. package/test/asset-paths.test.js +28 -28
  254. package/test/audio-buffer-continuity.test.js +77 -77
  255. package/test/audio-inventory.test.js +212 -212
  256. package/test/audio-track-bitrate.test.js +40 -40
  257. package/test/available-share.test.js +76 -76
  258. package/test/background-fill.test.js +190 -190
  259. package/test/client-log-file.test.js +110 -110
  260. package/test/completed-files.test.js +115 -115
  261. package/test/concurrent-cost.test.js +148 -148
  262. package/test/container-media-info.test.js +228 -228
  263. package/test/contention.test.js +76 -76
  264. package/test/core-dumps.test.js +57 -57
  265. package/test/coverage-follows-the-disk.test.js +228 -228
  266. package/test/coverage-map.test.js +195 -195
  267. package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
  268. package/test/cut-times-timeline.test.js +65 -65
  269. package/test/cuts-follow-published-grid.test.js +109 -109
  270. package/test/data-channel-frame.test.js +63 -63
  271. package/test/data-channel-viewer-lifecycle.test.js +92 -92
  272. package/test/dead-torrent-handle.test.js +52 -52
  273. package/test/declared-rates.test.js +116 -116
  274. package/test/declared-tracks.test.js +43 -43
  275. package/test/decode-cost-fit.test.js +202 -202
  276. package/test/delivery-probe.test.js +393 -393
  277. package/test/demand-register.test.js +206 -206
  278. package/test/dht-bootstrap.test.js +72 -72
  279. package/test/drain-threat.test.js +65 -65
  280. package/test/duplicate-add.test.js +48 -48
  281. package/test/encode-admission.test.js +146 -146
  282. package/test/encode-cost.test.js +275 -275
  283. package/test/encode-exit.test.js +111 -111
  284. package/test/encode-orchestrator.test.js +426 -426
  285. package/test/encode-plan-viewers.test.js +725 -725
  286. package/test/encode-plan.test.js +545 -545
  287. package/test/encode-run-state.test.js +314 -314
  288. package/test/encode-run.test.js +38 -0
  289. package/test/encode-slope.test.js +46 -46
  290. package/test/encoded-output.test.js +39 -39
  291. package/test/encoder-kinds.test.js +122 -122
  292. package/test/encoder-readings.test.js +68 -68
  293. package/test/every-claimant-is-told-its-share.test.js +152 -152
  294. package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
  295. package/test/failed-start-breaker.test.js +202 -202
  296. package/test/fastest-wires.test.js +143 -143
  297. package/test/ffmpeg-stream-counts.test.js +78 -78
  298. package/test/file-edges.test.js +191 -191
  299. package/test/fixtures/refused-audio-20261002.json +10 -0
  300. package/test/fmp4-presentation-clock.test.js +4 -2
  301. package/test/fmp4-self-contained.test.js +80 -80
  302. package/test/fmp4-timestamps.test.js +140 -140
  303. package/test/fmp4-track-completeness.test.js +41 -41
  304. package/test/health-metrics.test.js +38 -38
  305. package/test/held-request-width.test.js +157 -157
  306. package/test/helpers/encode-run.js +240 -240
  307. package/test/helpers/output-spec.js +63 -63
  308. package/test/host-load.test.js +68 -68
  309. package/test/index-accuracy-is-about-the-file.test.js +128 -128
  310. package/test/init-compat.test.js +219 -219
  311. package/test/input-lost-quiets-the-plan.test.js +261 -261
  312. package/test/input-unavailable.test.js +48 -48
  313. package/test/installation-id.test.js +39 -39
  314. package/test/keeping-period.test.js +83 -83
  315. package/test/keyframe-index-accuracy.test.js +188 -188
  316. package/test/keyframes-belong-to-the-file.test.js +249 -249
  317. package/test/learned-median.test.js +37 -37
  318. package/test/letting-go-asks-who-is-watching.test.js +191 -191
  319. package/test/limit-row-by-area.test.js +60 -60
  320. package/test/link-budget.test.js +111 -111
  321. package/test/link-probe.test.js +56 -56
  322. package/test/local-encoding-data.test.js +273 -273
  323. package/test/logger-repeats.test.js +134 -134
  324. package/test/look-ahead-coverage.test.js +56 -56
  325. package/test/manager-surface.test.js +291 -291
  326. package/test/matroska-cues-track.test.js +192 -192
  327. package/test/media-time.test.js +62 -62
  328. package/test/memory-budget.test.js +390 -390
  329. package/test/move-cost.test.js +216 -216
  330. package/test/mp4-subtitles.test.js +173 -173
  331. package/test/one-authority.test.js +285 -285
  332. package/test/one-budget.test.js +149 -149
  333. package/test/one-resource-divided.test.js +185 -185
  334. package/test/one-serving-output-per-height.test.js +458 -458
  335. package/test/orchestrator-wired.test.js +207 -207
  336. package/test/output-catalog.test.js +39 -39
  337. package/test/output-shape.test.js +21 -21
  338. package/test/output-spec.test.js +251 -251
  339. package/test/output-speed.test.js +87 -87
  340. package/test/outputs-being-prepared.test.js +40 -40
  341. package/test/packet-witness-ring.test.js +236 -236
  342. package/test/packet-witness.test.js +148 -148
  343. package/test/piece-completeness.test.js +37 -1
  344. package/test/piece-disk-store.test.js +401 -401
  345. package/test/piece-from-whole-file.test.js +129 -129
  346. package/test/piece-lru.test.js +319 -319
  347. package/test/piece-reader.test.js +162 -162
  348. package/test/piece-selection-offset.test.js +85 -85
  349. package/test/piece-store-eviction.test.js +595 -595
  350. package/test/piece-store-never-refuses.test.js +153 -153
  351. package/test/piece-store-reservations.test.js +403 -403
  352. package/test/piece-store-slow-disk.test.js +159 -159
  353. package/test/piece-tail.test.js +97 -97
  354. package/test/plan-host-timings.test.js +68 -68
  355. package/test/plan-is-steady.test.js +225 -225
  356. package/test/playback-readiness.test.js +518 -518
  357. package/test/playback-start-regressions.test.js +101 -101
  358. package/test/priority-map-download.test.js +174 -174
  359. package/test/priority-map-emptied.test.js +204 -204
  360. package/test/priority-map-per-output.test.js +215 -215
  361. package/test/priority-map.test.js +163 -163
  362. package/test/probe-wedge-certainty.test.js +60 -60
  363. package/test/produced-copy-choice.test.js +213 -213
  364. package/test/published-timeline.test.js +83 -83
  365. package/test/quality-offer.test.js +160 -160
  366. package/test/rate-control-in-the-output.test.js +180 -180
  367. package/test/read-survives-withdrawal.test.js +164 -164
  368. package/test/read-window.test.js +365 -365
  369. package/test/resume-warm.test.js +39 -39
  370. package/test/run-graph-drift.test.js +37 -37
  371. package/test/run-inputs.test.js +80 -80
  372. package/test/run-intervals.test.js +100 -100
  373. package/test/run-position-follows-published-grid.test.js +89 -89
  374. package/test/run-progress.test.js +64 -64
  375. package/test/same-height-switch.test.js +663 -663
  376. package/test/seek-frees-the-viewers-requests.test.js +217 -217
  377. package/test/seek-landing.test.js +109 -109
  378. package/test/seek-target-not-superseded.test.js +195 -195
  379. package/test/segment-cut-times.test.js +63 -63
  380. package/test/segment-demand.test.js +80 -80
  381. package/test/segment-hold-supersede.test.js +91 -91
  382. package/test/segment-store-eviction.test.js +247 -247
  383. package/test/segment-store.test.js +352 -352
  384. package/test/segment-true-start.test.js +86 -86
  385. package/test/segments-are-shared.test.js +176 -176
  386. package/test/send-chunk-bytes.test.js +86 -86
  387. package/test/serving-output.test.js +102 -102
  388. package/test/shared-piece-store.test.js +310 -310
  389. package/test/sidecar-naming.test.js +142 -142
  390. package/test/soundtrack-rate-bound.test.js +289 -289
  391. package/test/source-file.test.js +133 -133
  392. package/test/source-registry.test.js +18 -18
  393. package/test/split-a-stretch.test.js +139 -139
  394. package/test/stale-request-after-seek.test.js +158 -158
  395. package/test/startup-readings.test.js +124 -124
  396. package/test/stream-route.test.js +237 -237
  397. package/test/subtitle-cue-framing.test.js +200 -200
  398. package/test/subtitle-cue-source.test.js +102 -102
  399. package/test/subtitle-cursor.test.js +63 -63
  400. package/test/subtitle-defaults.test.js +118 -118
  401. package/test/subtitle-language.test.js +252 -252
  402. package/test/supply-margin.test.js +190 -190
  403. package/test/swarm-follows-readers.test.js +174 -174
  404. package/test/swarm-reach.test.js +130 -130
  405. package/test/swarm-selection.test.js +220 -220
  406. package/test/tail-duplication.test.js +167 -167
  407. package/test/the-channel-has-both-ends.test.js +91 -91
  408. package/test/the-torrent-thread-serves-bytes.test.js +107 -107
  409. package/test/thread-pool.test.js +53 -53
  410. package/test/timeline.test.js +84 -84
  411. package/test/torrent-contents.test.js +235 -235
  412. package/test/torrent-cost.test.js +71 -71
  413. package/test/torrent-files.test.js +221 -221
  414. package/test/torrent-naming.test.js +255 -255
  415. package/test/tracks-begin-together.test.js +209 -209
  416. package/test/tunnel-renewal.test.js +167 -167
  417. package/test/upload-hurry.test.js +186 -186
  418. package/test/usrsctp-state.test.js +131 -131
  419. package/test/video-facts.test.js +102 -102
  420. package/test/viewer-assignments.test.js +143 -143
  421. package/test/viewer-outputs.test.js +285 -285
  422. package/test/viewer-presence.test.js +116 -116
  423. package/test/viewer-states.test.js +189 -189
  424. package/test/viewer-subtitle-subscription.test.js +96 -96
  425. package/test/viewer.test.js +139 -139
  426. package/test/visible-rung.test.js +37 -37
  427. package/test/wait-ledger.test.js +100 -100
  428. package/test/webrtc-shim.test.js +149 -149
  429. package/test/wedge-certainty.test.js +131 -131
  430. package/test/withdraw-piece-claim.test.js +212 -212
  431. package/test/worker-channel.test.js +158 -158
  432. package/utils/client-log-file.js +151 -151
  433. package/utils/logger.js +312 -312
  434. package/utils/parse-range.js +25 -25
  435. package/utils/perf.js +59 -59
  436. package/utils/torrent-source-key.js +38 -38
@@ -1,92 +1,92 @@
1
- # Design: Adaptive bitrate (proxy)
2
-
3
- Read before coding: `services/encode/hwaccel.js` (`chooseSoftwareEncodeSettings`,
4
- the software descriptor's `buildVideoArgs`), `services/hls-session-manager.js`
5
- (the `BUDGET_*` constants, `#chooseEncodeBudget`, the budget interval check
6
- `#enforceRealtimeBudget`, `#classifyTranscodeBound`, `#applyBudgetDownshift`,
7
- session fields around `budgetLadder`), `routes/api/transcode-sessions/`
8
- (route file conventions), `server.js` (route wiring, deps injection).
9
-
10
- ## (a) Caps — constrained CRF
11
-
12
- Nominal H.264 rates by rung height (nearest rung wins for odd heights):
13
-
14
- RUNG_NOMINAL_KBPS = { 1080: 5000, 720: 2800, 480: 1400, 360: 800, 240: 400 }
15
- CAP_MAXRATE_FACTOR = 1.3
16
- CAP_BUFSIZE_FACTOR = 1.5
17
-
18
- In the software descriptor's video args (where `-crf`/`-preset` are emitted),
19
- add for encode height H (the actual encode height after budget/manual
20
- selection, not the source height):
21
-
22
- -maxrate <round(nominal(H) * 1.3)>k
23
- -bufsize <round(nominal(H) * 1.5)>k
24
-
25
- CRF stays the quality driver; the caps only bound peaks (standard
26
- constrained-CRF). Do NOT touch hardware descriptors in this change.
27
-
28
- ## (b) Net report intake
29
-
30
- Route: `POST /api/transcode-sessions/:id/net-report` (data-channel path, so
31
- add the prefix to the allowlist regex in `transport/data-channel-handler.js` if not
32
- covered by the existing `/api/` rule — verify). Body:
33
-
34
- { linkMbps: number, bufferedAheadSec: number }
35
-
36
- Validation: both finite numbers, `linkMbps > 0`, else 400. Unknown session →
37
- 404. On success 204. Handler stores on the session:
38
-
39
- session.netReport = { linkMbps, bufferedAheadSec, at: Date.now() }
40
-
41
- ## (b) Budget-loop trigger
42
-
43
- Constants (near the other BUDGET_ constants):
44
-
45
- LINK_REPORT_FRESH_MS = 30_000 // ignore stale reports
46
- LINK_SAFETY = 0.8 // usable share of reported link
47
- LINK_SLOW_WINDOW_MS = 15_000 // sustained deficit before acting
48
- LINK_LOW_BUFFER_SEC = 10 // only act while the viewer runs dry
49
-
50
- In the periodic budget check, for each active software-transcode session,
51
- alongside the CPU check:
52
-
53
- report fresh (now - at < LINK_REPORT_FRESH_MS)?
54
- manualQuality not set?
55
- observed = observed stream bitrate, Mbit/s — recent produced segment
56
- bytes / segment duration (reuse/extend whatever the CPU path
57
- reads; a rolling average over the last ~5 segments)
58
- deficit = report.linkMbps * LINK_SAFETY < observed
59
- if deficit AND report.bufferedAheadSec < LINK_LOW_BUFFER_SEC:
60
- accumulate slow-time (same pattern as the CPU slow window)
61
- if slow ≥ LINK_SLOW_WINDOW_MS → #applyBudgetDownshift(session,
62
- reason "link") // existing cooldown + step cap + floor apply
63
- else: reset the link slow window
64
-
65
- `#applyBudgetDownshift` is reused as-is except the log line carries the
66
- reason: `budget downshift (link) …` vs the current CPU wording, so the
67
- client-log pipeline can tell them apart in the field.
68
-
69
- No upswitch in v1 (mirrors the CPU budget's conservatism). A downshifted
70
- session stays down until re-opened.
71
-
72
- ## Interactions
73
-
74
- - Caps make `observed` honest: without (a) a complex scene can spike far
75
- above nominal and flap the trigger; with caps observed ≤ maxrate.
76
- - CPU trigger and link trigger share the cooldown inside
77
- `#applyBudgetDownshift` — they cannot double-fire.
78
- - `manualQuality` set → link trigger skipped entirely (user pinned quality
79
- explicitly; the proxy respects it, matching the manual-quality contract).
80
- - Old browsers never POST reports → trigger never fires → behaviour
81
- identical to today plus caps.
82
-
83
- ## Verification
84
-
85
- - Unit: nominal table lookup (exact rungs + nearest for odd heights); ffmpeg
86
- arg assembly contains the caps; trigger logic with a fake clock (fresh vs
87
- stale report, deficit accumulating to a downshift, buffer-high suppresses,
88
- manualQuality suppresses).
89
- - `node --check` on touched files.
90
- - Field (after the server change ships): cellular session shows
91
- `budget downshift (link)` and the stream settles at a rung whose bitrate
92
- fits the link; segment sizes bounded (~maxrate × segDur / 8 max).
1
+ # Design: Adaptive bitrate (proxy)
2
+
3
+ Read before coding: `services/encode/hwaccel.js` (`chooseSoftwareEncodeSettings`,
4
+ the software descriptor's `buildVideoArgs`), `services/hls-session-manager.js`
5
+ (the `BUDGET_*` constants, `#chooseEncodeBudget`, the budget interval check
6
+ `#enforceRealtimeBudget`, `#classifyTranscodeBound`, `#applyBudgetDownshift`,
7
+ session fields around `budgetLadder`), `routes/api/transcode-sessions/`
8
+ (route file conventions), `server.js` (route wiring, deps injection).
9
+
10
+ ## (a) Caps — constrained CRF
11
+
12
+ Nominal H.264 rates by rung height (nearest rung wins for odd heights):
13
+
14
+ RUNG_NOMINAL_KBPS = { 1080: 5000, 720: 2800, 480: 1400, 360: 800, 240: 400 }
15
+ CAP_MAXRATE_FACTOR = 1.3
16
+ CAP_BUFSIZE_FACTOR = 1.5
17
+
18
+ In the software descriptor's video args (where `-crf`/`-preset` are emitted),
19
+ add for encode height H (the actual encode height after budget/manual
20
+ selection, not the source height):
21
+
22
+ -maxrate <round(nominal(H) * 1.3)>k
23
+ -bufsize <round(nominal(H) * 1.5)>k
24
+
25
+ CRF stays the quality driver; the caps only bound peaks (standard
26
+ constrained-CRF). Do NOT touch hardware descriptors in this change.
27
+
28
+ ## (b) Net report intake
29
+
30
+ Route: `POST /api/transcode-sessions/:id/net-report` (data-channel path, so
31
+ add the prefix to the allowlist regex in `transport/data-channel-handler.js` if not
32
+ covered by the existing `/api/` rule — verify). Body:
33
+
34
+ { linkMbps: number, bufferedAheadSec: number }
35
+
36
+ Validation: both finite numbers, `linkMbps > 0`, else 400. Unknown session →
37
+ 404. On success 204. Handler stores on the session:
38
+
39
+ session.netReport = { linkMbps, bufferedAheadSec, at: Date.now() }
40
+
41
+ ## (b) Budget-loop trigger
42
+
43
+ Constants (near the other BUDGET_ constants):
44
+
45
+ LINK_REPORT_FRESH_MS = 30_000 // ignore stale reports
46
+ LINK_SAFETY = 0.8 // usable share of reported link
47
+ LINK_SLOW_WINDOW_MS = 15_000 // sustained deficit before acting
48
+ LINK_LOW_BUFFER_SEC = 10 // only act while the viewer runs dry
49
+
50
+ In the periodic budget check, for each active software-transcode session,
51
+ alongside the CPU check:
52
+
53
+ report fresh (now - at < LINK_REPORT_FRESH_MS)?
54
+ manualQuality not set?
55
+ observed = observed stream bitrate, Mbit/s — recent produced segment
56
+ bytes / segment duration (reuse/extend whatever the CPU path
57
+ reads; a rolling average over the last ~5 segments)
58
+ deficit = report.linkMbps * LINK_SAFETY < observed
59
+ if deficit AND report.bufferedAheadSec < LINK_LOW_BUFFER_SEC:
60
+ accumulate slow-time (same pattern as the CPU slow window)
61
+ if slow ≥ LINK_SLOW_WINDOW_MS → #applyBudgetDownshift(session,
62
+ reason "link") // existing cooldown + step cap + floor apply
63
+ else: reset the link slow window
64
+
65
+ `#applyBudgetDownshift` is reused as-is except the log line carries the
66
+ reason: `budget downshift (link) …` vs the current CPU wording, so the
67
+ client-log pipeline can tell them apart in the field.
68
+
69
+ No upswitch in v1 (mirrors the CPU budget's conservatism). A downshifted
70
+ session stays down until re-opened.
71
+
72
+ ## Interactions
73
+
74
+ - Caps make `observed` honest: without (a) a complex scene can spike far
75
+ above nominal and flap the trigger; with caps observed ≤ maxrate.
76
+ - CPU trigger and link trigger share the cooldown inside
77
+ `#applyBudgetDownshift` — they cannot double-fire.
78
+ - `manualQuality` set → link trigger skipped entirely (user pinned quality
79
+ explicitly; the proxy respects it, matching the manual-quality contract).
80
+ - Old browsers never POST reports → trigger never fires → behaviour
81
+ identical to today plus caps.
82
+
83
+ ## Verification
84
+
85
+ - Unit: nominal table lookup (exact rungs + nearest for odd heights); ffmpeg
86
+ arg assembly contains the caps; trigger logic with a fake clock (fresh vs
87
+ stale report, deficit accumulating to a downshift, buffer-high suppresses,
88
+ manualQuality suppresses).
89
+ - `node --check` on touched files.
90
+ - Field (after the server change ships): cellular session shows
91
+ `budget downshift (link)` and the stream settles at a rung whose bitrate
92
+ fits the link; segment sizes bounded (~maxrate × segDur / 8 max).
@@ -1,56 +1,56 @@
1
- # Proposal: Adaptive bitrate for thin viewer links (proxy side)
2
-
3
- ## Why
4
-
5
- Field evidence (iPhone on cellular, 2026-07-10, session `14cc1017`): encode
6
- bitrate is unbounded — transcoded segments reached 9 MB per 4 s (~18 Mbit/s)
7
- against a measured 1–5.8 Mbit/s cellular link. Result: 45 s prebuffer, buffer
8
- draining during playback (`bottleneck delta=-7.1s`), "было так себе". The
9
- realtime budget protects the PROXY's CPU but nothing protects the VIEWER's
10
- link: quality never adapts to how fast the viewer can actually download.
11
-
12
- ## What Changes
13
-
14
- Two parts, one release:
15
-
16
- - **(a) Bitrate caps (constrained CRF).** Software video encodes gain
17
- `-maxrate`/`-bufsize` sized per resolution rung from a nominal-rate table
18
- (H.264 ladder: 1080p→5000K, 720p→2800K, 480p→1400K, 360p→800K, 240p→400K;
19
- nearest rung by encode height). Multipliers from webtor's production
20
- ladder: `maxrate = 1.3×` nominal, `bufsize = 1.5×` nominal. CRF/preset
21
- selection stays — the caps only bound the peaks that killed the cellular
22
- session. Applies to the software (libx264) path; hardware encoders keep
23
- their current args (follow-up — their rate-control flags differ and any
24
- change must pass the strict startup test on real hardware).
25
- - **(b) Viewer-link downshift trigger.** A new data-channel route lets the
26
- browser report its measured link state (`linkMbps` — rolling median of
27
- per-segment transfer throughput; `bufferedAheadSec`) every ~10 s. The
28
- existing realtime-budget loop gains a SECOND downshift trigger: when
29
- reports are fresh, `manualQuality` is not set, the viewer's link is
30
- sustainedly slower than the produced stream (`linkMbps × 0.8 <` observed
31
- segment bitrate for ≥ 15 s) AND the viewer's buffer is low (< 10 s), step
32
- one rung down via the existing `#applyBudgetDownshift` (same cooldown,
33
- step cap, no upswitch v1). Distinct log reason (`reason=link`) so field
34
- logs distinguish CPU-bound from link-bound downshifts.
35
-
36
- Missing/stale reports change nothing (old clients keep working — the trigger
37
- simply never fires). Manual quality pins win: when `manualQuality` is set the
38
- trigger is skipped.
39
-
40
- ## Capabilities
41
-
42
- ### Modified Capabilities
43
-
44
- - `transcode-quality`: encodes are bitrate-capped per rung; the budget loop
45
- adapts to the viewer's link, not only to the proxy's CPU.
46
-
47
- ## Impact
48
-
49
- - `services/encode/hwaccel.js` — nominal-rate table + caps in the software
50
- descriptor's video args.
51
- - `services/hls-session-manager.js` — accept/store net reports per session;
52
- link trigger in the budget check; log line.
53
- - `routes/api/transcode-sessions/net-report/post.js` (new) + `server.js`
54
- wiring — report intake.
55
- - Proxy release + ha-addon bump. The server-side reporter is a separate
56
- change (`server/viewer-net-report`); release proxy+addon FIRST.
1
+ # Proposal: Adaptive bitrate for thin viewer links (proxy side)
2
+
3
+ ## Why
4
+
5
+ Field evidence (iPhone on cellular, 2026-07-10, session `14cc1017`): encode
6
+ bitrate is unbounded — transcoded segments reached 9 MB per 4 s (~18 Mbit/s)
7
+ against a measured 1–5.8 Mbit/s cellular link. Result: 45 s prebuffer, buffer
8
+ draining during playback (`bottleneck delta=-7.1s`), "было так себе". The
9
+ realtime budget protects the PROXY's CPU but nothing protects the VIEWER's
10
+ link: quality never adapts to how fast the viewer can actually download.
11
+
12
+ ## What Changes
13
+
14
+ Two parts, one release:
15
+
16
+ - **(a) Bitrate caps (constrained CRF).** Software video encodes gain
17
+ `-maxrate`/`-bufsize` sized per resolution rung from a nominal-rate table
18
+ (H.264 ladder: 1080p→5000K, 720p→2800K, 480p→1400K, 360p→800K, 240p→400K;
19
+ nearest rung by encode height). Multipliers from webtor's production
20
+ ladder: `maxrate = 1.3×` nominal, `bufsize = 1.5×` nominal. CRF/preset
21
+ selection stays — the caps only bound the peaks that killed the cellular
22
+ session. Applies to the software (libx264) path; hardware encoders keep
23
+ their current args (follow-up — their rate-control flags differ and any
24
+ change must pass the strict startup test on real hardware).
25
+ - **(b) Viewer-link downshift trigger.** A new data-channel route lets the
26
+ browser report its measured link state (`linkMbps` — rolling median of
27
+ per-segment transfer throughput; `bufferedAheadSec`) every ~10 s. The
28
+ existing realtime-budget loop gains a SECOND downshift trigger: when
29
+ reports are fresh, `manualQuality` is not set, the viewer's link is
30
+ sustainedly slower than the produced stream (`linkMbps × 0.8 <` observed
31
+ segment bitrate for ≥ 15 s) AND the viewer's buffer is low (< 10 s), step
32
+ one rung down via the existing `#applyBudgetDownshift` (same cooldown,
33
+ step cap, no upswitch v1). Distinct log reason (`reason=link`) so field
34
+ logs distinguish CPU-bound from link-bound downshifts.
35
+
36
+ Missing/stale reports change nothing (old clients keep working — the trigger
37
+ simply never fires). Manual quality pins win: when `manualQuality` is set the
38
+ trigger is skipped.
39
+
40
+ ## Capabilities
41
+
42
+ ### Modified Capabilities
43
+
44
+ - `transcode-quality`: encodes are bitrate-capped per rung; the budget loop
45
+ adapts to the viewer's link, not only to the proxy's CPU.
46
+
47
+ ## Impact
48
+
49
+ - `services/encode/hwaccel.js` — nominal-rate table + caps in the software
50
+ descriptor's video args.
51
+ - `services/hls-session-manager.js` — accept/store net reports per session;
52
+ link trigger in the budget check; log line.
53
+ - `routes/api/transcode-sessions/net-report/post.js` (new) + `server.js`
54
+ wiring — report intake.
55
+ - Proxy release + ha-addon bump. The server-side reporter is a separate
56
+ change (`server/viewer-net-report`); release proxy+addon FIRST.
@@ -1,51 +1,51 @@
1
- # adaptive-bitrate — delta spec (proxy)
2
-
3
- ## ADDED Requirements
4
-
5
- ### Requirement: Software encodes are bitrate-capped per resolution rung
6
-
7
- Software video encodes SHALL carry `-maxrate`/`-bufsize` derived from a
8
- per-rung nominal-rate table (1.3× / 1.5× of the rung's nominal), keeping the
9
- existing CRF/preset quality selection (constrained CRF). The cap follows the
10
- ACTUAL encode height after budget or manual selection.
11
-
12
- #### Scenario: Complex scene on a capped encode
13
- - **WHEN** a transcoded scene would spike far above the rung's nominal rate
14
- - **THEN** the produced segments stay bounded by the cap instead of reaching
15
- multi-megabyte sizes that a thin viewer link cannot download in time
16
-
17
- ### Requirement: The proxy accepts viewer link reports
18
-
19
- A data-channel route SHALL accept periodic reports
20
- `{ linkMbps, bufferedAheadSec }` for an active transcode session and store
21
- the latest report with its arrival time. Invalid bodies are rejected;
22
- unknown sessions get 404; missing reports are not an error condition.
23
-
24
- #### Scenario: Old client
25
- - **WHEN** a browser never sends net reports
26
- - **THEN** the session behaves exactly as before this change (plus caps)
27
-
28
- ### Requirement: The budget loop downshifts on a sustained link deficit
29
-
30
- When a fresh report shows the viewer's usable link (reported × safety
31
- margin) sustainedly below the observed produced bitrate AND the viewer's
32
- buffer is low, the existing budget downshift SHALL step the encode one rung
33
- down (same cooldown, step cap and floor as the CPU trigger; no upswitch).
34
- The trigger SHALL be skipped when `manualQuality` is set, when reports are
35
- stale, or when the viewer's buffer is comfortable. The downshift log line
36
- SHALL name the reason (`link`) distinctly from the CPU reason.
37
-
38
- #### Scenario: Cellular viewer, stream too fat
39
- - **WHEN** reports show 3 Mbit/s usable link against 6 Mbit/s produced
40
- bitrate for longer than the slow window, with a draining buffer
41
- - **THEN** the encode steps down one rung and the log shows a link-reason
42
- downshift
43
-
44
- #### Scenario: Slow link but full buffer
45
- - **WHEN** the link is slower than the stream but the viewer's buffer stays
46
- comfortable (e.g. paused playback filling ahead)
47
- - **THEN** no downshift happens
48
-
49
- #### Scenario: Manual quality pinned
50
- - **WHEN** the viewer picked a forced quality
51
- - **THEN** link reports never trigger a downshift
1
+ # adaptive-bitrate — delta spec (proxy)
2
+
3
+ ## ADDED Requirements
4
+
5
+ ### Requirement: Software encodes are bitrate-capped per resolution rung
6
+
7
+ Software video encodes SHALL carry `-maxrate`/`-bufsize` derived from a
8
+ per-rung nominal-rate table (1.3× / 1.5× of the rung's nominal), keeping the
9
+ existing CRF/preset quality selection (constrained CRF). The cap follows the
10
+ ACTUAL encode height after budget or manual selection.
11
+
12
+ #### Scenario: Complex scene on a capped encode
13
+ - **WHEN** a transcoded scene would spike far above the rung's nominal rate
14
+ - **THEN** the produced segments stay bounded by the cap instead of reaching
15
+ multi-megabyte sizes that a thin viewer link cannot download in time
16
+
17
+ ### Requirement: The proxy accepts viewer link reports
18
+
19
+ A data-channel route SHALL accept periodic reports
20
+ `{ linkMbps, bufferedAheadSec }` for an active transcode session and store
21
+ the latest report with its arrival time. Invalid bodies are rejected;
22
+ unknown sessions get 404; missing reports are not an error condition.
23
+
24
+ #### Scenario: Old client
25
+ - **WHEN** a browser never sends net reports
26
+ - **THEN** the session behaves exactly as before this change (plus caps)
27
+
28
+ ### Requirement: The budget loop downshifts on a sustained link deficit
29
+
30
+ When a fresh report shows the viewer's usable link (reported × safety
31
+ margin) sustainedly below the observed produced bitrate AND the viewer's
32
+ buffer is low, the existing budget downshift SHALL step the encode one rung
33
+ down (same cooldown, step cap and floor as the CPU trigger; no upswitch).
34
+ The trigger SHALL be skipped when `manualQuality` is set, when reports are
35
+ stale, or when the viewer's buffer is comfortable. The downshift log line
36
+ SHALL name the reason (`link`) distinctly from the CPU reason.
37
+
38
+ #### Scenario: Cellular viewer, stream too fat
39
+ - **WHEN** reports show 3 Mbit/s usable link against 6 Mbit/s produced
40
+ bitrate for longer than the slow window, with a draining buffer
41
+ - **THEN** the encode steps down one rung and the log shows a link-reason
42
+ downshift
43
+
44
+ #### Scenario: Slow link but full buffer
45
+ - **WHEN** the link is slower than the stream but the viewer's buffer stays
46
+ comfortable (e.g. paused playback filling ahead)
47
+ - **THEN** no downshift happens
48
+
49
+ #### Scenario: Manual quality pinned
50
+ - **WHEN** the viewer picked a forced quality
51
+ - **THEN** link reports never trigger a downshift
@@ -1,40 +1,40 @@
1
- # Tasks: Adaptive bitrate (proxy)
2
-
3
- ## 1. Caps
4
-
5
- - [x] 1.1 `encode/hwaccel.js`: `RUNG_NOMINAL_KBPS` table + nearest-rung lookup;
6
- software descriptor's video args emit `-maxrate`/`-bufsize`
7
- (1.3× / 1.5× nominal for the actual encode height). Hardware
8
- descriptors untouched.
9
-
10
- ## 2. Net report intake
11
-
12
- - [x] 2.1 `routes/api/transcode-sessions/net-report/post.js`: validate
13
- `{ linkMbps, bufferedAheadSec }`, store
14
- `session.netReport = { linkMbps, bufferedAheadSec, at }`, 204/400/404.
15
- - [x] 2.2 Wire in `server.js`; verify the data-channel path allowlist covers
16
- the route.
17
-
18
- ## 3. Budget trigger
19
-
20
- - [x] 3.1 Constants `LINK_REPORT_FRESH_MS`, `LINK_SAFETY`,
21
- `LINK_SLOW_WINDOW_MS`, `LINK_LOW_BUFFER_SEC`.
22
- - [x] 3.2 Observed produced bitrate (rolling, last ~5 segments) available to
23
- the budget check.
24
- - [x] 3.3 Link deficit accumulation + `#applyBudgetDownshift(session,
25
- reason "link")`; skip on manualQuality/stale/comfortable buffer; log
26
- reason distinctly.
27
-
28
- ## 4. Verification
29
-
30
- - [x] 4.1 Unit: table lookup, arg assembly, trigger logic (fake clock):
31
- deficit→downshift, stale report→no-op, high buffer→no-op,
32
- manualQuality→no-op.
33
- - [x] 4.2 `node --check` on touched files.
34
-
35
- ## 5. Release
36
-
37
- - [ ] 5.1 CHANGELOG (next patch) + `npm run patch` (user OTP), then addon
38
- bump + push + HA update. Release BEFORE the server reporter change.
39
- - [ ] 5.2 Field: cellular Poirot/Mavka run shows `budget downshift (link)`
40
- and bounded segment sizes; correlate via client-log pipeline.
1
+ # Tasks: Adaptive bitrate (proxy)
2
+
3
+ ## 1. Caps
4
+
5
+ - [x] 1.1 `encode/hwaccel.js`: `RUNG_NOMINAL_KBPS` table + nearest-rung lookup;
6
+ software descriptor's video args emit `-maxrate`/`-bufsize`
7
+ (1.3× / 1.5× nominal for the actual encode height). Hardware
8
+ descriptors untouched.
9
+
10
+ ## 2. Net report intake
11
+
12
+ - [x] 2.1 `routes/api/transcode-sessions/net-report/post.js`: validate
13
+ `{ linkMbps, bufferedAheadSec }`, store
14
+ `session.netReport = { linkMbps, bufferedAheadSec, at }`, 204/400/404.
15
+ - [x] 2.2 Wire in `server.js`; verify the data-channel path allowlist covers
16
+ the route.
17
+
18
+ ## 3. Budget trigger
19
+
20
+ - [x] 3.1 Constants `LINK_REPORT_FRESH_MS`, `LINK_SAFETY`,
21
+ `LINK_SLOW_WINDOW_MS`, `LINK_LOW_BUFFER_SEC`.
22
+ - [x] 3.2 Observed produced bitrate (rolling, last ~5 segments) available to
23
+ the budget check.
24
+ - [x] 3.3 Link deficit accumulation + `#applyBudgetDownshift(session,
25
+ reason "link")`; skip on manualQuality/stale/comfortable buffer; log
26
+ reason distinctly.
27
+
28
+ ## 4. Verification
29
+
30
+ - [x] 4.1 Unit: table lookup, arg assembly, trigger logic (fake clock):
31
+ deficit→downshift, stale report→no-op, high buffer→no-op,
32
+ manualQuality→no-op.
33
+ - [x] 4.2 `node --check` on touched files.
34
+
35
+ ## 5. Release
36
+
37
+ - [ ] 5.1 CHANGELOG (next patch) + `npm run patch` (user OTP), then addon
38
+ bump + push + HA update. Release BEFORE the server reporter change.
39
+ - [ ] 5.2 Field: cellular Poirot/Mavka run shows `budget downshift (link)`
40
+ and bounded segment sizes; correlate via client-log pipeline.
@@ -1,2 +1,2 @@
1
- schema: spec-driven
2
- created: 2026-07-06
1
+ schema: spec-driven
2
+ created: 2026-07-06
@@ -1,53 +1,53 @@
1
- # Proposal: Proxy as a single binary + service registration (research, queued)
2
-
3
- ## Why
4
-
5
- Bare-npm installation assumes Node.js on the host — a real barrier for the
6
- non-technical proxy owners the pool model targets. Node's Single Executable
7
- Applications (SEA, stable-ish since Node 20/21) allow shipping the proxy as
8
- one self-contained binary per platform (win/linux/macOS, x64/arm64): download,
9
- run, done. A binary can also offer — with the user's explicit consent — to
10
- register itself as an auto-starting service, closing the "survives reboot"
11
- gap that the HA addon solves today.
12
-
13
- ## What Changes (research scope first)
14
-
15
- - **SEA build pipeline**: `node --experimental-sea-config` + postject-injected
16
- blob, one artifact per platform/arch, published as GitHub release assets
17
- alongside the npm package.
18
- - **Known hard parts to resolve in research**:
19
- - Native addons (`node-datachannel`, `utp-native`) cannot live inside the
20
- SEA blob — they must ship next to the binary or self-extract on first
21
- run to a data dir.
22
- - `ffmpeg-static`'s binary likewise ships alongside (or the system ffmpeg
23
- is required, as the HA addon already does with `--ffmpeg-bin`).
24
- - **Service registration (opt-in, explicit user action)**:
25
- - Linux: `proxy install-service` writes a systemd unit and runs
26
- `systemctl enable --now` (requires sudo — the consent step).
27
- - Windows: register via `sc create` / a service wrapper (admin prompt =
28
- consent).
29
- - macOS: launchd plist in `~/Library/LaunchAgents` (user-level, no admin).
30
- - Uninstall counterpart mandatory (`proxy uninstall-service`).
31
- - Stays deployment-agnostic: the binary is a fourth distribution channel
32
- next to HA addon, bare npm and Docker; no code paths may assume it.
33
-
34
- ## Capabilities
35
-
36
- ### New Capabilities
37
-
38
- - `distribution`: how the proxy is packaged and installed on bare hosts.
39
-
40
- ### Modified Capabilities
41
-
42
- <!-- none -->
43
-
44
- ## Impact
45
-
46
- - Build/release tooling in this repo; a `service` CLI subcommand.
47
- - No changes to runtime behaviour for existing channels.
48
-
49
- ## Priority
50
-
51
- MINIMAL — explicitly NOT part of the POC (owner decision, 2026-07-07).
52
- Research for the eventual "pool of non-technical owners" product goal; do
53
- not pick up while any POC work remains.
1
+ # Proposal: Proxy as a single binary + service registration (research, queued)
2
+
3
+ ## Why
4
+
5
+ Bare-npm installation assumes Node.js on the host — a real barrier for the
6
+ non-technical proxy owners the pool model targets. Node's Single Executable
7
+ Applications (SEA, stable-ish since Node 20/21) allow shipping the proxy as
8
+ one self-contained binary per platform (win/linux/macOS, x64/arm64): download,
9
+ run, done. A binary can also offer — with the user's explicit consent — to
10
+ register itself as an auto-starting service, closing the "survives reboot"
11
+ gap that the HA addon solves today.
12
+
13
+ ## What Changes (research scope first)
14
+
15
+ - **SEA build pipeline**: `node --experimental-sea-config` + postject-injected
16
+ blob, one artifact per platform/arch, published as GitHub release assets
17
+ alongside the npm package.
18
+ - **Known hard parts to resolve in research**:
19
+ - Native addons (`node-datachannel`, `utp-native`) cannot live inside the
20
+ SEA blob — they must ship next to the binary or self-extract on first
21
+ run to a data dir.
22
+ - `ffmpeg-static`'s binary likewise ships alongside (or the system ffmpeg
23
+ is required, as the HA addon already does with `--ffmpeg-bin`).
24
+ - **Service registration (opt-in, explicit user action)**:
25
+ - Linux: `proxy install-service` writes a systemd unit and runs
26
+ `systemctl enable --now` (requires sudo — the consent step).
27
+ - Windows: register via `sc create` / a service wrapper (admin prompt =
28
+ consent).
29
+ - macOS: launchd plist in `~/Library/LaunchAgents` (user-level, no admin).
30
+ - Uninstall counterpart mandatory (`proxy uninstall-service`).
31
+ - Stays deployment-agnostic: the binary is a fourth distribution channel
32
+ next to HA addon, bare npm and Docker; no code paths may assume it.
33
+
34
+ ## Capabilities
35
+
36
+ ### New Capabilities
37
+
38
+ - `distribution`: how the proxy is packaged and installed on bare hosts.
39
+
40
+ ### Modified Capabilities
41
+
42
+ <!-- none -->
43
+
44
+ ## Impact
45
+
46
+ - Build/release tooling in this repo; a `service` CLI subcommand.
47
+ - No changes to runtime behaviour for existing channels.
48
+
49
+ ## Priority
50
+
51
+ MINIMAL — explicitly NOT part of the POC (owner decision, 2026-07-07).
52
+ Research for the eventual "pool of non-technical owners" product goal; do
53
+ not pick up while any POC work remains.