@blockcast/mmt-container 0.1.0-main.149f78a4adae

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 (678) hide show
  1. package/README.md +432 -0
  2. package/dist/.build-stamp +0 -0
  3. package/dist/__tests__/test-utils.d.ts +71 -0
  4. package/dist/__tests__/test-utils.d.ts.map +1 -0
  5. package/dist/align.d.ts +148 -0
  6. package/dist/align.d.ts.map +1 -0
  7. package/dist/align.js +88 -0
  8. package/dist/align.js.map +7 -0
  9. package/dist/audio-init-parser.d.ts +66 -0
  10. package/dist/audio-init-parser.d.ts.map +1 -0
  11. package/dist/audio-init-parser.js +118 -0
  12. package/dist/audio-init-parser.js.map +7 -0
  13. package/dist/audio-ring-buffer.d.ts +50 -0
  14. package/dist/audio-ring-buffer.d.ts.map +1 -0
  15. package/dist/audio-ring-buffer.js +93 -0
  16. package/dist/audio-ring-buffer.js.map +7 -0
  17. package/dist/authored-timing.d.ts +32 -0
  18. package/dist/authored-timing.d.ts.map +1 -0
  19. package/dist/authored-timing.js +32 -0
  20. package/dist/authored-timing.js.map +7 -0
  21. package/dist/browser-runtime.js +17109 -0
  22. package/dist/browser-runtime.js.map +7 -0
  23. package/dist/carp-catalog.d.ts +318 -0
  24. package/dist/carp-catalog.d.ts.map +1 -0
  25. package/dist/carp-catalog.js +549 -0
  26. package/dist/carp-catalog.js.map +7 -0
  27. package/dist/catalog-fec-params.d.ts +82 -0
  28. package/dist/catalog-fec-params.d.ts.map +1 -0
  29. package/dist/catalog-fec-params.js +66 -0
  30. package/dist/catalog-fec-params.js.map +7 -0
  31. package/dist/catalog-network-source.d.ts +19 -0
  32. package/dist/catalog-network-source.d.ts.map +1 -0
  33. package/dist/catalog-network-source.js +22 -0
  34. package/dist/catalog-network-source.js.map +7 -0
  35. package/dist/catalog-validator.d.ts +18 -0
  36. package/dist/catalog-validator.d.ts.map +1 -0
  37. package/dist/catalog-validator.js +7747 -0
  38. package/dist/catalog-validator.js.map +7 -0
  39. package/dist/cmaf-block-assembler.d.ts +168 -0
  40. package/dist/cmaf-block-assembler.d.ts.map +1 -0
  41. package/dist/cmaf-block-assembler.js +1755 -0
  42. package/dist/cmaf-block-assembler.js.map +7 -0
  43. package/dist/cmaf-group-batcher.d.ts +38 -0
  44. package/dist/cmaf-group-batcher.d.ts.map +1 -0
  45. package/dist/cmaf-group-batcher.js +195 -0
  46. package/dist/cmaf-group-batcher.js.map +7 -0
  47. package/dist/cmcd-producer.d.ts +104 -0
  48. package/dist/cmcd-producer.d.ts.map +1 -0
  49. package/dist/cmcd-producer.js +207 -0
  50. package/dist/cmcd-producer.js.map +7 -0
  51. package/dist/codec-utils.d.ts +235 -0
  52. package/dist/codec-utils.d.ts.map +1 -0
  53. package/dist/codec-utils.js +1225 -0
  54. package/dist/codec-utils.js.map +7 -0
  55. package/dist/fec-codec.d.ts +32 -0
  56. package/dist/fec-codec.d.ts.map +1 -0
  57. package/dist/fec-codec.js +47 -0
  58. package/dist/fec-codec.js.map +7 -0
  59. package/dist/fec-manager.d.ts +266 -0
  60. package/dist/fec-manager.d.ts.map +1 -0
  61. package/dist/fec-manager.js +1447 -0
  62. package/dist/fec-manager.js.map +7 -0
  63. package/dist/fec-starvation-detector.d.ts +59 -0
  64. package/dist/fec-starvation-detector.d.ts.map +1 -0
  65. package/dist/fec-starvation-detector.js +75 -0
  66. package/dist/fec-starvation-detector.js.map +7 -0
  67. package/dist/fec-worker-setup.d.ts +76 -0
  68. package/dist/fec-worker-setup.d.ts.map +1 -0
  69. package/dist/fec-worker-setup.js +603 -0
  70. package/dist/fec-worker-setup.js.map +7 -0
  71. package/dist/fmp4-completion.d.ts +30 -0
  72. package/dist/fmp4-completion.d.ts.map +1 -0
  73. package/dist/fmp4-completion.js +21 -0
  74. package/dist/fmp4-completion.js.map +7 -0
  75. package/dist/fmp4-reader.d.ts +37 -0
  76. package/dist/fmp4-reader.d.ts.map +1 -0
  77. package/dist/fmp4-reader.js +100 -0
  78. package/dist/fmp4-reader.js.map +7 -0
  79. package/dist/index.d.ts +47 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +330 -0
  82. package/dist/index.js.map +7 -0
  83. package/dist/iso-bmff-timing.d.ts +47 -0
  84. package/dist/iso-bmff-timing.d.ts.map +1 -0
  85. package/dist/iso-bmff-timing.js +526 -0
  86. package/dist/iso-bmff-timing.js.map +7 -0
  87. package/dist/loc.d.ts +68 -0
  88. package/dist/loc.d.ts.map +1 -0
  89. package/dist/loc.js +143 -0
  90. package/dist/loc.js.map +7 -0
  91. package/dist/manifest-auth-plan.d.ts +30 -0
  92. package/dist/manifest-auth-plan.d.ts.map +1 -0
  93. package/dist/manifest-auth-plan.js +1267 -0
  94. package/dist/manifest-auth-plan.js.map +7 -0
  95. package/dist/media-router.d.ts +338 -0
  96. package/dist/media-router.d.ts.map +1 -0
  97. package/dist/media-router.js +802 -0
  98. package/dist/media-router.js.map +7 -0
  99. package/dist/mfu-reassembler.d.ts +264 -0
  100. package/dist/mfu-reassembler.d.ts.map +1 -0
  101. package/dist/mfu-reassembler.js +2668 -0
  102. package/dist/mfu-reassembler.js.map +7 -0
  103. package/dist/mfu-reassembly-policy.d.ts +24 -0
  104. package/dist/mfu-reassembly-policy.d.ts.map +1 -0
  105. package/dist/mfu-reassembly-policy.js +99 -0
  106. package/dist/mfu-reassembly-policy.js.map +7 -0
  107. package/dist/mmtp-container-parser.d.ts +304 -0
  108. package/dist/mmtp-container-parser.d.ts.map +1 -0
  109. package/dist/mmtp-container-parser.js +5378 -0
  110. package/dist/mmtp-container-parser.js.map +7 -0
  111. package/dist/mmtp-container-parser.no-modules.js +7853 -0
  112. package/dist/mmtp-container-parser.no-modules.js.map +7 -0
  113. package/dist/mmtp-decode-pipeline.d.ts +96 -0
  114. package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
  115. package/dist/mmtp-decode-pipeline.js +5589 -0
  116. package/dist/mmtp-decode-pipeline.js.map +7 -0
  117. package/dist/mmtp-router.d.ts +383 -0
  118. package/dist/mmtp-router.d.ts.map +1 -0
  119. package/dist/mmtp-router.js +8384 -0
  120. package/dist/mmtp-router.js.map +7 -0
  121. package/dist/mmtp-to-cmaf.d.ts +166 -0
  122. package/dist/mmtp-to-cmaf.d.ts.map +1 -0
  123. package/dist/mmtp-to-cmaf.js +781 -0
  124. package/dist/mmtp-to-cmaf.js.map +7 -0
  125. package/dist/multicast-auth-catalog.d.ts +115 -0
  126. package/dist/multicast-auth-catalog.d.ts.map +1 -0
  127. package/dist/multicast-auth-catalog.js +202 -0
  128. package/dist/multicast-auth-catalog.js.map +7 -0
  129. package/dist/multicast-input.d.ts +107 -0
  130. package/dist/multicast-input.d.ts.map +1 -0
  131. package/dist/multicast-input.js +256 -0
  132. package/dist/multicast-input.js.map +7 -0
  133. package/dist/multicast-manager.d.ts +330 -0
  134. package/dist/multicast-manager.d.ts.map +1 -0
  135. package/dist/multicast-manager.js +11032 -0
  136. package/dist/multicast-manager.js.map +7 -0
  137. package/dist/multicast-transport.d.ts +62 -0
  138. package/dist/multicast-transport.d.ts.map +1 -0
  139. package/dist/multicast-transport.js +338 -0
  140. package/dist/multicast-transport.js.map +7 -0
  141. package/dist/multicast-unwrap.d.ts +57 -0
  142. package/dist/multicast-unwrap.d.ts.map +1 -0
  143. package/dist/multicast-unwrap.js +384 -0
  144. package/dist/multicast-unwrap.js.map +7 -0
  145. package/dist/no-modules.d.ts +9 -0
  146. package/dist/no-modules.d.ts.map +1 -0
  147. package/dist/no-modules.js +7819 -0
  148. package/dist/no-modules.js.map +7 -0
  149. package/dist/phase14-catalog-override.d.ts +126 -0
  150. package/dist/phase14-catalog-override.d.ts.map +1 -0
  151. package/dist/phase14-catalog-override.js +155 -0
  152. package/dist/phase14-catalog-override.js.map +7 -0
  153. package/dist/repair-fetch.d.ts +58 -0
  154. package/dist/repair-fetch.d.ts.map +1 -0
  155. package/dist/repair-fetch.js +117 -0
  156. package/dist/repair-fetch.js.map +7 -0
  157. package/dist/sample-combiner.d.ts +182 -0
  158. package/dist/sample-combiner.d.ts.map +1 -0
  159. package/dist/sample-combiner.js +571 -0
  160. package/dist/sample-combiner.js.map +7 -0
  161. package/dist/signaling.d.ts +29 -0
  162. package/dist/signaling.d.ts.map +1 -0
  163. package/dist/signaling.js +232 -0
  164. package/dist/signaling.js.map +7 -0
  165. package/dist/source-auth.d.ts +2 -0
  166. package/dist/source-auth.d.ts.map +1 -0
  167. package/dist/source-auth.js +1220 -0
  168. package/dist/source-auth.js.map +7 -0
  169. package/dist/switching-set-alignment.d.ts +61 -0
  170. package/dist/switching-set-alignment.d.ts.map +1 -0
  171. package/dist/switching-set-alignment.js +85 -0
  172. package/dist/switching-set-alignment.js.map +7 -0
  173. package/dist/timestamp-codec.d.ts +34 -0
  174. package/dist/timestamp-codec.d.ts.map +1 -0
  175. package/dist/timestamp-codec.js +456 -0
  176. package/dist/timestamp-codec.js.map +7 -0
  177. package/dist/track-pipeline-setup.d.ts +72 -0
  178. package/dist/track-pipeline-setup.d.ts.map +1 -0
  179. package/dist/track-pipeline-setup.js +306 -0
  180. package/dist/track-pipeline-setup.js.map +7 -0
  181. package/dist/track-pipeline.d.ts +121 -0
  182. package/dist/track-pipeline.d.ts.map +1 -0
  183. package/dist/track-pipeline.js +229 -0
  184. package/dist/track-pipeline.js.map +7 -0
  185. package/dist/track-session.d.ts +269 -0
  186. package/dist/track-session.d.ts.map +1 -0
  187. package/dist/track-session.js +597 -0
  188. package/dist/track-session.js.map +7 -0
  189. package/dist/transport-pool.d.ts +69 -0
  190. package/dist/transport-pool.d.ts.map +1 -0
  191. package/dist/transport-pool.js +88 -0
  192. package/dist/transport-pool.js.map +7 -0
  193. package/dist/wasm-mfu-reassembler.d.ts +81 -0
  194. package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
  195. package/dist/wasm-mfu-reassembler.js +1915 -0
  196. package/dist/wasm-mfu-reassembler.js.map +7 -0
  197. package/dist/wasm-mmtp-container-parser.d.ts +10 -0
  198. package/dist/wasm-mmtp-container-parser.d.ts.map +1 -0
  199. package/dist/wasm-mmtp-container-parser.js +5635 -0
  200. package/dist/wasm-mmtp-container-parser.js.map +7 -0
  201. package/node_modules/@blockcast/fec-worker/README.md +213 -0
  202. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  203. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +80 -0
  204. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -0
  205. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +270 -0
  206. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -0
  207. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +252 -0
  208. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +1 -0
  209. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +11 -0
  210. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +1 -0
  211. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +14 -0
  212. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +1 -0
  213. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +565 -0
  214. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +7 -0
  215. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +10 -0
  216. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +1 -0
  217. package/node_modules/@blockcast/fec-worker/dist/index.js +8 -0
  218. package/node_modules/@blockcast/fec-worker/dist/index.js.map +1 -0
  219. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +20 -0
  220. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +1 -0
  221. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +349 -0
  222. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +7 -0
  223. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +104 -0
  224. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +1 -0
  225. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +181 -0
  226. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +1 -0
  227. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +179 -0
  228. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +1 -0
  229. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +47 -0
  230. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +1 -0
  231. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +9 -0
  232. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +1 -0
  233. package/node_modules/@blockcast/fec-worker/dist/transfer.js +13 -0
  234. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +1 -0
  235. package/node_modules/@blockcast/fec-worker/package.json +59 -0
  236. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +309 -0
  237. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +268 -0
  238. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +1048 -0
  239. package/node_modules/@blockcast/fec-worker/src/index.ts +32 -0
  240. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +558 -0
  241. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +222 -0
  242. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +194 -0
  243. package/node_modules/@blockcast/fec-worker/src/transfer.ts +12 -0
  244. package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  245. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  246. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  247. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  248. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  249. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  250. package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  251. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  252. package/node_modules/@blockcast/mmt-base/README.md +120 -0
  253. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  254. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
  255. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  256. package/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
  257. package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  258. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +275 -0
  259. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  260. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +644 -0
  261. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  262. package/node_modules/@blockcast/mmt-base/package.json +49 -0
  263. package/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
  264. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +898 -0
  265. package/node_modules/@blockcast/mmt-fec/README.md +270 -0
  266. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  267. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +80 -0
  268. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +1 -0
  269. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +294 -0
  270. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +1 -0
  271. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +72 -0
  272. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +1 -0
  273. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +192 -0
  274. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +1 -0
  275. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +46 -0
  276. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +1 -0
  277. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +96 -0
  278. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +1 -0
  279. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +33 -0
  280. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +1 -0
  281. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +57 -0
  282. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +1 -0
  283. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +185 -0
  284. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +1 -0
  285. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +273 -0
  286. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +1 -0
  287. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +22 -0
  288. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +1 -0
  289. package/node_modules/@blockcast/mmt-fec/dist/index.js +22 -0
  290. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +1 -0
  291. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +20 -0
  292. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +1 -0
  293. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +28 -0
  294. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +1 -0
  295. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +174 -0
  296. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
  297. package/node_modules/@blockcast/mmt-fec/dist/oti.js +256 -0
  298. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +1 -0
  299. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +41 -0
  300. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +1 -0
  301. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +81 -0
  302. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +1 -0
  303. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  304. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  305. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  306. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  307. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  308. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  309. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  310. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  311. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +120 -0
  312. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  313. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
  314. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  315. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
  316. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  317. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +275 -0
  318. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  319. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +644 -0
  320. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  321. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +49 -0
  322. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
  323. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +898 -0
  324. package/node_modules/@blockcast/mmt-fec/package.json +79 -0
  325. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +369 -0
  326. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +277 -0
  327. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +127 -0
  328. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +68 -0
  329. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +332 -0
  330. package/node_modules/@blockcast/mmt-fec/src/index.ts +77 -0
  331. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +34 -0
  332. package/node_modules/@blockcast/mmt-fec/src/oti.ts +319 -0
  333. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +101 -0
  334. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  335. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +28 -0
  336. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +1 -0
  337. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +128 -0
  338. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +1 -0
  339. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +4 -0
  340. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +1 -0
  341. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +4 -0
  342. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +1 -0
  343. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +37 -0
  344. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +1 -0
  345. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +179 -0
  346. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +1 -0
  347. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +90 -0
  348. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +1 -0
  349. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +462 -0
  350. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +1 -0
  351. package/node_modules/@blockcast/mmt-manifest-verify/package.json +45 -0
  352. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +171 -0
  353. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +3 -0
  354. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +230 -0
  355. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +636 -0
  356. package/node_modules/@noble/hashes/LICENSE +21 -0
  357. package/node_modules/@noble/hashes/README.md +521 -0
  358. package/node_modules/@noble/hashes/_assert.d.ts +17 -0
  359. package/node_modules/@noble/hashes/_assert.d.ts.map +1 -0
  360. package/node_modules/@noble/hashes/_assert.js +18 -0
  361. package/node_modules/@noble/hashes/_assert.js.map +1 -0
  362. package/node_modules/@noble/hashes/_blake.d.ts +14 -0
  363. package/node_modules/@noble/hashes/_blake.d.ts.map +1 -0
  364. package/node_modules/@noble/hashes/_blake.js +50 -0
  365. package/node_modules/@noble/hashes/_blake.js.map +1 -0
  366. package/node_modules/@noble/hashes/_md.d.ts +51 -0
  367. package/node_modules/@noble/hashes/_md.d.ts.map +1 -0
  368. package/node_modules/@noble/hashes/_md.js +162 -0
  369. package/node_modules/@noble/hashes/_md.js.map +1 -0
  370. package/node_modules/@noble/hashes/_u64.d.ts +55 -0
  371. package/node_modules/@noble/hashes/_u64.d.ts.map +1 -0
  372. package/node_modules/@noble/hashes/_u64.js +90 -0
  373. package/node_modules/@noble/hashes/_u64.js.map +1 -0
  374. package/node_modules/@noble/hashes/argon2.d.ts +32 -0
  375. package/node_modules/@noble/hashes/argon2.d.ts.map +1 -0
  376. package/node_modules/@noble/hashes/argon2.js +401 -0
  377. package/node_modules/@noble/hashes/argon2.js.map +1 -0
  378. package/node_modules/@noble/hashes/blake1.d.ts +106 -0
  379. package/node_modules/@noble/hashes/blake1.d.ts.map +1 -0
  380. package/node_modules/@noble/hashes/blake1.js +459 -0
  381. package/node_modules/@noble/hashes/blake1.js.map +1 -0
  382. package/node_modules/@noble/hashes/blake2.d.ts +116 -0
  383. package/node_modules/@noble/hashes/blake2.d.ts.map +1 -0
  384. package/node_modules/@noble/hashes/blake2.js +420 -0
  385. package/node_modules/@noble/hashes/blake2.js.map +1 -0
  386. package/node_modules/@noble/hashes/blake2b.d.ts +11 -0
  387. package/node_modules/@noble/hashes/blake2b.d.ts.map +1 -0
  388. package/node_modules/@noble/hashes/blake2b.js +14 -0
  389. package/node_modules/@noble/hashes/blake2b.js.map +1 -0
  390. package/node_modules/@noble/hashes/blake2s.d.ts +20 -0
  391. package/node_modules/@noble/hashes/blake2s.d.ts.map +1 -0
  392. package/node_modules/@noble/hashes/blake2s.js +24 -0
  393. package/node_modules/@noble/hashes/blake2s.js.map +1 -0
  394. package/node_modules/@noble/hashes/blake3.d.ts +54 -0
  395. package/node_modules/@noble/hashes/blake3.d.ts.map +1 -0
  396. package/node_modules/@noble/hashes/blake3.js +255 -0
  397. package/node_modules/@noble/hashes/blake3.js.map +1 -0
  398. package/node_modules/@noble/hashes/crypto.d.ts +2 -0
  399. package/node_modules/@noble/hashes/crypto.d.ts.map +1 -0
  400. package/node_modules/@noble/hashes/crypto.js +5 -0
  401. package/node_modules/@noble/hashes/crypto.js.map +1 -0
  402. package/node_modules/@noble/hashes/cryptoNode.d.ts +2 -0
  403. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +1 -0
  404. package/node_modules/@noble/hashes/cryptoNode.js +18 -0
  405. package/node_modules/@noble/hashes/cryptoNode.js.map +1 -0
  406. package/node_modules/@noble/hashes/eskdf.d.ts +47 -0
  407. package/node_modules/@noble/hashes/eskdf.d.ts.map +1 -0
  408. package/node_modules/@noble/hashes/eskdf.js +166 -0
  409. package/node_modules/@noble/hashes/eskdf.js.map +1 -0
  410. package/node_modules/@noble/hashes/esm/_assert.d.ts +17 -0
  411. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +1 -0
  412. package/node_modules/@noble/hashes/esm/_assert.js +15 -0
  413. package/node_modules/@noble/hashes/esm/_assert.js.map +1 -0
  414. package/node_modules/@noble/hashes/esm/_blake.d.ts +14 -0
  415. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +1 -0
  416. package/node_modules/@noble/hashes/esm/_blake.js +45 -0
  417. package/node_modules/@noble/hashes/esm/_blake.js.map +1 -0
  418. package/node_modules/@noble/hashes/esm/_md.d.ts +51 -0
  419. package/node_modules/@noble/hashes/esm/_md.d.ts.map +1 -0
  420. package/node_modules/@noble/hashes/esm/_md.js +155 -0
  421. package/node_modules/@noble/hashes/esm/_md.js.map +1 -0
  422. package/node_modules/@noble/hashes/esm/_u64.d.ts +55 -0
  423. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +1 -0
  424. package/node_modules/@noble/hashes/esm/_u64.js +67 -0
  425. package/node_modules/@noble/hashes/esm/_u64.js.map +1 -0
  426. package/node_modules/@noble/hashes/esm/argon2.d.ts +32 -0
  427. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +1 -0
  428. package/node_modules/@noble/hashes/esm/argon2.js +392 -0
  429. package/node_modules/@noble/hashes/esm/argon2.js.map +1 -0
  430. package/node_modules/@noble/hashes/esm/blake1.d.ts +106 -0
  431. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +1 -0
  432. package/node_modules/@noble/hashes/esm/blake1.js +452 -0
  433. package/node_modules/@noble/hashes/esm/blake1.js.map +1 -0
  434. package/node_modules/@noble/hashes/esm/blake2.d.ts +116 -0
  435. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +1 -0
  436. package/node_modules/@noble/hashes/esm/blake2.js +413 -0
  437. package/node_modules/@noble/hashes/esm/blake2.js.map +1 -0
  438. package/node_modules/@noble/hashes/esm/blake2b.d.ts +11 -0
  439. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +1 -0
  440. package/node_modules/@noble/hashes/esm/blake2b.js +11 -0
  441. package/node_modules/@noble/hashes/esm/blake2b.js.map +1 -0
  442. package/node_modules/@noble/hashes/esm/blake2s.d.ts +20 -0
  443. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +1 -0
  444. package/node_modules/@noble/hashes/esm/blake2s.js +21 -0
  445. package/node_modules/@noble/hashes/esm/blake2s.js.map +1 -0
  446. package/node_modules/@noble/hashes/esm/blake3.d.ts +54 -0
  447. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +1 -0
  448. package/node_modules/@noble/hashes/esm/blake3.js +251 -0
  449. package/node_modules/@noble/hashes/esm/blake3.js.map +1 -0
  450. package/node_modules/@noble/hashes/esm/crypto.d.ts +2 -0
  451. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +1 -0
  452. package/node_modules/@noble/hashes/esm/crypto.js +2 -0
  453. package/node_modules/@noble/hashes/esm/crypto.js.map +1 -0
  454. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +2 -0
  455. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +1 -0
  456. package/node_modules/@noble/hashes/esm/cryptoNode.js +15 -0
  457. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +1 -0
  458. package/node_modules/@noble/hashes/esm/eskdf.d.ts +47 -0
  459. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +1 -0
  460. package/node_modules/@noble/hashes/esm/eskdf.js +160 -0
  461. package/node_modules/@noble/hashes/esm/eskdf.js.map +1 -0
  462. package/node_modules/@noble/hashes/esm/hkdf.d.ts +36 -0
  463. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +1 -0
  464. package/node_modules/@noble/hashes/esm/hkdf.js +82 -0
  465. package/node_modules/@noble/hashes/esm/hkdf.js.map +1 -0
  466. package/node_modules/@noble/hashes/esm/hmac.d.ts +35 -0
  467. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +1 -0
  468. package/node_modules/@noble/hashes/esm/hmac.js +86 -0
  469. package/node_modules/@noble/hashes/esm/hmac.js.map +1 -0
  470. package/node_modules/@noble/hashes/esm/index.d.ts +2 -0
  471. package/node_modules/@noble/hashes/esm/index.d.ts.map +1 -0
  472. package/node_modules/@noble/hashes/esm/index.js +33 -0
  473. package/node_modules/@noble/hashes/esm/index.js.map +1 -0
  474. package/node_modules/@noble/hashes/esm/legacy.d.ts +71 -0
  475. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +1 -0
  476. package/node_modules/@noble/hashes/esm/legacy.js +281 -0
  477. package/node_modules/@noble/hashes/esm/legacy.js.map +1 -0
  478. package/node_modules/@noble/hashes/esm/package.json +10 -0
  479. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +23 -0
  480. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +1 -0
  481. package/node_modules/@noble/hashes/esm/pbkdf2.js +97 -0
  482. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +1 -0
  483. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +13 -0
  484. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +1 -0
  485. package/node_modules/@noble/hashes/esm/ripemd160.js +13 -0
  486. package/node_modules/@noble/hashes/esm/ripemd160.js.map +1 -0
  487. package/node_modules/@noble/hashes/esm/scrypt.d.ts +34 -0
  488. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +1 -0
  489. package/node_modules/@noble/hashes/esm/scrypt.js +228 -0
  490. package/node_modules/@noble/hashes/esm/scrypt.js.map +1 -0
  491. package/node_modules/@noble/hashes/esm/sha1.d.ts +11 -0
  492. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +1 -0
  493. package/node_modules/@noble/hashes/esm/sha1.js +11 -0
  494. package/node_modules/@noble/hashes/esm/sha1.js.map +1 -0
  495. package/node_modules/@noble/hashes/esm/sha2.d.ts +159 -0
  496. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +1 -0
  497. package/node_modules/@noble/hashes/esm/sha2.js +375 -0
  498. package/node_modules/@noble/hashes/esm/sha2.js.map +1 -0
  499. package/node_modules/@noble/hashes/esm/sha256.d.ts +20 -0
  500. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +1 -0
  501. package/node_modules/@noble/hashes/esm/sha256.js +20 -0
  502. package/node_modules/@noble/hashes/esm/sha256.js.map +1 -0
  503. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +142 -0
  504. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +1 -0
  505. package/node_modules/@noble/hashes/esm/sha3-addons.js +393 -0
  506. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +1 -0
  507. package/node_modules/@noble/hashes/esm/sha3.d.ts +53 -0
  508. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +1 -0
  509. package/node_modules/@noble/hashes/esm/sha3.js +234 -0
  510. package/node_modules/@noble/hashes/esm/sha3.js.map +1 -0
  511. package/node_modules/@noble/hashes/esm/sha512.d.ts +26 -0
  512. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +1 -0
  513. package/node_modules/@noble/hashes/esm/sha512.js +26 -0
  514. package/node_modules/@noble/hashes/esm/sha512.js.map +1 -0
  515. package/node_modules/@noble/hashes/esm/utils.d.ts +161 -0
  516. package/node_modules/@noble/hashes/esm/utils.d.ts.map +1 -0
  517. package/node_modules/@noble/hashes/esm/utils.js +281 -0
  518. package/node_modules/@noble/hashes/esm/utils.js.map +1 -0
  519. package/node_modules/@noble/hashes/hkdf.d.ts +36 -0
  520. package/node_modules/@noble/hashes/hkdf.d.ts.map +1 -0
  521. package/node_modules/@noble/hashes/hkdf.js +88 -0
  522. package/node_modules/@noble/hashes/hkdf.js.map +1 -0
  523. package/node_modules/@noble/hashes/hmac.d.ts +35 -0
  524. package/node_modules/@noble/hashes/hmac.d.ts.map +1 -0
  525. package/node_modules/@noble/hashes/hmac.js +91 -0
  526. package/node_modules/@noble/hashes/hmac.js.map +1 -0
  527. package/node_modules/@noble/hashes/index.d.ts +1 -0
  528. package/node_modules/@noble/hashes/index.d.ts.map +1 -0
  529. package/node_modules/@noble/hashes/index.js +33 -0
  530. package/node_modules/@noble/hashes/index.js.map +1 -0
  531. package/node_modules/@noble/hashes/legacy.d.ts +71 -0
  532. package/node_modules/@noble/hashes/legacy.d.ts.map +1 -0
  533. package/node_modules/@noble/hashes/legacy.js +287 -0
  534. package/node_modules/@noble/hashes/legacy.js.map +1 -0
  535. package/node_modules/@noble/hashes/package.json +266 -0
  536. package/node_modules/@noble/hashes/pbkdf2.d.ts +23 -0
  537. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +1 -0
  538. package/node_modules/@noble/hashes/pbkdf2.js +101 -0
  539. package/node_modules/@noble/hashes/pbkdf2.js.map +1 -0
  540. package/node_modules/@noble/hashes/ripemd160.d.ts +13 -0
  541. package/node_modules/@noble/hashes/ripemd160.d.ts.map +1 -0
  542. package/node_modules/@noble/hashes/ripemd160.js +16 -0
  543. package/node_modules/@noble/hashes/ripemd160.js.map +1 -0
  544. package/node_modules/@noble/hashes/scrypt.d.ts +34 -0
  545. package/node_modules/@noble/hashes/scrypt.d.ts.map +1 -0
  546. package/node_modules/@noble/hashes/scrypt.js +232 -0
  547. package/node_modules/@noble/hashes/scrypt.js.map +1 -0
  548. package/node_modules/@noble/hashes/sha1.d.ts +11 -0
  549. package/node_modules/@noble/hashes/sha1.d.ts.map +1 -0
  550. package/node_modules/@noble/hashes/sha1.js +14 -0
  551. package/node_modules/@noble/hashes/sha1.js.map +1 -0
  552. package/node_modules/@noble/hashes/sha2.d.ts +159 -0
  553. package/node_modules/@noble/hashes/sha2.d.ts.map +1 -0
  554. package/node_modules/@noble/hashes/sha2.js +384 -0
  555. package/node_modules/@noble/hashes/sha2.js.map +1 -0
  556. package/node_modules/@noble/hashes/sha256.d.ts +20 -0
  557. package/node_modules/@noble/hashes/sha256.d.ts.map +1 -0
  558. package/node_modules/@noble/hashes/sha256.js +23 -0
  559. package/node_modules/@noble/hashes/sha256.js.map +1 -0
  560. package/node_modules/@noble/hashes/sha3-addons.d.ts +142 -0
  561. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +1 -0
  562. package/node_modules/@noble/hashes/sha3-addons.js +402 -0
  563. package/node_modules/@noble/hashes/sha3-addons.js.map +1 -0
  564. package/node_modules/@noble/hashes/sha3.d.ts +53 -0
  565. package/node_modules/@noble/hashes/sha3.d.ts.map +1 -0
  566. package/node_modules/@noble/hashes/sha3.js +239 -0
  567. package/node_modules/@noble/hashes/sha3.js.map +1 -0
  568. package/node_modules/@noble/hashes/sha512.d.ts +26 -0
  569. package/node_modules/@noble/hashes/sha512.d.ts.map +1 -0
  570. package/node_modules/@noble/hashes/sha512.js +29 -0
  571. package/node_modules/@noble/hashes/sha512.js.map +1 -0
  572. package/node_modules/@noble/hashes/src/_assert.ts +22 -0
  573. package/node_modules/@noble/hashes/src/_blake.ts +50 -0
  574. package/node_modules/@noble/hashes/src/_md.ts +176 -0
  575. package/node_modules/@noble/hashes/src/_u64.ts +91 -0
  576. package/node_modules/@noble/hashes/src/argon2.ts +497 -0
  577. package/node_modules/@noble/hashes/src/blake1.ts +534 -0
  578. package/node_modules/@noble/hashes/src/blake2.ts +486 -0
  579. package/node_modules/@noble/hashes/src/blake2b.ts +10 -0
  580. package/node_modules/@noble/hashes/src/blake2s.ts +20 -0
  581. package/node_modules/@noble/hashes/src/blake3.ts +272 -0
  582. package/node_modules/@noble/hashes/src/crypto.ts +9 -0
  583. package/node_modules/@noble/hashes/src/cryptoNode.ts +15 -0
  584. package/node_modules/@noble/hashes/src/eskdf.ts +187 -0
  585. package/node_modules/@noble/hashes/src/hkdf.ts +88 -0
  586. package/node_modules/@noble/hashes/src/hmac.ts +94 -0
  587. package/node_modules/@noble/hashes/src/index.ts +31 -0
  588. package/node_modules/@noble/hashes/src/legacy.ts +293 -0
  589. package/node_modules/@noble/hashes/src/pbkdf2.ts +122 -0
  590. package/node_modules/@noble/hashes/src/ripemd160.ts +12 -0
  591. package/node_modules/@noble/hashes/src/scrypt.ts +257 -0
  592. package/node_modules/@noble/hashes/src/sha1.ts +10 -0
  593. package/node_modules/@noble/hashes/src/sha2.ts +402 -0
  594. package/node_modules/@noble/hashes/src/sha256.ts +24 -0
  595. package/node_modules/@noble/hashes/src/sha3-addons.ts +499 -0
  596. package/node_modules/@noble/hashes/src/sha3.ts +258 -0
  597. package/node_modules/@noble/hashes/src/sha512.ts +34 -0
  598. package/node_modules/@noble/hashes/src/utils.ts +395 -0
  599. package/node_modules/@noble/hashes/utils.d.ts +161 -0
  600. package/node_modules/@noble/hashes/utils.d.ts.map +1 -0
  601. package/node_modules/@noble/hashes/utils.js +313 -0
  602. package/node_modules/@noble/hashes/utils.js.map +1 -0
  603. package/package.json +203 -0
  604. package/schemas/catalog.schema.json +283 -0
  605. package/src/align.ts +235 -0
  606. package/src/audio-init-parser.ts +189 -0
  607. package/src/audio-ring-buffer.ts +120 -0
  608. package/src/authored-timing.ts +63 -0
  609. package/src/carp-catalog.ts +776 -0
  610. package/src/catalog-fec-params.ts +167 -0
  611. package/src/catalog-network-source.ts +49 -0
  612. package/src/catalog-validator.ts +34 -0
  613. package/src/cmaf-block-assembler.ts +1058 -0
  614. package/src/cmaf-group-batcher.ts +104 -0
  615. package/src/cmcd-producer.ts +329 -0
  616. package/src/codec-utils.ts +1539 -0
  617. package/src/fec-codec.ts +61 -0
  618. package/src/fec-manager.ts +1939 -0
  619. package/src/fec-starvation-detector.ts +128 -0
  620. package/src/fec-worker-setup.ts +177 -0
  621. package/src/fmp4-completion.ts +47 -0
  622. package/src/fmp4-reader.ts +112 -0
  623. package/src/index.ts +443 -0
  624. package/src/iso-bmff-timing.ts +688 -0
  625. package/src/loc.ts +181 -0
  626. package/src/manifest-auth-plan.ts +107 -0
  627. package/src/media-router.ts +1185 -0
  628. package/src/mfu-reassembler.ts +2842 -0
  629. package/src/mfu-reassembly-policy.ts +134 -0
  630. package/src/mmtp-container-parser.ts +1498 -0
  631. package/src/mmtp-decode-pipeline.ts +315 -0
  632. package/src/mmtp-router.ts +3027 -0
  633. package/src/mmtp-to-cmaf.ts +956 -0
  634. package/src/multicast-auth-catalog.ts +340 -0
  635. package/src/multicast-input.ts +439 -0
  636. package/src/multicast-manager.ts +1503 -0
  637. package/src/multicast-transport.ts +320 -0
  638. package/src/multicast-unwrap.ts +132 -0
  639. package/src/no-modules.ts +20 -0
  640. package/src/phase14-catalog-override.ts +336 -0
  641. package/src/repair-fetch.ts +217 -0
  642. package/src/sample-combiner.ts +846 -0
  643. package/src/signaling.ts +78 -0
  644. package/src/source-auth.ts +1 -0
  645. package/src/switching-set-alignment.ts +109 -0
  646. package/src/timestamp-codec.ts +265 -0
  647. package/src/track-pipeline-setup.ts +189 -0
  648. package/src/track-pipeline.ts +332 -0
  649. package/src/track-session.ts +778 -0
  650. package/src/transport-pool.ts +128 -0
  651. package/src/wasm-mfu-reassembler.ts +372 -0
  652. package/src/wasm-mmtp-container-parser.ts +23 -0
  653. package/test-vectors/align.json +199 -0
  654. package/test-vectors/catalog/README.md +25 -0
  655. package/test-vectors/catalog/negative/legacy-selection-params.json +15 -0
  656. package/test-vectors/catalog/negative/missing-mmtp-fields.json +12 -0
  657. package/test-vectors/catalog/negative/multicast-auth-padded-key.json +37 -0
  658. package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +53 -0
  659. package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +37 -0
  660. package/test-vectors/catalog/negative/multicast-endpoint-missing-protocol-and-source.json +24 -0
  661. package/test-vectors/catalog/negative/network-source-object.json +25 -0
  662. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +26 -0
  663. package/test-vectors/catalog/negative/repair-track-legacy-shape.json +18 -0
  664. package/test-vectors/catalog/negative/track-role-empty.json +16 -0
  665. package/test-vectors/catalog/negative/track-role-non-string.json +16 -0
  666. package/test-vectors/catalog/positive/fec-multicast.json +51 -0
  667. package/test-vectors/catalog/positive/flat-av.json +30 -0
  668. package/test-vectors/catalog/positive/multicast-auth-rotation.json +55 -0
  669. package/test-vectors/catalog/positive/ticks-and-mixed-timescales.json +39 -0
  670. package/test-vectors/catalog/positive/track-role.json +49 -0
  671. package/test-vectors/catalog/releases/moqcast-native-v16-v1/catalog.json +65 -0
  672. package/test-vectors/catalog/releases/moqcast-native-v16-v1.archive.sha256 +1 -0
  673. package/test-vectors/catalog-multicast-auth-v1.json +26 -0
  674. package/test-vectors/catalog-validation.json +148 -0
  675. package/test-vectors/catalog-validator-public-api.mjs +25 -0
  676. package/test-vectors/generate-source-auth-fixture.mjs +114 -0
  677. package/test-vectors/source-auth-v1.json +61 -0
  678. package/test-vectors/subsample-placement-v1.json +230 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/align.ts", "../src/track-session.ts", "../src/media-router.ts"],
