@blockcast/mmt-container 0.1.0-main.0399efef1f07

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 (694) 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 +152 -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 +20087 -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 +873 -0
  26. package/dist/carp-catalog.js.map +7 -0
  27. package/dist/catalog-fec-params.d.ts +202 -0
  28. package/dist/catalog-fec-params.d.ts.map +1 -0
  29. package/dist/catalog-fec-params.js +568 -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 +217 -0
  40. package/dist/cmaf-block-assembler.d.ts.map +1 -0
  41. package/dist/cmaf-block-assembler.js +1769 -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 +248 -0
  52. package/dist/codec-utils.d.ts.map +1 -0
  53. package/dist/codec-utils.js +1268 -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 +307 -0
  60. package/dist/fec-manager.d.ts.map +1 -0
  61. package/dist/fec-manager.js +1530 -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 +88 -0
  68. package/dist/fec-worker-setup.d.ts.map +1 -0
  69. package/dist/fec-worker-setup.js +614 -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 +380 -0
  82. package/dist/index.js.map +7 -0
  83. package/dist/iso-bmff-timing.d.ts +78 -0
  84. package/dist/iso-bmff-timing.d.ts.map +1 -0
  85. package/dist/iso-bmff-timing.js +618 -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 +1285 -0
  94. package/dist/manifest-auth-plan.js.map +7 -0
  95. package/dist/media-router.d.ts +251 -0
  96. package/dist/media-router.d.ts.map +1 -0
  97. package/dist/media-router.js +624 -0
  98. package/dist/media-router.js.map +7 -0
  99. package/dist/mfu-reassembler.d.ts +396 -0
  100. package/dist/mfu-reassembler.d.ts.map +1 -0
  101. package/dist/mfu-reassembler.js +3085 -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 +434 -0
  108. package/dist/mmtp-container-parser.d.ts.map +1 -0
  109. package/dist/mmtp-container-parser.js +6164 -0
  110. package/dist/mmtp-container-parser.js.map +7 -0
  111. package/dist/mmtp-container-parser.no-modules.js +8999 -0
  112. package/dist/mmtp-container-parser.no-modules.js.map +7 -0
  113. package/dist/mmtp-decode-pipeline.d.ts +132 -0
  114. package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
  115. package/dist/mmtp-decode-pipeline.js +6413 -0
  116. package/dist/mmtp-decode-pipeline.js.map +7 -0
  117. package/dist/mmtp-router.d.ts +390 -0
  118. package/dist/mmtp-router.d.ts.map +1 -0
  119. package/dist/mmtp-router.js +9242 -0
  120. package/dist/mmtp-router.js.map +7 -0
  121. package/dist/mmtp-to-cmaf.d.ts +181 -0
  122. package/dist/mmtp-to-cmaf.d.ts.map +1 -0
  123. package/dist/mmtp-to-cmaf.js +802 -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 +369 -0
  134. package/dist/multicast-manager.d.ts.map +1 -0
  135. package/dist/multicast-manager.js +13400 -0
  136. package/dist/multicast-manager.js.map +7 -0
  137. package/dist/multicast-transport.d.ts +61 -0
  138. package/dist/multicast-transport.d.ts.map +1 -0
  139. package/dist/multicast-transport.js +1665 -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 +8965 -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 +127 -0
  156. package/dist/repair-fetch.js.map +7 -0
  157. package/dist/repair-layers.d.ts +141 -0
  158. package/dist/repair-layers.d.ts.map +1 -0
  159. package/dist/repair-layers.js +303 -0
  160. package/dist/repair-layers.js.map +7 -0
  161. package/dist/sample-combiner.d.ts +212 -0
  162. package/dist/sample-combiner.d.ts.map +1 -0
  163. package/dist/sample-combiner.js +616 -0
  164. package/dist/sample-combiner.js.map +7 -0
  165. package/dist/signaling.d.ts +29 -0
  166. package/dist/signaling.d.ts.map +1 -0
  167. package/dist/signaling.js +232 -0
  168. package/dist/signaling.js.map +7 -0
  169. package/dist/source-auth.d.ts +2 -0
  170. package/dist/source-auth.d.ts.map +1 -0
  171. package/dist/source-auth.js +1238 -0
  172. package/dist/source-auth.js.map +7 -0
  173. package/dist/switching-set-alignment.d.ts +61 -0
  174. package/dist/switching-set-alignment.d.ts.map +1 -0
  175. package/dist/switching-set-alignment.js +85 -0
  176. package/dist/switching-set-alignment.js.map +7 -0
  177. package/dist/timestamp-codec.d.ts +34 -0
  178. package/dist/timestamp-codec.d.ts.map +1 -0
  179. package/dist/timestamp-codec.js +456 -0
  180. package/dist/timestamp-codec.js.map +7 -0
  181. package/dist/track-pipeline-setup.d.ts +72 -0
  182. package/dist/track-pipeline-setup.d.ts.map +1 -0
  183. package/dist/track-pipeline-setup.js +306 -0
  184. package/dist/track-pipeline-setup.js.map +7 -0
  185. package/dist/track-pipeline.d.ts +121 -0
  186. package/dist/track-pipeline.d.ts.map +1 -0
  187. package/dist/track-pipeline.js +229 -0
  188. package/dist/track-pipeline.js.map +7 -0
  189. package/dist/track-session.d.ts +398 -0
  190. package/dist/track-session.d.ts.map +1 -0
  191. package/dist/track-session.js +767 -0
  192. package/dist/track-session.js.map +7 -0
  193. package/dist/transport-pool.d.ts +69 -0
  194. package/dist/transport-pool.d.ts.map +1 -0
  195. package/dist/transport-pool.js +88 -0
  196. package/dist/transport-pool.js.map +7 -0
  197. package/dist/wasm-mfu-reassembler.d.ts +92 -0
  198. package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
  199. package/dist/wasm-mfu-reassembler.js +2139 -0
  200. package/dist/wasm-mfu-reassembler.js.map +7 -0
  201. package/dist/wasm-mmtp-container-parser.d.ts +10 -0
  202. package/dist/wasm-mmtp-container-parser.d.ts.map +1 -0
  203. package/dist/wasm-mmtp-container-parser.js +6418 -0
  204. package/dist/wasm-mmtp-container-parser.js.map +7 -0
  205. package/node_modules/@blockcast/fec-worker/README.md +213 -0
  206. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  207. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +87 -0
  208. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -0
  209. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +279 -0
  210. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -0
  211. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +252 -0
  212. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +1 -0
  213. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +11 -0
  214. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +1 -0
  215. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +14 -0
  216. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +1 -0
  217. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +565 -0
  218. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +7 -0
  219. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +10 -0
  220. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +1 -0
  221. package/node_modules/@blockcast/fec-worker/dist/index.js +8 -0
  222. package/node_modules/@blockcast/fec-worker/dist/index.js.map +1 -0
  223. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +20 -0
  224. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +1 -0
  225. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +349 -0
  226. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +7 -0
  227. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +104 -0
  228. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +1 -0
  229. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +181 -0
  230. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +1 -0
  231. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +179 -0
  232. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +1 -0
  233. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +47 -0
  234. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +1 -0
  235. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +9 -0
  236. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +1 -0
  237. package/node_modules/@blockcast/fec-worker/dist/transfer.js +13 -0
  238. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +1 -0
  239. package/node_modules/@blockcast/fec-worker/package.json +59 -0
  240. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +319 -0
  241. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +268 -0
  242. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +1048 -0
  243. package/node_modules/@blockcast/fec-worker/src/index.ts +32 -0
  244. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +558 -0
  245. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +222 -0
  246. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +194 -0
  247. package/node_modules/@blockcast/fec-worker/src/transfer.ts +12 -0
  248. package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  249. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  250. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  251. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  252. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  253. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  254. package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  255. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  256. package/node_modules/@blockcast/mmt-base/README.md +120 -0
  257. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  258. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
  259. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  260. package/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
  261. package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  262. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +276 -0
  263. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  264. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +645 -0
  265. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  266. package/node_modules/@blockcast/mmt-base/package.json +49 -0
  267. package/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
  268. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +899 -0
  269. package/node_modules/@blockcast/mmt-fec/README.md +270 -0
  270. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  271. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +82 -0
  272. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +1 -0
  273. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +296 -0
  274. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +1 -0
  275. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +72 -0
  276. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +1 -0
  277. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +192 -0
  278. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +1 -0
  279. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +46 -0
  280. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +1 -0
  281. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +96 -0
  282. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +1 -0
  283. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +33 -0
  284. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +1 -0
  285. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +57 -0
  286. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +1 -0
  287. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +185 -0
  288. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +1 -0
  289. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +273 -0
  290. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +1 -0
  291. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +22 -0
  292. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +1 -0
  293. package/node_modules/@blockcast/mmt-fec/dist/index.js +22 -0
  294. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +1 -0
  295. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +20 -0
  296. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +1 -0
  297. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +28 -0
  298. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +1 -0
  299. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +172 -0
  300. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
  301. package/node_modules/@blockcast/mmt-fec/dist/oti.js +254 -0
  302. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +1 -0
  303. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +41 -0
  304. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +1 -0
  305. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +81 -0
  306. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +1 -0
  307. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  308. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  309. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  310. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  311. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  312. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  313. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  314. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  315. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +120 -0
  316. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  317. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
  318. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  319. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
  320. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  321. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +276 -0
  322. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  323. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +645 -0
  324. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  325. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +49 -0
  326. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
  327. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +899 -0
  328. package/node_modules/@blockcast/mmt-fec/package.json +79 -0
  329. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +371 -0
  330. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +277 -0
  331. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +127 -0
  332. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +68 -0
  333. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +332 -0
  334. package/node_modules/@blockcast/mmt-fec/src/index.ts +77 -0
  335. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +34 -0
  336. package/node_modules/@blockcast/mmt-fec/src/oti.ts +317 -0
  337. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +101 -0
  338. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  339. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +28 -0
  340. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +1 -0
  341. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +128 -0
  342. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +1 -0
  343. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +4 -0
  344. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +1 -0
  345. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +4 -0
  346. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +1 -0
  347. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +37 -0
  348. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +1 -0
  349. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +179 -0
  350. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +1 -0
  351. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +99 -0
  352. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +1 -0
  353. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +492 -0
  354. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +1 -0
  355. package/node_modules/@blockcast/mmt-manifest-verify/package.json +45 -0
  356. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +171 -0
  357. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +3 -0
  358. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +230 -0
  359. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +669 -0
  360. package/node_modules/@noble/hashes/LICENSE +21 -0
  361. package/node_modules/@noble/hashes/README.md +521 -0
  362. package/node_modules/@noble/hashes/_assert.d.ts +17 -0
  363. package/node_modules/@noble/hashes/_assert.d.ts.map +1 -0
  364. package/node_modules/@noble/hashes/_assert.js +18 -0
  365. package/node_modules/@noble/hashes/_assert.js.map +1 -0
  366. package/node_modules/@noble/hashes/_blake.d.ts +14 -0
  367. package/node_modules/@noble/hashes/_blake.d.ts.map +1 -0
  368. package/node_modules/@noble/hashes/_blake.js +50 -0
  369. package/node_modules/@noble/hashes/_blake.js.map +1 -0
  370. package/node_modules/@noble/hashes/_md.d.ts +51 -0
  371. package/node_modules/@noble/hashes/_md.d.ts.map +1 -0
  372. package/node_modules/@noble/hashes/_md.js +162 -0
  373. package/node_modules/@noble/hashes/_md.js.map +1 -0
  374. package/node_modules/@noble/hashes/_u64.d.ts +55 -0
  375. package/node_modules/@noble/hashes/_u64.d.ts.map +1 -0
  376. package/node_modules/@noble/hashes/_u64.js +90 -0
  377. package/node_modules/@noble/hashes/_u64.js.map +1 -0
  378. package/node_modules/@noble/hashes/argon2.d.ts +32 -0
  379. package/node_modules/@noble/hashes/argon2.d.ts.map +1 -0
  380. package/node_modules/@noble/hashes/argon2.js +401 -0
  381. package/node_modules/@noble/hashes/argon2.js.map +1 -0
  382. package/node_modules/@noble/hashes/blake1.d.ts +106 -0
  383. package/node_modules/@noble/hashes/blake1.d.ts.map +1 -0
  384. package/node_modules/@noble/hashes/blake1.js +459 -0
  385. package/node_modules/@noble/hashes/blake1.js.map +1 -0
  386. package/node_modules/@noble/hashes/blake2.d.ts +116 -0
  387. package/node_modules/@noble/hashes/blake2.d.ts.map +1 -0
  388. package/node_modules/@noble/hashes/blake2.js +420 -0
  389. package/node_modules/@noble/hashes/blake2.js.map +1 -0
  390. package/node_modules/@noble/hashes/blake2b.d.ts +11 -0
  391. package/node_modules/@noble/hashes/blake2b.d.ts.map +1 -0
  392. package/node_modules/@noble/hashes/blake2b.js +14 -0
  393. package/node_modules/@noble/hashes/blake2b.js.map +1 -0
  394. package/node_modules/@noble/hashes/blake2s.d.ts +20 -0
  395. package/node_modules/@noble/hashes/blake2s.d.ts.map +1 -0
  396. package/node_modules/@noble/hashes/blake2s.js +24 -0
  397. package/node_modules/@noble/hashes/blake2s.js.map +1 -0
  398. package/node_modules/@noble/hashes/blake3.d.ts +54 -0
  399. package/node_modules/@noble/hashes/blake3.d.ts.map +1 -0
  400. package/node_modules/@noble/hashes/blake3.js +255 -0
  401. package/node_modules/@noble/hashes/blake3.js.map +1 -0
  402. package/node_modules/@noble/hashes/crypto.d.ts +2 -0
  403. package/node_modules/@noble/hashes/crypto.d.ts.map +1 -0
  404. package/node_modules/@noble/hashes/crypto.js +5 -0
  405. package/node_modules/@noble/hashes/crypto.js.map +1 -0
  406. package/node_modules/@noble/hashes/cryptoNode.d.ts +2 -0
  407. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +1 -0
  408. package/node_modules/@noble/hashes/cryptoNode.js +18 -0
  409. package/node_modules/@noble/hashes/cryptoNode.js.map +1 -0
  410. package/node_modules/@noble/hashes/eskdf.d.ts +47 -0
  411. package/node_modules/@noble/hashes/eskdf.d.ts.map +1 -0
  412. package/node_modules/@noble/hashes/eskdf.js +166 -0
  413. package/node_modules/@noble/hashes/eskdf.js.map +1 -0
  414. package/node_modules/@noble/hashes/esm/_assert.d.ts +17 -0
  415. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +1 -0
  416. package/node_modules/@noble/hashes/esm/_assert.js +15 -0
  417. package/node_modules/@noble/hashes/esm/_assert.js.map +1 -0
  418. package/node_modules/@noble/hashes/esm/_blake.d.ts +14 -0
  419. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +1 -0
  420. package/node_modules/@noble/hashes/esm/_blake.js +45 -0
  421. package/node_modules/@noble/hashes/esm/_blake.js.map +1 -0
  422. package/node_modules/@noble/hashes/esm/_md.d.ts +51 -0
  423. package/node_modules/@noble/hashes/esm/_md.d.ts.map +1 -0
  424. package/node_modules/@noble/hashes/esm/_md.js +155 -0
  425. package/node_modules/@noble/hashes/esm/_md.js.map +1 -0
  426. package/node_modules/@noble/hashes/esm/_u64.d.ts +55 -0
  427. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +1 -0
  428. package/node_modules/@noble/hashes/esm/_u64.js +67 -0
  429. package/node_modules/@noble/hashes/esm/_u64.js.map +1 -0
  430. package/node_modules/@noble/hashes/esm/argon2.d.ts +32 -0
  431. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +1 -0
  432. package/node_modules/@noble/hashes/esm/argon2.js +392 -0
  433. package/node_modules/@noble/hashes/esm/argon2.js.map +1 -0
  434. package/node_modules/@noble/hashes/esm/blake1.d.ts +106 -0
  435. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +1 -0
  436. package/node_modules/@noble/hashes/esm/blake1.js +452 -0
  437. package/node_modules/@noble/hashes/esm/blake1.js.map +1 -0
  438. package/node_modules/@noble/hashes/esm/blake2.d.ts +116 -0
  439. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +1 -0
  440. package/node_modules/@noble/hashes/esm/blake2.js +413 -0
  441. package/node_modules/@noble/hashes/esm/blake2.js.map +1 -0
  442. package/node_modules/@noble/hashes/esm/blake2b.d.ts +11 -0
  443. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +1 -0
  444. package/node_modules/@noble/hashes/esm/blake2b.js +11 -0
  445. package/node_modules/@noble/hashes/esm/blake2b.js.map +1 -0
  446. package/node_modules/@noble/hashes/esm/blake2s.d.ts +20 -0
  447. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +1 -0
  448. package/node_modules/@noble/hashes/esm/blake2s.js +21 -0
  449. package/node_modules/@noble/hashes/esm/blake2s.js.map +1 -0
  450. package/node_modules/@noble/hashes/esm/blake3.d.ts +54 -0
  451. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +1 -0
  452. package/node_modules/@noble/hashes/esm/blake3.js +251 -0
  453. package/node_modules/@noble/hashes/esm/blake3.js.map +1 -0
  454. package/node_modules/@noble/hashes/esm/crypto.d.ts +2 -0
  455. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +1 -0
  456. package/node_modules/@noble/hashes/esm/crypto.js +2 -0
  457. package/node_modules/@noble/hashes/esm/crypto.js.map +1 -0
  458. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +2 -0
  459. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +1 -0
  460. package/node_modules/@noble/hashes/esm/cryptoNode.js +15 -0
  461. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +1 -0
  462. package/node_modules/@noble/hashes/esm/eskdf.d.ts +47 -0
  463. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +1 -0
  464. package/node_modules/@noble/hashes/esm/eskdf.js +160 -0
  465. package/node_modules/@noble/hashes/esm/eskdf.js.map +1 -0
  466. package/node_modules/@noble/hashes/esm/hkdf.d.ts +36 -0
  467. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +1 -0
  468. package/node_modules/@noble/hashes/esm/hkdf.js +82 -0
  469. package/node_modules/@noble/hashes/esm/hkdf.js.map +1 -0
  470. package/node_modules/@noble/hashes/esm/hmac.d.ts +35 -0
  471. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +1 -0
  472. package/node_modules/@noble/hashes/esm/hmac.js +86 -0
  473. package/node_modules/@noble/hashes/esm/hmac.js.map +1 -0
  474. package/node_modules/@noble/hashes/esm/index.d.ts +2 -0
  475. package/node_modules/@noble/hashes/esm/index.d.ts.map +1 -0
  476. package/node_modules/@noble/hashes/esm/index.js +33 -0
  477. package/node_modules/@noble/hashes/esm/index.js.map +1 -0
  478. package/node_modules/@noble/hashes/esm/legacy.d.ts +71 -0
  479. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +1 -0
  480. package/node_modules/@noble/hashes/esm/legacy.js +281 -0
  481. package/node_modules/@noble/hashes/esm/legacy.js.map +1 -0
  482. package/node_modules/@noble/hashes/esm/package.json +10 -0
  483. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +23 -0
  484. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +1 -0
  485. package/node_modules/@noble/hashes/esm/pbkdf2.js +97 -0
  486. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +1 -0
  487. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +13 -0
  488. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +1 -0
  489. package/node_modules/@noble/hashes/esm/ripemd160.js +13 -0
  490. package/node_modules/@noble/hashes/esm/ripemd160.js.map +1 -0
  491. package/node_modules/@noble/hashes/esm/scrypt.d.ts +34 -0
  492. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +1 -0
  493. package/node_modules/@noble/hashes/esm/scrypt.js +228 -0
  494. package/node_modules/@noble/hashes/esm/scrypt.js.map +1 -0
  495. package/node_modules/@noble/hashes/esm/sha1.d.ts +11 -0
  496. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +1 -0
  497. package/node_modules/@noble/hashes/esm/sha1.js +11 -0
  498. package/node_modules/@noble/hashes/esm/sha1.js.map +1 -0
  499. package/node_modules/@noble/hashes/esm/sha2.d.ts +159 -0
  500. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +1 -0
  501. package/node_modules/@noble/hashes/esm/sha2.js +375 -0
  502. package/node_modules/@noble/hashes/esm/sha2.js.map +1 -0
  503. package/node_modules/@noble/hashes/esm/sha256.d.ts +20 -0
  504. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +1 -0
  505. package/node_modules/@noble/hashes/esm/sha256.js +20 -0
  506. package/node_modules/@noble/hashes/esm/sha256.js.map +1 -0
  507. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +142 -0
  508. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +1 -0
  509. package/node_modules/@noble/hashes/esm/sha3-addons.js +393 -0
  510. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +1 -0
  511. package/node_modules/@noble/hashes/esm/sha3.d.ts +53 -0
  512. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +1 -0
  513. package/node_modules/@noble/hashes/esm/sha3.js +234 -0
  514. package/node_modules/@noble/hashes/esm/sha3.js.map +1 -0
  515. package/node_modules/@noble/hashes/esm/sha512.d.ts +26 -0
  516. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +1 -0
  517. package/node_modules/@noble/hashes/esm/sha512.js +26 -0
  518. package/node_modules/@noble/hashes/esm/sha512.js.map +1 -0
  519. package/node_modules/@noble/hashes/esm/utils.d.ts +161 -0
  520. package/node_modules/@noble/hashes/esm/utils.d.ts.map +1 -0
  521. package/node_modules/@noble/hashes/esm/utils.js +281 -0
  522. package/node_modules/@noble/hashes/esm/utils.js.map +1 -0
  523. package/node_modules/@noble/hashes/hkdf.d.ts +36 -0
  524. package/node_modules/@noble/hashes/hkdf.d.ts.map +1 -0
  525. package/node_modules/@noble/hashes/hkdf.js +88 -0
  526. package/node_modules/@noble/hashes/hkdf.js.map +1 -0
  527. package/node_modules/@noble/hashes/hmac.d.ts +35 -0
  528. package/node_modules/@noble/hashes/hmac.d.ts.map +1 -0
  529. package/node_modules/@noble/hashes/hmac.js +91 -0
  530. package/node_modules/@noble/hashes/hmac.js.map +1 -0
  531. package/node_modules/@noble/hashes/index.d.ts +1 -0
  532. package/node_modules/@noble/hashes/index.d.ts.map +1 -0
  533. package/node_modules/@noble/hashes/index.js +33 -0
  534. package/node_modules/@noble/hashes/index.js.map +1 -0
  535. package/node_modules/@noble/hashes/legacy.d.ts +71 -0
  536. package/node_modules/@noble/hashes/legacy.d.ts.map +1 -0
  537. package/node_modules/@noble/hashes/legacy.js +287 -0
  538. package/node_modules/@noble/hashes/legacy.js.map +1 -0
  539. package/node_modules/@noble/hashes/package.json +266 -0
  540. package/node_modules/@noble/hashes/pbkdf2.d.ts +23 -0
  541. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +1 -0
  542. package/node_modules/@noble/hashes/pbkdf2.js +101 -0
  543. package/node_modules/@noble/hashes/pbkdf2.js.map +1 -0
  544. package/node_modules/@noble/hashes/ripemd160.d.ts +13 -0
  545. package/node_modules/@noble/hashes/ripemd160.d.ts.map +1 -0
  546. package/node_modules/@noble/hashes/ripemd160.js +16 -0
  547. package/node_modules/@noble/hashes/ripemd160.js.map +1 -0
  548. package/node_modules/@noble/hashes/scrypt.d.ts +34 -0
  549. package/node_modules/@noble/hashes/scrypt.d.ts.map +1 -0
  550. package/node_modules/@noble/hashes/scrypt.js +232 -0
  551. package/node_modules/@noble/hashes/scrypt.js.map +1 -0
  552. package/node_modules/@noble/hashes/sha1.d.ts +11 -0
  553. package/node_modules/@noble/hashes/sha1.d.ts.map +1 -0
  554. package/node_modules/@noble/hashes/sha1.js +14 -0
  555. package/node_modules/@noble/hashes/sha1.js.map +1 -0
  556. package/node_modules/@noble/hashes/sha2.d.ts +159 -0
  557. package/node_modules/@noble/hashes/sha2.d.ts.map +1 -0
  558. package/node_modules/@noble/hashes/sha2.js +384 -0
  559. package/node_modules/@noble/hashes/sha2.js.map +1 -0
  560. package/node_modules/@noble/hashes/sha256.d.ts +20 -0
  561. package/node_modules/@noble/hashes/sha256.d.ts.map +1 -0
  562. package/node_modules/@noble/hashes/sha256.js +23 -0
  563. package/node_modules/@noble/hashes/sha256.js.map +1 -0
  564. package/node_modules/@noble/hashes/sha3-addons.d.ts +142 -0
  565. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +1 -0
  566. package/node_modules/@noble/hashes/sha3-addons.js +402 -0
  567. package/node_modules/@noble/hashes/sha3-addons.js.map +1 -0
  568. package/node_modules/@noble/hashes/sha3.d.ts +53 -0
  569. package/node_modules/@noble/hashes/sha3.d.ts.map +1 -0
  570. package/node_modules/@noble/hashes/sha3.js +239 -0
  571. package/node_modules/@noble/hashes/sha3.js.map +1 -0
  572. package/node_modules/@noble/hashes/sha512.d.ts +26 -0
  573. package/node_modules/@noble/hashes/sha512.d.ts.map +1 -0
  574. package/node_modules/@noble/hashes/sha512.js +29 -0
  575. package/node_modules/@noble/hashes/sha512.js.map +1 -0
  576. package/node_modules/@noble/hashes/src/_assert.ts +22 -0
  577. package/node_modules/@noble/hashes/src/_blake.ts +50 -0
  578. package/node_modules/@noble/hashes/src/_md.ts +176 -0
  579. package/node_modules/@noble/hashes/src/_u64.ts +91 -0
  580. package/node_modules/@noble/hashes/src/argon2.ts +497 -0
  581. package/node_modules/@noble/hashes/src/blake1.ts +534 -0
  582. package/node_modules/@noble/hashes/src/blake2.ts +486 -0
  583. package/node_modules/@noble/hashes/src/blake2b.ts +10 -0
  584. package/node_modules/@noble/hashes/src/blake2s.ts +20 -0
  585. package/node_modules/@noble/hashes/src/blake3.ts +272 -0
  586. package/node_modules/@noble/hashes/src/crypto.ts +9 -0
  587. package/node_modules/@noble/hashes/src/cryptoNode.ts +15 -0
  588. package/node_modules/@noble/hashes/src/eskdf.ts +187 -0
  589. package/node_modules/@noble/hashes/src/hkdf.ts +88 -0
  590. package/node_modules/@noble/hashes/src/hmac.ts +94 -0
  591. package/node_modules/@noble/hashes/src/index.ts +31 -0
  592. package/node_modules/@noble/hashes/src/legacy.ts +293 -0
  593. package/node_modules/@noble/hashes/src/pbkdf2.ts +122 -0
  594. package/node_modules/@noble/hashes/src/ripemd160.ts +12 -0
  595. package/node_modules/@noble/hashes/src/scrypt.ts +257 -0
  596. package/node_modules/@noble/hashes/src/sha1.ts +10 -0
  597. package/node_modules/@noble/hashes/src/sha2.ts +402 -0
  598. package/node_modules/@noble/hashes/src/sha256.ts +24 -0
  599. package/node_modules/@noble/hashes/src/sha3-addons.ts +499 -0
  600. package/node_modules/@noble/hashes/src/sha3.ts +258 -0
  601. package/node_modules/@noble/hashes/src/sha512.ts +34 -0
  602. package/node_modules/@noble/hashes/src/utils.ts +395 -0
  603. package/node_modules/@noble/hashes/utils.d.ts +161 -0
  604. package/node_modules/@noble/hashes/utils.d.ts.map +1 -0
  605. package/node_modules/@noble/hashes/utils.js +313 -0
  606. package/node_modules/@noble/hashes/utils.js.map +1 -0
  607. package/package.json +203 -0
  608. package/schemas/catalog.schema.json +360 -0
  609. package/src/align.ts +239 -0
  610. package/src/audio-init-parser.ts +189 -0
  611. package/src/audio-ring-buffer.ts +120 -0
  612. package/src/authored-timing.ts +63 -0
  613. package/src/carp-catalog.ts +845 -0
  614. package/src/catalog-fec-params.ts +535 -0
  615. package/src/catalog-network-source.ts +49 -0
  616. package/src/catalog-validator.ts +34 -0
  617. package/src/cmaf-block-assembler.ts +1167 -0
  618. package/src/cmaf-group-batcher.ts +104 -0
  619. package/src/cmcd-producer.ts +334 -0
  620. package/src/codec-utils.ts +1611 -0
  621. package/src/fec-codec.ts +61 -0
  622. package/src/fec-manager.ts +2081 -0
  623. package/src/fec-starvation-detector.ts +128 -0
  624. package/src/fec-worker-setup.ts +213 -0
  625. package/src/fmp4-completion.ts +47 -0
  626. package/src/fmp4-reader.ts +112 -0
  627. package/src/index.ts +486 -0
  628. package/src/iso-bmff-timing.ts +813 -0
  629. package/src/loc.ts +181 -0
  630. package/src/manifest-auth-plan.ts +107 -0
  631. package/src/media-router.ts +795 -0
  632. package/src/mfu-reassembler.ts +3741 -0
  633. package/src/mfu-reassembly-policy.ts +134 -0
  634. package/src/mmtp-container-parser.ts +1836 -0
  635. package/src/mmtp-decode-pipeline.ts +389 -0
  636. package/src/mmtp-router.ts +3136 -0
  637. package/src/mmtp-to-cmaf.ts +1024 -0
  638. package/src/multicast-auth-catalog.ts +340 -0
  639. package/src/multicast-input.ts +439 -0
  640. package/src/multicast-manager.ts +1720 -0
  641. package/src/multicast-transport.ts +321 -0
  642. package/src/multicast-unwrap.ts +132 -0
  643. package/src/no-modules.ts +20 -0
  644. package/src/phase14-catalog-override.ts +336 -0
  645. package/src/repair-fetch.ts +238 -0
  646. package/src/repair-layers.ts +490 -0
  647. package/src/sample-combiner.ts +938 -0
  648. package/src/signaling.ts +78 -0
  649. package/src/source-auth.ts +1 -0
  650. package/src/switching-set-alignment.ts +109 -0
  651. package/src/timestamp-codec.ts +265 -0
  652. package/src/track-pipeline-setup.ts +189 -0
  653. package/src/track-pipeline.ts +332 -0
  654. package/src/track-session.ts +994 -0
  655. package/src/transport-pool.ts +128 -0
  656. package/src/wasm-mfu-reassembler.ts +389 -0
  657. package/src/wasm-mmtp-container-parser.ts +23 -0
  658. package/test-vectors/align.json +199 -0
  659. package/test-vectors/catalog/README.md +40 -0
  660. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +27 -0
  661. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +32 -0
  662. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +30 -0
  663. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +31 -0
  664. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +33 -0
  665. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +23 -0
  666. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +32 -0
  667. package/test-vectors/catalog/negative/legacy-selection-params.json +15 -0
  668. package/test-vectors/catalog/negative/media-track-repair-layer.json +16 -0
  669. package/test-vectors/catalog/negative/missing-mmtp-fields.json +12 -0
  670. package/test-vectors/catalog/negative/multicast-auth-padded-key.json +37 -0
  671. package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +53 -0
  672. package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +37 -0
  673. package/test-vectors/catalog/negative/multicast-endpoint-missing-protocol-and-source.json +24 -0
  674. package/test-vectors/catalog/negative/network-source-object.json +25 -0
  675. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +23 -0
  676. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +26 -0
  677. package/test-vectors/catalog/negative/repair-track-legacy-shape.json +19 -0
  678. package/test-vectors/catalog/negative/track-role-empty.json +16 -0
  679. package/test-vectors/catalog/negative/track-role-non-string.json +16 -0
  680. package/test-vectors/catalog/positive/fec-layered-repair.json +104 -0
  681. package/test-vectors/catalog/positive/fec-multicast.json +51 -0
  682. package/test-vectors/catalog/positive/flat-av.json +30 -0
  683. package/test-vectors/catalog/positive/multicast-auth-rotation.json +55 -0
  684. package/test-vectors/catalog/positive/ticks-and-mixed-timescales.json +39 -0
  685. package/test-vectors/catalog/positive/track-role.json +49 -0
  686. package/test-vectors/catalog/releases/moqcast-native-v16-v1/catalog.json +65 -0
  687. package/test-vectors/catalog/releases/moqcast-native-v16-v1.archive.sha256 +1 -0
  688. package/test-vectors/catalog-multicast-auth-v1.json +26 -0
  689. package/test-vectors/catalog-validation.json +148 -0
  690. package/test-vectors/catalog-validator-public-api.mjs +25 -0
  691. package/test-vectors/generate-source-auth-fixture.mjs +114 -0
  692. package/test-vectors/source-auth-v1.json +61 -0
  693. package/test-vectors/source-contiguity-v1.json +131 -0
  694. package/test-vectors/subsample-placement-v1.json +230 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/track-session.ts", "../src/media-router.ts"],