4
+ "sourcesContent": ["/**\n * Shared group-alignment primitive for MMT over MoQ switching sets.\n *\n * Pure math \u2014 no MMT wire parsing, no CMCD, no I/O. Callers wire the output\n * into their own drift-observation plumbing (see media-router.ts for the\n * libmmt wiring).\n *\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * Contract (integer-ticks form, authoritative):\n *\n * groupId = ticks < 0 ? 0 : floor(ticks / groupDurationTicks)\n *\n * Where `ticks` is a media timestamp expressed at the catalog's `timescale`\n * (Hz), and `groupDurationTicks = groupDurationSec * timescale`.\n *\n * Integer math means there is no epsilon and no float rounding to reason\n * about. The seconds-form wrapper `groupIdFor(Seconds, seconds)` exists for\n * legacy callers but converts to ticks internally.\n *\n * Every sender/receiver that emits or consumes MoQ groups from an MMT or\n * CMAF switching set MUST derive groupId from this formula so that base,\n * delta, and repair tracks agree on the group boundary for a given media\n * timestamp across languages and processes.\n *\n * Test vectors that exercise the contract live at\n * `packages/container/test-vectors/align.json`; the Go and Rust siblings\n * load the same fixture so cross-language agreement is executable, not\n * prose. **Formula changes require updating the fixture AND all three\n * sibling implementations in the same release** \u2014 divergence between\n * sender/relay/receiver is worse than any formula change.\n *\n * Canonical implementations:\n * Go: Blockcast/multicast `cmd/caddy/sender/group_align.go`\n * (commit 104eeff) \u2014 sender-side groupId stamping on egress.\n * Rust: moqtail/moqtail `apps/relay/src/server/abr.rs`\n * (commit 39accbf; experimental branch, may be rebased \u2014 rely\n * on the SHA, not the branch name) \u2014 relay-side check.\n * TS: THIS FILE (libmmt) \u2014 consumed by moq-lib + moqtail-ts via\n * @blockcast/mmt-container.\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n */\n\n/**\n * Branded seconds type. Construct only via `secondsFromTicks(ticks, timescale)`\n * or `asSeconds(n)` (the latter exists solely for tests and bring-up). The\n * brand prevents accidentally passing a raw tick count \u2014 or a Date.now()\n * millisecond \u2014 to an API that expects a media-time in seconds.\n */\nexport type Seconds = number & { readonly __brand: 'Seconds' }\n\n/**\n * Explicit conversion constructor: ticks at `timescale` Hz \u2192 Seconds.\n *\n * Call this at the wire/catalog boundary so the brand flows with the value\n * through the rest of the system. Throws on non-finite inputs or\n * non-positive timescale (CLAUDE.md \u00A7Architectural Principles: error on\n * bad init).\n */\nexport function secondsFromTicks(ticks: number, timescale: number): Seconds {\n if (!Number.isFinite(ticks)) {\n throw new Error(`[align] secondsFromTicks: ticks must be finite, got ${ticks}`)\n }\n if (!Number.isFinite(timescale) || timescale <= 0) {\n throw new Error(\n `[align] secondsFromTicks: timescale must be positive and finite, got ${timescale}`,\n )\n }\n return (ticks / timescale) as Seconds\n}\n\n/**\n * Escape hatch for tests / bring-up code that holds a raw seconds number\n * (e.g. a catalog field that ships as float seconds). Production code\n * should prefer `secondsFromTicks` so the conversion site is explicit.\n */\nexport function asSeconds(n: number): Seconds {\n if (!Number.isFinite(n)) {\n throw new Error(`[align] asSeconds: value must be finite, got ${n}`)\n }\n return n as Seconds\n}\n\n/**\n * Deterministic groupId from integer ticks. Authoritative form of the\n * contract (see module docstring).\n *\n * @param ticks \u2014 media presentation time in ticks (integer).\n * Negative values clamp to group 0 (matches\n * the Go sibling's base-clamp).\n * @param groupDurationTicks \u2014 group duration in ticks (positive integer).\n * @returns non-negative integer groupId.\n *\n * Throws on non-integer/non-finite inputs or non-positive duration. These\n * are init-time errors, not packet-hot-path errors \u2014 callers ingesting\n * wire data should validate once at catalog load, not once per packet.\n */\nexport function groupIdForTicks(ticks: number, groupDurationTicks: number): number {\n if (!Number.isInteger(ticks)) {\n throw new Error(\n `[align] groupIdForTicks: ticks must be an integer, got ${ticks}`,\n )\n }\n if (!Number.isInteger(groupDurationTicks) || groupDurationTicks <= 0) {\n throw new Error(\n `[align] groupIdForTicks: groupDurationTicks must be a positive integer, got ${groupDurationTicks}`,\n )\n }\n if (ticks < 0) return 0\n return Math.floor(ticks / groupDurationTicks)\n}\n\n/**\n * Seconds-form wrapper around `groupIdForTicks`. Kept for backwards\n * compatibility with existing callers that hold `Seconds` (or a raw\n * `number` that semantically represents seconds).\n *\n * Internally converts to ticks using a synthetic 1e9 Hz timescale so the\n * integer-math contract is the single source of truth. The `| number`\n * branch of the parameter type is a migration ramp \u2014 new code should\n * pass `Seconds` explicitly (via `secondsFromTicks` or `asSeconds`).\n *\n * @param mediaTimeSec \u2014 finite number of seconds; negatives clamp to 0.\n * @param groupDurationSec \u2014 positive finite seconds.\n *\n * @deprecated Use `groupIdForTicks(ticks, groupDurationTicks)` with\n * tick-space inputs from the catalog. The seconds wrapper rounds to the\n * nearest nanosecond and throws above ~104 days of media time; the\n * integer-ticks form has no rounding and a much wider safe range.\n *\n * The rounding is not benign: when `groupDurationTicks / timescale` is not\n * ns-exact (33333@90k, 1001@30k, 1024@44.1k \u2014 all real catalog cadences)\n * the quantized duration under-counts and `expected` slips a whole group.\n * See the divergence fixture in `__tests__/align.test.ts`.\n *\n * No non-test caller remains in this package: `MediaRouter` now takes\n * `GroupBoundaryTicks` (PCL-2211). Removal is tracked by BLO-21176.\n */\nexport function groupIdFor(\n mediaTimeSec: Seconds | number,\n groupDurationSec: number,\n): number {\n if (!Number.isFinite(mediaTimeSec as number)) {\n throw new Error(\n `[align] groupIdFor: mediaTimeSec must be a finite number, got ${mediaTimeSec}`,\n )\n }\n if (!Number.isFinite(groupDurationSec) || groupDurationSec <= 0) {\n throw new Error(\n `[align] groupIdFor: groupDurationSec must be a positive finite number, got ${groupDurationSec}`,\n )\n }\n if ((mediaTimeSec as number) < 0) return 0\n // 1e9 Hz keeps both operands inside Number.MAX_SAFE_INTEGER (2^53-1)\n // for ~104 days of media time. Beyond that, Math.round returns a\n // non-safe-integer float that Number.isInteger still accepts \u2014 throw\n // explicitly so the caller picks a narrower timescale rather than\n // silently quantizing.\n const TS = 1_000_000_000\n const ticks = Math.round((mediaTimeSec as number) * TS)\n const durTicks = Math.round(groupDurationSec * TS)\n if (durTicks <= 0) {\n throw new Error(\n `[align] groupIdFor: groupDurationSec=${groupDurationSec} rounds to <1 tick at 1e9 Hz; pass groupIdForTicks directly for sub-nanosecond durations`,\n )\n }\n if (!Number.isSafeInteger(ticks)) {\n throw new Error(\n `[align] groupIdFor: mediaTimeSec=${mediaTimeSec} overflows safe-integer ticks at 1e9 Hz (>~104 days); use groupIdForTicks with a narrower timescale`,\n )\n }\n return groupIdForTicks(ticks, durTicks)\n}\n\n/**\n * Result of a drift check between expected and actual groupIds.\n *\n * Discriminated on `kind` so callers must handle both outcomes explicitly\n * (the previous `{ drifted: boolean, deltaGroups: number }` shape let a\n * drifted result slip through with a silent `if (result.drifted)` gate).\n */\nexport type DriftCheckResult =\n | { readonly kind: 'aligned'; readonly deltaGroups: 0 }\n | { readonly kind: 'drifted'; readonly deltaGroups: number }\n\n/**\n * Compare a packet-stamped groupId against the formula-derived expectation.\n *\n * Pure observer \u2014 does not throw on drift, does not mutate anything.\n * Callers decide how to react (e.g. bump a CMCD counter, log, ignore).\n *\n * @param expected \u2014 groupId as derived via `groupIdForTicks` or `groupIdFor`.\n * @param actual \u2014 groupId as stamped on the incoming packet/group.\n */\nexport function driftCheck(expected: number, actual: number): DriftCheckResult {\n if (!Number.isInteger(expected) || !Number.isInteger(actual)) {\n throw new Error(\n `[align] driftCheck: both arguments must be integer groupIds, got expected=${expected} actual=${actual}`,\n )\n }\n const deltaGroups = actual - expected\n if (deltaGroups === 0) return { kind: 'aligned', deltaGroups: 0 }\n return { kind: 'drifted', deltaGroups }\n}\n\nexport type MmtpSubgroupKeyInput =\n | { readonly kind: 'mpu-metadata' }\n | {\n readonly kind: 'timed'\n readonly movieFragmentSequenceNumber: number\n readonly sampleNumber: number\n }\n | { readonly kind: 'non-timed'; readonly itemId: number }\n\n/**\n * Canonical, domain-separated key for MMTP payloads sharing one MoQ subgroup.\n * This is a map/storage key, not the scalar MoQ subgroup ID. MPU metadata is\n * carried in subgroup 0; media identity remains explicit and collision-free.\n */\nexport function subgroupKeyFor(input: MmtpSubgroupKeyInput): string {\n if (input.kind === 'mpu-metadata') return 'metadata:0'\n\n const values =\n input.kind === 'timed'\n ? [input.movieFragmentSequenceNumber, input.sampleNumber]\n : [input.itemId]\n for (const value of values) {\n if (!Number.isInteger(value) || value < 0 || value > 0xffff_ffff) {\n throw new Error(`[align] subgroup key coordinates must be uint32, got ${value}`)\n }\n }\n\n return input.kind === 'timed'\n ? `timed:${input.movieFragmentSequenceNumber}:${input.sampleNumber}`\n : `non-timed:${input.itemId}`\n}\n", "/**\n * TrackSession \u2014 per-track transport state machine.\n * SessionManager \u2014 orchestrates all sessions + TransportPool.\n *\n * Implements Patent Claim 9 (draft-ramadan-moq-fec APPLICATION-1):\n * (a) subscribe unicast\n * (b) simultaneously join multicast\n * (c) render from unicast during join\n * (d) activate multicast on RAP, suppress unicast\n * (e) cross-transport FEC repair during transition\n * (f) hysteresis before switching away from multicast\n * (g) re-subscribe unicast on sustained failure\n *\n * PIM: manages transport state without inspecting packet contents.\n */\n\nimport {\n TransportPool,\n type Subscription,\n type SubscriptionMode,\n type AcquireOptions,\n type DirectSocketHealth,\n type TransportPoolOptions,\n} from './transport-pool.js'\nimport { deriveFecInterleaveDepth } from '@blockcast/mmt-fec'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TrackState =\n | 'idle'\n | 'unicast'\n | 'unicast-fallback'\n | 'joining'\n | 'multicast'\n | 'amt'\n | 'failing'\n\nexport interface TrackSessionConfig {\n trackName: string\n trackType: 'video' | 'audio'\n packetId: number\n /** FEC interleave depth from catalog (ms). Used for hysteresis timer. */\n fecInterleaveMs: number\n /** FEC K from catalog. Used for audio worklet latency calculation. */\n fecK: number\n /** FEC repair symbols per block (P). NOT symbol size. */\n fecRepairCount: number\n /** FEC symbol size in bytes (T). Required for WASM decoder OTI. */\n fecSymbolSize: number\n /** MoQ repair track name from catalog FEC config. */\n repairTrackName?: string\n /** Frame samples from initData parsing (audio-init-parser.ts). */\n frameSamples?: number\n /** Audio sample rate from catalog. */\n sampleRate?: number\n /** Ordered transport preference: best first. */\n preferredTransports: SubscriptionMode[]\n /** Subscription key for multicast/AMT: \"(S,G,port)\". */\n multicastKey?: string\n /** MoQ track name for unicast subscription. */\n moqTrackName?: string\n /** AMT relay address (if AMT is in preference chain). */\n amtRelay?: string\n /** MoQ relay URL (for WebTransport subscription). */\n moqRelayUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// PIM callbacks (protocol-independent \u2014 transport state only)\n// ---------------------------------------------------------------------------\n\nexport interface SessionManagerCallbacks {\n /** Fired on every transport state transition. */\n onStateChange?(trackName: string, from: TrackState, to: TrackState): void\n /** Fired to suppress/unsuppress unicast for this track (transport arbitration). */\n onSuppress?(trackName: string, suppressed: boolean): void\n /**\n * Fired when a track sat in `joining` for SessionManager.takeoverDeadlineMs\n * without a RAP (BLO-9413/D9). The session has already returned to `unicast`\n * (nothing was suppressed, so nothing to restore) \u2014 the host should abandon\n * the multicast attempt (e.g. MediaRouter.failbackToMoq()) so the router\n * stops waiting for a keyframe that is never coming.\n */\n onTakeoverTimeout?(trackName: string): void\n}\n\n// ---------------------------------------------------------------------------\n// MMTP application-layer extension (FEC lifecycle, media resets)\n// ---------------------------------------------------------------------------\n\n/**\n * MMTP track events (discriminated union).\n * Each protocol layer defines its own event union \u2014 MMTP events cover\n * media resets, latency, and FEC lifecycle.\n */\nexport type MmtpTrackEvent =\n | { type: 'reset' }\n | { type: 'latency'; latencyMs: number }\n | { type: 'fec-setup' }\n | { type: 'fec-teardown' }\n\n/**\n * MMTP application-layer extension of SessionManagerCallbacks.\n * Single onTrackEvent dispatches all MMTP-specific events.\n * PIM consumers use the base SessionManagerCallbacks (no onTrackEvent).\n *\n * IWA can define its own subinterface with its own TrackEvent union\n * (e.g., IwaTrackEvent for socket health, AMT state) using the same pattern.\n */\nexport interface MmtpSessionCallbacks extends SessionManagerCallbacks {\n onTrackEvent?(trackName: string, event: MmtpTrackEvent, config: TrackSessionConfig): void\n}\n\n/**\n * Loss-hysteresis window covering the signaled interleave window plus jitter.\n *\n * This path lacks media cadence, so it intentionally does not estimate a full\n * source-plus-repair production horizon. FECManager uses\n * computeFecDeliveryWindowMs with media cadence when configuring block\n * inactivity deadlines.\n *\n * `fecK` no longer feeds the math; it is still validated as a catalog\n * well-formedness guard and kept in the signature for positional API compat\n * (removal is a follow-up with the next cross-repo pin bump).\n */\nexport function computeFecLossWindowMs(fecInterleaveMs: number, fecK: number, jitterBufferMs?: number): number {\n return computeFecDeliveryWindowMs({\n interleaveDepthMs: fecInterleaveMs,\n sourceSymbols: fecK,\n jitterBufferMs,\n })\n}\n\nexport interface FecDeliveryWindowConfig {\n interleaveDepthMs: number\n sourceSymbols: number\n repairSymbols?: number\n symbolSize?: number\n mediaUnitsPerSecond?: number\n bandwidthBps?: number\n /**\n * Jitter + one-way-delay + reorder budget in ms (draft-ramadan-moq-fec \u00A79),\n * sourced from the catalog `jitterBufferMs`. Added on top of the max-of-spans\n * core; it is NOT a multiplier. See the margin-policy note in the function.\n */\n jitterBufferMs?: number\n}\n\n/**\n * Catalog-derived silence window that covers interleave geometry plus the time\n * required to produce or serialize a complete source+repair block. The catalog\n * field remains D media units; this function does not feed mapper geometry.\n */\nexport function computeFecDeliveryWindowMs(\n config: FecDeliveryWindowConfig,\n): number {\n const fecInterleaveMs = config.interleaveDepthMs\n const fecK = config.sourceSymbols\n if (fecInterleaveMs <= 0) {\n throw new Error(\n `[TrackSession] fecInterleaveMs=${fecInterleaveMs}. ` +\n `Derive a positive FEC interleave window from the stream catalog.`\n )\n }\n if (fecK <= 0) {\n throw new Error(\n `[TrackSession] fecK=${fecK}. ` +\n `Derive a positive FEC source-symbol count from the stream catalog.`\n )\n }\n\n const spansMs = [fecInterleaveMs]\n if (config.mediaUnitsPerSecond && config.mediaUnitsPerSecond > 0) {\n const mediaUnitMs = 1000 / config.mediaUnitsPerSecond\n // interleaveDepthMs is integer catalog signaling while media-unit duration\n // is often fractional (for example 4/30s = 133.33ms). Recover D using the\n // producer's nearest-ms quantization, then cover every source and repair\n // round-robin cycle. A decoder cannot recover a partial MFU until the\n // repair tail arrives, so a K-only deadline expires the source fragments\n // before the final repair symbols that complete them.\n const interleaveUnits = deriveFecInterleaveDepth(fecInterleaveMs, mediaUnitMs)\n const deliverySymbols = fecK + Math.max(0, config.repairSymbols ?? 0)\n spansMs.push(deliverySymbols * interleaveUnits * mediaUnitMs)\n }\n if (\n config.symbolSize &&\n config.symbolSize > 0 &&\n config.bandwidthBps &&\n config.bandwidthBps > 0\n ) {\n const repairSymbols = Math.max(0, config.repairSymbols ?? 0)\n spansMs.push(\n ((fecK + repairSymbols) * config.symbolSize * 8 * 1000) /\n config.bandwidthBps,\n )\n }\n\n // draft-ramadan-moq-fec \u00A79: the recovery timeout is a DERIVED budget \u2014\n // max(interleave span, source+repair production horizon, block serialization) PLUS\n // an ADDITIVE margin for jitter + one-way delay + reorder. It MUST NOT be a\n // fixed multiple of the interleave span (this replaces the retired `* 3` /\n // `* 2` safety factors). The margin is the catalog `jitterBufferMs`.\n //\n // MARGIN-POLICY (operator sign-off \u2014 see PR): no catalog currently emits\n // `jitterBufferMs`. Rather than throw (which would break every existing\n // catalog) or invent a magic constant, the default margin is the un-rounded\n // interleave window \u2014 the spec's natural reorder-budget bound and already a\n // catalog value. Emit `jitterBufferMs` in the catalog to set it explicitly.\n const coreMs = Math.max(...spansMs)\n const marginMs =\n config.jitterBufferMs !== undefined && config.jitterBufferMs >= 0\n ? config.jitterBufferMs\n : fecInterleaveMs\n return Math.ceil(coreMs) + Math.ceil(marginMs)\n}\n\n/** Convert the shared FEC delivery deadline into an integer media timescale. */\nexport function computeFecDeliveryWindowMediaUnits(\n config: FecDeliveryWindowConfig,\n timestampTimescale: number,\n): number {\n if (!Number.isFinite(timestampTimescale) || timestampTimescale <= 0) {\n throw new Error(\n `[TrackSession] timestampTimescale=${timestampTimescale}. ` +\n 'Derive a positive media timescale from the container.',\n )\n }\n const deliveryWindow = Math.ceil(\n computeFecDeliveryWindowMs(config) * timestampTimescale / 1000,\n )\n if (!Number.isSafeInteger(deliveryWindow)) {\n throw new Error('[TrackSession] FEC delivery window exceeds the media timestamp range.')\n }\n return deliveryWindow\n}\n\n// ---------------------------------------------------------------------------\n// TrackSession\n// ---------------------------------------------------------------------------\n\nexport class TrackSession {\n readonly config: TrackSessionConfig\n #state: TrackState = 'idle'\n #subscription: Subscription | null = null\n #hysteresisTimer: ReturnType<typeof setTimeout> | null = null\n #joinDeadlineTimer: ReturnType<typeof setTimeout> | null = null\n #previousPrimary: SubscriptionMode | null = null\n #manager: SessionManager\n // Single suppression authority flag (BLO-9413 failback coherence): every\n // suppress this session fires is recorded here, and every path that leaves\n // a suppressed state unwinds through #unsuppress(). Before this flag,\n // unsubscribe() only un-suppressed from multicast/amt \u2014 a session torn down\n // from `failing` (exactly where a starving track sits) was re-subscribed to\n // unicast with the host's media still muted: MoQ playing into a dead sink.\n #suppressFired = false\n\n constructor(config: TrackSessionConfig, manager: SessionManager) {\n this.config = config\n this.#manager = manager\n }\n\n // -----------------------------------------------------------------------\n // State\n // -----------------------------------------------------------------------\n\n get state(): TrackState { return this.#state }\n get subscription(): Subscription | null { return this.#subscription }\n\n /** True while this session's onSuppress(true) is outstanding (no matching\n * onSuppress(false) yet). Test/assertion surface for failback coherence. */\n get isSuppressed(): boolean { return this.#suppressFired }\n\n /** Current active transport mode, or null if idle. */\n get transport(): SubscriptionMode | null {\n switch (this.#state) {\n case 'multicast': return 'multicast'\n case 'amt': return 'amt'\n case 'unicast': return 'moq'\n case 'unicast-fallback': return 'moq' // SSM bridge absent \u2014 MoQ unicast is the only source\n case 'joining': return 'moq' // MoQ still primary during join\n case 'failing': return this.#previousPrimary\n default: return null\n }\n }\n\n // -----------------------------------------------------------------------\n // Computed (no magic numbers)\n // -----------------------------------------------------------------------\n\n /** Audio worklet latency: max(fecInterleaveMs, frameDurationMs * fecK).\n * Returns undefined if frame duration unknown (no initData). */\n get audioLatencyMs(): number | undefined {\n const fd = this.frameDurationMs\n if (fd === undefined) {\n return this.config.fecInterleaveMs > 0 ? this.config.fecInterleaveMs : undefined\n }\n return Math.max(this.config.fecInterleaveMs, fd * this.config.fecK)\n }\n\n /** Frame duration in ms, derived from frameSamples + sampleRate. */\n get frameDurationMs(): number | undefined {\n const { frameSamples, sampleRate } = this.config\n if (!frameSamples || !sampleRate || sampleRate <= 0) return undefined\n return (frameSamples / sampleRate) * 1000\n }\n\n // -----------------------------------------------------------------------\n // Transitions (Patent Claim 9)\n // -----------------------------------------------------------------------\n\n /** idle \u2192 unicast: player subscribes to this track. */\n subscribe(): void {\n if (this.#state !== 'idle') return\n this.#transition('unicast')\n\n // Emit audio latency on first subscribe\n if (this.config.trackType === 'audio') {\n const latency = this.audioLatencyMs\n if (latency !== undefined) {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'latency', latencyMs: latency }, this.config)\n }\n }\n }\n\n /** unicast \u2192 joining: multicast/AMT capability detected, join in progress. */\n joinStarted(): void {\n if (this.#state !== 'unicast') return\n this.#transition('joining')\n // FEC decoders must be ready before multicast data arrives (Claim 9e: cross-transport repair)\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'fec-setup' }, this.config)\n }\n\n /**\n * any non-fallback state \u2192 unicast-fallback. Used when the SSM bridge is\n * absent (vanilla Chromium / no extension) and the host falls back to a\n * MoQ unicast subscription per track. Terminal-ish: activateMulticast,\n * failbackAll, and transportFailed are no-ops while in this state.\n *\n * Fires fec-setup so the per-track repair worker spins up \u2014 repair packets\n * still flow over the MoQ repair-track subscription, so FEC source+repair\n * can converge as in the multicast path.\n *\n * Audio latency is NOT re-emitted here: subscribe() already fires latency\n * once on the idle \u2192 unicast edge, and enterUnicastFallback is only ever\n * reached from a non-idle state (SessionManager skips idle sessions).\n */\n enterUnicastFallback(): void {\n if (this.#state === 'unicast-fallback') return\n this.#clearHysteresis()\n this.#transition('unicast-fallback')\n // Suppress the redundant unicast media decoder. On the no-bridge path the\n // host feeds MoQ-unicast source packets into MulticastManager (the render\n // consumer); a second base-media subscribe (the @moq/watch decoder) would\n // split the single connection's QUIC flow-control budget and stall delivery\n // (BLO-8969). Fire suppress here, on the fallback edge, so the decoder is\n // gated off before it subscribes rather than after the relay forward task\n // has already wedged.\n this.#suppress()\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'fec-setup' }, this.config)\n }\n\n /**\n * unicast-fallback \u2192 unicast. Called when the SSM bridge appears mid-session\n * (extension installed after fallback engaged). Host first aborts the fallback\n * drainers, then transitions sessions back to unicast so the regular\n * multicast/AMT preference chain can take over via joinStarted().\n */\n exitUnicastFallback(): void {\n if (this.#state !== 'unicast-fallback') return\n this.#transition('unicast')\n // Un-suppress the unicast decoder: the bridge appeared mid-session, so the\n // host tears down the fallback drainers and the regular preference chain\n // (joinStarted -> joining -> rapReceived) will re-suppress on multicast RAP.\n this.#unsuppress()\n }\n\n /**\n * joining \u2192 multicast or amt: RAP received on the joined transport (Claim 9d).\n * Resets audio worklet, then suppresses MoQ.\n */\n rapReceived(mode: 'multicast' | 'amt'): void {\n if (this.#state !== 'joining') return\n this.#previousPrimary = mode\n this.#transition(mode)\n if (this.config.trackType === 'audio') {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'reset' }, this.config)\n }\n this.#suppress()\n }\n\n /** multicast/amt \u2192 failing: loss detected, start hysteresis timer (Claim 9f). */\n lossDetected(): void {\n if (this.#state !== 'multicast' && this.#state !== 'amt') return\n this.#previousPrimary = this.#state as SubscriptionMode\n this.#transition('failing')\n\n // Hysteresis timer: sustained loss before failback (Claim 9f). The window\n // covers a complete FEC block plus safety margin: the catalog interleave\n // value IS the block span (interleaveDepthMs, \u00A75.1), scaled by the same\n // safety factor FECManager uses for its block timeout.\n const hysteresisMs = computeFecLossWindowMs(this.config.fecInterleaveMs, this.config.fecK)\n this.#hysteresisTimer = setTimeout(() => {\n this.hysteresisExpired()\n }, hysteresisMs)\n }\n\n /** failing \u2192 previous primary: loss recovered within hysteresis window. */\n lossRecovered(): void {\n if (this.#state !== 'failing') return\n this.#clearHysteresis()\n if (this.#previousPrimary) {\n this.#transition(this.#previousPrimary as TrackState)\n }\n }\n\n /**\n * Transport failed (AMT relay down, multicast interface gone).\n * Try next transport in preference chain.\n */\n transportFailed(): void {\n this.#clearHysteresis()\n // Unicast-fallback is terminal \u2014 the host owns the subscription and\n // SessionManager transport machinery does not arbitrate it.\n if (this.#state === 'unicast-fallback') return\n const current = this.#previousPrimary ?? this.transport\n const prefs = this.config.preferredTransports\n const currentIdx = current ? prefs.indexOf(current) : -1\n const nextMode = prefs[currentIdx + 1]\n\n if (nextMode) {\n // Failover to next in chain\n if (nextMode === 'moq') {\n this.#transition('unicast')\n this.#unsuppress()\n if (this.config.trackType === 'audio') {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'reset' }, this.config)\n }\n } else {\n // Next is multicast or AMT \u2014 start joining. `joining` means MoQ is\n // primary again (see transport getter) \u2014 if the failed transport had\n // suppressed unicast, restore it for the join window or the viewer\n // watches black video until the next RAP (which may never come).\n this.#transition('joining')\n this.#unsuppress()\n }\n } else {\n // End of chain \u2014 fall back to unicast (Claim 9g)\n this.#transition('unicast')\n this.#unsuppress()\n if (this.config.trackType === 'audio') {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'reset' }, this.config)\n }\n }\n }\n\n /** Hysteresis expired \u2192 failback to next transport (Claim 9g). */\n hysteresisExpired(): void {\n if (this.#state !== 'failing') return\n this.#clearHysteresis()\n this.transportFailed()\n }\n\n /** \u2192 idle: player unsubscribes / tab close / cleanup. */\n unsubscribe(): void {\n this.#clearHysteresis()\n if (this.#state !== 'idle') {\n this.#transition('idle')\n // Unwind any outstanding suppression regardless of which state we left.\n // The old `wasActive` check (multicast/amt only) leaked suppression when\n // tearing down from `failing` or `unicast-fallback` \u2014 failbackAll()'s\n // unsubscribe\u2192subscribe cycle then re-entered unicast with the host's\n // media still muted (BLO-9413 freeze: \"failing back\" logged, video dead).\n this.#unsuppress()\n }\n }\n\n /** True when SessionManager.enterUnicastFallback put this track into the\n * host-driven unicast subscription mode. Host can iterate sessions to\n * decide whether to keep its fallback drainer alive across MediaRouter\n * arbitration events. */\n get isUnicastFallback(): boolean {\n return this.#state === 'unicast-fallback'\n }\n\n dispose(): void {\n this.#clearHysteresis()\n this.#clearJoinDeadline()\n this.#state = 'idle'\n this.#subscription = null\n }\n\n // -----------------------------------------------------------------------\n // Internal\n // -----------------------------------------------------------------------\n\n #transition(to: TrackState): void {\n const from = this.#state\n if (from === to) return\n this.#state = to\n // Takeover deadline (BLO-9413/D9): every entry into `joining` arms it\n // (joinStarted AND transportFailed-failover both route through here);\n // every exit cancels it. Without this, a transport that accepts the\n // subscribe but never delivers a RAP parks the session in `joining`\n // forever (observed live in the Phase-0 headless repro).\n if (from === 'joining') this.#clearJoinDeadline()\n if (to === 'joining') this.#armJoinDeadline()\n // FEC teardown: leaving FEC-active states \u2192 non-FEC state\n const fecActive = (s: TrackState) =>\n s === 'joining' ||\n s === 'multicast' ||\n s === 'amt' ||\n s === 'failing' ||\n s === 'unicast-fallback'\n if (fecActive(from) && !fecActive(to)) {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'fec-teardown' }, this.config)\n }\n this.#manager.callbacks?.onStateChange?.(this.config.trackName, from, to)\n }\n\n #clearHysteresis(): void {\n if (this.#hysteresisTimer !== null) {\n clearTimeout(this.#hysteresisTimer)\n this.#hysteresisTimer = null\n }\n }\n\n #armJoinDeadline(): void {\n this.#clearJoinDeadline()\n const deadlineMs = this.#manager.takeoverDeadlineMs\n if (deadlineMs <= 0) return // disabled\n this.#joinDeadlineTimer = setTimeout(() => {\n this.#joinDeadlineTimer = null\n if (this.#state !== 'joining') return\n // No RAP within the deadline: return to unicast. MoQ was never\n // suppressed in `joining`, so there is nothing to restore \u2014 this edge\n // must stay silent on onSuppress (asserted in tests).\n this.#transition('unicast')\n this.#manager.callbacks?.onTakeoverTimeout?.(this.config.trackName)\n }, deadlineMs)\n }\n\n #clearJoinDeadline(): void {\n if (this.#joinDeadlineTimer !== null) {\n clearTimeout(this.#joinDeadlineTimer)\n this.#joinDeadlineTimer = null\n }\n }\n\n /** Fire onSuppress(true) exactly once per suppression epoch. */\n #suppress(): void {\n if (this.#suppressFired) return\n this.#suppressFired = true\n this.#manager.callbacks?.onSuppress?.(this.config.trackName, true)\n }\n\n /** Unwind an outstanding suppression; no-op when none is outstanding. */\n #unsuppress(): void {\n if (!this.#suppressFired) return\n this.#suppressFired = false\n this.#manager.callbacks?.onSuppress?.(this.config.trackName, false)\n }\n}\n\n// ---------------------------------------------------------------------------\n// SessionManager\n// ---------------------------------------------------------------------------\n\nexport class SessionManager {\n readonly pool: TransportPool\n readonly sessions = new Map<string, TrackSession>()\n callbacks: SessionManagerCallbacks | null = null\n /**\n * Takeover deadline (BLO-9413/D9): max time a session may sit in `joining`\n * without a RAP before returning to `unicast` and firing onTakeoverTimeout.\n * MoQ is never suppressed during `joining`, so expiry is a silent abandon \u2014\n * playback never glitches. Hosts tune per stream (e.g. GOP length); 0\n * disables.\n */\n takeoverDeadlineMs = 5000\n\n /** Narrow to MMTP callbacks (onTrackEvent). Safe \u2014 optional chaining handles PIM-only consumers. */\n get mmtp(): MmtpSessionCallbacks | null {\n return this.callbacks as MmtpSessionCallbacks | null\n }\n\n constructor(poolOptions?: TransportPoolOptions) {\n this.pool = new TransportPool(poolOptions)\n\n // Wire pool failure \u2192 per-track failover\n this.pool.onSubscriptionFailed(sub => {\n for (const trackName of sub.consumers) {\n const session = this.sessions.get(trackName)\n if (session) session.transportFailed()\n }\n })\n }\n\n // -----------------------------------------------------------------------\n // Track management\n // -----------------------------------------------------------------------\n\n /** Add a track session from catalog config. */\n addTrack(config: TrackSessionConfig): TrackSession {\n if (this.sessions.has(config.trackName)) {\n throw new Error(`[SessionManager] Track \"${config.trackName}\" already exists`)\n }\n const session = new TrackSession(config, this)\n this.sessions.set(config.trackName, session)\n return session\n }\n\n /** Remove a track session. Releases subscription with grace period. */\n removeTrack(trackName: string): void {\n const session = this.sessions.get(trackName)\n if (!session) return\n session.unsubscribe()\n\n // Release pool subscription\n const subKey = session.config.multicastKey ?? session.config.moqTrackName\n if (subKey) this.pool.releaseSubscription(subKey, trackName)\n\n session.dispose()\n this.sessions.delete(trackName)\n }\n\n // -----------------------------------------------------------------------\n // Atomic multi-track operations\n // -----------------------------------------------------------------------\n\n /**\n * All tracks with multicast RAP \u2192 multicast state (atomic).\n * Called when MediaRouter fires onSourceSwitch('multicast').\n * Prevents split-brain between video and audio.\n *\n * A takeover deadline can return a track from `joining` to `unicast` while\n * the multicast socket keeps delivering packets. If a valid RAP arrives\n * later, allow that late-RAP edge to suppress unicast and promote multicast.\n */\n activateMulticast(): void {\n for (const [, session] of this.sessions) {\n if (session.state === 'unicast') {\n session.joinStarted()\n }\n if (session.state === 'joining') {\n session.rapReceived('multicast')\n }\n }\n }\n\n /**\n * All tracks with AMT RAP \u2192 amt state (atomic).\n */\n activateAmt(): void {\n for (const [, session] of this.sessions) {\n if (session.state === 'unicast') {\n session.joinStarted()\n }\n if (session.state === 'joining') {\n session.rapReceived('amt')\n }\n }\n }\n\n /**\n * All tracks \u2192 unicast (failback, atomic).\n * Called when MulticastManager fires onFailback().\n * unicast-fallback sessions are skipped \u2014 the host owns those subscriptions\n * and they must drain uninterrupted across transport-state events.\n *\n * Coherence invariant (BLO-9413): after this returns, every non-fallback\n * session is `unicast` (or `idle`) AND unsuppressed \u2014 unsubscribe() unwinds\n * any outstanding suppression regardless of the state it tears down from\n * (`failing` included; see TrackSession.#suppressFired).\n */\n failbackAll(): void {\n for (const [, session] of this.sessions) {\n if (\n session.state !== 'idle' &&\n session.state !== 'unicast' &&\n session.state !== 'unicast-fallback'\n ) {\n session.unsubscribe()\n session.subscribe() // re-enter unicast\n }\n }\n }\n\n /**\n * All tracks \u2192 unicast-fallback (atomic).\n * Called by the host when the multicast gateway is absent and\n * the player will subscribe to source tracks over MoQ unicast and feed\n * packets via MulticastManager.feedUnicastSource() (or manager.router\n * directly). Sessions in this state stay terminal until the host calls\n * unsubscribe() / disposes the SessionManager.\n */\n enterUnicastFallback(): void {\n for (const [, session] of this.sessions) {\n if (session.state === 'idle') continue\n session.enterUnicastFallback()\n }\n }\n\n /**\n * True if any session is currently in unicast-fallback. Hosts use this to\n * gate `multicastTrack.close()` on failback events \u2014 closing the shared\n * broadcast would kill the host's fallback drainers.\n */\n hasActiveFallback(): boolean {\n for (const [, session] of this.sessions) {\n if (session.isUnicastFallback) return true\n }\n return false\n }\n\n /**\n * All fallback tracks \u2192 unicast (atomic).\n * Called when the SSM bridge appears mid-session and the host has aborted\n * the fallback drainers. The session re-enters the regular preference chain\n * via joinStarted() (issued by the host immediately after).\n */\n exitUnicastFallback(): void {\n for (const [, session] of this.sessions) {\n if (session.isUnicastFallback) session.exitUnicastFallback()\n }\n }\n\n // -----------------------------------------------------------------------\n // Cleanup lifecycle\n // -----------------------------------------------------------------------\n\n /** Tab closed \u2014 release all subscriptions for this tab (grace period). */\n tabClosed(tabId: string): void {\n // All tracks owned by this tab \u2192 idle\n for (const [trackName, session] of this.sessions) {\n session.unsubscribe()\n this.pool.releaseAllForConsumer(trackName)\n }\n }\n\n /** Bridge WebTransport connection dropped \u2014 same as tab close. */\n bridgeDisconnected(): void {\n this.tabClosed('bridge')\n }\n\n /**\n * Track selection changed (user or ABR).\n * Old track released (grace period), new track subscribed.\n */\n trackSelectionChanged(oldTrackName: string, newConfig: TrackSessionConfig): void {\n this.removeTrack(oldTrackName)\n const session = this.addTrack(newConfig)\n session.subscribe()\n }\n\n // -----------------------------------------------------------------------\n // Query\n // -----------------------------------------------------------------------\n\n /** Get a track session by name. */\n getTrack(trackName: string): TrackSession | undefined {\n return this.sessions.get(trackName)\n }\n\n // -----------------------------------------------------------------------\n // Cleanup\n // -----------------------------------------------------------------------\n\n dispose(): void {\n for (const [, session] of this.sessions) {\n session.dispose()\n }\n this.sessions.clear()\n this.pool.dispose()\n this.callbacks = null\n }\n}\n", "/**\n * MediaRouter \u2014 source arbitration for MoQ \u2194 multicast switching.\n *\n * Both moq-lib and moqtail wire their MoQ and multicast frame outputs\n * through this router. The router decides which source reaches the decoder:\n *\n * - MoQ frames pass through while multicast is inactive.\n * - Multicast video frames are suppressed until the first keyframe.\n * - Once multicast has both a video keyframe and audio, takeover is requested.\n * - MoQ is suppressed only after the source-switch callback completes.\n * - failbackToMoq() reverses the switch immediately.\n *\n * Per-track setTrackSource() allows individual tracks to be wired to a\n * specific transport source (video=multicast, audio=moq, overlay=amt) without\n * changing the binary MoQ\u2194multicast switching for other tracks.\n *\n * notifyGroupBoundary() is an observer hook: callers report the\n * packet-stamped groupId + media time for each group, and the router\n * cross-checks against the shared `align.ts` formula. Drift is emitted via\n * onGroupDrift so the player can bump the CMCD `x-bc-switch-drift-us` /\n * `x-bc-switch-miscount` counters. Observation only \u2014 does NOT alter routing.\n */\n\nimport { groupIdForTicks, driftCheck } from './align.js'\nimport { computeFecDeliveryWindowMs } from './track-session.js'\n\nexport type FrameMeta = {\n timestamp: number\n keyframe: boolean\n packetId?: number\n packetSequenceNumber?: number\n source?: 'moq' | 'multicast' | 'repair'\n recovered?: boolean\n sourceDiscontinuity?: boolean\n}\nexport type FrameKind = 'video' | 'audio'\nexport type SourceKind = 'moq' | 'multicast'\n\n/**\n * Tick-space timing for one group boundary \u2014 the exact, authoritative form.\n *\n * These are the integers the catalog already carries (`groupDurationTicks`,\n * `timescale`) plus the packet's raw timestamp in the same units. Passing\n * them straight through keeps the whole drift check in integer math.\n *\n * Prefer this over the seconds overload: `groupDurationTicks / timescale`\n * is not exactly representable for the common non-integer-second cadences\n * (33333@90k, 1001@30k, 1024@44.1k), and the seconds path has to quantize\n * to whole nanoseconds \u2014 which shifts `expected` by a whole group from the\n * second boundary onward and reports drift on a perfectly aligned stream.\n */\nexport interface GroupBoundaryTicks {\n /** Packet media timestamp in `timescale` units. Integer; negatives clamp to 0. */\n readonly mediaTimeTicks: number\n /** Group duration in `timescale` units. Positive integer. */\n readonly groupDurationTicks: number\n /**\n * Catalog timescale in Hz. Positive integer \u2014 **validated, and a hard gate**:\n * a non-integer or non-positive value skips the drift check entirely rather\n * than reporting against it. It does not feed the groupId computation (that\n * is `mediaTimeTicks / groupDurationTicks` alone), but it does derive the\n * `mediaTimeSec` / `groupDurationSec` on every GroupDriftEvent \u2014 so letting a\n * bad value through would push NaN/Infinity into CMCD `x-bc-switch-drift-us`,\n * which is worse than skipping.\n */\n readonly timescale: number\n}\n\n/** Payload emitted by MediaRouter.onGroupDrift when a packet-stamped groupId\n * disagrees with the shared `align.ts` formula. */\nexport interface GroupDriftEvent {\n trackName: string\n expectedGroupId: number\n actualGroupId: number\n /** `actual - expected` (positive = ahead, negative = behind). */\n deltaGroups: number\n /** Seconds view, kept for CMCD consumers. Derived when the caller used ticks. */\n mediaTimeSec: number\n groupDurationSec: number\n /** Exact tick-space inputs. Present only when the caller used the ticks form. */\n timing?: GroupBoundaryTicks\n}\n\n/** Normalized output of the two notifyGroupBoundary input forms. */\ntype ResolvedBoundary = {\n readonly expected: number\n readonly mediaTimeSec: number\n readonly groupDurationSec: number\n readonly timing?: GroupBoundaryTicks\n}\n\n/**\n * Distinct failure modes on the notifyGroupBoundary drift path. Each gets its\n * own bounded warn budget (see `#skipWithWarning`), so a stream that trips one\n * cannot permanently silence the diagnostic for a genuinely different one.\n *\n * Deliberately a closed union rather than `string`: the tag is the dedup key,\n * and a typo would silently mint a second budget for an existing reason.\n */\ntype GroupAlignSkipReason =\n | 'invalid-tick-inputs'\n // Currently UNREACHABLE, kept as defence in depth \u2014 see the catch in\n // #resolveTicks. Do not write a test chasing it.\n | 'ticks-formula-threw'\n | 'invalid-seconds-inputs'\n | 'sub-nanosecond-duration'\n | 'media-time-overflow'\n | 'seconds-formula-threw'\n | 'callback-threw'\n\n/**\n * Warns allowed per `GroupAlignSkipReason` for the lifetime of a router.\n *\n * Not a single flat number (PCL-2211, #329 review). The six resolver reasons\n * are closed, deterministic conditions: once you have seen\n * `sub-nanosecond-duration` on this router, the second occurrence carries no\n * new information, so one slot is the right budget and repeated malformed\n * catalog input stays silent.\n *\n * `callback-threw` is the opposite. It wraps arbitrary downstream consumer\n * code and interpolates `err.message` *because the error varies*. Under one\n * slot, a consumer that throws at startup permanently hides an unrelated\n * exception later \u2014 including one from a *replacement* consumer registered\n * through `onGroupDrift()`, which is a public setter with no budget reset.\n * That is the same argument `#skipWithWarning` makes against a single shared\n * boolean, one level down. A small cap bounds the flood, at the cost of\n * silencing distinct errors past the cap: three throws at startup exhaust\n * `callback-threw`, and a replacement consumer's unrelated exception is then\n * silenced permanently \u2014 the same failure as the one-slot budget, moved from\n * the 1st throw to the 4th. That residual is the inherent price of a bounded\n * budget and is accepted deliberately; the alternative is an unbounded warn.\n *\n * Deliberately NOT a reset on `onGroupDrift()` re-registration: that setter is\n * public and unthrottled, so a caller re-registering per boundary would\n * refresh the budget every time and restore the exact flood this prevents.\n *\n * A total `Record` rather than a lookup with a default: the union is closed, so\n * a new reason that forgets to declare its budget is a compile error \u2014 the\n * same typo-safety rationale that makes the union closed in the first place.\n */\nconst GROUP_ALIGN_WARN_BUDGET: Record<GroupAlignSkipReason, number> = {\n 'invalid-tick-inputs': 1,\n 'ticks-formula-threw': 1,\n 'invalid-seconds-inputs': 1,\n 'sub-nanosecond-duration': 1,\n 'media-time-overflow': 1,\n 'seconds-formula-threw': 1,\n 'callback-threw': 3,\n}\n\ntype FrameCb = (data: Uint8Array, meta: FrameMeta) => void\ntype SwitchCb = (source: SourceKind) => void | Promise<void>\n\nexport interface MediaRouterOptions {\n /** Set false for video-only streams. Default: true. */\n requireAudio?: boolean\n /**\n * Audio fallback timeout (ms): force multicast activation if video keyframe\n * arrived but audio hasn't within this window. Catalog-derived via\n * computeFecDeliveryWindowMs (the single FEC recovery-window source of truth).\n * Omit to disable the independent audio-fallback timer (no magic default).\n */\n audioFallbackMs?: number\n /**\n * Number of binary-path frames to allow through during a crossfade transition.\n * Derive from FEC block size (e.g., ceil(K / groupsPerBlock)). Default: 10.\n */\n crossfadeFrames?: number\n /**\n * Max pre-boundary overlay frames to buffer before dropping.\n * Derive from FEC config (e.g., ceil(K * 1.5)). Default: 128.\n */\n maxOverlayBuffer?: number\n /**\n * Max concurrent in-flight repairs. Prevents relay SUBSCRIBE flood (T-13-01 mitigation).\n * Default: 2.\n */\n maxRepairInFlight?: number\n /**\n * Allow multicast takeover from a delta frame after the player has confirmed\n * that MoQ already seeded the same decoder timeline with a video keyframe.\n * Default false: decoder-reset paths still need multicast to provide a RAP.\n */\n allowDeltaMulticastTakeoverAfterMoqKeyframe?: boolean\n}\n\n/**\n * A per-track source that emits frames independently of the binary MoQ/multicast path.\n * Used by setTrackSource() to wire specific tracks to specific transport sources.\n */\nexport interface TrackSource {\n name: string\n /**\n * Register a frame callback. Returns an unsubscribe function.\n * MediaRouter calls unsubscribe() when clearTrackSource() is called.\n */\n onFrame(cb: (data: Uint8Array, meta: FrameMeta) => void): () => void\n /**\n * Optional error callback for when the source fails.\n * MediaRouter uses this to trigger failback to binary switching for this track.\n */\n onError?(cb: () => void): () => void\n /** Optional dispose hook called when clearTrackSource() is called. */\n dispose?(): void\n}\n\n/**\n * Thrown when setTrackSource() is called for a track that already has a source\n * without an intervening clearTrackSource(). Prevents silent concurrent-assignment\n * races (T-02-16 mitigation).\n */\nexport class TrackSourceConflictError extends Error {\n constructor(public readonly trackName: string) {\n super(`Track source already assigned for '${trackName}'; call clearTrackSource first`)\n this.name = 'TrackSourceConflictError'\n }\n}\n\n/**\n * Migration policy for setTrackSource() transport switching.\n *\n * - 'crossfade': Overlap old (binary) and new source briefly (CROSSFADE_WINDOW_FRAMES),\n * then suppress the binary path. Default for audio.\n * - 'drop': Cut to new source immediately. Binary path suppressed at once.\n * Suitable for latency-critical tracks.\n * - 'wait': Defer migration until first FEC block boundary (same as overlay=true).\n * Default for video \u2014 avoids decoder reset mid-block.\n */\nexport type MigrationPolicy = 'crossfade' | 'drop' | 'wait'\n\n/** Default crossfade window \u2014 override via MediaRouterOptions.crossfadeFrames. */\nconst DEFAULT_CROSSFADE_FRAMES = 10\n\n/** Options passed to setTrackSource(). */\nexport interface SetTrackSourceOptions {\n /**\n * If true, this source is an overlay track that must wait for the first FEC\n * block boundary signal (via notifyFecBlockBoundary()) before emitting frames.\n * Frames arriving before the first boundary are buffered up to MAX_OVERLAY_BUFFER.\n *\n * Backwards compat: equivalent to policy: 'wait'.\n */\n overlay?: boolean\n /**\n * Migration policy for the transport switch. Overrides `overlay` when provided.\n * See MigrationPolicy for semantics.\n */\n policy?: MigrationPolicy\n}\n\n/** Default overlay buffer cap \u2014 override via MediaRouterOptions.maxOverlayBuffer. */\nconst DEFAULT_MAX_OVERLAY_BUFFER = 128\n\n/** Internal per-track source record. */\ninterface TrackSourceRecord {\n source: TrackSource\n unsubscribeFrame: () => void\n unsubscribeError?: () => void\n emitCount: number\n}\n\nexport class MediaRouter {\n #active: SourceKind = 'moq'\n #multicastVideoKeyed = false\n #multicastAudioReceived = false\n #moqVideoKeyframeEmitted = false\n #requireAudio: boolean\n #suppressed = false // true after multicast confirmed \u2014 suppresses MoQ\n #audioFallbackTimer: ReturnType<typeof setTimeout> | null = null\n\n // Configurable constants (from catalog/FEC config, not magic numbers)\n readonly #audioFallbackMs: number\n readonly #crossfadeFrames: number\n readonly #maxOverlayBuffer: number\n readonly #allowDeltaMulticastTakeoverAfterMoqKeyframe: boolean\n\n // \u2500\u2500 Keyframe repair state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Groups currently being repaired \u2014 idempotent guard per Pitfall 2 (T-13-01 mitigation) */\n #repairInFlight = new Set<number>()\n /** Max concurrent repairs \u2014 prevents relay SUBSCRIBE flood (T-13-01 mitigation) */\n readonly #maxRepairInFlight: number\n /** Callback to request MoQ subscribe for a specific group (wired by player) */\n #onRepairRequest: ((expectedGroupId: number, trackName: string) => void) | null = null\n\n #onVideoFrame: FrameCb | null = null\n #onAudioFrame: FrameCb | null = null\n #onOverlayFrame: FrameCb | null = null\n #onSourceSwitch: SwitchCb | null = null\n #sourceSwitchReady: Promise<void> | null = null\n #sourceSwitchToken = 0\n #pendingMulticastToken: number | null = null\n #pendingMulticastVideo: { data: Uint8Array; meta: FrameMeta } | null = null\n #pendingMulticastAudio: { data: Uint8Array; meta: FrameMeta } | null = null\n\n // Per-track source state\n #trackSources = new Map<string, TrackSourceRecord>()\n\n // Track type registry: trackName \u2192 { type, source }.\n // Init data / inline signaling is authoritative until catalog updates.\n // Priority: 'init' > 'catalog' > 'name' (backward compat fallback).\n #trackTypes = new Map<string, { type: FrameKind; source: 'catalog' | 'init' }>()\n\n // Crossfade state: maps trackName \u2192 remaining binary-path frames allowed during window\n #crossfadeCounters = new Map<string, number>()\n\n // Overlay alignment state\n #overlayBoundaryReached = false\n #overlayBuffer: Array<{ data: Uint8Array; meta: FrameMeta }> = []\n\n // \u2500\u2500 Phase 14: deltaActive state machine (D-11..D-14) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // No timers \u2014 state transitions driven entirely by notifyDeltaBlock() callbacks.\n // All thresholds derived from catalog fields or throw at configureDelta() time.\n #deltaConfigured = false\n #deltaActive: boolean = true\n #deltaWindow: boolean[] = [] // ring buffer: true = block failed\n #deltaWindowSize: number = 0\n #deltaFailThresholdRatio: number = 0 // P/K\n #deltaRecoverThresholdRatio: number = 0 // (P/K)/2 \u2014 hysteresis (D-13)\n #deltaProbeIntervalMs: number = 0\n #lastProbeAt: number = 0\n #onDeltaFallbackCb: (() => void) | null = null\n #onDeltaRecoverCb: (() => void) | null = null\n\n constructor(options?: MediaRouterOptions) {\n this.#requireAudio = options?.requireAudio ?? true\n // Catalog-derived \u2014 the caller passes an audioFallbackMs computed from the\n // FEC recovery window (computeFecDeliveryWindowMs). No magic default: when\n // absent, the independent audio-fallback timer is disabled (0) and\n // activation waits for real audio arrival.\n this.#audioFallbackMs = options?.audioFallbackMs ?? 0\n this.#crossfadeFrames = options?.crossfadeFrames ?? DEFAULT_CROSSFADE_FRAMES\n this.#maxOverlayBuffer = options?.maxOverlayBuffer ?? DEFAULT_MAX_OVERLAY_BUFFER\n this.#maxRepairInFlight = options?.maxRepairInFlight ?? 2\n this.#allowDeltaMulticastTakeoverAfterMoqKeyframe =\n options?.allowDeltaMulticastTakeoverAfterMoqKeyframe ?? false\n }\n\n onVideoFrame(cb: FrameCb) { this.#onVideoFrame = cb }\n onAudioFrame(cb: FrameCb) { this.#onAudioFrame = cb }\n onOverlayFrame(cb: FrameCb) { this.#onOverlayFrame = cb }\n onSourceSwitch(cb: SwitchCb) { this.#onSourceSwitch = cb }\n\n // \u2500\u2500 Group-alignment observer (align.ts formula) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Observation only \u2014 does NOT alter routing. Producers are wired in\n // follow-up PRs; when they land they will call\n // CmcdProducer.incrementSwitchMiscount() + emitSwitchDrift() from the\n // onGroupDrift callback so divergence between packet-stamped groupIds\n // and the shared formula surfaces on `x-bc-switch-drift-us` /\n // `x-bc-switch-miscount`. Canonical Go impl: Blockcast/multicast\n // cmd/caddy/sender/group_align.go (commit 104eeff). Canonical Rust impl:\n // moqtail/moqtail apps/relay/src/server/abr.rs (branch\n // experimental/server-side-abr, commit 39accbf).\n #onGroupDrift: ((evt: GroupDriftEvent) => void) | null = null\n #groupAlignWarnCounts = new Map<GroupAlignSkipReason, number>()\n onGroupDrift(cb: (evt: GroupDriftEvent) => void): void {\n this.#onGroupDrift = cb\n }\n\n /**\n * Report a group boundary observed on an incoming track. Cross-checks the\n * packet-stamped groupId against the shared `align.ts` formula and fires\n * the onGroupDrift callback when they disagree.\n *\n * Two input forms:\n * - **Ticks (preferred).** `notifyGroupBoundary(track, id, { mediaTimeTicks,\n * groupDurationTicks, timescale })` \u2014 pure integer math via\n * `align.groupIdForTicks`, no rounding anywhere.\n * - **Seconds (deprecated).** The 4-argument form. Quantizes both operands\n * to whole nanoseconds, which is exact only when `groupDurationSec` is a\n * whole number of nanoseconds. It is not for 33333@90k, 1001@30k or\n * 1024@44.1k, and there `expected` diverges by a whole group from the\n * second boundary onward \u2014 a false drift event on every subsequent\n * group, and a systematically wrong CMCD `x-bc-switch-drift-us`.\n *\n * No-op when no onGroupDrift callback is registered (avoid unnecessary work\n * on hot paths).\n *\n * Hot-path robustness (does NOT throw on bad inputs \u2014 the ingest stream\n * must not be torn down by a malformed catalog or a disposed producer):\n * - Invalid duration (NaN / <=0) or non-finite media time \u2192 warn + early\n * return (same convention as audioFallbackMs).\n * - Any throw out of the align formula (e.g. the safe-integer overflow\n * guard at ~104 days of media time on the seconds path) is caught +\n * logged so an unusually long-running stream cannot tear down ingest.\n * - Callback throws are caught + logged; ingest continues.\n * - Skip warnings are coalesced **per failure mode** for the lifetime\n * of the router (never reset, including across `dispose()`), so a hot\n * path cannot flood the log while a genuinely new failure mode still\n * gets heard. Deterministic modes get one line; a throwing consumer\n * callback gets a small budget because its error varies. See\n * `#skipWithWarning` and `GROUP_ALIGN_WARN_BUDGET`.\n *\n * Init-track filter: media time === 0 with a non-zero stamped groupId is\n * treated as an init/ambient track emitting pre-PTS-zero packets and is\n * skipped (matches Go's base-clamp intent without producing spurious\n * drift).\n *\n * NOTE: do NOT call from the repair path (feedRepair). Repair frames carry\n * the groupId they are recovering for, not a freshly-stamped one.\n */\n notifyGroupBoundary(\n trackName: string,\n packetGroupId: number,\n timing: GroupBoundaryTicks,\n ): void\n /**\n * @deprecated Pass a {@link GroupBoundaryTicks} instead. The seconds form\n * rounds to whole nanoseconds and reports phantom drift on catalogs whose\n * `groupDurationTicks / timescale` is not ns-exact.\n */\n notifyGroupBoundary(\n trackName: string,\n packetGroupId: number,\n mediaTimeSec: number,\n groupDurationSec: number,\n ): void\n notifyGroupBoundary(\n trackName: string,\n packetGroupId: number,\n timingOrMediaTimeSec: GroupBoundaryTicks | number,\n groupDurationSec?: number,\n ): void {\n if (!this.#onGroupDrift) return\n\n const resolved =\n typeof timingOrMediaTimeSec === 'object' && timingOrMediaTimeSec !== null\n ? this.#resolveTicks(trackName, timingOrMediaTimeSec)\n : this.#resolveSeconds(\n trackName,\n timingOrMediaTimeSec as number,\n groupDurationSec as number,\n )\n if (!resolved) return\n\n // Init/ambient track heuristic: mediaTime=0 with stamped groupId !== 0\n // is a signaling packet pre-PTS-zero, not a real drift. Skipping here\n // avoids every stream opening with a false drift event.\n if (resolved.mediaTimeSec === 0 && packetGroupId !== 0) return\n\n const expected = resolved.expected\n const result = driftCheck(expected, packetGroupId)\n if (result.kind === 'aligned') return\n\n try {\n this.#onGroupDrift({\n trackName,\n expectedGroupId: expected,\n actualGroupId: packetGroupId,\n deltaGroups: result.deltaGroups,\n mediaTimeSec: resolved.mediaTimeSec,\n groupDurationSec: resolved.groupDurationSec,\n ...(resolved.timing ? { timing: resolved.timing } : {}),\n })\n } catch (err) {\n // A disposed CmcdProducer (or any downstream consumer) throwing here\n // must NOT tear down the MoQ ingest stream. Log and continue.\n //\n // Routed through the same per-reason budget as the resolver skips: this\n // fires once per DRIFT EVENT, so a consumer that throws on every\n // boundary would otherwise flood at boundary rate \u2014 exactly the failure\n // mode #skipWithWarning exists to prevent, and exactly the disposed-\n // producer scenario this comment already cites (#329 review).\n this.#skipWithWarning(\n 'callback-threw',\n `[MediaRouter] onGroupDrift callback threw (track=${trackName} expected=${expected} actual=${packetGroupId}); continuing: ${(err as Error)?.message ?? err}`,\n )\n }\n }\n\n /**\n * Warn at most `GROUP_ALIGN_WARN_BUDGET[reason]` times **per reason**, across\n * every skip and post-emit failure on the drift path.\n *\n * Note the two call shapes: the resolvers call this *instead of* computing a\n * groupId, and return its `null` to skip the drift check; the `onGroupDrift`\n * catch calls it after the check already ran and the event was already\n * emitted, so there only the warn is coalesced and the `null` is discarded.\n *\n * Keyed on a short stable `reason` tag, NOT on the formatted message: the\n * messages interpolate track name and operand values, so message-keying\n * would hand a malformed stream a fresh warn slot on every boundary \u2014 the\n * log flood this coalescing exists to prevent.\n *\n * Per-reason rather than one shared boolean (PCL-2211, #329 review): the\n * budget is never reset \u2014 not by `dispose()`, not on source switch \u2014 so a\n * single flag means the FIRST malformed boundary of a long-running stream\n * permanently silences every later, genuinely different failure mode. The\n * throw/overflow diagnostics carry the operand values (`ticks=`, `durTicks=`,\n * `err.message`) an incident responder actually needs, and under one shared\n * flag those are precisely the ones most likely to be already spent by an\n * unrelated NaN at catalog load.\n *\n * A closed, bounded set of reasons times a small per-reason budget is still\n * bounded, so this cannot flood. See `GROUP_ALIGN_WARN_BUDGET` for why the\n * budget is not uniform.\n */\n #skipWithWarning(reason: GroupAlignSkipReason, message: string): null {\n const seen = this.#groupAlignWarnCounts.get(reason) ?? 0\n if (seen < GROUP_ALIGN_WARN_BUDGET[reason]) {\n this.#groupAlignWarnCounts.set(reason, seen + 1)\n console.warn(message)\n }\n return null\n }\n\n /**\n * Exact path: integers straight from the catalog, no rounding. `mediaTimeSec`\n * / `groupDurationSec` are derived for reporting only and never feed the\n * groupId computation.\n */\n #resolveTicks(\n trackName: string,\n timing: GroupBoundaryTicks,\n ): ResolvedBoundary | null {\n const { mediaTimeTicks, groupDurationTicks, timescale } = timing\n if (\n !Number.isInteger(mediaTimeTicks) ||\n !Number.isInteger(groupDurationTicks) ||\n groupDurationTicks <= 0 ||\n !Number.isInteger(timescale) ||\n timescale <= 0\n ) {\n return this.#skipWithWarning(\n 'invalid-tick-inputs',\n `[MediaRouter] notifyGroupBoundary invalid tick inputs (track=${trackName} mediaTimeTicks=${mediaTimeTicks} groupDurationTicks=${groupDurationTicks} timescale=${timescale}); skipping drift check. Pre-validate at catalog load time.`,\n )\n }\n try {\n return {\n expected: groupIdForTicks(mediaTimeTicks, groupDurationTicks),\n mediaTimeSec: mediaTimeTicks / timescale,\n groupDurationSec: groupDurationTicks / timescale,\n // Snapshot, not the caller's reference: a producer that reuses one\n // scratch object per boundary would otherwise retroactively mutate\n // every GroupDriftEvent it has already emitted. Three integers.\n timing: { mediaTimeTicks, groupDurationTicks, timescale },\n }\n } catch (err) {\n // UNREACHABLE as of today, and deliberately kept. groupIdForTicks throws\n // on exactly two conditions \u2014 !Number.isInteger(ticks) and\n // !Number.isInteger(groupDurationTicks) || groupDurationTicks <= 0 \u2014 and\n // the guard above pre-rejects both; Math.floor(a / b) does not throw.\n //\n // Kept because align.ts is a separate module that can grow a new\n // precondition (a safe-integer check, say) without this file changing,\n // and the no-throw contract must hold on that day too. Nobody should\n // write a test chasing this branch \u2014 see BLO-21176 note.\n //\n // NOTE the asymmetry this implies: the seconds path is the only one that\n // can actually reach its catch, because durTicks <= 0 cannot catch\n // Infinity and Number.isSafeInteger guards only `ticks`. That is the real\n // shape of the AC#5a bug, not an oversight in this resolver.\n return this.#skipWithWarning(\n 'ticks-formula-threw',\n `[MediaRouter] notifyGroupBoundary: groupIdForTicks threw (track=${trackName} mediaTimeTicks=${mediaTimeTicks} groupDurationTicks=${groupDurationTicks}); skipping drift check: ${(err as Error)?.message ?? err}`,\n )\n }\n }\n\n /**\n * Legacy seconds path. Quantizes to a synthetic 1e9 Hz timescale \u2014 the exact\n * behaviour the deprecated `align.groupIdFor` wrapper had, reproduced here so\n * that wrapper has no non-test callers left and can be deleted (BLO-21176).\n *\n * Inherits that wrapper's ns-rounding error; see the `@deprecated` overload.\n * Delete this branch once every caller passes ticks.\n */\n #resolveSeconds(\n trackName: string,\n mediaTimeSec: number,\n groupDurationSec: number,\n ): ResolvedBoundary | null {\n if (\n !Number.isFinite(groupDurationSec) ||\n groupDurationSec <= 0 ||\n !Number.isFinite(mediaTimeSec)\n ) {\n return this.#skipWithWarning(\n 'invalid-seconds-inputs',\n `[MediaRouter] notifyGroupBoundary invalid inputs (track=${trackName} mediaTimeSec=${mediaTimeSec} groupDurationSec=${groupDurationSec}); skipping drift check. Pre-validate at catalog load time.`,\n )\n }\n // 1e9 Hz keeps both operands inside MAX_SAFE_INTEGER for ~104 days of\n // media time. Past that Math.round yields a non-safe integer that\n // Number.isInteger still accepts, so bail explicitly rather than\n // silently quantizing \u2014 ingest must survive either way.\n const TS = 1_000_000_000\n const ticks = mediaTimeSec < 0 ? 0 : Math.round(mediaTimeSec * TS)\n const durTicks = Math.round(groupDurationSec * TS)\n if (durTicks <= 0) {\n return this.#skipWithWarning(\n 'sub-nanosecond-duration',\n `[MediaRouter] notifyGroupBoundary: groupDurationSec=${groupDurationSec} rounds to <1 tick at 1e9 Hz (track=${trackName}); skipping drift check. Pass GroupBoundaryTicks for sub-nanosecond durations.`,\n )\n }\n if (!Number.isSafeInteger(ticks)) {\n return this.#skipWithWarning(\n 'media-time-overflow',\n `[MediaRouter] notifyGroupBoundary: mediaTimeSec=${mediaTimeSec} overflows safe-integer ticks at 1e9 Hz (>~104 days, track=${trackName}); skipping drift check. Pass GroupBoundaryTicks with the catalog timescale.`,\n )\n }\n // Same try as #resolveTicks: a finite groupDurationSec can still overflow\n // the * 1e9 multiply to Infinity, which clears the durTicks <= 0 bail above\n // but fails Number.isInteger inside groupIdForTicks. Without this catch the\n // throw escapes notifyGroupBoundary and breaks its documented no-throw\n // contract \u2014 the one path #resolveTicks already guards and this did not.\n try {\n return {\n expected: groupIdForTicks(ticks, durTicks),\n mediaTimeSec,\n groupDurationSec,\n }\n } catch (err) {\n return this.#skipWithWarning(\n 'seconds-formula-threw',\n `[MediaRouter] notifyGroupBoundary: groupIdForTicks threw (track=${trackName} mediaTimeSec=${mediaTimeSec} groupDurationSec=${groupDurationSec} ticks=${ticks} durTicks=${durTicks}); skipping drift check: ${(err as Error)?.message ?? err}`,\n )\n }\n }\n\n get activeSource(): SourceKind { return this.#active }\n\n // \u2500\u2500 Keyframe repair API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Register callback for keyframe repair requests. Player wires this to issue MoQ SUBSCRIBE. */\n onRepairRequest(cb: (expectedGroupId: number, trackName: string) => void): void {\n this.#onRepairRequest = cb\n }\n\n /**\n * Request repair for a lost keyframe group. Only fires when activeSource is 'multicast'.\n * Idempotent per groupId \u2014 duplicate calls for the same group are suppressed.\n * @param expectedGroupId - The group ID expected but not received\n * @param trackName - Track name for the SUBSCRIBE (e.g. 'video'). Default: 'video'.\n */\n repairLostGroup(expectedGroupId: number, trackName = 'video'): void {\n if (this.#active !== 'multicast') return // only repair on multicast path\n if (this.#repairInFlight.has(expectedGroupId)) return // idempotent\n if (this.#repairInFlight.size >= this.#maxRepairInFlight) {\n console.warn(\n `[MediaRouter] max repair in flight (${this.#maxRepairInFlight}), dropping repair for group ${expectedGroupId}`,\n )\n return\n }\n this.#repairInFlight.add(expectedGroupId)\n this.#onRepairRequest?.(expectedGroupId, trackName)\n }\n\n /**\n * Feed a recovered keyframe frame from the repair path.\n * Bypasses source arbitration entirely \u2014 goes directly to decoder callbacks.\n * Does NOT change activeSource. Does NOT trigger failbackToMoq.\n */\n feedRepair(data: Uint8Array, meta: FrameMeta, kind: FrameKind): void {\n // Repair only makes sense when multicast is active.\n // Silently drop if MoQ became active (race with failbackToMoq) to avoid\n // injecting stale-timestamp frames into the MoQ decoder path (WR-02).\n if (this.#active !== 'multicast') return\n const routedMeta = { ...meta, source: 'repair' as const, recovered: true }\n if (kind === 'video') this.#onVideoFrame?.(data, routedMeta)\n else this.#onAudioFrame?.(data, routedMeta)\n }\n\n /**\n * Clear repair-in-flight state for a group (called after repair completes or times out).\n */\n clearRepairInFlight(groupId: number): void {\n this.#repairInFlight.delete(groupId)\n }\n\n // \u2500\u2500 Phase 14: deltaActive state machine API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Configure the delta-track fallback state machine from catalog parameters.\n *\n * D-11: Thresholds derive from fec.sourceSymbols, fec.repairSymbols, framerate.\n * D-13: Probe interval derives from deltaProbeIntervalMs or interleaveDepthMs*2.\n * CLAUDE.md: error on bad init \u2014 throws if required inputs are absent.\n *\n * Calling again resets deltaActive=true and clears the window buffer (stream restart).\n */\n configureDelta(cfg: {\n framerate: number\n sourceSymbols: number\n repairSymbols: number\n deltaFailWindowBlocks?: number\n deltaProbeIntervalMs?: number\n interleaveDepthMs?: number\n }): void {\n if (typeof cfg.framerate !== 'number' || !Number.isFinite(cfg.framerate) || cfg.framerate <= 0) {\n throw new Error('[MediaRouter] configureDelta: framerate required and must be > 0')\n }\n if (typeof cfg.sourceSymbols !== 'number' || cfg.sourceSymbols <= 0) {\n throw new Error('[MediaRouter] configureDelta: sourceSymbols (K_delta) required and must be > 0')\n }\n if (typeof cfg.repairSymbols !== 'number' || cfg.repairSymbols <= 0) {\n throw new Error('[MediaRouter] configureDelta: repairSymbols (P_delta) required and must be > 0')\n }\n if (cfg.deltaProbeIntervalMs === undefined && cfg.interleaveDepthMs === undefined) {\n throw new Error(\n '[MediaRouter] configureDelta: either deltaProbeIntervalMs or interleaveDepthMs required for probe interval derivation'\n )\n }\n\n // Derivations \u2014 no magic floors, no hardcoded defaults (CLAUDE.md).\n this.#deltaWindowSize = cfg.deltaFailWindowBlocks ?? Math.ceil(cfg.framerate)\n this.#deltaFailThresholdRatio = cfg.repairSymbols / cfg.sourceSymbols\n this.#deltaRecoverThresholdRatio = this.#deltaFailThresholdRatio / 2\n // Probe interval routes through the ONE FEC recovery-window derivation\n // (computeFecDeliveryWindowMs). Interleave-span + jitter margin only \u2014 the\n // K-cycle production horizon (mediaUnitsPerSecond) is deliberately omitted:\n // this is a re-subscribe cadence, not a block-recovery deadline. With no\n // catalog jitterBufferMs the margin defaults to one interleave window, so\n // the value equals the retired interleaveDepthMs*2 but is now catalog-tunable.\n this.#deltaProbeIntervalMs = cfg.deltaProbeIntervalMs ?? computeFecDeliveryWindowMs({\n interleaveDepthMs: cfg.interleaveDepthMs!,\n sourceSymbols: cfg.sourceSymbols,\n })\n\n // Reset state \u2014 useful for stream restart.\n this.#deltaActive = true\n this.#deltaWindow = []\n this.#lastProbeAt = 0\n this.#deltaConfigured = true\n }\n\n /**\n * Notify the state machine of a completed FEC block for the delta track.\n *\n * @param failed - true if the block was unrecoverable (FEC failure)\n * @param nowMs - caller-injected monotonic ms (no internal timer \u2014 D-13, CLAUDE.md)\n *\n * If configureDelta() has not been called, this is a silent no-op (pre-catalog state).\n */\n notifyDeltaBlock(failed: boolean, nowMs: number): void {\n if (!this.#deltaConfigured) return\n\n // Maintain sliding window \u2014 ring buffer semantics.\n this.#deltaWindow.push(failed)\n if (this.#deltaWindow.length > this.#deltaWindowSize) {\n this.#deltaWindow.shift()\n }\n\n // Window not yet full \u2014 insufficient data for decision.\n if (this.#deltaWindow.length < this.#deltaWindowSize) return\n\n const failedCount = this.#deltaWindow.filter(x => x).length\n const ratio = failedCount / this.#deltaWindowSize\n\n if (this.#deltaActive) {\n // Fallback transition: active \u2192 inactive (T-14-03: fires exactly once)\n if (ratio > this.#deltaFailThresholdRatio) {\n this.#deltaActive = false\n this.#lastProbeAt = nowMs\n this.#onDeltaFallbackCb?.()\n }\n } else {\n // Recovery transition: inactive \u2192 active (after probe interval + low ratio)\n if (\n (nowMs - this.#lastProbeAt) >= this.#deltaProbeIntervalMs &&\n ratio < this.#deltaRecoverThresholdRatio\n ) {\n this.#deltaActive = true\n this.#lastProbeAt = nowMs\n this.#onDeltaRecoverCb?.()\n }\n }\n }\n\n /** Register callback fired when delta track degrades past threshold (D-12). */\n onDeltaFallback(cb: () => void): void {\n this.#onDeltaFallbackCb = cb\n }\n\n /** Register callback fired when delta track recovers below recovery threshold (D-13). */\n onDeltaRecover(cb: () => void): void {\n this.#onDeltaRecoverCb = cb\n }\n\n /** Whether the delta track is currently considered healthy (true = active). */\n get deltaActive(): boolean {\n return this.#deltaActive\n }\n\n // \u2500\u2500 Test-visible accessors for threshold verification \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // These expose internal derived values for test assertions (Tests 3,4,5,6,7).\n // Prefixed with 'delta' to namespace clearly within the public API surface.\n\n /** Derived window size \u2014 exposed for test assertions (Test 3, 4). */\n get deltaWindowSize(): number { return this.#deltaWindowSize }\n /** Derived fail threshold ratio (P/K) \u2014 exposed for test assertions (Test 6). */\n get deltaFailThresholdRatio(): number { return this.#deltaFailThresholdRatio }\n /** Derived recovery threshold ratio ((P/K)/2) \u2014 exposed for test assertions (Test 7). */\n get deltaRecoverThresholdRatio(): number { return this.#deltaRecoverThresholdRatio }\n /** Derived probe interval ms \u2014 exposed for test assertions (Test 5a). */\n get deltaProbeIntervalMs(): number { return this.#deltaProbeIntervalMs }\n\n /**\n * Register a track name \u2192 type mapping from catalog.\n * Catalog is the default authority. Init data / inline signaling\n * takes precedence until the next catalog update.\n */\n registerTrackType(trackName: string, type: FrameKind): void {\n // Catalog overwrites any previous registration (including init).\n this.#trackTypes.set(trackName, { type, source: 'catalog' })\n }\n\n /**\n * Register a track name \u2192 type from init data / inline signaling (MMTP FT=0).\n * Authoritative until the next catalog update per \u00A75.3.\n * Does NOT overwrite an existing catalog registration.\n */\n registerTrackTypeFromInit(trackName: string, type: FrameKind): void {\n const existing = this.#trackTypes.get(trackName)\n if (existing?.source === 'catalog') return // catalog already set \u2014 don't downgrade\n this.#trackTypes.set(trackName, { type, source: 'init' })\n }\n\n /**\n * Assign a per-track source. Frames from this source bypass the binary\n * MoQ\u2194multicast switching for this track only.\n *\n * Throws TrackSourceConflictError if a source is already assigned for this\n * track without a clearTrackSource() in between.\n */\n setTrackSource(trackName: string, source: TrackSource, opts?: SetTrackSourceOptions): void {\n if (this.#trackSources.has(trackName)) {\n throw new TrackSourceConflictError(trackName)\n }\n\n // Resolve effective policy. opts.policy takes precedence; overlay=true is legacy 'wait'.\n const effectivePolicy: MigrationPolicy | undefined =\n opts?.policy ?? (opts?.overlay ? 'wait' : undefined)\n\n // For crossfade: open the binary-path window immediately so the first per-track frame\n // triggers the window (counter starts before the frame callback is wired).\n if (effectivePolicy === 'crossfade') {\n this.#crossfadeCounters.set(trackName, this.#crossfadeFrames)\n }\n\n const record: TrackSourceRecord = {\n source,\n unsubscribeFrame: () => {},\n emitCount: 0,\n }\n\n // Wire the frame callback\n record.unsubscribeFrame = source.onFrame((data, meta) => {\n record.emitCount++\n if (effectivePolicy === 'wait') {\n this.#handleOverlayFrame(data, meta)\n } else {\n // 'drop', 'crossfade', or undefined: emit immediately\n this.#emitForTrack(trackName, data, meta)\n }\n })\n\n // Wire the optional error callback \u2014 failback to binary switching\n if (source.onError) {\n record.unsubscribeError = source.onError(() => {\n this.clearTrackSource(trackName)\n })\n }\n\n this.#trackSources.set(trackName, record)\n }\n\n /**\n * Remove the per-track source for the given track.\n * Binary switching resumes for this track after this call.\n */\n clearTrackSource(trackName: string): void {\n const record = this.#trackSources.get(trackName)\n if (!record) return\n\n record.unsubscribeFrame()\n record.unsubscribeError?.()\n record.source.dispose?.()\n this.#trackSources.delete(trackName)\n\n // Clean up crossfade window if one was active for this track\n this.#crossfadeCounters.delete(trackName)\n\n // Reset overlay buffer if removing a non-media track (overlay/unknown)\n const reg = this.#trackTypes.get(trackName)\n const isMedia = reg?.type === 'video' || reg?.type === 'audio' ||\n (reg === undefined && (trackName === 'video' || trackName === 'audio'))\n if (!isMedia) {\n this.#overlayBoundaryReached = false\n this.#overlayBuffer = []\n }\n }\n\n /**\n * Signal that a FEC block boundary has been reached on the base track.\n * Overlay tracks waiting for alignment flush their buffered frames now.\n */\n notifyFecBlockBoundary(): void {\n if (this.#overlayBoundaryReached) return\n this.#overlayBoundaryReached = true\n\n // Flush buffered overlay frames\n const buffered = this.#overlayBuffer.splice(0)\n for (const { data, meta } of buffered) {\n this.#onOverlayFrame?.(data, meta)\n }\n }\n\n /**\n * Return the number of frames emitted from the per-track source for a given\n * track. Used in test-mode to verify per-track routing histogram.\n * Returns 0 if no per-track source is active for the track.\n */\n trackSourceEmitCount(trackName: string): number {\n return this.#trackSources.get(trackName)?.emitCount ?? 0\n }\n\n /** Feed a frame from the MoQ path. Suppressed when multicast is active OR a per-track source owns this track. */\n feedMoq(data: Uint8Array, meta: FrameMeta, kind: FrameKind) {\n // Per-track source takes precedence; allow through during crossfade window\n if (this.#trackSources.has(kind)) {\n const remaining = this.#crossfadeCounters.get(kind)\n if (remaining && remaining > 0) {\n // Decrement crossfade counter and allow this frame through\n const next = remaining - 1\n if (next === 0) {\n this.#crossfadeCounters.delete(kind)\n } else {\n this.#crossfadeCounters.set(kind, next)\n }\n // Fall through to emit below (no return)\n } else {\n return // suppressed \u2014 no crossfade window active\n }\n }\n if (this.#suppressed) return\n // Audio suppression is handled by SessionManager onSuppress callback \u2014\n // do NOT independently suppress based on multicastAudioReceived.\n // The state machine decides when to switch; the router just routes.\n const routedMeta = { ...meta, source: 'moq' as const }\n if (kind === 'video') this.#onVideoFrame?.(data, routedMeta)\n else this.#onAudioFrame?.(data, routedMeta)\n }\n\n /** Feed a frame from the multicast path. Video waits for keyframe before activating. */\n feedMulticast(data: Uint8Array, meta: FrameMeta, kind: FrameKind) {\n // Per-track source takes precedence; allow through during crossfade window\n if (this.#trackSources.has(kind)) {\n const remaining = this.#crossfadeCounters.get(kind)\n if (remaining && remaining > 0) {\n const next = remaining - 1\n if (next === 0) {\n this.#crossfadeCounters.delete(kind)\n } else {\n this.#crossfadeCounters.set(kind, next)\n }\n // Fall through to emit below (no return)\n } else {\n return // suppressed\n }\n }\n\n if (kind === 'video') {\n if (!this.#multicastVideoKeyed) {\n const moqSeededDelta =\n !meta.keyframe &&\n this.#allowDeltaMulticastTakeoverAfterMoqKeyframe &&\n this.#moqVideoKeyframeEmitted\n if (!meta.keyframe && !moqSeededDelta) return // drop until first safe decode point\n this.#multicastVideoKeyed = true\n const pendingMeta: FrameMeta = { ...meta, source: 'multicast' }\n if (!moqSeededDelta) pendingMeta.sourceDiscontinuity = true\n this.#pendingMulticastVideo = {\n data: data.slice(),\n meta: pendingMeta,\n }\n // If audio is required but hasn't arrived yet, start a fallback timer.\n // Audio FEC blocks may recover after video \u2014 don't block activation forever.\n // Only arms when a catalog-derived audioFallbackMs was supplied (>0).\n if (this.#requireAudio && this.#audioFallbackMs > 0 && !this.#multicastAudioReceived && !this.#audioFallbackTimer) {\n this.#audioFallbackTimer = setTimeout(() => {\n if (!this.#multicastAudioReceived && this.#active !== 'multicast') {\n this.#multicastAudioReceived = true // force\n // Also release the MoQ-audio-warmup gate so the fallback actually\n // activates \u2014 otherwise #tryActivateMulticast would still bail.\n this.#moqAudioWarmed = true\n this.#tryActivateMulticast()\n }\n }, this.#audioFallbackMs)\n }\n } else if (this.#active !== 'multicast') {\n // Do not feed video into the multicast decoder while the source switch\n // is still pending. Keep the newest keyframe as the post-activation\n // decoder opener and drop deltas until takeover commits.\n if (meta.keyframe) {\n this.#pendingMulticastVideo = {\n data: data.slice(),\n meta: { ...meta, source: 'multicast', sourceDiscontinuity: true },\n }\n }\n this.#tryActivateMulticast()\n return\n }\n if (this.#active === 'multicast') this.#onVideoFrame?.(data, { ...meta, source: 'multicast' })\n this.#tryActivateMulticast()\n } else {\n this.#multicastAudioReceived = true\n if (this.#audioFallbackTimer) {\n clearTimeout(this.#audioFallbackTimer)\n this.#audioFallbackTimer = null\n }\n // BLO-3035 layer 5: in a multicast-first scenario (no MoQ unicast audio\n // source) `notifyMoqAudioEmit()` is never called and the audio-fallback\n // timer's force-set is bypassed (the timer's inner `!multicastAudioReceived`\n // check returns early because audio already arrived). Without this gate\n // `#moqAudioWarmed` stays false forever and `#tryActivateMulticast` never\n // fires `onSourceSwitch('multicast')` \u2014 the player can't know to emit\n // `__MULTICAST_TAKEOVER_TIME`. Treat the arrival of multicast audio as\n // sufficient evidence to release the warmup gate. For the MoQ\u2192multicast\n // handoff case `notifyMoqAudioEmit()` will already have set this flag, so\n // the assignment is idempotent.\n if (!this.#moqAudioWarmed) this.#moqAudioWarmed = true\n // Activate BEFORE delivering the frame \u2014 the source switch callback\n // (e.g. ring buffer reset) must fire before the first multicast audio\n // frame reaches the backend. Otherwise the NTP timestamp hits the\n // MoQ-era ring buffer base \u2192 overflow \u2192 silence gap.\n this.#tryActivateMulticast()\n // Only emit multicast audio AFTER takeover succeeds. Before takeover,\n // MoQ audio is still flowing through feedMoq \u2192 #onAudioFrame; emitting\n // multicast audio too would double-feed the backend (2x audio frames,\n // pitch wobble and distortion). Post-takeover, feedMoq is suppressed\n // (#suppressed=true) so only multicast audio reaches the backend.\n if (this.#active === 'multicast') {\n this.#onAudioFrame?.(data, { ...meta, source: 'multicast' })\n } else if (this.#pendingMulticastToken !== null) {\n this.#pendingMulticastAudio = { data: data.slice(), meta: { ...meta, source: 'multicast' } }\n }\n }\n }\n\n /**\n * MoQ unicast audio warmup gate. When audio is required, multicast takeover\n * waits until the MoQ unicast audio decoder has emitted at least one frame\n * (so downstream samples-before-takeover counters observe nonzero) or until\n * the existing audio-fallback timer forces the flag true.\n */\n #moqAudioWarmed = false\n\n /** Called by the player when the MoQ unicast audio decoder emits first frame. */\n public notifyMoqAudioEmit(): void {\n if (this.#moqAudioWarmed) return\n this.#moqAudioWarmed = true\n this.#tryActivateMulticast()\n }\n\n /**\n * Called by the player after a MoQ video keyframe has reached the active\n * decoder path. Used only by MSE/MMTP continuity switching; decoder-reset\n * paths leave allowDeltaMulticastTakeoverAfterMoqKeyframe disabled.\n */\n public notifyMoqVideoKeyframeEmit(): void {\n this.#moqVideoKeyframeEmitted = true\n this.#tryActivateMulticast()\n }\n\n #tryActivateMulticast() {\n if (this.#active === 'multicast') return\n if (this.#pendingMulticastToken !== null) return\n if (!this.#multicastVideoKeyed) return\n if (this.#requireAudio && !this.#multicastAudioReceived) return\n if (this.#requireAudio && !this.#moqAudioWarmed) return\n const token = ++this.#sourceSwitchToken\n const maybeReady = this.#onSourceSwitch?.('multicast')\n if (!maybeReady || typeof (maybeReady as Promise<void>).then !== 'function') {\n this.#commitMulticastActivation(token)\n return\n }\n const ready = Promise.resolve(maybeReady)\n this.#pendingMulticastToken = token\n this.#sourceSwitchReady = ready\n void ready.then(\n () => this.#commitMulticastActivation(token),\n err => this.#rejectMulticastActivation(token, err),\n ).finally(() => {\n if (this.#sourceSwitchReady === ready) this.#sourceSwitchReady = null\n })\n }\n\n #commitMulticastActivation(token: number) {\n if (token !== this.#sourceSwitchToken) return\n this.#active = 'multicast'\n this.#suppressed = true\n this.#pendingMulticastToken = null\n const video = this.#pendingMulticastVideo\n this.#pendingMulticastVideo = null\n const audio = this.#pendingMulticastAudio\n this.#pendingMulticastAudio = null\n if (video) this.#onVideoFrame?.(video.data, video.meta)\n if (audio) this.#onAudioFrame?.(audio.data, audio.meta)\n }\n\n #rejectMulticastActivation(token: number, err: unknown) {\n if (token !== this.#sourceSwitchToken) return\n if (this.#pendingMulticastToken === token) this.#pendingMulticastToken = null\n this.#pendingMulticastVideo = null\n this.#pendingMulticastAudio = null\n console.warn('[MediaRouter] onSourceSwitch(multicast) failed', err)\n }\n\n /** Switch back to MoQ immediately (called on multicast failback). */\n failbackToMoq() {\n const hadPendingMulticast = this.#pendingMulticastToken !== null\n if (this.#active === 'moq' && !hadPendingMulticast) return // already on MoQ, no-op\n ++this.#sourceSwitchToken\n this.#active = 'moq'\n this.#suppressed = false\n this.#pendingMulticastToken = null\n this.#pendingMulticastVideo = null\n this.#pendingMulticastAudio = null\n this.#sourceSwitchReady = null\n this.#multicastVideoKeyed = false\n this.#multicastAudioReceived = false\n this.#moqVideoKeyframeEmitted = false\n // A pending audio-fallback timer belongs to the abandoned takeover attempt.\n // Left running it would force-set the audio flags against the next attempt's\n // fresh state (BLO-9413 failback coherence \u2014 every takeover-era flag/timer\n // unwinds on this edge).\n if (this.#audioFallbackTimer) {\n clearTimeout(this.#audioFallbackTimer)\n this.#audioFallbackTimer = null\n }\n this.#onSourceSwitch?.('moq')\n }\n\n /** Dispose timers and per-track sources. Call on teardown to prevent\n * stale timer callbacks firing into garbage-collected closures (WR-03). */\n dispose(): void {\n if (this.#audioFallbackTimer) {\n clearTimeout(this.#audioFallbackTimer)\n this.#audioFallbackTimer = null\n }\n for (const [name] of this.#trackSources) {\n this.clearTrackSource(name)\n }\n }\n\n // \u2500\u2500 Private helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Emit a frame on the correct per-track callback using the type registry.\n * Falls back to name match if no type registered (backward compat). */\n #emitForTrack(trackName: string, data: Uint8Array, meta: FrameMeta): void {\n // Prefer explicit type registry (catalog or init data)\n const reg = this.#trackTypes.get(trackName)\n if (reg?.type === 'video') {\n this.#onVideoFrame?.(data, meta)\n } else if (reg?.type === 'audio') {\n this.#onAudioFrame?.(data, meta)\n } else if (reg !== undefined) {\n // Registered but unknown type \u2014 overlay\n this.#onOverlayFrame?.(data, meta)\n } else {\n // Not registered \u2014 fall back to name match (backward compat for tests/legacy)\n if (trackName === 'video') this.#onVideoFrame?.(data, meta)\n else if (trackName === 'audio') this.#onAudioFrame?.(data, meta)\n else this.#onOverlayFrame?.(data, meta)\n }\n }\n\n /** Handle an overlay frame: buffer until first FEC block boundary, then pass through. */\n #handleOverlayFrame(data: Uint8Array, meta: FrameMeta): void {\n if (this.#overlayBoundaryReached) {\n this.#onOverlayFrame?.(data, meta)\n return\n }\n\n // Buffer until boundary \u2014 enforcing cap for T-02-17 DoS mitigation\n if (this.#overlayBuffer.length >= this.#maxOverlayBuffer) {\n console.warn(\n `[MediaRouter] overlay buffer full (${this.#maxOverlayBuffer} frames); dropping frame ts=${meta.timestamp}`\n )\n return\n }\n this.#overlayBuffer.push({ data, meta })\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * @mmt/container - MFU Fragment Reassembler
3
+ *
4
+ * Handles authored fragmented MFUs (Media Fragment Units) according to
5
+ * ISO/IEC 23008-1:2023 Section 9.2.3.3. Fragmented runs are admitted in the
6
+ * exact FI/fragment_counter countdown carried on the wire. Transport-run
7
+ * association additionally requires the repository's contiguous-source
8
+ * producer profile; ISO-permitted interleaving remains incomplete and fails
9
+ * closed.
10
+ *
11
+ * Fragmentation Indicator (FI) values:
12
+ * - 0: Complete MFU (single packet, no fragmentation)
13
+ * - 1: First fragment
14
+ * - 2: Middle fragment
15
+ * - 3: Last fragment
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ /** Source transport admitted by the MMTP router. */
20
+ export type MmtpSourcePath = 'multicast' | 'moq';
21
+ /**
22
+ * Strict whole-frame delivery provenance, folded over EVERY contributing
23
+ * fragment rather than the head fragment alone.
24
+ *
25
+ * This is deliberately NOT `source`. `source` answers a routing question —
26
+ * "is this the reliable-MoQ copy that must not confirm multicast takeover?" —
27
+ * and head-fragment-wins is the documented, deterministic answer to it.
28
+ * Provenance answers an evidence question: "did every byte of this frame
29
+ * physically arrive over multicast?" A frame with a multicast head and a
30
+ * QUIC-delivered tail is a legitimate routing win for `source: 'multicast'`
31
+ * and is emphatically NOT multicast-exclusive delivery.
32
+ *
33
+ * - 'multicast' — every contributing fragment was explicitly multicast.
34
+ * The ONLY value that may be counted as physical-delivery evidence.
35
+ * - 'mixed' — at least one contributing fragment arrived over MoQ.
36
+ * - 'unknown' — no fragment arrived over MoQ, but at least one carried no
37
+ * attribution at all. Absence is preserved as its own state rather than
38
+ * defaulted, so a consumer cannot mistake "never recorded" for "proven".
39
+ */
40
+ export type MmtpDeliveryProvenance = 'multicast' | 'mixed' | 'unknown';
41
+ /**
42
+ * Fold one fragment's `source` into a running provenance verdict.
43
+ *
44
+ * Fail-closed, order-independent and associative: 'mixed' absorbs everything,
45
+ * a missing attribution degrades 'multicast' to 'unknown', and nothing ever
46
+ * upgrades a verdict. Fold over an empty set is 'unknown' — no fragment ever
47
+ * proved anything.
48
+ */
49
+ export declare function foldDeliveryProvenance(verdict: MmtpDeliveryProvenance, source: MmtpSourcePath | undefined): MmtpDeliveryProvenance;
50
+ /** Strict provenance for a complete fragment set. */
51
+ export declare function deliveryProvenanceOf(fragments: readonly {
52
+ source?: MmtpSourcePath;
53
+ deliveryProvenance?: MmtpDeliveryProvenance;
54
+ }[]): MmtpDeliveryProvenance;
55
+ /**
56
+ * MFU Fragment for reassembly
57
+ */
58
+ export interface MFUFragment {
59
+ fragmentationIndicator: number;
60
+ mpuSequenceNumber: number;
61
+ fragmentCounter: number;
62
+ data: Uint8Array;
63
+ timestamp: bigint;
64
+ /** Random Access Point flag from MMTP header - indicates keyframe */
65
+ rapFlag: boolean;
66
+ /** MFU DU priority. FFmpeg marks random-access samples with high priority. */
67
+ priority?: number;
68
+ sampleNumber?: number;
69
+ movieFragmentSequenceNumber?: number;
70
+ sampleOffset?: number;
71
+ /** Non-timed MFU Item_ID from the 4-byte DU header. */
72
+ itemId?: number;
73
+ /** Authored u32 MMTP packet_sequence_number, including on recovered packets. */
74
+ packetSequenceNumber?: number;
75
+ /** True when this accepted source packet was reconstructed by FEC. */
76
+ recovered?: boolean;
77
+ /** Transport that supplied this accepted fragment. Absent for recovered data. */
78
+ source?: MmtpSourcePath;
79
+ /**
80
+ * Explicit provenance for this fragment, overriding what `source` implies.
81
+ * Set by the router for FEC-recovered fragments, which have no `source` of
82
+ * their own but whose FEC block's symbol provenance is known.
83
+ */
84
+ deliveryProvenance?: MmtpDeliveryProvenance;
85
+ /** Immutable catalog identity supplied by the owning ingress subscription. */
86
+ catalogTrackName?: string;
87
+ /** Physical MMTP stream identity when no catalog identity is available. */
88
+ packetId?: number;
89
+ }
90
+ /**
91
+ * Reassembled MFU result
92
+ */
93
+ export interface ReassembledMFU {
94
+ mpuSequenceNumber: number;
95
+ /** MMTP packet_sequence_number of the first fragment in delivery order. */
96
+ packetSequenceNumber?: number;
97
+ /** Every authoritative MMTP source-packet sequence that formed this MFU. */
98
+ packetSequenceNumbers?: number[];
99
+ /** Subset of packetSequenceNumbers reconstructed by FEC. */
100
+ repairedPacketSequenceNumbers?: number[];
101
+ data: Uint8Array;
102
+ timestamp: bigint;
103
+ fragmentCount: number;
104
+ /** Random Access Point flag - indicates keyframe (from first fragment) */
105
+ rapFlag: boolean;
106
+ /** MFU DU priority from the first fragment that carried it. */
107
+ priority?: number;
108
+ sampleNumber?: number;
109
+ movieFragmentSequenceNumber?: number;
110
+ /**
111
+ * This DU's authored base byte offset within its sample (moqcast #37). 0
112
+ * for a whole-sample DU; nonzero identifies one subsample DU among
113
+ * siblings partitioning an oversized sample. Constant across the DU's own
114
+ * fragments — feed into a sample combiner to reassemble the full sample.
115
+ */
116
+ sampleOffset?: number;
117
+ /**
118
+ * Transport that supplied the accepted FI=1 head fragment (or the complete
119
+ * FI=0 frame). This is the deterministic winner for mixed-path assembly.
120
+ *
121
+ * ROUTING ONLY. It describes one fragment, so it is not evidence about the
122
+ * whole frame — read {@link ReassembledMFU.deliveryProvenance} for that.
123
+ */
124
+ source?: MmtpSourcePath;
125
+ /**
126
+ * Strict provenance folded over every contributing fragment. Only
127
+ * 'multicast' proves multicast-exclusive delivery of this frame.
128
+ */
129
+ deliveryProvenance: MmtpDeliveryProvenance;
130
+ /** Catalog identity from the accepted FI=1 head fragment, or FI=0 frame. */
131
+ catalogTrackName?: string;
132
+ }
133
+ /**
134
+ * Reassembler statistics
135
+ */
136
+ export interface ReassemblerStats {
137
+ totalFragments: number;
138
+ reassembledMFUs: number;
139
+ /** Fragments rejected by strict authored DU-state validation. */
140
+ rejectedFragments: number;
141
+ /** Incomplete MFUs discarded after their configured deadline. */
142
+ timedOutMFUs: number;
143
+ /** Incomplete MFUs discarded to enforce the configured memory bound. */
144
+ evictedMemoryLimit: number;
145
+ outOfOrderFragments: number;
146
+ }
147
+ export declare const MFU_REASSEMBLY_ERROR_CODES: readonly ["invalid-fragment-state", "missing-data-unit-identity", "missing-packet-sequence", "conflicting-data-unit-identity", "overlapping-data-unit", "fragment-counter-mismatch", "data-unit-identity-mismatch", "offset-overflow", "active-data-unit-limit", "incomplete-terminal-fragment"];
148
+ export type MfuReassemblyErrorCode = typeof MFU_REASSEMBLY_ERROR_CODES[number];
149
+ export declare function isMfuReassemblyErrorCode(value: unknown): value is MfuReassemblyErrorCode;
150
+ export declare class MfuReassemblyError extends Error {
151
+ readonly code: MfuReassemblyErrorCode;
152
+ readonly name = "MfuReassemblyError";
153
+ constructor(code: MfuReassemblyErrorCode, message: string);
154
+ }
155
+ export interface PendingMfuDebug {
156
+ key: string;
157
+ fragments: number;
158
+ fi0: number;
159
+ fi1: number;
160
+ fi2: number;
161
+ fi3: number;
162
+ firstFi: number;
163
+ lastFi: number;
164
+ firstOrder: number;
165
+ lastOrder: number;
166
+ minCounter: number;
167
+ maxCounter: number;
168
+ minOffset?: number;
169
+ maxOffset?: number;
170
+ movieFragmentSequenceNumber?: number;
171
+ sampleNumber?: number;
172
+ mpuSequenceNumber?: number;
173
+ sortedHead: Array<{
174
+ fi: number;
175
+ counter: number;
176
+ order: number;
177
+ seq?: number;
178
+ offset?: number;
179
+ }>;
180
+ sortedTail: Array<{
181
+ fi: number;
182
+ counter: number;
183
+ order: number;
184
+ seq?: number;
185
+ offset?: number;
186
+ }>;
187
+ }
188
+ export interface MFUReassemblerOptions {
189
+ /** Timeout (ms) for incomplete MFUs. Derive from frame duration or FEC interleaveDepthMs.
190
+ * Pass null for live clock-free operation; memory remains bounded by maxBuffered. */
191
+ timeoutMs: number | null;
192
+ /** Max concurrent incomplete MFUs, including continuations awaiting FI=1. */
193
+ maxBuffered?: number;
194
+ }
195
+ /** Reassembly surface consumed by MmtpContainerParser and MmtpRouter.
196
+ * Implemented by both the TypeScript engine and the shared
197
+ * Rust/WASM IFC adapter. */
198
+ export interface MfuReassemblerBackend {
199
+ timeoutMs: number | null;
200
+ readonly stats: ReassemblerStats;
201
+ onDeferredAssembly?: (result: ReassembledMFU) => void;
202
+ /** Returns null only for a valid incomplete DU; strict rejection throws
203
+ * `MfuReassemblyError`. */
204
+ addFragment(fragment: MFUFragment): ReassembledMFU | null;
205
+ flush(): void;
206
+ resetStream(catalogTrackName: string): void;
207
+ reset(): void;
208
+ readonly pendingCount: number;
209
+ getStatus(): {
210
+ bufferedMFUs: number;
211
+ pendingFragments: number;
212
+ pendingSamples: PendingMfuDebug[];
213
+ stats: ReassemblerStats;
214
+ };
215
+ }
216
+ /**
217
+ * MFU Reassembler with bounded reordered-fragment association
218
+ * Based on ISO/IEC 23008-1:2023 Section 9.2.3.3
219
+ */
220
+ export declare class MFUReassembler {
221
+ #private;
222
+ /** Statistics for debugging */
223
+ readonly stats: ReassemblerStats;
224
+ constructor(options: MFUReassemblerOptions);
225
+ /** Update the expiry policy for all retained and future incomplete MFUs. */
226
+ set timeoutMs(ms: number | null);
227
+ get timeoutMs(): number | null;
228
+ /**
229
+ * Add a fragment and return reassembled MFU if complete
230
+ */
231
+ addFragment(fragment: MFUFragment): ReassembledMFU | null;
232
+ /** Callback for deferred assembly results (set by caller for async frame delivery) */
233
+ onDeferredAssembly?: (result: ReassembledMFU) => void;
234
+ /**
235
+ * Flush all buffered fragments
236
+ */
237
+ flush(): void;
238
+ /** Drop one catalog stream's incomplete and dedup state. */
239
+ resetStream(catalogTrackName: string): void;
240
+ /** Reset all state — flush buffers and zero stats */
241
+ reset(): void;
242
+ /**
243
+ * Get number of pending MFUs
244
+ */
245
+ get pendingCount(): number;
246
+ /**
247
+ * Get current buffer status
248
+ */
249
+ getStatus(): {
250
+ bufferedMFUs: number;
251
+ pendingFragments: number;
252
+ pendingSamples: PendingMfuDebug[];
253
+ stats: ReassemblerStats;
254
+ };
255
+ }
256
+ /**
257
+ * Create MFU fragment from MMTP packet for reassembly
258
+ */
259
+ export declare function createFragment(mmtpPacket: Uint8Array): MFUFragment;
260
+ /**
261
+ * Process MMTP packet with fragment reassembly
262
+ */
263
+ export declare function processMMTPWithReassembly(mmtpPacket: Uint8Array, reassembler: MFUReassembler): ReassembledMFU | null;
264
+ //# sourceMappingURL=mfu-reassembler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mfu-reassembler.d.ts","sourceRoot":"","sources":["../src/mfu-reassembler.ts"],"names":[],"mappings":"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"}