4
+ "sourcesContent": ["/**\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 { fecBlockBudgetMs } 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 /** Explicit catalog jitter/reorder allowance in ms. */\n fecReorderToleranceMs?: 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 /**\n * Repair symbols per block this receiver actually joined: the sum of\n * `repairSymbols` over the joined BASE layers, not a count of layers\n * (draft-ramadan-moq-fec 6.3). A keyframe overlay is a separate FEC instance\n * and is not included.\n *\n * This is runtime join state, not catalog geometry: a receiver on a clean\n * path joins layer 0 only while a lossy one adds layers, so the catalog's\n * total P is not what this receiver is receiving. Diagnostics that report P\n * (the worker snapshot, CMCD `x-bc-fec-p`) must read this.\n *\n * Absent when the caller joins no layers explicitly, in which case P_0 is\n * what was joined. That is an identity on a single-layer track, not a\n * catalog default: with one layer, P_0 is the total.\n */\n joinedRepairCount?: 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) and it keeps its FEC\n * decoder armed, because the multicast socket is still joined and the router\n * still admits the stream's symbols. The host should abandon the router-side\n * attempt (e.g. MediaRouter.failbackToMoq()) so the router stops waiting for\n * a keyframe that is never coming; it must not dispose the decoder here. The\n * decoder is released on the next transport edge (unsubscribe, failbackAll,\n * or transportFailed falling back to unicast) through fec-teardown.\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 FEC block span plus jitter.\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 * Explicit jitter/reorder allowance in ms, added once to the complete span.\n * Callers pass their signaled catalog allowance, never a local default.\n */\n jitterBufferMs?: number\n}\n\n/**\n * The catalog interleaveDepthMs is the complete FEC block span, including the\n * repair tail. K, P, cadence, and bandwidth must not expand it again. Geometry\n * fields remain accepted for existing callers, but do not feed deadline math.\n */\nexport function computeFecDeliveryWindowMs(\n config: FecDeliveryWindowConfig,\n): number {\n const fecInterleaveMs = config.interleaveDepthMs\n const fecK = config.sourceSymbols\n if (!Number.isFinite(fecInterleaveMs) || 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 (!Number.isFinite(fecK) || 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 // Absence stays absent; it does not authorize a synthesized reorder budget.\n if (config.jitterBufferMs === undefined) return Math.ceil(fecInterleaveMs)\n return fecBlockBudgetMs(fecInterleaveMs, config.jitterBufferMs)\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 // Single FEC authority flag: set by the fec-setup edges (joinStarted,\n // enterUnicastFallback), cleared by the fec-teardown edge in #transition.\n // Setup and teardown are paired by construction, so a takeover timeout that\n // parks the session in `unicast` keeps the decoder the still-joined\n // transport is feeding, and the later unsubscribe still tears it down.\n #fecArmed = 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:\n // cross-transport repair). A session re-joining after a takeover timeout\n // still owns its decoder, so fec-setup fires once per FEC epoch.\n this.#armFec()\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.#armFec()\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). Use the\n // same complete span plus explicit allowance as FECManager and workers.\n const hysteresisMs = computeFecLossWindowMs(\n this.config.fecInterleaveMs,\n this.config.fecK,\n this.config.fecReorderToleranceMs,\n )\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: 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 !== undefined && nextMode !== 'moq') {\n // Next is multicast or AMT: start joining. `joining` means MoQ is\n // primary again (see transport getter), so 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 // The chain position moves with the attempt: a join that times out\n // parks the session in `unicast` with this transport recorded, so the\n // next failure continues past it rather than re-trying the transport\n // that already failed and re-firing onTakeoverTimeout on every lap.\n this.#previousPrimary = nextMode\n this.#transition('joining')\n this.#unsuppress()\n return\n }\n // MoQ is next, or the chain is exhausted: unicast either way (Claim 9g).\n // For a session parked in `unicast` with its decoder retained after a\n // takeover timeout, this transition is a self-edge and fires no teardown,\n // so the release is explicit. It is the \"transportFailed falling back to\n // unicast\" release path the onTakeoverTimeout doc names, and #disarmFec is\n // idempotent, so it is a no-op when the transition already fired the\n // teardown. The audio reset is not flag-guarded, so it is tied to the edge\n // itself: a session already resting in unicast changed no transport and\n // owes the host no reset, however many pool subscriptions fail for it.\n const from = this.#state\n this.#previousPrimary = null\n this.#transition('unicast')\n this.#disarmFec()\n this.#unsuppress()\n if (from !== 'unicast' && this.config.trackType === 'audio') {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'reset' }, this.config)\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 /**\n * True while this track owns a FEC decoder (fec-setup fired, fec-teardown\n * not yet). Stays true across a takeover timeout that parked the session in\n * `unicast`; SessionManager.failbackAll uses it to release such sessions.\n */\n get isFecArmed(): boolean {\n return this.#fecArmed\n }\n\n /**\n * Start the deadline a `joining` entry deferred while the join was inactive\n * (SessionManager.joinActive). The track gets the FULL deadline from here,\n * because that is when media can first arrive. No-op outside `joining` and\n * when a deadline is already running, so it never shortens or restarts one.\n *\n * The `joinActive` guard is what makes this safe to call from anywhere: a\n * track parked in `joining` with a deferred (null) timer satisfies the other\n * two, so without it a host calling this while the join is still inactive\n * would arm the exact deadline the gate exists to defer -- silently\n * reinstating BLO-34289. It is a no-op for the internal caller, which writes\n * `#joinActive = true` before iterating the sessions.\n */\n armDeferredJoinDeadline(): void {\n if (this.#state !== 'joining' || this.#joinDeadlineTimer !== null) return\n if (!this.#manager.joinActive) return\n this.#armJoinDeadline(this.#manager.takeoverDeadlineMs)\n }\n\n dispose(): void {\n this.#clearHysteresis()\n this.#clearJoinDeadline()\n this.#state = 'idle'\n this.#subscription = null\n // Release through the teardown edge, not a bare assignment: the\n // retained-FEC park makes `unicast` + isFecArmed a resting state, and\n // SessionManager.dispose() calls this directly (removeTrack() reaches it\n // via unsubscribe(), which has already disarmed). A bare assignment there\n // drops the host's decoder without a fec-teardown -- a leaked Worker.\n this.#disarmFec()\n }\n\n // -----------------------------------------------------------------------\n // Internal\n // -----------------------------------------------------------------------\n\n #transition(to: TrackState, opts?: { retainFec?: boolean }): void {\n const from = this.#state\n if (from === to) return\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). The value is\n // resolved before any state write, so the one residual failure -- a\n // manager whose deadline was never set at all, which addTrack() refuses --\n // leaves the session in its previous state rather than half-transitioned.\n // Invalid values never reach here: SessionManager's setter refuses them.\n // The read is unconditional even when `joinActive` defers the arm, so a\n // manager without a deadline still fails closed on every joining edge.\n const joinDeadlineMs = to === 'joining' ? this.#manager.takeoverDeadlineMs : undefined\n this.#state = to\n if (from === 'joining') this.#clearJoinDeadline()\n if (joinDeadlineMs !== undefined && this.#manager.joinActive) {\n this.#armJoinDeadline(joinDeadlineMs)\n }\n // FEC teardown: entering a state without a FEC decoder releases the one\n // this track armed, unless the caller keeps it (takeover timeout: the\n // transport is still joined and still feeding the decoder).\n const fecActive = (s: TrackState) =>\n s === 'joining' ||\n s === 'multicast' ||\n s === 'amt' ||\n s === 'failing' ||\n s === 'unicast-fallback'\n if (!fecActive(to) && !opts?.retainFec) this.#disarmFec()\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(deadlineMs: number): void {\n this.#clearJoinDeadline()\n if (deadlineMs === 0) return // a declared 0 disables the deadline\n this.#joinDeadlineTimer = setTimeout(() => {\n this.#joinDeadlineTimer = null\n if (this.#state !== 'joining') return\n // No RAP within the deadline: return to unicast for arbitration only.\n // MoQ was never suppressed in `joining`, so there is nothing to restore\n // and this edge stays silent on onSuppress (asserted in tests). The\n // multicast socket is still joined and the router still admits this\n // stream's symbols, so the FEC decoder stays armed; the next transport\n // edge releases it.\n this.#transition('unicast', { retainFec: true })\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 fec-setup exactly once per FEC epoch. */\n #armFec(): void {\n if (this.#fecArmed) return\n this.#fecArmed = true\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'fec-setup' }, this.config)\n }\n\n /** Fire fec-teardown for an armed decoder; no-op when none is armed. */\n #disarmFec(): void {\n if (!this.#fecArmed) return\n this.#fecArmed = false\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'fec-teardown' }, this.config)\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\n/**\n * Largest takeover deadline setTimeout represents faithfully. setTimeout\n * coerces its delay to a signed 32-bit int, so a larger value fires on the\n * next tick (Node also warns TimeoutOverflowWarning) instead of after the\n * window the host asked for -- silently inverting the feature. Rejecting it\n * keeps the classic units bug (seconds-as-ms, a wrong timescale) loud.\n */\nexport const MAX_TAKEOVER_DEADLINE_MS = 2_147_483_647\n\n/**\n * How long `joinActive` may stay false before the library says so. The deferral\n * is unbounded by construction -- that is the feature -- so a host that never\n * flips it back parks every track in `joining` with no timer at all: exactly the\n * forever-join the takeover deadline exists to prevent, only silent. MoQ stays\n * unsuppressed so the viewer sees video, but onTakeoverTimeout never fires, the\n * FEC decoder stays armed, and the preference chain never advances past the\n * transport that failed. It looks like a slow join. A host is one un-awaited\n * rejection between `false` and `true` away from it, so the library complains\n * rather than trusting every future host to get all of its exit paths right.\n *\n * Generous against a genuinely slow subscribe -- a bridge reconnect or AMT relay\n * handshake is seconds -- so anything still unsettled here is broken, not slow.\n */\nexport const JOIN_DEFER_WARN_MS = 30_000\n\nexport class SessionManager {\n readonly pool: TransportPool\n readonly sessions = new Map<string, TrackSession>()\n callbacks: SessionManagerCallbacks | null = null\n #takeoverDeadlineMs: number | undefined = undefined\n #joinActive = true\n #joinDeferWarnTimer: ReturnType<typeof setTimeout> | null = null\n\n /**\n * Takeover deadline (BLO-9413/D9): the 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 abandons the takeover\n * without a playback glitch. The host derives it from the catalog (RAP\n * cadence from groupDurationTicks/timescale plus the signaled FEC delivery\n * window or reorder tolerance) and sets it before addTrack(). There is no\n * default: an unset value fails addTrack() closed, and reading the getter\n * before assignment throws the same way, so no host ever holds a\n * `number | undefined` it could coalesce back into a default.\n * `hasTakeoverDeadline` answers whether one was admitted. A declared 0\n * disables the deadline. The value is an integer number of milliseconds:\n * setTimeout truncates fractions, so a host that divides ticks by a\n * timescale must round before assigning.\n *\n * Assignment validates, so the diagnostic lands at the host's own call site.\n * That is what bounds the blast radius: `joining` is entered from\n * joinStarted(), from the transportFailed() failover branch -- itself\n * reachable from a setTimeout via hysteresisExpired(), where no caller could\n * catch a throw -- and from the activateMulticast()/activateAmt()/pool-failure\n * loops, where a throw on one session would abandon its siblings mid-loop\n * (the split-brain those loops exist to prevent). Refusing a bad value at\n * assignment means no host mutation can make an already-admitted session\n * throw out of a transport edge.\n */\n get takeoverDeadlineMs(): number {\n return this.#requireTakeoverDeadlineMs()\n }\n\n set takeoverDeadlineMs(value: number) {\n if (!Number.isInteger(value) || value < 0 || value > MAX_TAKEOVER_DEADLINE_MS) {\n throw new Error(\n `[SessionManager] takeoverDeadlineMs must be an integer from 0 to ${MAX_TAKEOVER_DEADLINE_MS} (0 disables it, larger values overflow setTimeout); got ${String(value)}`,\n )\n }\n this.#takeoverDeadlineMs = value\n }\n\n /** True once the host has admitted a deadline; a declared 0 counts. */\n get hasTakeoverDeadline(): boolean {\n return this.#takeoverDeadlineMs !== undefined\n }\n\n /**\n * Whether the transport the joining tracks are waiting on is actually\n * subscribed. The takeover deadline is a media-arrival budget and media\n * cannot arrive before the join exists, so while this is false, entering\n * `joining` arms no deadline; flipping it true starts the FULL deadline for\n * every track already parked there.\n *\n * Default true, which is the arm-on-`joining` behaviour every host had\n * before: a host that only calls joinStarted() after its own join resolves\n * (moqtail-private's player, post-BLO-33339) is already at the right edge and\n * needs no gate. A host that arms joinStarted() up front \u2014 to have FEC\n * decoders ready before the first datagram \u2014 sets this false before addTrack()\n * and true when its subscribe settles. Charging a slow subscribe (bridge\n * reconnect, AMT relay handshake) against the media budget expired the\n * deadline before the subscription existed and abandoned the join on the\n * happy path. BLO-34289.\n *\n * The flag tracks the host's primary join, not per-track transport state. A\n * track that fails over to the next transport in its own chain while this is\n * false (transportFailed's failover branch also enters `joining`) has that\n * deadline gated on the primary join activating, not on its own transport.\n * Under the documented usage -- false once at setup, true once the subscribe\n * settles -- the two coincide.\n *\n * The gate is on the ENTRY into `joining`, and false cannot take back a\n * deadline already running: the `true -> false` edge arms the warn below and\n * clears nothing. So set it false BEFORE the `joining` entry it is meant to\n * gate. A host that instead flipped it false after a `transportFailed()`\n * failover had already entered `joining` would leave the full deadline\n * counting against a subscribe that had not begun -- BLO-34289 again, on the\n * failover edge. Clearing running deadlines on that edge is deliberately NOT\n * done: this flag is manager-wide, so one transport's brief false would\n * cancel an unrelated track's live deadline and then re-arm it at FULL length\n * on the next activation (armDeferredJoinDeadline sees `joining` + a null\n * timer and cannot tell a cleared deadline from a deferred one). Every\n * unrelated transport blip would restart the whole budget, so under repeated\n * failover a track that should have timed out is extended indefinitely and\n * never advances. Only a false that never returns true loses the timer\n * outright, and JOIN_DEFER_WARN_MS already covers that.\n *\n * Leaving this false is not free: see JOIN_DEFER_WARN_MS.\n */\n get joinActive(): boolean {\n return this.#joinActive\n }\n\n set joinActive(active: boolean) {\n const wasActive = this.#joinActive\n this.#joinActive = active\n if (!active) {\n // Edge-triggered: a host re-declaring false has not restarted its\n // subscribe, and total deferral is what the warning is about.\n if (wasActive) this.#armJoinDeferWarn()\n return\n }\n this.#clearJoinDeferWarn()\n if (wasActive) return\n for (const [, session] of this.sessions) session.armDeferredJoinDeadline()\n }\n\n #armJoinDeferWarn(): void {\n this.#joinDeferWarnTimer = setTimeout(() => {\n this.#joinDeferWarnTimer = null\n const deferred = [...this.sessions.values()].filter(s => s.state === 'joining').length\n console.warn(\n `[SessionManager] joinActive has been false for ${JOIN_DEFER_WARN_MS} ms and ${deferred} track(s) sit in 'joining' with no takeover deadline. Set joinActive = true when the subscribe settles, on every exit path including failures, or these tracks never time out, never release their FEC decoders, and never advance to the next transport.`,\n )\n }, JOIN_DEFER_WARN_MS)\n }\n\n #clearJoinDeferWarn(): void {\n if (this.#joinDeferWarnTimer === null) return\n clearTimeout(this.#joinDeferWarnTimer)\n this.#joinDeferWarnTimer = null\n }\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 /**\n * The admitted takeover deadline. Throws only when the host never set one,\n * every other invalid value being refused by the setter, and returns a\n * declared 0 as 0. The getter and addTrack() both route through it, so a\n * session cannot exist without an admitted deadline, which is what makes\n * the #transition() read unable to throw on an already-admitted session.\n */\n #requireTakeoverDeadlineMs(): number {\n const value = this.#takeoverDeadlineMs\n if (value === undefined) {\n throw new Error(\n '[SessionManager] takeoverDeadlineMs is unset; set a catalog-derived takeover deadline (0 disables it) before adding tracks',\n )\n }\n return value\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 this.#requireTakeoverDeadlineMs()\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 and the track keeps its FEC\n * decoder armed. If a valid RAP arrives later, that late-RAP edge suppresses\n * unicast and promotes multicast without a second fec-setup.\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`), unsuppressed, and without a FEC decoder.\n * unsubscribe() unwinds any outstanding suppression regardless of the state\n * it tears down from (`failing` included; see TrackSession.#suppressFired),\n * and a session that a takeover timeout parked in `unicast` with its decoder\n * still armed is cycled too, because this transport edge is what releases\n * that decoder (unsubscribe fires fec-teardown).\n */\n failbackAll(): void {\n for (const [, session] of this.sessions) {\n const parkedWithFec = session.state === 'unicast' && session.isFecArmed\n if (\n parkedWithFec ||\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 this.#clearJoinDeferWarn()\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\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 /**\n * Raw MMTP header timestamp (RFC 5905 short NTP, 32 bits): the publisher's\n * send-wall clock for the packet that carried this frame. On timed media it\n * is distinct from `timestamp`, the authored media presentation time. On a\n * non-timed MFU there is no authored presentation time, so `MmtpRouter`\n * derives `timestamp` from this same clock and the two carry no latency\n * between them. Populated from `RoutedFrameMeta.mmtpTimestamp` and\n * preserved by every re-emit on this router (multicast, MoQ, and repair) so\n * players can measure network ingress on the multicast legs; absent when\n * the packet header could not be read.\n */\n mmtpTimestamp?: bigint\n}\nexport type FrameKind = 'video' | 'audio'\nexport type SourceKind = 'moq' | 'multicast'\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 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 // Without an explicit probe interval, use one complete signaled block span.\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,396 @@
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. FI=1 supplies the
7
+ * intrinsic data-unit key. Every fragmented fragment is addressed by
8
+ * (stream scope, MPU, packetSequenceNumber + fragmentCounter mod 2^32); a
9
+ * continuation joins, parks under, or is stale against the one run holding
10
+ * its address, and other runs are never consulted.
11
+ *
12
+ * Fragmentation Indicator (FI) values:
13
+ * - 0: Complete MFU (single packet, no fragmentation)
14
+ * - 1: First fragment
15
+ * - 2: Middle fragment
16
+ * - 3: Last fragment
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+ /** Source transport admitted by the MMTP router. */
21
+ export type MmtpSourcePath = 'multicast' | 'moq';
22
+ /**
23
+ * Strict whole-frame delivery provenance, folded over EVERY contributing
24
+ * fragment rather than the head fragment alone.
25
+ *
26
+ * This is deliberately NOT `source`. `source` answers a routing question —
27
+ * "is this the reliable-MoQ copy that must not confirm multicast takeover?" —
28
+ * and head-fragment-wins is the documented, deterministic answer to it.
29
+ * Provenance answers an evidence question: "did every byte of this frame
30
+ * physically arrive over multicast?" A frame with a multicast head and a
31
+ * QUIC-delivered tail is a legitimate routing win for `source: 'multicast'`
32
+ * and is emphatically NOT multicast-exclusive delivery.
33
+ *
34
+ * - 'multicast' — every contributing fragment was explicitly multicast.
35
+ * The ONLY value that may be counted as physical-delivery evidence.
36
+ * - 'mixed' — at least one contributing fragment arrived over MoQ.
37
+ * - 'unknown' — no fragment arrived over MoQ, but at least one carried no
38
+ * attribution at all. Absence is preserved as its own state rather than
39
+ * defaulted, so a consumer cannot mistake "never recorded" for "proven".
40
+ */
41
+ export type MmtpDeliveryProvenance = 'multicast' | 'mixed' | 'unknown';
42
+ /**
43
+ * Fold one fragment's `source` into a running provenance verdict.
44
+ *
45
+ * Fail-closed, order-independent and associative: 'mixed' absorbs everything,
46
+ * a missing attribution degrades 'multicast' to 'unknown', and nothing ever
47
+ * upgrades a verdict. Fold over an empty set is 'unknown' — no fragment ever
48
+ * proved anything.
49
+ */
50
+ export declare function foldDeliveryProvenance(verdict: MmtpDeliveryProvenance, source: MmtpSourcePath | undefined): MmtpDeliveryProvenance;
51
+ /** Strict provenance for a complete fragment set. */
52
+ export declare function deliveryProvenanceOf(fragments: readonly {
53
+ source?: MmtpSourcePath;
54
+ deliveryProvenance?: MmtpDeliveryProvenance;
55
+ }[]): MmtpDeliveryProvenance;
56
+ /**
57
+ * MFU Fragment for reassembly
58
+ */
59
+ export interface MFUFragment {
60
+ fragmentationIndicator: number;
61
+ mpuSequenceNumber: number;
62
+ fragmentCounter: number;
63
+ data: Uint8Array;
64
+ timestamp: bigint;
65
+ /** Random Access Point flag from MMTP header - indicates keyframe */
66
+ rapFlag: boolean;
67
+ /** MFU DU priority. FFmpeg marks random-access samples with high priority. */
68
+ priority?: number;
69
+ sampleNumber?: number;
70
+ movieFragmentSequenceNumber?: number;
71
+ sampleOffset?: number;
72
+ /** Non-timed MFU Item_ID from the 4-byte DU header. */
73
+ itemId?: number;
74
+ /** Authored u32 MMTP packet_sequence_number, including on recovered packets. */
75
+ packetSequenceNumber?: number;
76
+ /** True when this accepted source packet was reconstructed by FEC. */
77
+ recovered?: boolean;
78
+ /** Transport that supplied this accepted fragment. Absent for recovered data. */
79
+ source?: MmtpSourcePath;
80
+ /**
81
+ * Explicit provenance for this fragment, overriding what `source` implies.
82
+ * Set by the router for FEC-recovered fragments, which have no `source` of
83
+ * their own but whose FEC block's symbol provenance is known.
84
+ */
85
+ deliveryProvenance?: MmtpDeliveryProvenance;
86
+ /** Immutable catalog identity supplied by the owning ingress subscription. */
87
+ catalogTrackName?: string;
88
+ /** Physical MMTP stream identity when no catalog identity is available. */
89
+ packetId?: number;
90
+ }
91
+ /**
92
+ * Reassembled MFU result
93
+ */
94
+ export interface ReassembledMFU {
95
+ mpuSequenceNumber: number;
96
+ /** MMTP packet_sequence_number of the first fragment in delivery order. */
97
+ packetSequenceNumber?: number;
98
+ /** Every authoritative MMTP source-packet sequence that formed this MFU. */
99
+ packetSequenceNumbers?: number[];
100
+ /** Subset of packetSequenceNumbers reconstructed by FEC. */
101
+ repairedPacketSequenceNumbers?: number[];
102
+ data: Uint8Array;
103
+ timestamp: bigint;
104
+ fragmentCount: number;
105
+ /** Random Access Point flag - indicates keyframe (from first fragment) */
106
+ rapFlag: boolean;
107
+ /** MFU DU priority from the first fragment that carried it. */
108
+ priority?: number;
109
+ sampleNumber?: number;
110
+ movieFragmentSequenceNumber?: number;
111
+ /**
112
+ * This DU's authored base byte offset within its sample (moqcast #37). 0
113
+ * for a whole-sample DU; nonzero identifies one subsample DU among
114
+ * siblings partitioning an oversized sample. Constant across the DU's own
115
+ * fragments — feed into a sample combiner to reassemble the full sample.
116
+ */
117
+ sampleOffset?: number;
118
+ /**
119
+ * Transport that supplied the accepted FI=1 head fragment (or the complete
120
+ * FI=0 frame). This is the deterministic winner for mixed-path assembly.
121
+ *
122
+ * ROUTING ONLY. It describes one fragment, so it is not evidence about the
123
+ * whole frame — read {@link ReassembledMFU.deliveryProvenance} for that.
124
+ */
125
+ source?: MmtpSourcePath;
126
+ /**
127
+ * Strict provenance folded over every contributing fragment. Only
128
+ * 'multicast' proves multicast-exclusive delivery of this frame.
129
+ */
130
+ deliveryProvenance: MmtpDeliveryProvenance;
131
+ /** Catalog identity from the accepted FI=1 head fragment, or FI=0 frame. */
132
+ catalogTrackName?: string;
133
+ }
134
+ /**
135
+ * Reassembler statistics
136
+ */
137
+ export interface ReassemblerStats {
138
+ totalFragments: number;
139
+ reassembledMFUs: number;
140
+ /** Fragments rejected by strict authored DU-state validation. */
141
+ rejectedFragments: number;
142
+ /** Incomplete MFUs discarded after their configured deadline. */
143
+ timedOutMFUs: number;
144
+ /** Incomplete MFUs discarded to enforce the configured memory bound. */
145
+ evictedMemoryLimit: number;
146
+ outOfOrderFragments: number;
147
+ }
148
+ 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", "ambiguous-continuation"];
149
+ export type MfuReassemblyErrorCode = (typeof MFU_REASSEMBLY_ERROR_CODES)[number];
150
+ export declare function isMfuReassemblyErrorCode(value: unknown): value is MfuReassemblyErrorCode;
151
+ export declare class MfuReassemblyError extends Error {
152
+ readonly code: MfuReassemblyErrorCode;
153
+ readonly name = "MfuReassemblyError";
154
+ constructor(code: MfuReassemblyErrorCode, message: string);
155
+ }
156
+ /**
157
+ * Identity-free test for a reassembly rejection.
158
+ *
159
+ * Consumers that inline `@blockcast/mmt-container` more than once (a component
160
+ * bundle whose `@blockcast/*` dependencies each carry their own copy) end up
161
+ * with several distinct `MfuReassemblyError` classes. An `instanceof` check
162
+ * against one copy then misses rejections raised by another, and a per-fragment
163
+ * rejection escapes as an uncaught throw that aborts the rest of the packet's
164
+ * processing. The wire contract of a rejection is its name plus one of the
165
+ * closed error codes, so that is what this guard checks.
166
+ */
167
+ export declare function isMfuReassemblyError(error: unknown): error is MfuReassemblyError;
168
+ export interface PendingMfuDebug {
169
+ key: string;
170
+ fragments: number;
171
+ fi0: number;
172
+ fi1: number;
173
+ fi2: number;
174
+ fi3: number;
175
+ firstFi: number;
176
+ lastFi: number;
177
+ firstOrder: number;
178
+ lastOrder: number;
179
+ minCounter: number;
180
+ maxCounter: number;
181
+ minOffset?: number;
182
+ maxOffset?: number;
183
+ movieFragmentSequenceNumber?: number;
184
+ sampleNumber?: number;
185
+ mpuSequenceNumber?: number;
186
+ sortedHead: Array<{
187
+ fi: number;
188
+ counter: number;
189
+ order: number;
190
+ seq?: number;
191
+ offset?: number;
192
+ }>;
193
+ sortedTail: Array<{
194
+ fi: number;
195
+ counter: number;
196
+ order: number;
197
+ seq?: number;
198
+ offset?: number;
199
+ }>;
200
+ }
201
+ export interface MFUReassemblerOptions {
202
+ /** Timeout (ms) for incomplete MFUs. Derive from frame duration or FEC interleaveDepthMs.
203
+ * Pass null for live clock-free operation; memory remains bounded by maxBuffered. */
204
+ timeoutMs: number | null;
205
+ /** Max concurrent incomplete MFUs, including continuations awaiting FI=1. */
206
+ maxBuffered?: number;
207
+ }
208
+ /** Reassembly surface consumed by MmtpContainerParser and MmtpRouter.
209
+ * Implemented by both the TypeScript engine and the shared
210
+ * Rust/WASM IFC adapter. */
211
+ export interface MfuReassemblerBackend {
212
+ timeoutMs: number | null;
213
+ readonly stats: ReassemblerStats;
214
+ onDeferredAssembly?: (result: ReassembledMFU) => void;
215
+ /** Returns null only for a valid incomplete DU; strict rejection throws
216
+ * `MfuReassemblyError`. */
217
+ addFragment(fragment: MFUFragment): ReassembledMFU | null;
218
+ flush(): void;
219
+ resetStream(catalogTrackName: string): void;
220
+ reset(): void;
221
+ readonly pendingCount: number;
222
+ getStatus(): {
223
+ bufferedMFUs: number;
224
+ pendingFragments: number;
225
+ pendingSamples: PendingMfuDebug[];
226
+ stats: ReassemblerStats;
227
+ };
228
+ }
229
+ /**
230
+ * MFU Reassembler with bounded reordered-fragment association
231
+ * Based on ISO/IEC 23008-1:2023 Section 9.2.3.3
232
+ */
233
+ /**
234
+ * Completed-address window, as a multiple of maxBuffered. A late continuation
235
+ * is refused as stale while its data unit's address is within this window and
236
+ * parks as a headless data unit once the address has been forgotten. The
237
+ * value is a cross-engine invariant shared with the Rust reassembler
238
+ * (mmt-core COMPLETED_ADDRESS_WINDOW_MULTIPLE); change both together.
239
+ */
240
+ export declare const COMPLETED_ADDRESS_WINDOW_MULTIPLE = 2;
241
+ /**
242
+ * Cross-engine anchors for the clock-free eviction guard. The bound itself is
243
+ * `maxBuffered`, a host parameter — these fix the one scenario both regression
244
+ * corpora drive so the Rust and TypeScript guards assert the same boundary
245
+ * rather than each deriving its own. Shared with the Rust reassembler
246
+ * (`EVICTION_GUARD_MAX_BUFFERED` / `EVICTION_GUARD_DATA_UNITS` in
247
+ * `mmt-core/src/reassembler/policy/mmt.rs`); change both together.
248
+ *
249
+ * `EVICTION_GUARD_DATA_UNITS` headless data units admitted against a bound of
250
+ * `EVICTION_GUARD_MAX_BUFFERED` must evict exactly the difference.
251
+ */
252
+ export declare const EVICTION_GUARD_MAX_BUFFERED = 4;
253
+ export declare const EVICTION_GUARD_DATA_UNITS = 12;
254
+ /**
255
+ * Does a parked continuation queue hold a `maxBuffered` slot of its own?
256
+ *
257
+ * One rule, four call sites. Two read the bound: the queue that is *counted*
258
+ * toward it (`#unopenedParkedRunCount`) is exactly the queue that is *offered*
259
+ * to eviction (`#evictLeastRecentlyAdmitted`). Two gate admission against it:
260
+ * `#parkContinuation` and the FI=1 arm of `#associateDataUnit` each ask whether
261
+ * this address is already charged before moving the bound. Stating the rule once
262
+ * is what keeps those four answers from drifting apart — a queue that charges a
263
+ * slot no eviction can reclaim wedges the receiver, because the bound then
264
+ * refuses every new data unit forever with `active-data-unit-limit`.
265
+ *
266
+ * Two exclusions, for two different reasons:
267
+ *
268
+ * - **An open run owns the address.** The data unit is already charged through
269
+ * that run, so its queue costs nothing further.
270
+ * - **The queue is empty.** It holds nothing, so it charges nothing and offers
271
+ * nothing.
272
+ *
273
+ * The emptiness half is belt-and-braces, not load-bearing. Both engines discard
274
+ * an emptied queue eagerly — `#deletePendingRun` here on every drain exit,
275
+ * `discard_empty_parked_run` in Rust — and `#parkContinuation` always inserts
276
+ * into a queue it just created, so an empty queue is not observable today.
277
+ * Correctness must not depend on that invariant holding in all three places per
278
+ * engine: routing every site that asks the question through this one predicate
279
+ * is what keeps the engines agreeing if a future edit ever misses one of them.
280
+ *
281
+ * Scope: this rule decides *slot accounting* — counted, offered, admitted. A
282
+ * queue carries two further per-slot facts, its admission ordinal and its
283
+ * retention anchor (`startedAt` plus the timer armed from it), and both must
284
+ * move when an empty queue is charged again. `#parkContinuation` resets them
285
+ * explicitly; Rust gets the same contract structurally, by deriving each from
286
+ * the queue's live entries. Same contract, two mechanisms.
287
+ *
288
+ * The *reap schedule* asks the same question, and every site that arms a
289
+ * parked queue reaches one function, `#armHeadlessPendingTimeout`, which takes
290
+ * the test through this rule: `set timeoutMs` calls it for every queue when
291
+ * the policy changes, `#parkContinuation` calls it after a created or
292
+ * re-anchored queue holds its first fragment, and a run closing without
293
+ * completing calls it for the queue it leaves behind. It never arms a timer
294
+ * for an empty queue, because a timeout reports a data unit that failed to
295
+ * complete in time and an empty queue holds no such data unit. Charging one
296
+ * would record an MFU that never existed. No site installs a parked-queue
297
+ * timer of its own, so neither the slot rule nor the `startedAt` anchoring can
298
+ * drift one site at a time.
299
+ *
300
+ * That is the answer Rust reaches structurally: `next_timeout_at` derives
301
+ * candidate deadlines from the queue's live entries, so an empty queue yields
302
+ * none and no sweep is ever scheduled on its account, and `cleanup_timeouts`
303
+ * retains by `parked_run_holds_a_slot`, so a sweep arriving for any *other*
304
+ * queue leaves an empty one in place rather than charging
305
+ * `record_policy_timeout` for it. Both engines therefore agree on when the
306
+ * reap is scheduled, and the recorded statistic is unchanged for every queue
307
+ * that really did hold a data unit: `#onPendingTimeout` charges
308
+ * `stats.timedOutMFUs`, the shape Rust's `record_policy_timeout` produces, and
309
+ * takes the same slot test before charging, so the statistic cannot be
310
+ * recorded for an empty queue whatever order the arming sites run in.
311
+ *
312
+ * The trade this makes, in both engines: a queue that is emptied without being
313
+ * discarded is invisible to the reap schedule, to the slot count, and to
314
+ * eviction, so nothing reclaims it until `reset()` clears the policy state.
315
+ * Disposal of an emptied queue therefore rests entirely on the eager discard
316
+ * sites (`#deletePendingRun` here, `discard_empty_parked_run` in Rust), where
317
+ * it is housekeeping rather than an expiry; a drain exit that skips that call
318
+ * leaks the queue for the life of the reassembler rather than charging a
319
+ * phantom timeout for it. Converged under BLO-32420. The arming half of this
320
+ * rule is pinned in `mfu-reassembler-eviction.test.ts`: a headless queue that
321
+ * holds fragments is armed by `set timeoutMs` and by its run closing, and
322
+ * disarmed by `timeoutMs = null`. The emptiness half is Rust-only, because
323
+ * `#pendingContinuations` is unreachable from this package's tests.
324
+ *
325
+ * The public counters are a separate matter and *are* governed: `pendingCount`
326
+ * — and through it `getStatus().bufferedMFUs` — delegates to
327
+ * `#unopenedParkedRunCount`, exactly as Rust's `buffered_count` is built on
328
+ * `unopened_parked_run_count`. Filtering either on the open run alone makes the
329
+ * engines report different numbers for the same state out of a host-observable
330
+ * API.
331
+ *
332
+ * Twin of `parked_run_holds_a_slot` in
333
+ * `mmt-core/src/reassembler/policy/mmt.rs`; change both together.
334
+ */
335
+ export declare function parkedRunHoldsASlot(hasOpenRun: boolean, fragmentCount: number): boolean;
336
+ export declare class MFUReassembler {
337
+ #private;
338
+ /** Statistics for debugging */
339
+ readonly stats: ReassemblerStats;
340
+ constructor(options: MFUReassemblerOptions);
341
+ /** Update the expiry policy for all retained and future incomplete MFUs. */
342
+ set timeoutMs(ms: number | null);
343
+ get timeoutMs(): number | null;
344
+ /**
345
+ * Add a fragment and return reassembled MFU if complete
346
+ */
347
+ addFragment(fragment: MFUFragment): ReassembledMFU | null;
348
+ /** Callback for deferred assembly results (set by caller for async frame delivery) */
349
+ onDeferredAssembly?: (result: ReassembledMFU) => void;
350
+ /**
351
+ * Flush all buffered fragments
352
+ */
353
+ flush(): void;
354
+ /** Drop one catalog stream's incomplete and dedup state. */
355
+ resetStream(catalogTrackName: string): void;
356
+ /** Reset all state — flush buffers and zero stats */
357
+ reset(): void;
358
+ /**
359
+ * Get number of pending MFUs: engine buffers plus every headless parked data
360
+ * unit that holds a slot.
361
+ *
362
+ * This is not one of the four charging sites, but it shares their second term,
363
+ * so it takes the same rule — by delegating to `#unopenedParkedRunCount`, the
364
+ * one site that applies it. Rust's public `buffered_count()` is
365
+ * `inner.buffered_count() + unopened_parked_run_count()`, and that count is
366
+ * `parked_run_holds_a_slot` — so filtering here on the open run alone would
367
+ * report a phantom slot for an empty published queue in TypeScript and not in
368
+ * Rust, out of a *host-observable* counter (`getStatus().bufferedMFUs`). Same
369
+ * state, two numbers, decided by which engine the host loaded: the divergence
370
+ * class this file exists to close, so the public counters have to agree too.
371
+ *
372
+ * Distinct from `#incompleteRunCount`, which sums `#activeDataUnits` rather
373
+ * than `#buffers`. Those are different quantities by design and are not being
374
+ * merged; only the parked term they share is required to agree, which is what
375
+ * routing both through one method makes structural rather than conventional.
376
+ */
377
+ get pendingCount(): number;
378
+ /**
379
+ * Get current buffer status
380
+ */
381
+ getStatus(): {
382
+ bufferedMFUs: number;
383
+ pendingFragments: number;
384
+ pendingSamples: PendingMfuDebug[];
385
+ stats: ReassemblerStats;
386
+ };
387
+ }
388
+ /**
389
+ * Create MFU fragment from MMTP packet for reassembly
390
+ */
391
+ export declare function createFragment(mmtpPacket: Uint8Array): MFUFragment;
392
+ /**
393
+ * Process MMTP packet with fragment reassembly
394
+ */
395
+ export declare function processMMTPWithReassembly(mmtpPacket: Uint8Array, reassembler: MFUReassembler): ReassembledMFU | null;
396
+ //# 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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"}