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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (678) hide show
  1. package/README.md +432 -0
  2. package/dist/.build-stamp +0 -0
  3. package/dist/__tests__/test-utils.d.ts +71 -0
  4. package/dist/__tests__/test-utils.d.ts.map +1 -0
  5. package/dist/align.d.ts +148 -0
  6. package/dist/align.d.ts.map +1 -0
  7. package/dist/align.js +88 -0
  8. package/dist/align.js.map +7 -0
  9. package/dist/audio-init-parser.d.ts +66 -0
  10. package/dist/audio-init-parser.d.ts.map +1 -0
  11. package/dist/audio-init-parser.js +118 -0
  12. package/dist/audio-init-parser.js.map +7 -0
  13. package/dist/audio-ring-buffer.d.ts +50 -0
  14. package/dist/audio-ring-buffer.d.ts.map +1 -0
  15. package/dist/audio-ring-buffer.js +93 -0
  16. package/dist/audio-ring-buffer.js.map +7 -0
  17. package/dist/authored-timing.d.ts +32 -0
  18. package/dist/authored-timing.d.ts.map +1 -0
  19. package/dist/authored-timing.js +32 -0
  20. package/dist/authored-timing.js.map +7 -0
  21. package/dist/browser-runtime.js +17109 -0
  22. package/dist/browser-runtime.js.map +7 -0
  23. package/dist/carp-catalog.d.ts +318 -0
  24. package/dist/carp-catalog.d.ts.map +1 -0
  25. package/dist/carp-catalog.js +549 -0
  26. package/dist/carp-catalog.js.map +7 -0
  27. package/dist/catalog-fec-params.d.ts +82 -0
  28. package/dist/catalog-fec-params.d.ts.map +1 -0
  29. package/dist/catalog-fec-params.js +66 -0
  30. package/dist/catalog-fec-params.js.map +7 -0
  31. package/dist/catalog-network-source.d.ts +19 -0
  32. package/dist/catalog-network-source.d.ts.map +1 -0
  33. package/dist/catalog-network-source.js +22 -0
  34. package/dist/catalog-network-source.js.map +7 -0
  35. package/dist/catalog-validator.d.ts +18 -0
  36. package/dist/catalog-validator.d.ts.map +1 -0
  37. package/dist/catalog-validator.js +7747 -0
  38. package/dist/catalog-validator.js.map +7 -0
  39. package/dist/cmaf-block-assembler.d.ts +168 -0
  40. package/dist/cmaf-block-assembler.d.ts.map +1 -0
  41. package/dist/cmaf-block-assembler.js +1755 -0
  42. package/dist/cmaf-block-assembler.js.map +7 -0
  43. package/dist/cmaf-group-batcher.d.ts +38 -0
  44. package/dist/cmaf-group-batcher.d.ts.map +1 -0
  45. package/dist/cmaf-group-batcher.js +195 -0
  46. package/dist/cmaf-group-batcher.js.map +7 -0
  47. package/dist/cmcd-producer.d.ts +104 -0
  48. package/dist/cmcd-producer.d.ts.map +1 -0
  49. package/dist/cmcd-producer.js +207 -0
  50. package/dist/cmcd-producer.js.map +7 -0
  51. package/dist/codec-utils.d.ts +235 -0
  52. package/dist/codec-utils.d.ts.map +1 -0
  53. package/dist/codec-utils.js +1225 -0
  54. package/dist/codec-utils.js.map +7 -0
  55. package/dist/fec-codec.d.ts +32 -0
  56. package/dist/fec-codec.d.ts.map +1 -0
  57. package/dist/fec-codec.js +47 -0
  58. package/dist/fec-codec.js.map +7 -0
  59. package/dist/fec-manager.d.ts +266 -0
  60. package/dist/fec-manager.d.ts.map +1 -0
  61. package/dist/fec-manager.js +1447 -0
  62. package/dist/fec-manager.js.map +7 -0
  63. package/dist/fec-starvation-detector.d.ts +59 -0
  64. package/dist/fec-starvation-detector.d.ts.map +1 -0
  65. package/dist/fec-starvation-detector.js +75 -0
  66. package/dist/fec-starvation-detector.js.map +7 -0
  67. package/dist/fec-worker-setup.d.ts +76 -0
  68. package/dist/fec-worker-setup.d.ts.map +1 -0
  69. package/dist/fec-worker-setup.js +603 -0
  70. package/dist/fec-worker-setup.js.map +7 -0
  71. package/dist/fmp4-completion.d.ts +30 -0
  72. package/dist/fmp4-completion.d.ts.map +1 -0
  73. package/dist/fmp4-completion.js +21 -0
  74. package/dist/fmp4-completion.js.map +7 -0
  75. package/dist/fmp4-reader.d.ts +37 -0
  76. package/dist/fmp4-reader.d.ts.map +1 -0
  77. package/dist/fmp4-reader.js +100 -0
  78. package/dist/fmp4-reader.js.map +7 -0
  79. package/dist/index.d.ts +47 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +330 -0
  82. package/dist/index.js.map +7 -0
  83. package/dist/iso-bmff-timing.d.ts +47 -0
  84. package/dist/iso-bmff-timing.d.ts.map +1 -0
  85. package/dist/iso-bmff-timing.js +526 -0
  86. package/dist/iso-bmff-timing.js.map +7 -0
  87. package/dist/loc.d.ts +68 -0
  88. package/dist/loc.d.ts.map +1 -0
  89. package/dist/loc.js +143 -0
  90. package/dist/loc.js.map +7 -0
  91. package/dist/manifest-auth-plan.d.ts +30 -0
  92. package/dist/manifest-auth-plan.d.ts.map +1 -0
  93. package/dist/manifest-auth-plan.js +1267 -0
  94. package/dist/manifest-auth-plan.js.map +7 -0
  95. package/dist/media-router.d.ts +338 -0
  96. package/dist/media-router.d.ts.map +1 -0
  97. package/dist/media-router.js +802 -0
  98. package/dist/media-router.js.map +7 -0
  99. package/dist/mfu-reassembler.d.ts +264 -0
  100. package/dist/mfu-reassembler.d.ts.map +1 -0
  101. package/dist/mfu-reassembler.js +2668 -0
  102. package/dist/mfu-reassembler.js.map +7 -0
  103. package/dist/mfu-reassembly-policy.d.ts +24 -0
  104. package/dist/mfu-reassembly-policy.d.ts.map +1 -0
  105. package/dist/mfu-reassembly-policy.js +99 -0
  106. package/dist/mfu-reassembly-policy.js.map +7 -0
  107. package/dist/mmtp-container-parser.d.ts +304 -0
  108. package/dist/mmtp-container-parser.d.ts.map +1 -0
  109. package/dist/mmtp-container-parser.js +5378 -0
  110. package/dist/mmtp-container-parser.js.map +7 -0
  111. package/dist/mmtp-container-parser.no-modules.js +7853 -0
  112. package/dist/mmtp-container-parser.no-modules.js.map +7 -0
  113. package/dist/mmtp-decode-pipeline.d.ts +96 -0
  114. package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
  115. package/dist/mmtp-decode-pipeline.js +5589 -0
  116. package/dist/mmtp-decode-pipeline.js.map +7 -0
  117. package/dist/mmtp-router.d.ts +383 -0
  118. package/dist/mmtp-router.d.ts.map +1 -0
  119. package/dist/mmtp-router.js +8384 -0
  120. package/dist/mmtp-router.js.map +7 -0
  121. package/dist/mmtp-to-cmaf.d.ts +166 -0
  122. package/dist/mmtp-to-cmaf.d.ts.map +1 -0
  123. package/dist/mmtp-to-cmaf.js +781 -0
  124. package/dist/mmtp-to-cmaf.js.map +7 -0
  125. package/dist/multicast-auth-catalog.d.ts +115 -0
  126. package/dist/multicast-auth-catalog.d.ts.map +1 -0
  127. package/dist/multicast-auth-catalog.js +202 -0
  128. package/dist/multicast-auth-catalog.js.map +7 -0
  129. package/dist/multicast-input.d.ts +107 -0
  130. package/dist/multicast-input.d.ts.map +1 -0
  131. package/dist/multicast-input.js +256 -0
  132. package/dist/multicast-input.js.map +7 -0
  133. package/dist/multicast-manager.d.ts +330 -0
  134. package/dist/multicast-manager.d.ts.map +1 -0
  135. package/dist/multicast-manager.js +11032 -0
  136. package/dist/multicast-manager.js.map +7 -0
  137. package/dist/multicast-transport.d.ts +62 -0
  138. package/dist/multicast-transport.d.ts.map +1 -0
  139. package/dist/multicast-transport.js +338 -0
  140. package/dist/multicast-transport.js.map +7 -0
  141. package/dist/multicast-unwrap.d.ts +57 -0
  142. package/dist/multicast-unwrap.d.ts.map +1 -0
  143. package/dist/multicast-unwrap.js +384 -0
  144. package/dist/multicast-unwrap.js.map +7 -0
  145. package/dist/no-modules.d.ts +9 -0
  146. package/dist/no-modules.d.ts.map +1 -0
  147. package/dist/no-modules.js +7819 -0
  148. package/dist/no-modules.js.map +7 -0
  149. package/dist/phase14-catalog-override.d.ts +126 -0
  150. package/dist/phase14-catalog-override.d.ts.map +1 -0
  151. package/dist/phase14-catalog-override.js +155 -0
  152. package/dist/phase14-catalog-override.js.map +7 -0
  153. package/dist/repair-fetch.d.ts +58 -0
  154. package/dist/repair-fetch.d.ts.map +1 -0
  155. package/dist/repair-fetch.js +117 -0
  156. package/dist/repair-fetch.js.map +7 -0
  157. package/dist/sample-combiner.d.ts +182 -0
  158. package/dist/sample-combiner.d.ts.map +1 -0
  159. package/dist/sample-combiner.js +571 -0
  160. package/dist/sample-combiner.js.map +7 -0
  161. package/dist/signaling.d.ts +29 -0
  162. package/dist/signaling.d.ts.map +1 -0
  163. package/dist/signaling.js +232 -0
  164. package/dist/signaling.js.map +7 -0
  165. package/dist/source-auth.d.ts +2 -0
  166. package/dist/source-auth.d.ts.map +1 -0
  167. package/dist/source-auth.js +1220 -0
  168. package/dist/source-auth.js.map +7 -0
  169. package/dist/switching-set-alignment.d.ts +61 -0
  170. package/dist/switching-set-alignment.d.ts.map +1 -0
  171. package/dist/switching-set-alignment.js +85 -0
  172. package/dist/switching-set-alignment.js.map +7 -0
  173. package/dist/timestamp-codec.d.ts +34 -0
  174. package/dist/timestamp-codec.d.ts.map +1 -0
  175. package/dist/timestamp-codec.js +456 -0
  176. package/dist/timestamp-codec.js.map +7 -0
  177. package/dist/track-pipeline-setup.d.ts +72 -0
  178. package/dist/track-pipeline-setup.d.ts.map +1 -0
  179. package/dist/track-pipeline-setup.js +306 -0
  180. package/dist/track-pipeline-setup.js.map +7 -0
  181. package/dist/track-pipeline.d.ts +121 -0
  182. package/dist/track-pipeline.d.ts.map +1 -0
  183. package/dist/track-pipeline.js +229 -0
  184. package/dist/track-pipeline.js.map +7 -0
  185. package/dist/track-session.d.ts +269 -0
  186. package/dist/track-session.d.ts.map +1 -0
  187. package/dist/track-session.js +597 -0
  188. package/dist/track-session.js.map +7 -0
  189. package/dist/transport-pool.d.ts +69 -0
  190. package/dist/transport-pool.d.ts.map +1 -0
  191. package/dist/transport-pool.js +88 -0
  192. package/dist/transport-pool.js.map +7 -0
  193. package/dist/wasm-mfu-reassembler.d.ts +81 -0
  194. package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
  195. package/dist/wasm-mfu-reassembler.js +1915 -0
  196. package/dist/wasm-mfu-reassembler.js.map +7 -0
  197. package/dist/wasm-mmtp-container-parser.d.ts +10 -0
  198. package/dist/wasm-mmtp-container-parser.d.ts.map +1 -0
  199. package/dist/wasm-mmtp-container-parser.js +5635 -0
  200. package/dist/wasm-mmtp-container-parser.js.map +7 -0
  201. package/node_modules/@blockcast/fec-worker/README.md +213 -0
  202. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  203. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +80 -0
  204. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -0
  205. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +270 -0
  206. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -0
  207. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +252 -0
  208. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +1 -0
  209. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +11 -0
  210. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +1 -0
  211. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +14 -0
  212. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +1 -0
  213. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +565 -0
  214. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +7 -0
  215. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +10 -0
  216. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +1 -0
  217. package/node_modules/@blockcast/fec-worker/dist/index.js +8 -0
  218. package/node_modules/@blockcast/fec-worker/dist/index.js.map +1 -0
  219. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +20 -0
  220. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +1 -0
  221. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +349 -0
  222. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +7 -0
  223. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +104 -0
  224. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +1 -0
  225. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +181 -0
  226. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +1 -0
  227. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +179 -0
  228. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +1 -0
  229. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +47 -0
  230. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +1 -0
  231. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +9 -0
  232. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +1 -0
  233. package/node_modules/@blockcast/fec-worker/dist/transfer.js +13 -0
  234. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +1 -0
  235. package/node_modules/@blockcast/fec-worker/package.json +59 -0
  236. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +309 -0
  237. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +268 -0
  238. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +1048 -0
  239. package/node_modules/@blockcast/fec-worker/src/index.ts +32 -0
  240. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +558 -0
  241. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +222 -0
  242. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +194 -0
  243. package/node_modules/@blockcast/fec-worker/src/transfer.ts +12 -0
  244. package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  245. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  246. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  247. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  248. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  249. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  250. package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  251. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  252. package/node_modules/@blockcast/mmt-base/README.md +120 -0
  253. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  254. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
  255. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  256. package/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
  257. package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  258. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +275 -0
  259. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  260. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +644 -0
  261. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  262. package/node_modules/@blockcast/mmt-base/package.json +49 -0
  263. package/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
  264. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +898 -0
  265. package/node_modules/@blockcast/mmt-fec/README.md +270 -0
  266. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  267. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +80 -0
  268. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +1 -0
  269. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +294 -0
  270. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +1 -0
  271. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +72 -0
  272. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +1 -0
  273. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +192 -0
  274. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +1 -0
  275. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +46 -0
  276. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +1 -0
  277. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +96 -0
  278. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +1 -0
  279. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +33 -0
  280. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +1 -0
  281. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +57 -0
  282. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +1 -0
  283. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +185 -0
  284. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +1 -0
  285. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +273 -0
  286. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +1 -0
  287. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +22 -0
  288. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +1 -0
  289. package/node_modules/@blockcast/mmt-fec/dist/index.js +22 -0
  290. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +1 -0
  291. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +20 -0
  292. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +1 -0
  293. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +28 -0
  294. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +1 -0
  295. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +174 -0
  296. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
  297. package/node_modules/@blockcast/mmt-fec/dist/oti.js +256 -0
  298. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +1 -0
  299. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +41 -0
  300. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +1 -0
  301. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +81 -0
  302. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +1 -0
  303. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  304. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  305. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  306. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  307. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  308. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  309. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  310. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  311. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +120 -0
  312. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  313. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
  314. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  315. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
  316. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  317. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +275 -0
  318. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  319. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +644 -0
  320. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  321. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +49 -0
  322. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
  323. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +898 -0
  324. package/node_modules/@blockcast/mmt-fec/package.json +79 -0
  325. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +369 -0
  326. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +277 -0
  327. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +127 -0
  328. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +68 -0
  329. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +332 -0
  330. package/node_modules/@blockcast/mmt-fec/src/index.ts +77 -0
  331. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +34 -0
  332. package/node_modules/@blockcast/mmt-fec/src/oti.ts +319 -0
  333. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +101 -0
  334. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  335. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +28 -0
  336. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +1 -0
  337. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +128 -0
  338. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +1 -0
  339. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +4 -0
  340. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +1 -0
  341. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +4 -0
  342. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +1 -0
  343. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +37 -0
  344. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +1 -0
  345. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +179 -0
  346. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +1 -0
  347. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +90 -0
  348. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +1 -0
  349. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +462 -0
  350. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +1 -0
  351. package/node_modules/@blockcast/mmt-manifest-verify/package.json +45 -0
  352. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +171 -0
  353. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +3 -0
  354. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +230 -0
  355. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +636 -0
  356. package/node_modules/@noble/hashes/LICENSE +21 -0
  357. package/node_modules/@noble/hashes/README.md +521 -0
  358. package/node_modules/@noble/hashes/_assert.d.ts +17 -0
  359. package/node_modules/@noble/hashes/_assert.d.ts.map +1 -0
  360. package/node_modules/@noble/hashes/_assert.js +18 -0
  361. package/node_modules/@noble/hashes/_assert.js.map +1 -0
  362. package/node_modules/@noble/hashes/_blake.d.ts +14 -0
  363. package/node_modules/@noble/hashes/_blake.d.ts.map +1 -0
  364. package/node_modules/@noble/hashes/_blake.js +50 -0
  365. package/node_modules/@noble/hashes/_blake.js.map +1 -0
  366. package/node_modules/@noble/hashes/_md.d.ts +51 -0
  367. package/node_modules/@noble/hashes/_md.d.ts.map +1 -0
  368. package/node_modules/@noble/hashes/_md.js +162 -0
  369. package/node_modules/@noble/hashes/_md.js.map +1 -0
  370. package/node_modules/@noble/hashes/_u64.d.ts +55 -0
  371. package/node_modules/@noble/hashes/_u64.d.ts.map +1 -0
  372. package/node_modules/@noble/hashes/_u64.js +90 -0
  373. package/node_modules/@noble/hashes/_u64.js.map +1 -0
  374. package/node_modules/@noble/hashes/argon2.d.ts +32 -0
  375. package/node_modules/@noble/hashes/argon2.d.ts.map +1 -0
  376. package/node_modules/@noble/hashes/argon2.js +401 -0
  377. package/node_modules/@noble/hashes/argon2.js.map +1 -0
  378. package/node_modules/@noble/hashes/blake1.d.ts +106 -0
  379. package/node_modules/@noble/hashes/blake1.d.ts.map +1 -0
  380. package/node_modules/@noble/hashes/blake1.js +459 -0
  381. package/node_modules/@noble/hashes/blake1.js.map +1 -0
  382. package/node_modules/@noble/hashes/blake2.d.ts +116 -0
  383. package/node_modules/@noble/hashes/blake2.d.ts.map +1 -0
  384. package/node_modules/@noble/hashes/blake2.js +420 -0
  385. package/node_modules/@noble/hashes/blake2.js.map +1 -0
  386. package/node_modules/@noble/hashes/blake2b.d.ts +11 -0
  387. package/node_modules/@noble/hashes/blake2b.d.ts.map +1 -0
  388. package/node_modules/@noble/hashes/blake2b.js +14 -0
  389. package/node_modules/@noble/hashes/blake2b.js.map +1 -0
  390. package/node_modules/@noble/hashes/blake2s.d.ts +20 -0
  391. package/node_modules/@noble/hashes/blake2s.d.ts.map +1 -0
  392. package/node_modules/@noble/hashes/blake2s.js +24 -0
  393. package/node_modules/@noble/hashes/blake2s.js.map +1 -0
  394. package/node_modules/@noble/hashes/blake3.d.ts +54 -0
  395. package/node_modules/@noble/hashes/blake3.d.ts.map +1 -0
  396. package/node_modules/@noble/hashes/blake3.js +255 -0
  397. package/node_modules/@noble/hashes/blake3.js.map +1 -0
  398. package/node_modules/@noble/hashes/crypto.d.ts +2 -0
  399. package/node_modules/@noble/hashes/crypto.d.ts.map +1 -0
  400. package/node_modules/@noble/hashes/crypto.js +5 -0
  401. package/node_modules/@noble/hashes/crypto.js.map +1 -0
  402. package/node_modules/@noble/hashes/cryptoNode.d.ts +2 -0
  403. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +1 -0
  404. package/node_modules/@noble/hashes/cryptoNode.js +18 -0
  405. package/node_modules/@noble/hashes/cryptoNode.js.map +1 -0
  406. package/node_modules/@noble/hashes/eskdf.d.ts +47 -0
  407. package/node_modules/@noble/hashes/eskdf.d.ts.map +1 -0
  408. package/node_modules/@noble/hashes/eskdf.js +166 -0
  409. package/node_modules/@noble/hashes/eskdf.js.map +1 -0
  410. package/node_modules/@noble/hashes/esm/_assert.d.ts +17 -0
  411. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +1 -0
  412. package/node_modules/@noble/hashes/esm/_assert.js +15 -0
  413. package/node_modules/@noble/hashes/esm/_assert.js.map +1 -0
  414. package/node_modules/@noble/hashes/esm/_blake.d.ts +14 -0
  415. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +1 -0
  416. package/node_modules/@noble/hashes/esm/_blake.js +45 -0
  417. package/node_modules/@noble/hashes/esm/_blake.js.map +1 -0
  418. package/node_modules/@noble/hashes/esm/_md.d.ts +51 -0
  419. package/node_modules/@noble/hashes/esm/_md.d.ts.map +1 -0
  420. package/node_modules/@noble/hashes/esm/_md.js +155 -0
  421. package/node_modules/@noble/hashes/esm/_md.js.map +1 -0
  422. package/node_modules/@noble/hashes/esm/_u64.d.ts +55 -0
  423. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +1 -0
  424. package/node_modules/@noble/hashes/esm/_u64.js +67 -0
  425. package/node_modules/@noble/hashes/esm/_u64.js.map +1 -0
  426. package/node_modules/@noble/hashes/esm/argon2.d.ts +32 -0
  427. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +1 -0
  428. package/node_modules/@noble/hashes/esm/argon2.js +392 -0
  429. package/node_modules/@noble/hashes/esm/argon2.js.map +1 -0
  430. package/node_modules/@noble/hashes/esm/blake1.d.ts +106 -0
  431. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +1 -0
  432. package/node_modules/@noble/hashes/esm/blake1.js +452 -0
  433. package/node_modules/@noble/hashes/esm/blake1.js.map +1 -0
  434. package/node_modules/@noble/hashes/esm/blake2.d.ts +116 -0
  435. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +1 -0
  436. package/node_modules/@noble/hashes/esm/blake2.js +413 -0
  437. package/node_modules/@noble/hashes/esm/blake2.js.map +1 -0
  438. package/node_modules/@noble/hashes/esm/blake2b.d.ts +11 -0
  439. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +1 -0
  440. package/node_modules/@noble/hashes/esm/blake2b.js +11 -0
  441. package/node_modules/@noble/hashes/esm/blake2b.js.map +1 -0
  442. package/node_modules/@noble/hashes/esm/blake2s.d.ts +20 -0
  443. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +1 -0
  444. package/node_modules/@noble/hashes/esm/blake2s.js +21 -0
  445. package/node_modules/@noble/hashes/esm/blake2s.js.map +1 -0
  446. package/node_modules/@noble/hashes/esm/blake3.d.ts +54 -0
  447. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +1 -0
  448. package/node_modules/@noble/hashes/esm/blake3.js +251 -0
  449. package/node_modules/@noble/hashes/esm/blake3.js.map +1 -0
  450. package/node_modules/@noble/hashes/esm/crypto.d.ts +2 -0
  451. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +1 -0
  452. package/node_modules/@noble/hashes/esm/crypto.js +2 -0
  453. package/node_modules/@noble/hashes/esm/crypto.js.map +1 -0
  454. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +2 -0
  455. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +1 -0
  456. package/node_modules/@noble/hashes/esm/cryptoNode.js +15 -0
  457. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +1 -0
  458. package/node_modules/@noble/hashes/esm/eskdf.d.ts +47 -0
  459. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +1 -0
  460. package/node_modules/@noble/hashes/esm/eskdf.js +160 -0
  461. package/node_modules/@noble/hashes/esm/eskdf.js.map +1 -0
  462. package/node_modules/@noble/hashes/esm/hkdf.d.ts +36 -0
  463. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +1 -0
  464. package/node_modules/@noble/hashes/esm/hkdf.js +82 -0
  465. package/node_modules/@noble/hashes/esm/hkdf.js.map +1 -0
  466. package/node_modules/@noble/hashes/esm/hmac.d.ts +35 -0
  467. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +1 -0
  468. package/node_modules/@noble/hashes/esm/hmac.js +86 -0
  469. package/node_modules/@noble/hashes/esm/hmac.js.map +1 -0
  470. package/node_modules/@noble/hashes/esm/index.d.ts +2 -0
  471. package/node_modules/@noble/hashes/esm/index.d.ts.map +1 -0
  472. package/node_modules/@noble/hashes/esm/index.js +33 -0
  473. package/node_modules/@noble/hashes/esm/index.js.map +1 -0
  474. package/node_modules/@noble/hashes/esm/legacy.d.ts +71 -0
  475. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +1 -0
  476. package/node_modules/@noble/hashes/esm/legacy.js +281 -0
  477. package/node_modules/@noble/hashes/esm/legacy.js.map +1 -0
  478. package/node_modules/@noble/hashes/esm/package.json +10 -0
  479. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +23 -0
  480. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +1 -0
  481. package/node_modules/@noble/hashes/esm/pbkdf2.js +97 -0
  482. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +1 -0
  483. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +13 -0
  484. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +1 -0
  485. package/node_modules/@noble/hashes/esm/ripemd160.js +13 -0
  486. package/node_modules/@noble/hashes/esm/ripemd160.js.map +1 -0
  487. package/node_modules/@noble/hashes/esm/scrypt.d.ts +34 -0
  488. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +1 -0
  489. package/node_modules/@noble/hashes/esm/scrypt.js +228 -0
  490. package/node_modules/@noble/hashes/esm/scrypt.js.map +1 -0
  491. package/node_modules/@noble/hashes/esm/sha1.d.ts +11 -0
  492. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +1 -0
  493. package/node_modules/@noble/hashes/esm/sha1.js +11 -0
  494. package/node_modules/@noble/hashes/esm/sha1.js.map +1 -0
  495. package/node_modules/@noble/hashes/esm/sha2.d.ts +159 -0
  496. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +1 -0
  497. package/node_modules/@noble/hashes/esm/sha2.js +375 -0
  498. package/node_modules/@noble/hashes/esm/sha2.js.map +1 -0
  499. package/node_modules/@noble/hashes/esm/sha256.d.ts +20 -0
  500. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +1 -0
  501. package/node_modules/@noble/hashes/esm/sha256.js +20 -0
  502. package/node_modules/@noble/hashes/esm/sha256.js.map +1 -0
  503. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +142 -0
  504. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +1 -0
  505. package/node_modules/@noble/hashes/esm/sha3-addons.js +393 -0
  506. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +1 -0
  507. package/node_modules/@noble/hashes/esm/sha3.d.ts +53 -0
  508. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +1 -0
  509. package/node_modules/@noble/hashes/esm/sha3.js +234 -0
  510. package/node_modules/@noble/hashes/esm/sha3.js.map +1 -0
  511. package/node_modules/@noble/hashes/esm/sha512.d.ts +26 -0
  512. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +1 -0
  513. package/node_modules/@noble/hashes/esm/sha512.js +26 -0
  514. package/node_modules/@noble/hashes/esm/sha512.js.map +1 -0
  515. package/node_modules/@noble/hashes/esm/utils.d.ts +161 -0
  516. package/node_modules/@noble/hashes/esm/utils.d.ts.map +1 -0
  517. package/node_modules/@noble/hashes/esm/utils.js +281 -0
  518. package/node_modules/@noble/hashes/esm/utils.js.map +1 -0
  519. package/node_modules/@noble/hashes/hkdf.d.ts +36 -0
  520. package/node_modules/@noble/hashes/hkdf.d.ts.map +1 -0
  521. package/node_modules/@noble/hashes/hkdf.js +88 -0
  522. package/node_modules/@noble/hashes/hkdf.js.map +1 -0
  523. package/node_modules/@noble/hashes/hmac.d.ts +35 -0
  524. package/node_modules/@noble/hashes/hmac.d.ts.map +1 -0
  525. package/node_modules/@noble/hashes/hmac.js +91 -0
  526. package/node_modules/@noble/hashes/hmac.js.map +1 -0
  527. package/node_modules/@noble/hashes/index.d.ts +1 -0
  528. package/node_modules/@noble/hashes/index.d.ts.map +1 -0
  529. package/node_modules/@noble/hashes/index.js +33 -0
  530. package/node_modules/@noble/hashes/index.js.map +1 -0
  531. package/node_modules/@noble/hashes/legacy.d.ts +71 -0
  532. package/node_modules/@noble/hashes/legacy.d.ts.map +1 -0
  533. package/node_modules/@noble/hashes/legacy.js +287 -0
  534. package/node_modules/@noble/hashes/legacy.js.map +1 -0
  535. package/node_modules/@noble/hashes/package.json +266 -0
  536. package/node_modules/@noble/hashes/pbkdf2.d.ts +23 -0
  537. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +1 -0
  538. package/node_modules/@noble/hashes/pbkdf2.js +101 -0
  539. package/node_modules/@noble/hashes/pbkdf2.js.map +1 -0
  540. package/node_modules/@noble/hashes/ripemd160.d.ts +13 -0
  541. package/node_modules/@noble/hashes/ripemd160.d.ts.map +1 -0
  542. package/node_modules/@noble/hashes/ripemd160.js +16 -0
  543. package/node_modules/@noble/hashes/ripemd160.js.map +1 -0
  544. package/node_modules/@noble/hashes/scrypt.d.ts +34 -0
  545. package/node_modules/@noble/hashes/scrypt.d.ts.map +1 -0
  546. package/node_modules/@noble/hashes/scrypt.js +232 -0
  547. package/node_modules/@noble/hashes/scrypt.js.map +1 -0
  548. package/node_modules/@noble/hashes/sha1.d.ts +11 -0
  549. package/node_modules/@noble/hashes/sha1.d.ts.map +1 -0
  550. package/node_modules/@noble/hashes/sha1.js +14 -0
  551. package/node_modules/@noble/hashes/sha1.js.map +1 -0
  552. package/node_modules/@noble/hashes/sha2.d.ts +159 -0
  553. package/node_modules/@noble/hashes/sha2.d.ts.map +1 -0
  554. package/node_modules/@noble/hashes/sha2.js +384 -0
  555. package/node_modules/@noble/hashes/sha2.js.map +1 -0
  556. package/node_modules/@noble/hashes/sha256.d.ts +20 -0
  557. package/node_modules/@noble/hashes/sha256.d.ts.map +1 -0
  558. package/node_modules/@noble/hashes/sha256.js +23 -0
  559. package/node_modules/@noble/hashes/sha256.js.map +1 -0
  560. package/node_modules/@noble/hashes/sha3-addons.d.ts +142 -0
  561. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +1 -0
  562. package/node_modules/@noble/hashes/sha3-addons.js +402 -0
  563. package/node_modules/@noble/hashes/sha3-addons.js.map +1 -0
  564. package/node_modules/@noble/hashes/sha3.d.ts +53 -0
  565. package/node_modules/@noble/hashes/sha3.d.ts.map +1 -0
  566. package/node_modules/@noble/hashes/sha3.js +239 -0
  567. package/node_modules/@noble/hashes/sha3.js.map +1 -0
  568. package/node_modules/@noble/hashes/sha512.d.ts +26 -0
  569. package/node_modules/@noble/hashes/sha512.d.ts.map +1 -0
  570. package/node_modules/@noble/hashes/sha512.js +29 -0
  571. package/node_modules/@noble/hashes/sha512.js.map +1 -0
  572. package/node_modules/@noble/hashes/src/_assert.ts +22 -0
  573. package/node_modules/@noble/hashes/src/_blake.ts +50 -0
  574. package/node_modules/@noble/hashes/src/_md.ts +176 -0
  575. package/node_modules/@noble/hashes/src/_u64.ts +91 -0
  576. package/node_modules/@noble/hashes/src/argon2.ts +497 -0
  577. package/node_modules/@noble/hashes/src/blake1.ts +534 -0
  578. package/node_modules/@noble/hashes/src/blake2.ts +486 -0
  579. package/node_modules/@noble/hashes/src/blake2b.ts +10 -0
  580. package/node_modules/@noble/hashes/src/blake2s.ts +20 -0
  581. package/node_modules/@noble/hashes/src/blake3.ts +272 -0
  582. package/node_modules/@noble/hashes/src/crypto.ts +9 -0
  583. package/node_modules/@noble/hashes/src/cryptoNode.ts +15 -0
  584. package/node_modules/@noble/hashes/src/eskdf.ts +187 -0
  585. package/node_modules/@noble/hashes/src/hkdf.ts +88 -0
  586. package/node_modules/@noble/hashes/src/hmac.ts +94 -0
  587. package/node_modules/@noble/hashes/src/index.ts +31 -0
  588. package/node_modules/@noble/hashes/src/legacy.ts +293 -0
  589. package/node_modules/@noble/hashes/src/pbkdf2.ts +122 -0
  590. package/node_modules/@noble/hashes/src/ripemd160.ts +12 -0
  591. package/node_modules/@noble/hashes/src/scrypt.ts +257 -0
  592. package/node_modules/@noble/hashes/src/sha1.ts +10 -0
  593. package/node_modules/@noble/hashes/src/sha2.ts +402 -0
  594. package/node_modules/@noble/hashes/src/sha256.ts +24 -0
  595. package/node_modules/@noble/hashes/src/sha3-addons.ts +499 -0
  596. package/node_modules/@noble/hashes/src/sha3.ts +258 -0
  597. package/node_modules/@noble/hashes/src/sha512.ts +34 -0
  598. package/node_modules/@noble/hashes/src/utils.ts +395 -0
  599. package/node_modules/@noble/hashes/utils.d.ts +161 -0
  600. package/node_modules/@noble/hashes/utils.d.ts.map +1 -0
  601. package/node_modules/@noble/hashes/utils.js +313 -0
  602. package/node_modules/@noble/hashes/utils.js.map +1 -0
  603. package/package.json +203 -0
  604. package/schemas/catalog.schema.json +283 -0
  605. package/src/align.ts +235 -0
  606. package/src/audio-init-parser.ts +189 -0
  607. package/src/audio-ring-buffer.ts +120 -0
  608. package/src/authored-timing.ts +63 -0
  609. package/src/carp-catalog.ts +776 -0
  610. package/src/catalog-fec-params.ts +167 -0
  611. package/src/catalog-network-source.ts +49 -0
  612. package/src/catalog-validator.ts +34 -0
  613. package/src/cmaf-block-assembler.ts +1058 -0
  614. package/src/cmaf-group-batcher.ts +104 -0
  615. package/src/cmcd-producer.ts +329 -0
  616. package/src/codec-utils.ts +1539 -0
  617. package/src/fec-codec.ts +61 -0
  618. package/src/fec-manager.ts +1939 -0
  619. package/src/fec-starvation-detector.ts +128 -0
  620. package/src/fec-worker-setup.ts +177 -0
  621. package/src/fmp4-completion.ts +47 -0
  622. package/src/fmp4-reader.ts +112 -0
  623. package/src/index.ts +443 -0
  624. package/src/iso-bmff-timing.ts +688 -0
  625. package/src/loc.ts +181 -0
  626. package/src/manifest-auth-plan.ts +107 -0
  627. package/src/media-router.ts +1185 -0
  628. package/src/mfu-reassembler.ts +2842 -0
  629. package/src/mfu-reassembly-policy.ts +134 -0
  630. package/src/mmtp-container-parser.ts +1498 -0
  631. package/src/mmtp-decode-pipeline.ts +315 -0
  632. package/src/mmtp-router.ts +3027 -0
  633. package/src/mmtp-to-cmaf.ts +956 -0
  634. package/src/multicast-auth-catalog.ts +340 -0
  635. package/src/multicast-input.ts +439 -0
  636. package/src/multicast-manager.ts +1503 -0
  637. package/src/multicast-transport.ts +320 -0
  638. package/src/multicast-unwrap.ts +132 -0
  639. package/src/no-modules.ts +20 -0
  640. package/src/phase14-catalog-override.ts +336 -0
  641. package/src/repair-fetch.ts +217 -0
  642. package/src/sample-combiner.ts +846 -0
  643. package/src/signaling.ts +78 -0
  644. package/src/source-auth.ts +1 -0
  645. package/src/switching-set-alignment.ts +109 -0
  646. package/src/timestamp-codec.ts +265 -0
  647. package/src/track-pipeline-setup.ts +189 -0
  648. package/src/track-pipeline.ts +332 -0
  649. package/src/track-session.ts +778 -0
  650. package/src/transport-pool.ts +128 -0
  651. package/src/wasm-mfu-reassembler.ts +372 -0
  652. package/src/wasm-mmtp-container-parser.ts +23 -0
  653. package/test-vectors/align.json +199 -0
  654. package/test-vectors/catalog/README.md +25 -0
  655. package/test-vectors/catalog/negative/legacy-selection-params.json +15 -0
  656. package/test-vectors/catalog/negative/missing-mmtp-fields.json +12 -0
  657. package/test-vectors/catalog/negative/multicast-auth-padded-key.json +37 -0
  658. package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +53 -0
  659. package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +37 -0
  660. package/test-vectors/catalog/negative/multicast-endpoint-missing-protocol-and-source.json +24 -0
  661. package/test-vectors/catalog/negative/network-source-object.json +25 -0
  662. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +26 -0
  663. package/test-vectors/catalog/negative/repair-track-legacy-shape.json +18 -0
  664. package/test-vectors/catalog/negative/track-role-empty.json +16 -0
  665. package/test-vectors/catalog/negative/track-role-non-string.json +16 -0
  666. package/test-vectors/catalog/positive/fec-multicast.json +51 -0
  667. package/test-vectors/catalog/positive/flat-av.json +30 -0
  668. package/test-vectors/catalog/positive/multicast-auth-rotation.json +55 -0
  669. package/test-vectors/catalog/positive/ticks-and-mixed-timescales.json +39 -0
  670. package/test-vectors/catalog/positive/track-role.json +49 -0
  671. package/test-vectors/catalog/releases/moqcast-native-v16-v1/catalog.json +65 -0
  672. package/test-vectors/catalog/releases/moqcast-native-v16-v1.archive.sha256 +1 -0
  673. package/test-vectors/catalog-multicast-auth-v1.json +26 -0
  674. package/test-vectors/catalog-validation.json +148 -0
  675. package/test-vectors/catalog-validator-public-api.mjs +25 -0
  676. package/test-vectors/generate-source-auth-fixture.mjs +114 -0
  677. package/test-vectors/source-auth-v1.json +61 -0
  678. package/test-vectors/subsample-placement-v1.json +230 -0
@@ -0,0 +1,1498 @@
1
+ import {
2
+ parseMMTPHeader,
3
+ extractMFUPayload,
4
+ extractFecPayloadId,
5
+ hasSourceFecPayloadId,
6
+ extractAltaTrailer,
7
+ PACKET_TYPE_MPU,
8
+ PACKET_TYPE_REPAIR,
9
+ } from '@blockcast/mmt-base'
10
+ import {
11
+ MFUReassembler,
12
+ MfuReassemblyError,
13
+ type MfuReassemblerBackend,
14
+ type MmtpSourcePath,
15
+ type MmtpDeliveryProvenance,
16
+ type ReassembledMFU,
17
+ } from './mfu-reassembler.js'
18
+ import {
19
+ SampleCombiner,
20
+ SampleCombineError,
21
+ type CombinedSample,
22
+ type SampleCombinerOptions,
23
+ type SampleCombinerStats,
24
+ } from './sample-combiner.js'
25
+ import { classifyH264Keyframe, mmtpTimestampToMicros } from './codec-utils.js'
26
+ import {
27
+ mmtpFrameReorderWindowFrames,
28
+ mfuReassemblyTimeoutMs,
29
+ type MfuReassemblyPolicy,
30
+ } from './mfu-reassembly-policy.js'
31
+ import {
32
+ IsoBmffTimingResolver,
33
+ mediaTicksToMicroseconds,
34
+ type IsoBmffSampleTiming,
35
+ } from './iso-bmff-timing.js'
36
+ import type { AuthoredTiming } from './authored-timing.js'
37
+
38
+ // ── Types ───────────────────────────────────────────────────────────
39
+
40
+ export interface TrackInfo {
41
+ type: 'video' | 'audio'
42
+ codec: string
43
+ /** Catalog/in-band frame cadence available to downstream media stages. */
44
+ framerate?: number
45
+ /** Catalog MMTP group duration available to downstream media stages. */
46
+ groupDurationMs?: number
47
+ }
48
+
49
+ export interface MmtpParserConfig {
50
+ tracks?: Map<number, TrackInfo>
51
+ reorderWindow?: number
52
+ /** MFU reassembly timeout (ms). Derive from FEC interleaveDepthMs or frame duration.
53
+ * Pass null for live clock-free operation; memory remains bounded by maxBuffered.
54
+ * Defaults to 1000ms if not provided. */
55
+ mfuTimeoutMs?: number | null
56
+ /** Explicit transport policy. Reliable MoQ is clock-free; datagrams require
57
+ * a catalog/in-band-derived deadline. Mutually exclusive with mfuTimeoutMs. */
58
+ reassemblyPolicy?: MfuReassemblyPolicy
59
+ /** Max concurrent active MFUs. A new first fragment is rejected at the bound. */
60
+ maxBufferedMfus?: number
61
+ /**
62
+ * Max subsample data units held for one partitioned sample (moqcast #37).
63
+ * A sample needs `ceil(sampleSize / (256 * maxPayload))` data units; at the
64
+ * ~1300-byte payloads this producer emits, one data unit spans ~327 KB, so
65
+ * the default admits a ~5 MB sample — beyond any 8K intra frame — while
66
+ * still bounding the per-insert overlap scan. Raise it only alongside a
67
+ * larger authored frame size, never to paper over a lost data unit.
68
+ */
69
+ maxSubsampleSegments?: number
70
+ /** Alternate backend. The TypeScript and Rust/WASM implementations enforce
71
+ * the same authored active-DU contract. */
72
+ reassembler?: MfuReassemblerBackend
73
+ /** Default track info for auto-discovered packetIds.
74
+ * When the parser receives an MMTP packet with an unregistered packetId,
75
+ * it creates a new track entry using this info. Required for correct
76
+ * keyframe detection with HEVC/AV1/VP9 (without it, defaults to H.264). */
77
+ defaultTrack?: TrackInfo
78
+ }
79
+
80
+ /** Structured result from parseHeader(). Used by MmtpRouter for two-phase parsing:
81
+ * 1. parseHeader() → get packetId + fecPayloadId for FEC feeding
82
+ * 2. feed() → MFU extraction, reassembly, keyframe detection, emit */
83
+ export interface PacketInfo {
84
+ packetId: number
85
+ packetType: number
86
+ timestamp: bigint
87
+ /** MMTP timestamp converted to monotonic microseconds for pacing/sync users. */
88
+ timestampUs: number
89
+ sequenceNumber: number
90
+ rapFlag: boolean
91
+ fecType: number
92
+ /** FEC source symbol ID (from 4-byte FEC Payload ID at packet end). Null if no FEC. */
93
+ fecPayloadId: { ssId: number } | null
94
+ /** True if this is a repair packet (packetType=0x03). */
95
+ isRepair: boolean
96
+ }
97
+
98
+ export interface FrameMeta {
99
+ /** Authored ISO-BMFF presentation time converted to microseconds. */
100
+ timestamp: number
101
+ /** Authored ISO-BMFF decode time converted to microseconds. */
102
+ decodeTimestamp?: number
103
+ /** Authored ISO-BMFF sample duration converted to microseconds. */
104
+ duration?: number
105
+ /** Exact authored decode time in mediaTimescale units. */
106
+ decodeTime?: bigint
107
+ /** Exact authored presentation time in mediaTimescale units. */
108
+ presentationTime?: bigint
109
+ /** Exact authored duration in mediaTimescale units. */
110
+ durationTicks?: number
111
+ /** mdhd media timescale for the exact timing fields. */
112
+ mediaTimescale?: number
113
+ /** Exact authored ISO-BMFF timing carried without narrowing to microseconds. */
114
+ authoredTiming?: AuthoredTiming
115
+ keyframe: boolean
116
+ packetId: number
117
+ trackType: 'video' | 'audio' | null
118
+ packetSequenceNumber?: number
119
+ /** Every authoritative MMTP source-packet sequence that formed this frame. */
120
+ packetSequenceNumbers?: number[]
121
+ /** Subset of packetSequenceNumbers reconstructed by FEC. */
122
+ repairedPacketSequenceNumbers?: number[]
123
+ /** Immutable catalog identity supplied by the owning ingress subscription. */
124
+ catalogTrackName?: string
125
+ /** Raw MMTP short-NTP timestamp retained for adapters that own the timeline. */
126
+ mmtpTimestamp?: bigint
127
+ mpuSequenceNumber?: number
128
+ movieFragmentSequenceNumber?: number
129
+ sampleNumber?: number
130
+ fragmentCount?: number
131
+ rapFlag?: boolean
132
+ priority?: number
133
+ recovered?: boolean
134
+ /** Starts a new publisher/source epoch for ordering and timestamp state. */
135
+ sourceDiscontinuity?: boolean
136
+ /** Source of the accepted FI=1/FI=0 frame path. Absent for recovery. */
137
+ source?: MmtpSourcePath
138
+ /**
139
+ * Strict whole-frame delivery provenance. Unlike `source`, which names the
140
+ * head fragment's transport for routing, this is folded over every
141
+ * contributing fragment (and, for FEC-recovered frames, over the transports
142
+ * that supplied the block's symbols). Only 'multicast' is evidence of
143
+ * multicast-exclusive delivery. Absent means no producer on this path
144
+ * recorded provenance — consumers MUST treat that as unproven, never as
145
+ * multicast.
146
+ */
147
+ deliveryProvenance?: MmtpDeliveryProvenance
148
+ }
149
+
150
+ export interface MmtpReorderStats {
151
+ droppedBeforeStartupKeyframe: number
152
+ droppedIncompleteStartupInterval: number
153
+ droppedStale: number
154
+ forcedWindowFlushes: number
155
+ droppedOnLiveDispose: number
156
+ publisherResets: number
157
+ droppedOnPublisherReset: number
158
+ bufferedFrames: number
159
+ }
160
+
161
+ export interface MmtpReorderFrameDiagnostics {
162
+ timestampUs: number
163
+ keyframe: boolean
164
+ sequenceNumber: number
165
+ packetSequenceNumber?: number
166
+ movieFragmentSequenceNumber?: number
167
+ sampleNumber?: number
168
+ fragmentCount?: number
169
+ }
170
+
171
+ export interface MmtpReorderStreamDiagnostics {
172
+ packetId: number
173
+ bufferedFrames: number
174
+ lastEmitted: MmtpReorderFrameDiagnostics | null
175
+ startupKeyframe: MmtpReorderFrameDiagnostics | null
176
+ head: MmtpReorderFrameDiagnostics[]
177
+ tail: MmtpReorderFrameDiagnostics[]
178
+ keyframes: MmtpReorderFrameDiagnostics[]
179
+ }
180
+
181
+ export type MmtpParserRecord =
182
+ | {
183
+ kind: 'repair' | 'non-media'
184
+ packet: PacketInfo
185
+ byteLength: number
186
+ }
187
+ | {
188
+ kind: 'init' | 'fragment'
189
+ packet: PacketInfo
190
+ data: Uint8Array
191
+ mpuSequenceNumber: number
192
+ fragmentationIndicator: number
193
+ fragmentCounter: number
194
+ }
195
+ | {
196
+ kind: 'pending'
197
+ packet: PacketInfo
198
+ mpuSequenceNumber: number
199
+ fragmentationIndicator: number
200
+ fragmentCounter: number
201
+ }
202
+ | {
203
+ kind: 'rejected'
204
+ packet: PacketInfo
205
+ mpuSequenceNumber: number
206
+ fragmentationIndicator: number
207
+ fragmentCounter: number
208
+ error: {
209
+ /**
210
+ * Either a single data unit's own fragment chain was rejected, or
211
+ * combining reassembled data units into a sample was (moqcast #37).
212
+ * Both are fail-closed drops of the same packet, so they share this
213
+ * record; the code says which class it came from.
214
+ */
215
+ code: MfuReassemblyError['code'] | SampleCombineError['code']
216
+ message: string
217
+ }
218
+ }
219
+ | {
220
+ kind: 'frame'
221
+ packet: PacketInfo
222
+ data: Uint8Array
223
+ meta: FrameMeta
224
+ mpuSequenceNumber: number
225
+ fragmentCount: number
226
+ rapFlag: boolean
227
+ priority?: number
228
+ }
229
+
230
+ type FrameCallback = (data: Uint8Array, meta: FrameMeta) => void
231
+ type InitCallback = (data: Uint8Array, packetId: number, catalogTrackName?: string) => void
232
+
233
+ export function isMfuKeyframeMetadata(rapFlag: boolean, priority?: number): boolean {
234
+ return rapFlag || (priority ?? 0) > 0x80
235
+ }
236
+
237
+ /** Per-packet detached ALTA authenticator trailer (08-07 Path 2).
238
+ * - `auth`: the authenticator bytes (length-prefixed at packet end).
239
+ * - `payloadEnd`: byte offset within the source packet where signed
240
+ * payload ends — consumers reconstruct `payload = packet[0..payloadEnd]`
241
+ * for verify_detached. */
242
+ export interface ParsedAltaTrailer {
243
+ auth: Uint8Array
244
+ payloadEnd: number
245
+ }
246
+
247
+ /** Source-symbol exposure for FEC consumers.
248
+ *
249
+ * Fires from {@link MmtpContainerParser.feed} once per FEC-protected source
250
+ * packet — after parseHeader confirms fec_payload_id is present, before MFU
251
+ * extraction.
252
+ *
253
+ * Arguments:
254
+ * - `packetId` – MMTP header `packet_id` (track identifier)
255
+ * - `ssId` – flat Source Symbol ID from the 4-byte fec_payload_id trailer
256
+ * - `rawSymbol` – packet bytes minus the 4-byte fec_payload_id trailer.
257
+ * NOT zero-padded to FEC symbol size — consumer pads if needed
258
+ * (FEC symbol size is FEC-config, not parser-config).
259
+ * - `alta` – detached ALTA authenticator if present, else undefined
260
+ *
261
+ * Designed so consumers (player, Shaka shim, upstream moqtail) can wire
262
+ * source-symbol delivery to whatever FEC pipeline they own, without depending
263
+ * on libmmt's MulticastManager/MmtpRouter/FECManager stack. */
264
+ type SourceSymbolCallback = (
265
+ packetId: number,
266
+ ssId: number,
267
+ rawSymbol: Uint8Array,
268
+ alta: ParsedAltaTrailer | undefined,
269
+ ) => void
270
+
271
+ export interface ParseHeaderOptions {
272
+ /** Recovered source symbols already had the trailing Source FEC Payload ID stripped. */
273
+ skipFecPayloadId?: boolean
274
+ /** Mark frames emitted from this packet as FEC-recovered. */
275
+ recovered?: boolean
276
+ /** Exact catalog track bound to this packet by its ingress subscription. */
277
+ catalogTrackName?: string
278
+ /** Source transport for parser-owned frame attribution. */
279
+ source?: MmtpSourcePath
280
+ /**
281
+ * Explicit strict provenance for the fragments this packet produces.
282
+ * The router sets it for FEC-recovered packets, which carry no `source`
283
+ * but whose FEC block's symbol provenance is known.
284
+ */
285
+ deliveryProvenance?: MmtpDeliveryProvenance
286
+ }
287
+
288
+ // ── FrameSequencer ──────────────────────────────────────────────────
289
+
290
+ interface PendingFrame {
291
+ data: Uint8Array
292
+ meta: FrameMeta
293
+ seq: number
294
+ }
295
+
296
+ interface SequencerStream {
297
+ buf: PendingFrame[]
298
+ lastEmitted: PendingFrame | null
299
+ }
300
+
301
+ function reorderFrameDiagnostics(frame: PendingFrame): MmtpReorderFrameDiagnostics {
302
+ return {
303
+ timestampUs: frame.meta.timestamp,
304
+ keyframe: frame.meta.keyframe,
305
+ sequenceNumber: frame.seq,
306
+ ...(frame.meta.packetSequenceNumber === undefined
307
+ ? {}
308
+ : { packetSequenceNumber: frame.meta.packetSequenceNumber }),
309
+ ...(frame.meta.movieFragmentSequenceNumber === undefined
310
+ ? {}
311
+ : { movieFragmentSequenceNumber: frame.meta.movieFragmentSequenceNumber }),
312
+ ...(frame.meta.sampleNumber === undefined ? {} : { sampleNumber: frame.meta.sampleNumber }),
313
+ ...(frame.meta.fragmentCount === undefined
314
+ ? {}
315
+ : { fragmentCount: frame.meta.fragmentCount }),
316
+ }
317
+ }
318
+
319
+ type SequencerStat = Exclude<keyof MmtpReorderStats, 'bufferedFrames'>
320
+
321
+ function serialSequenceDelta(a: number, b: number): number {
322
+ return (a - b) | 0
323
+ }
324
+
325
+ function compareFrameOrder(
326
+ a: PendingFrame,
327
+ b: PendingFrame,
328
+ ): number {
329
+ if (a.meta.decodeTime !== undefined && b.meta.decodeTime !== undefined &&
330
+ a.meta.mediaTimescale === b.meta.mediaTimescale) {
331
+ if (a.meta.decodeTime < b.meta.decodeTime) return -1
332
+ if (a.meta.decodeTime > b.meta.decodeTime) return 1
333
+ } else {
334
+ const decodeDelta = (a.meta.decodeTimestamp ?? a.meta.timestamp) -
335
+ (b.meta.decodeTimestamp ?? b.meta.timestamp)
336
+ if (decodeDelta !== 0) return decodeDelta
337
+ }
338
+ const movieDelta = serialSequenceDelta(
339
+ a.meta.movieFragmentSequenceNumber ?? 0,
340
+ b.meta.movieFragmentSequenceNumber ?? 0,
341
+ )
342
+ if (movieDelta !== 0) return movieDelta
343
+ const sampleDelta = (a.meta.sampleNumber ?? 0) - (b.meta.sampleNumber ?? 0)
344
+ return sampleDelta !== 0 ? sampleDelta : serialSequenceDelta(a.seq, b.seq)
345
+ }
346
+
347
+ class FrameSequencer {
348
+ #streams = new Map<number, SequencerStream>()
349
+ #windowSize: number
350
+ #emit: (data: Uint8Array, meta: FrameMeta) => void
351
+ #recordStat: (stat: SequencerStat, count?: number) => void
352
+
353
+ constructor(
354
+ windowSize: number,
355
+ emit: (data: Uint8Array, meta: FrameMeta) => void,
356
+ recordStat: (stat: SequencerStat, count?: number) => void = () => {},
357
+ ) {
358
+ this.#windowSize = windowSize
359
+ this.#emit = emit
360
+ this.#recordStat = recordStat
361
+ }
362
+
363
+ push(data: Uint8Array, meta: FrameMeta, seq: number): void {
364
+ if (this.#windowSize <= 0) {
365
+ this.#emit(data, meta)
366
+ return
367
+ }
368
+ const frame: PendingFrame = { data, meta: { ...meta }, seq }
369
+ const stream = this.#streamFor(meta.packetId)
370
+ if (meta.sourceDiscontinuity) {
371
+ this.#recordStat('publisherResets')
372
+ this.#recordStat('droppedOnPublisherReset', stream.buf.length)
373
+ stream.buf = []
374
+ stream.lastEmitted = null
375
+ frame.meta.sourceDiscontinuity = true
376
+ }
377
+ if (this.#isStale(stream, frame)) {
378
+ this.#recordStat('droppedStale')
379
+ return
380
+ }
381
+ if (meta.keyframe && stream.lastEmitted === null) {
382
+ // The first RAP establishes decoder sync. Discard any pre-RAP media and
383
+ // emit it immediately so startup does not pay the reorder-window delay.
384
+ this.#recordStat('droppedBeforeStartupKeyframe', stream.buf.length)
385
+ stream.buf = []
386
+ this.#emitFrame(stream, frame)
387
+ return
388
+ }
389
+ let lo = 0, hi = stream.buf.length
390
+ while (lo < hi) {
391
+ const mid = (lo + hi) >>> 1
392
+ if (compareFrameOrder(stream.buf[mid]!, frame) < 0) lo = mid + 1
393
+ else hi = mid
394
+ }
395
+ stream.buf.splice(lo, 0, frame)
396
+ while (stream.buf.length > this.#windowSize) this.#flushHead(stream)
397
+ }
398
+
399
+ flush(): void {
400
+ for (const packetId of [...this.#streams.keys()].sort((a, b) => a - b)) {
401
+ const stream = this.#streams.get(packetId)
402
+ while (stream && stream.buf.length > 0) this.#flushHead(stream)
403
+ }
404
+ }
405
+
406
+ get buffered(): number {
407
+ let total = 0
408
+ for (const stream of this.#streams.values()) total += stream.buf.length
409
+ return total
410
+ }
411
+
412
+ get diagnostics(): Omit<MmtpReorderStreamDiagnostics, 'packetId'>[] {
413
+ return [...this.#streams.values()].map(stream => ({
414
+ bufferedFrames: stream.buf.length,
415
+ lastEmitted: stream.lastEmitted ? reorderFrameDiagnostics(stream.lastEmitted) : null,
416
+ startupKeyframe: null,
417
+ head: stream.buf.slice(0, 4).map(reorderFrameDiagnostics),
418
+ tail: stream.buf.slice(-4).map(reorderFrameDiagnostics),
419
+ keyframes: stream.buf.filter(frame => frame.meta.keyframe).slice(0, 8)
420
+ .map(reorderFrameDiagnostics),
421
+ }))
422
+ }
423
+
424
+ discard(): void {
425
+ for (const stream of this.#streams.values()) {
426
+ this.#recordStat('droppedOnLiveDispose', stream.buf.length)
427
+ stream.buf = []
428
+ }
429
+ this.#streams.clear()
430
+ }
431
+
432
+ #streamFor(packetId: number): SequencerStream {
433
+ let stream = this.#streams.get(packetId)
434
+ if (!stream) {
435
+ stream = { buf: [], lastEmitted: null }
436
+ this.#streams.set(packetId, stream)
437
+ }
438
+ return stream
439
+ }
440
+
441
+ #flushHead(stream: SequencerStream): boolean {
442
+ const f = stream.buf.shift()
443
+ if (!f) return false
444
+ return this.#emitFrame(stream, f)
445
+ }
446
+
447
+ #isStale(stream: SequencerStream, frame: PendingFrame): boolean {
448
+ return stream.lastEmitted !== null &&
449
+ compareFrameOrder(frame, stream.lastEmitted) <= 0
450
+ }
451
+
452
+ #emitFrame(stream: SequencerStream, frame: PendingFrame): boolean {
453
+ if (this.#isStale(stream, frame)) {
454
+ this.#recordStat('droppedStale')
455
+ return false
456
+ }
457
+ stream.lastEmitted = frame
458
+ this.#emit(frame.data, frame.meta)
459
+ return true
460
+ }
461
+ }
462
+
463
+ // ── fMP4 box detection ──────────────────────────────────────────────
464
+
465
+ const FMPU_BOX_TYPES = new Set(['ftyp', 'moov', 'moof'])
466
+
467
+ function isFmp4Box(data: Uint8Array): boolean {
468
+ if (data.length < 8) return false
469
+ const type = String.fromCharCode(data[4]!, data[5]!, data[6]!, data[7]!)
470
+ return FMPU_BOX_TYPES.has(type)
471
+ }
472
+
473
+ interface TimingStreamState {
474
+ packetId: number
475
+ resolver: IsoBmffTimingResolver
476
+ }
477
+
478
+ // ── MmtpContainerParser ─────────────────────────────────────────────
479
+
480
+ export class MmtpContainerParser {
481
+ #tracks = new Map<number, TrackInfo>()
482
+ #defaultTrack: TrackInfo | null = null
483
+ #reassembler: MfuReassemblerBackend
484
+ /**
485
+ * Combines subsample data units back into whole samples (moqcast #37), one
486
+ * combiner per parser stream. Per-stream is required, not an optimization:
487
+ * {@link SampleCombiner} identifies a sample by
488
+ * `(catalogTrackName, mpu, movieFragment, sampleNumber)`, so two streams
489
+ * that carry no catalog identity would collide on every sample number and
490
+ * each would see the other's data units as duplicates of its own. The
491
+ * parser already isolates timing and reordering by this same stream key.
492
+ *
493
+ * Inert until a producer partitions — which it does the moment a frame
494
+ * exceeds one data unit's 256-payload capacity (~327 KB here, routinely
495
+ * exceeded by 4K/8K intra frames).
496
+ */
497
+ #sampleCombiners = new Map<string, SampleCombiner>()
498
+ #sampleCombinerOptions: SampleCombinerOptions
499
+ /** Counters folded in from combiners dropped by a track reset, so the
500
+ * totals below stay monotonic across a publisher change. */
501
+ #retiredSampleCombinerStats: SampleCombinerStats = {
502
+ combinedSamples: 0,
503
+ passthroughSamples: 0,
504
+ timedOutSamples: 0,
505
+ evictedSamples: 0,
506
+ }
507
+ #timingStreams = new Map<string, TimingStreamState>()
508
+ #maxPendingTimingSamples: number
509
+ #pendingTimingSamples = 0
510
+ #sequencers = new Map<string, { packetId: number; sequencer: FrameSequencer }>()
511
+ #reorderWindow = 0
512
+ #reassemblyPolicy: MfuReassemblyPolicy | null = null
513
+ #frameCb: FrameCallback | null = null
514
+ #initCb: InitCallback | null = null
515
+ #sourceSymbolCb: SourceSymbolCallback | null = null
516
+ #trackFrameCbs = new Map<number, FrameCallback>()
517
+ #reorderStats: Omit<MmtpReorderStats, 'bufferedFrames'> = {
518
+ droppedBeforeStartupKeyframe: 0,
519
+ droppedIncompleteStartupInterval: 0,
520
+ droppedStale: 0,
521
+ forcedWindowFlushes: 0,
522
+ droppedOnLiveDispose: 0,
523
+ publisherResets: 0,
524
+ droppedOnPublisherReset: 0,
525
+ }
526
+ /** Streams (keyed by physical parser stream, value is packetId) that have
527
+ * observed their first RAP under a reliable-moq policy. Reliable MoQ
528
+ * resolves to a zero parser reorder window (transport ordering is
529
+ * protocol-owned), so #sequencerFor returns null and its own startup gate
530
+ * never runs; this map preserves an order-independent "discard delta
531
+ * frames before the first RAP" gate for that path without reintroducing
532
+ * reordering into the parser. */
533
+ #reliableMoqStartupGate = new Map<string, number>()
534
+ #disposed = false
535
+
536
+ #streamKey(packetId: number, catalogTrackName?: string): string {
537
+ return JSON.stringify([packetId, catalogTrackName ?? null])
538
+ }
539
+
540
+ #sampleCombinerFor(streamKey: string): SampleCombiner {
541
+ let combiner = this.#sampleCombiners.get(streamKey)
542
+ if (combiner === undefined) {
543
+ combiner = new SampleCombiner(this.#sampleCombinerOptions)
544
+ this.#sampleCombiners.set(streamKey, combiner)
545
+ }
546
+ return combiner
547
+ }
548
+
549
+ /** Drop one stream's combiner, folding its counters into the retained
550
+ * totals so a reset cannot make the exposed stats run backwards. */
551
+ #dropSampleCombiner(streamKey: string): void {
552
+ const combiner = this.#sampleCombiners.get(streamKey)
553
+ if (combiner === undefined) return
554
+ const stats = combiner.stats
555
+ this.#retiredSampleCombinerStats.combinedSamples += stats.combinedSamples
556
+ this.#retiredSampleCombinerStats.passthroughSamples += stats.passthroughSamples
557
+ this.#retiredSampleCombinerStats.timedOutSamples += stats.timedOutSamples
558
+ this.#retiredSampleCombinerStats.evictedSamples += stats.evictedSamples
559
+ this.#sampleCombiners.delete(streamKey)
560
+ }
561
+
562
+ constructor(config?: MmtpParserConfig) {
563
+ if (config?.reassemblyPolicy && config.mfuTimeoutMs !== undefined) {
564
+ throw new Error('[MmtpContainerParser] use reassemblyPolicy or mfuTimeoutMs, not both')
565
+ }
566
+ const reassemblyPolicy = config?.reassemblyPolicy ?? null
567
+ const requestedReorderWindow = config?.reorderWindow ?? 0
568
+ if (!Number.isFinite(requestedReorderWindow) || requestedReorderWindow < 0) {
569
+ throw new Error('[MmtpContainerParser] reorderWindow must be a finite number >= 0')
570
+ }
571
+ const normalizedReorderWindow = Math.ceil(requestedReorderWindow)
572
+ const requestedMfuTimeout = reassemblyPolicy
573
+ ? mfuReassemblyTimeoutMs(reassemblyPolicy)
574
+ : config?.mfuTimeoutMs
575
+ if (reassemblyPolicy) {
576
+ mmtpFrameReorderWindowFrames(reassemblyPolicy, normalizedReorderWindow)
577
+ }
578
+ if (
579
+ requestedMfuTimeout !== undefined &&
580
+ requestedMfuTimeout !== null &&
581
+ requestedMfuTimeout <= 0
582
+ ) {
583
+ throw new Error('[MmtpContainerParser] mfuTimeoutMs must be > 0')
584
+ }
585
+ const maxPendingTimingSamples = config?.maxBufferedMfus ?? 100
586
+ if (!Number.isInteger(maxPendingTimingSamples) || maxPendingTimingSamples <= 0) {
587
+ throw new Error('[MmtpContainerParser] maxBufferedMfus must be a positive integer')
588
+ }
589
+ this.#maxPendingTimingSamples = maxPendingTimingSamples
590
+
591
+ const maxSubsampleSegments = config?.maxSubsampleSegments ?? 16
592
+ if (!Number.isInteger(maxSubsampleSegments) || maxSubsampleSegments <= 0) {
593
+ throw new Error('[MmtpContainerParser] maxSubsampleSegments must be a positive integer')
594
+ }
595
+
596
+ // Everything above only reads `config` — the full policy/window
597
+ // combination is validated before this point, so it is now safe to
598
+ // mutate this instance or an injected, caller-owned reassembler.
599
+ this.#reassemblyPolicy = reassemblyPolicy
600
+ if (config?.reassembler) {
601
+ this.#reassembler = config.reassembler
602
+ // An injected backend owns its existing transport policy unless this
603
+ // parser was given an explicit policy/legacy timeout override.
604
+ if (requestedMfuTimeout !== undefined) {
605
+ this.#reassembler.timeoutMs = requestedMfuTimeout
606
+ }
607
+ } else {
608
+ this.#reassembler = new MFUReassembler({
609
+ timeoutMs: requestedMfuTimeout === undefined ? 1000 : requestedMfuTimeout,
610
+ ...(config?.maxBufferedMfus === undefined ? {} : { maxBuffered: config.maxBufferedMfus }),
611
+ })
612
+ }
613
+ this.#reorderWindow = normalizedReorderWindow
614
+ this.#sampleCombinerOptions = {
615
+ maxBuffered: maxPendingTimingSamples,
616
+ // The parser resolves every timed sample's authored size from FT=1
617
+ // before its data units reach the combiner, so "no size yet" always
618
+ // means the size has not arrived -- never that the host declines to
619
+ // supply one. Waiting is therefore correct: treating an offset-zero
620
+ // data unit as a whole sample in that window would truncate a
621
+ // partitioned sample instead of completing it.
622
+ requireAuthoredSize: true,
623
+ maxSegmentsPerSample: maxSubsampleSegments,
624
+ }
625
+ this.#defaultTrack = config?.defaultTrack ?? null
626
+ if (this.#defaultTrack) this.#validateReassemblyPolicy()
627
+ if (config?.tracks) {
628
+ for (const [id, info] of config.tracks) {
629
+ this.#validateReassemblyPolicy()
630
+ this.#tracks.set(id, info)
631
+ }
632
+ }
633
+ }
634
+
635
+ /** The MFU reassembler that buffers fragmented MFUs fed via feed().
636
+ * Exposed so a co-operating FEC-recovery layer (MmtpRouter) feeds recovered
637
+ * fragments into the SAME reassembler: a frame split between source-received
638
+ * and FEC-recovered fragments must share one buffer to complete (BLO-8151).
639
+ * Before this was shared, recovered video symbols reported "0 emitted,
640
+ * pending-reassembly=N" because they landed in a separate instance. */
641
+ get reassembler(): MfuReassemblerBackend {
642
+ return this.#reassembler
643
+ }
644
+
645
+ /**
646
+ * Subsample-combining counters (moqcast #37). `combinedSamples` counts
647
+ * samples reassembled from two or more data units and `passthroughSamples`
648
+ * those that arrived whole; the two are deliberately separate, so a
649
+ * producer that silently stopped partitioning cannot hide behind a single
650
+ * "samples delivered" total. This is the typed acceptance signal for
651
+ * large-frame playback — never parse the console for it.
652
+ */
653
+ get sampleCombinerStats(): Readonly<SampleCombinerStats> {
654
+ const total = { ...this.#retiredSampleCombinerStats }
655
+ for (const combiner of this.#sampleCombiners.values()) {
656
+ const stats = combiner.stats
657
+ total.combinedSamples += stats.combinedSamples
658
+ total.passthroughSamples += stats.passthroughSamples
659
+ total.timedOutSamples += stats.timedOutSamples
660
+ total.evictedSamples += stats.evictedSamples
661
+ }
662
+ return total
663
+ }
664
+
665
+ get reorderStats(): Readonly<MmtpReorderStats> {
666
+ let bufferedFrames = 0
667
+ for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered
668
+ return { ...this.#reorderStats, bufferedFrames }
669
+ }
670
+
671
+ get reorderDiagnostics(): ReadonlyArray<MmtpReorderStreamDiagnostics> {
672
+ const streams: MmtpReorderStreamDiagnostics[] = []
673
+ for (const { packetId, sequencer } of this.#sequencers.values()) {
674
+ for (const diagnostics of sequencer.diagnostics) streams.push({ packetId, ...diagnostics })
675
+ }
676
+ return streams
677
+ }
678
+
679
+ /**
680
+ * Phase 1 of two-phase parsing. Returns structured header info for FEC feeding.
681
+ * Call this before feed() when you need to extract FEC payload ID and feed
682
+ * symbols to FECManager before MFU extraction.
683
+ * Returns null for malformed packets.
684
+ */
685
+ parseHeader(packet: Uint8Array, options?: ParseHeaderOptions): PacketInfo | null {
686
+ try {
687
+ const header = parseMMTPHeader(packet)
688
+ const isRepair = header.packetType === PACKET_TYPE_REPAIR
689
+ const fecPayloadId =
690
+ !options?.skipFecPayloadId &&
691
+ !isRepair && hasSourceFecPayloadId(packet)
692
+ ? extractFecPayloadId(packet)
693
+ : null
694
+ return {
695
+ packetId: header.packetId,
696
+ packetType: header.packetType,
697
+ timestamp: header.timestamp,
698
+ timestampUs: mmtpTimestampToMicros(header.timestamp),
699
+ sequenceNumber: header.sequenceNumber,
700
+ rapFlag: header.rapFlag,
701
+ fecType: header.fecType,
702
+ fecPayloadId,
703
+ isRepair,
704
+ }
705
+ } catch {
706
+ return null
707
+ }
708
+ }
709
+
710
+ /** Feed a complete MMTP packet. For two-phase use, call parseHeader() first. */
711
+ feed(packet: Uint8Array, options?: ParseHeaderOptions): MmtpParserRecord | null {
712
+ if (this.#disposed) return null
713
+
714
+ const info = this.parseHeader(packet, options)
715
+ if (!info) return null
716
+
717
+ if (info.packetType !== PACKET_TYPE_MPU) {
718
+ return {
719
+ kind: info.isRepair ? 'repair' : 'non-media',
720
+ packet: info,
721
+ byteLength: packet.byteLength,
722
+ }
723
+ }
724
+
725
+ // ── FEC source-symbol exposure ──
726
+ // Fire onSourceSymbol BEFORE MFU extraction so the consumer-side FEC
727
+ // pipeline (e.g. FecWorkerClient.feedSource) starts as early as possible.
728
+ // Source-only: parseHeader already gates fecPayloadId on !isRepair.
729
+ if (info.fecPayloadId !== null && this.#sourceSymbolCb) {
730
+ // Strip the 4-byte fec_payload_id trailer per ISO 23008-1 §C.5.2.
731
+ // Symbol-size zero-padding is the consumer's responsibility — FEC
732
+ // symbol size is a FEC-config concern, not a parser concern.
733
+ const rawSymbol = packet.subarray(0, packet.length - 4)
734
+ const altaTrailer = extractAltaTrailer(packet)
735
+ const alta = altaTrailer
736
+ ? { auth: altaTrailer.auth, payloadEnd: altaTrailer.signedEnd }
737
+ : undefined
738
+ try {
739
+ this.#sourceSymbolCb(info.packetId, info.fecPayloadId.ssId, rawSymbol, alta)
740
+ } catch {
741
+ // Source-symbol consumer threw — never block MFU extraction on
742
+ // FEC-pipeline errors. The frame path stays viable; the consumer
743
+ // is responsible for its own error surfacing.
744
+ }
745
+ }
746
+
747
+ const extraction = extractMFUPayload(packet, {
748
+ skipFecId: options?.skipFecPayloadId,
749
+ })
750
+ if (!extraction) return null
751
+
752
+ const { mpu, fragmentType, data, mfuDuHeader } = extraction
753
+ if (mpu.timedFlag !== 1) return null
754
+ const streamKey = this.#streamKey(info.packetId, options?.catalogTrackName)
755
+
756
+ if (fragmentType === 0) {
757
+ if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0) return null
758
+ const resolver = this.#timingResolver(streamKey, info.packetId, true)
759
+ if (!resolver?.addInitialization(data)) return null
760
+ this.#initCb?.(data, info.packetId, options?.catalogTrackName)
761
+ return {
762
+ kind: 'init',
763
+ packet: info,
764
+ data,
765
+ mpuSequenceNumber: mpu.mpuSequenceNumber,
766
+ fragmentationIndicator: mpu.fragmentationIndicator,
767
+ fragmentCounter: mpu.fragmentCounter,
768
+ }
769
+ }
770
+
771
+ if (fragmentType === 1) {
772
+ if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0) return null
773
+ const resolver = this.#timingResolver(streamKey, info.packetId, false)
774
+ if (!resolver) return null
775
+ const pendingBefore = resolver.pendingSampleCount
776
+ const remaining = this.#maxPendingTimingSamples - this.#pendingTimingSamples
777
+ if (!resolver.addMovieFragment(mpu.mpuSequenceNumber, data, remaining)) return null
778
+ this.#pendingTimingSamples += resolver.pendingSampleCount - pendingBefore
779
+ return {
780
+ kind: 'fragment',
781
+ packet: info,
782
+ data,
783
+ mpuSequenceNumber: mpu.mpuSequenceNumber,
784
+ fragmentationIndicator: mpu.fragmentationIndicator,
785
+ fragmentCounter: mpu.fragmentCounter,
786
+ }
787
+ }
788
+ if (fragmentType !== 2) return null
789
+
790
+ const resolver = this.#timingResolver(streamKey, info.packetId, false)
791
+ if (!resolver) return null
792
+ if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0) return null
793
+ if (mpu.fragmentationIndicator === 0 || mpu.fragmentationIndicator === 1) {
794
+ if (!mfuDuHeader || mfuDuHeader.movieFragmentSequenceNumber === 0 ||
795
+ mfuDuHeader.sampleNumber === 0) {
796
+ return null
797
+ }
798
+ const headTiming = resolver.sampleTiming(
799
+ mpu.mpuSequenceNumber,
800
+ mfuDuHeader.movieFragmentSequenceNumber,
801
+ mfuDuHeader.sampleNumber,
802
+ )
803
+ if (!headTiming) return null
804
+ // A data unit's authored base offset is its position within the sample
805
+ // (moqcast #37): 0 for a whole-sample DU, nonzero for one subsample DU
806
+ // among siblings. Sizes below are therefore relative to that base, not
807
+ // to the sample's start -- comparing a subsample DU against the whole
808
+ // sample's size is what dropped every partitioned 4K/8K frame.
809
+ const offset = mfuDuHeader.offset
810
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset >= headTiming.size) return null
811
+ const remaining = headTiming.size - offset
812
+ // FI=0 carries its data unit whole, in a single payload. Only a
813
+ // partitioned sample's FINAL data unit can be shorter than a full data
814
+ // unit -- every earlier one spans exactly 256 payloads and so is always
815
+ // fragmented -- which makes a whole-carried DU exactly the bytes left
816
+ // from its base. At offset 0 this is the pre-amendment check unchanged.
817
+ if (mpu.fragmentationIndicator === 0 && data.byteLength !== remaining) return null
818
+ if (mpu.fragmentationIndicator === 1 && data.byteLength >= remaining) return null
819
+
820
+ // Publish the authored size before this data unit can complete, so the
821
+ // combiner never has to guess whether an offset-zero DU is a whole
822
+ // sample. Registering can itself complete a sample whose data units all
823
+ // arrived while the size was still unknown; that sample is delivered
824
+ // through routeFrame like any other, and this packet's own fragment
825
+ // continues below.
826
+ const readyOnRegister = this.#sampleCombinerFor(streamKey).registerAuthoredSize(
827
+ options?.catalogTrackName,
828
+ mpu.mpuSequenceNumber,
829
+ mfuDuHeader.movieFragmentSequenceNumber,
830
+ mfuDuHeader.sampleNumber,
831
+ headTiming.size,
832
+ )
833
+ if (readyOnRegister) {
834
+ this.#emitCombinedSample(readyOnRegister, info, resolver, options?.recovered === true)
835
+ }
836
+ }
837
+
838
+ let assembled: ReassembledMFU | null
839
+ try {
840
+ assembled = this.#reassembler.addFragment({
841
+ fragmentationIndicator: mpu.fragmentationIndicator,
842
+ mpuSequenceNumber: mpu.mpuSequenceNumber,
843
+ fragmentCounter: mpu.fragmentCounter,
844
+ data,
845
+ timestamp: info.timestamp,
846
+ rapFlag: info.rapFlag,
847
+ priority: mfuDuHeader?.priority,
848
+ sampleNumber: mfuDuHeader?.sampleNumber,
849
+ movieFragmentSequenceNumber: mfuDuHeader?.movieFragmentSequenceNumber,
850
+ sampleOffset: mfuDuHeader?.offset,
851
+ packetSequenceNumber: info.sequenceNumber,
852
+ recovered: options?.recovered === true,
853
+ source: options?.source,
854
+ deliveryProvenance: options?.deliveryProvenance,
855
+ catalogTrackName: options?.catalogTrackName,
856
+ packetId: info.packetId,
857
+ })
858
+ } catch (error) {
859
+ if (!(error instanceof MfuReassemblyError)) throw error
860
+ console.warn(
861
+ `[MmtpContainerParser] rejected MFU fragment: code=${error.code} ` +
862
+ `mpu=${mpu.mpuSequenceNumber} fi=${mpu.fragmentationIndicator} ` +
863
+ `counter=${mpu.fragmentCounter}`,
864
+ )
865
+ return {
866
+ kind: 'rejected',
867
+ packet: info,
868
+ mpuSequenceNumber: mpu.mpuSequenceNumber,
869
+ fragmentationIndicator: mpu.fragmentationIndicator,
870
+ fragmentCounter: mpu.fragmentCounter,
871
+ error: { code: error.code, message: error.message },
872
+ }
873
+ }
874
+
875
+ if (!assembled) {
876
+ return {
877
+ kind: 'pending',
878
+ packet: info,
879
+ mpuSequenceNumber: mpu.mpuSequenceNumber,
880
+ fragmentationIndicator: mpu.fragmentationIndicator,
881
+ fragmentCounter: mpu.fragmentCounter,
882
+ }
883
+ }
884
+
885
+ const movieFragmentSequenceNumber = assembled.movieFragmentSequenceNumber
886
+ const sampleNumber = assembled.sampleNumber
887
+ if (movieFragmentSequenceNumber === undefined || sampleNumber === undefined) return null
888
+ // Resolve authored timing BEFORE the combiner sees this data unit. An
889
+ // assembled data unit whose sample identity has no authored timing is not
890
+ // a sample this stream declared, so it must be dropped outright -- letting
891
+ // it buffer would occupy a combiner slot and could evict a legitimate
892
+ // partitioned sample waiting on a sibling.
893
+ if (!resolver.sampleTiming(
894
+ assembled.mpuSequenceNumber,
895
+ movieFragmentSequenceNumber,
896
+ sampleNumber,
897
+ )) return null
898
+ if (assembled.mpuSequenceNumber !== mpu.mpuSequenceNumber) return null
899
+
900
+ // One reassembled data unit is not necessarily one whole sample. A
901
+ // partitioned sample completes only once every sibling data unit has
902
+ // arrived and they are byte-adjacent from zero to the authored size; an
903
+ // unpartitioned sample passes straight through with no buffering.
904
+ let combined: CombinedSample | null
905
+ try {
906
+ combined = this.#sampleCombinerFor(streamKey).addMfu(assembled)
907
+ } catch (error) {
908
+ if (!(error instanceof SampleCombineError)) throw error
909
+ console.warn(
910
+ `[MmtpContainerParser] rejected subsample data unit: code=${error.code} ` +
911
+ `mpu=${assembled.mpuSequenceNumber} sample=${sampleNumber} ` +
912
+ `offset=${assembled.sampleOffset ?? 0}`,
913
+ )
914
+ return {
915
+ kind: 'rejected',
916
+ packet: info,
917
+ mpuSequenceNumber: mpu.mpuSequenceNumber,
918
+ fragmentationIndicator: mpu.fragmentationIndicator,
919
+ fragmentCounter: mpu.fragmentCounter,
920
+ error: { code: error.code, message: error.message },
921
+ }
922
+ }
923
+ if (!combined) {
924
+ return {
925
+ kind: 'pending',
926
+ packet: info,
927
+ mpuSequenceNumber: mpu.mpuSequenceNumber,
928
+ fragmentationIndicator: mpu.fragmentationIndicator,
929
+ fragmentCounter: mpu.fragmentCounter,
930
+ }
931
+ }
932
+
933
+ return this.#emitCombinedSample(combined, info, resolver, options?.recovered === true)
934
+ }
935
+
936
+ /**
937
+ * Deliver one combined sample: resolve its authored timing, build frame
938
+ * metadata, retire the timing entry, and route it. Shared by the ordinary
939
+ * completion path and by the rarer completion that falls out of registering
940
+ * an authored size for data units that arrived before it.
941
+ */
942
+ #emitCombinedSample(
943
+ combined: CombinedSample,
944
+ info: PacketInfo,
945
+ resolver: IsoBmffTimingResolver,
946
+ recovered: boolean,
947
+ ): MmtpParserRecord | null {
948
+ const timing = resolver.sampleTiming(
949
+ combined.mpuSequenceNumber,
950
+ combined.movieFragmentSequenceNumber,
951
+ combined.sampleNumber,
952
+ )
953
+ if (!timing) return null
954
+ // The combiner already completes against the registered authored size;
955
+ // re-checking here keeps the unpartitioned passthrough path fail-closed
956
+ // on the same invariant rather than trusting it by construction.
957
+ if (combined.data.byteLength !== timing.size) return null
958
+
959
+ const meta = this.#buildFrameMeta(
960
+ combined,
961
+ info.packetId,
962
+ info.sequenceNumber,
963
+ timing,
964
+ recovered,
965
+ )
966
+ if (!meta) return null
967
+ if (!resolver.consumeSample(
968
+ combined.mpuSequenceNumber,
969
+ combined.movieFragmentSequenceNumber,
970
+ combined.sampleNumber,
971
+ timing,
972
+ )) return null
973
+ this.#pendingTimingSamples--
974
+ this.routeFrame(combined.data, meta)
975
+ return {
976
+ kind: 'frame',
977
+ packet: info,
978
+ data: combined.data,
979
+ meta,
980
+ mpuSequenceNumber: combined.mpuSequenceNumber,
981
+ fragmentCount: combined.fragmentCount,
982
+ rapFlag: combined.rapFlag,
983
+ ...(combined.priority === undefined ? {} : { priority: combined.priority }),
984
+ }
985
+ }
986
+
987
+ onInit(cb: InitCallback): void {
988
+ this.#initCb = cb
989
+ }
990
+
991
+ onFrame(cb: FrameCallback): void {
992
+ this.#frameCb = cb
993
+ }
994
+
995
+ /** Register a per-packet FEC source-symbol callback. Fires for each MMTP
996
+ * source MPU packet that carries a Source FEC Payload ID (ISO 23008-1 §C.5.2).
997
+ *
998
+ * Consumer responsibilities:
999
+ * - Zero-pad `rawSymbol` to the FEC `symbolSize` if shorter (FEC config is
1000
+ * out-of-band — typically from the CMSF catalog's `fec.symbolSize`).
1001
+ * - Decide oversized handling (rawSymbol.length > symbolSize is not FEC-
1002
+ * decodable; consumer typically skips that block's source-feed).
1003
+ * - Look up the right FEC consumer (e.g. FecWorkerClient) by packetId.
1004
+ *
1005
+ * Designed for spec-clean reuse: Shaka, upstream moqtail, or any consumer
1006
+ * that uses MmtpContainerParser can wire FEC source delivery without
1007
+ * pulling in MulticastManager/MmtpRouter/FECManager. */
1008
+ onSourceSymbol(cb: SourceSymbolCallback): void {
1009
+ this.#sourceSymbolCb = cb
1010
+ }
1011
+
1012
+ onTrackFrame(packetId: number, cb: FrameCallback): void {
1013
+ this.#trackFrameCbs.set(packetId, cb)
1014
+ }
1015
+
1016
+ setTrack(packetId: number, info: TrackInfo): void {
1017
+ this.#validateReassemblyPolicy()
1018
+ for (const [key, entry] of this.#sequencers) {
1019
+ if (entry.packetId !== packetId) continue
1020
+ entry.sequencer.flush()
1021
+ this.#sequencers.delete(key)
1022
+ }
1023
+ for (const [key, state] of this.#timingStreams) {
1024
+ if (state.packetId === packetId) this.#dropTimingStream(key)
1025
+ }
1026
+ for (const key of [...this.#sampleCombiners.keys()]) {
1027
+ if ((JSON.parse(key) as [number, string | null])[0] === packetId) {
1028
+ this.#dropSampleCombiner(key)
1029
+ }
1030
+ }
1031
+ for (const [key, gatedPacketId] of this.#reliableMoqStartupGate) {
1032
+ if (gatedPacketId === packetId) this.#reliableMoqStartupGate.delete(key)
1033
+ }
1034
+ this.#tracks.set(packetId, info)
1035
+ }
1036
+
1037
+ /** Drop one physical stream's ordering and MFU state for retained replay. */
1038
+ resetTrack(packetId: number, catalogTrackName: string): void {
1039
+ if (!catalogTrackName) {
1040
+ throw new Error('[MmtpContainerParser] catalogTrackName is required for track reset')
1041
+ }
1042
+ for (const [key, entry] of this.#sequencers) {
1043
+ if (key !== this.#streamKey(packetId, catalogTrackName)) continue
1044
+ entry.sequencer.discard()
1045
+ this.#sequencers.delete(key)
1046
+ }
1047
+ const streamKey = this.#streamKey(packetId, catalogTrackName)
1048
+ this.#dropTimingStream(streamKey)
1049
+ this.#reliableMoqStartupGate.delete(streamKey)
1050
+ this.#reassembler.resetStream(catalogTrackName)
1051
+ // Drop half-combined samples too: a partitioned sample whose siblings
1052
+ // straddle the reset would otherwise complete against a stale authored
1053
+ // size and hand the decoder bytes from two different publisher epochs.
1054
+ this.#dropSampleCombiner(streamKey)
1055
+ }
1056
+
1057
+ setMfuTimeoutMs(ms: number | null): void {
1058
+ if (ms !== null && ms <= 0) throw new Error('[MmtpContainerParser] mfuTimeoutMs must be > 0')
1059
+ this.#reassembler.timeoutMs = ms
1060
+ }
1061
+
1062
+ setReassemblyPolicy(policy: MfuReassemblyPolicy): void {
1063
+ const timeoutMs = mfuReassemblyTimeoutMs(policy)
1064
+ this.#validateReassemblyPolicy(policy)
1065
+ this.#flushSequencers()
1066
+ this.#reassemblyPolicy = policy
1067
+ this.#reliableMoqStartupGate.clear()
1068
+ this.setMfuTimeoutMs(timeoutMs)
1069
+ }
1070
+
1071
+ setReorderWindow(windowSize: number): void {
1072
+ if (!Number.isFinite(windowSize) || windowSize < 0) {
1073
+ throw new Error('[MmtpContainerParser] reorderWindow must be a finite number >= 0')
1074
+ }
1075
+ const nextWindow = Math.ceil(windowSize)
1076
+ this.#validateReassemblyPolicy(this.#reassemblyPolicy, nextWindow)
1077
+ this.#flushSequencers()
1078
+ this.#reorderWindow = nextWindow
1079
+ if (this.#reorderWindow <= 0) this.#sequencers.clear()
1080
+ }
1081
+
1082
+ /** Route an already-assembled frame through the same reorder/callback path
1083
+ * used by source packets. FEC recovery uses this to merge recovered frames
1084
+ * with direct source frames before the decoder sees them. */
1085
+ routeFrame(data: Uint8Array, meta: FrameMeta): void {
1086
+ const seq = meta.packetSequenceNumber
1087
+ const sequencer = this.#sequencerFor(meta.packetId, meta.trackType, meta.catalogTrackName)
1088
+ if (sequencer && seq !== undefined && Number.isFinite(seq)) {
1089
+ sequencer.push(data, meta, seq)
1090
+ return
1091
+ }
1092
+ if (this.#reassemblyPolicy?.transport === 'reliable-moq' &&
1093
+ meta.trackType === 'video' &&
1094
+ !this.#passReliableMoqStartupGate(meta)) {
1095
+ return
1096
+ }
1097
+ this.#emitFrame(data, meta)
1098
+ }
1099
+
1100
+ /**
1101
+ * Claim and return the authored timing for a recovered sample. Recovery does
1102
+ * not pass through the ordinary `feed()` completion path, so it must retire
1103
+ * the same bounded resolver entry once the router accepts the recovered
1104
+ * sample. A missing entry is a fail-closed signal: callers must not replace
1105
+ * it with an MMTP send timestamp or cadence guess.
1106
+ */
1107
+ consumeAuthoredTimingFor(
1108
+ packetId: number,
1109
+ mpuSequenceNumber: number,
1110
+ movieFragmentSequenceNumber: number | undefined,
1111
+ sampleNumber: number | undefined,
1112
+ catalogTrackName?: string,
1113
+ ): AuthoredTiming | null {
1114
+ if (movieFragmentSequenceNumber === undefined || sampleNumber === undefined) return null
1115
+ const resolver = this.#timingResolver(
1116
+ this.#streamKey(packetId, catalogTrackName),
1117
+ packetId,
1118
+ false,
1119
+ )
1120
+ const timing = resolver?.sampleTiming(
1121
+ mpuSequenceNumber,
1122
+ movieFragmentSequenceNumber,
1123
+ sampleNumber,
1124
+ )
1125
+ if (!timing || !resolver || !resolver.consumeSample(
1126
+ mpuSequenceNumber,
1127
+ movieFragmentSequenceNumber,
1128
+ sampleNumber,
1129
+ timing,
1130
+ )) return null
1131
+ this.#pendingTimingSamples--
1132
+ if (this.#pendingTimingSamples < 0) {
1133
+ throw new Error('[MmtpContainerParser] pending timing sample accounting underflow')
1134
+ }
1135
+ return {
1136
+ decodeTime: timing.decodeTime,
1137
+ presentationTime: timing.presentationTime,
1138
+ durationTicks: timing.duration,
1139
+ mediaTimescale: timing.timescale,
1140
+ }
1141
+ }
1142
+
1143
+ /** Order-independent reliable-moq startup gate: discard delta frames for a
1144
+ * stream until its first RAP is observed, without buffering/reordering. */
1145
+ #passReliableMoqStartupGate(meta: FrameMeta): boolean {
1146
+ const streamKey = this.#streamKey(meta.packetId, meta.catalogTrackName)
1147
+ if (meta.sourceDiscontinuity) this.#reliableMoqStartupGate.delete(streamKey)
1148
+ if (this.#reliableMoqStartupGate.has(streamKey)) return true
1149
+ if (!meta.keyframe) {
1150
+ this.#reorderStats.droppedBeforeStartupKeyframe++
1151
+ return false
1152
+ }
1153
+ this.#reliableMoqStartupGate.set(streamKey, meta.packetId)
1154
+ return true
1155
+ }
1156
+
1157
+ /** Emit all ordered frames currently buffered by the parser. Finite-stream
1158
+ * consumers may call this before dispose(); live cancellation should not. */
1159
+ flush(): void {
1160
+ if (this.#disposed) return
1161
+ this.#flushSequencers()
1162
+ }
1163
+
1164
+ dispose(): void {
1165
+ this.#disposed = true
1166
+ for (const { sequencer } of this.#sequencers.values()) sequencer.discard()
1167
+ this.#sequencers.clear()
1168
+ this.#timingStreams.clear()
1169
+ this.#pendingTimingSamples = 0
1170
+ this.#reliableMoqStartupGate.clear()
1171
+ this.#reassembler.reset()
1172
+ this.#frameCb = null
1173
+ this.#initCb = null
1174
+ this.#trackFrameCbs.clear()
1175
+ }
1176
+
1177
+ // ── Private ─────────────────────────────────────────────────────
1178
+
1179
+ #timingResolver(
1180
+ streamKey: string,
1181
+ packetId: number,
1182
+ create: boolean,
1183
+ ): IsoBmffTimingResolver | null {
1184
+ const existing = this.#timingStreams.get(streamKey)
1185
+ if (existing) return existing.resolver
1186
+ if (!create) return null
1187
+ const resolver = new IsoBmffTimingResolver(this.#maxPendingTimingSamples)
1188
+ this.#timingStreams.set(streamKey, { packetId, resolver })
1189
+ return resolver
1190
+ }
1191
+
1192
+ #dropTimingStream(streamKey: string): void {
1193
+ const state = this.#timingStreams.get(streamKey)
1194
+ if (!state) return
1195
+ this.#timingStreams.delete(streamKey)
1196
+ this.#pendingTimingSamples -= state.resolver.pendingSampleCount
1197
+ if (this.#pendingTimingSamples < 0) {
1198
+ throw new Error('[MmtpContainerParser] pending timing sample accounting underflow')
1199
+ }
1200
+ }
1201
+
1202
+ #validateReassemblyPolicy(
1203
+ policy: MfuReassemblyPolicy | null = this.#reassemblyPolicy,
1204
+ reorderWindow = this.#reorderWindow,
1205
+ ): void {
1206
+ if (!policy) return
1207
+ mmtpFrameReorderWindowFrames(policy, reorderWindow)
1208
+ }
1209
+
1210
+ #buildFrameMeta(
1211
+ assembled: CombinedSample,
1212
+ packetId: number,
1213
+ seq: number,
1214
+ timing: IsoBmffSampleTiming,
1215
+ recovered = false,
1216
+ ): FrameMeta | null {
1217
+ const { data, timestamp: mmtpTimestamp, rapFlag, priority } = assembled
1218
+
1219
+ if (isFmp4Box(data)) return null
1220
+ const presentationTimestamp = mediaTicksToMicroseconds(
1221
+ timing.presentationTime,
1222
+ timing.timescale,
1223
+ )
1224
+ const decodeTimestamp = mediaTicksToMicroseconds(timing.decodeTime, timing.timescale)
1225
+ const duration = mediaTicksToMicroseconds(BigInt(timing.duration), timing.timescale)
1226
+ if (presentationTimestamp === null || decodeTimestamp === null || duration === null ||
1227
+ duration <= 0) {
1228
+ return null
1229
+ }
1230
+
1231
+ let track = this.#tracks.get(packetId)
1232
+ if (!track && this.#defaultTrack) {
1233
+ track = this.#defaultTrack
1234
+ this.#tracks.set(packetId, track)
1235
+ }
1236
+ const codec = track?.codec ?? ''
1237
+
1238
+ const keyframe = MmtpContainerParser.isKeyframe(data, isMfuKeyframeMetadata(rapFlag, priority), codec)
1239
+
1240
+ const meta: FrameMeta = {
1241
+ timestamp: presentationTimestamp,
1242
+ decodeTimestamp,
1243
+ duration,
1244
+ decodeTime: timing.decodeTime,
1245
+ presentationTime: timing.presentationTime,
1246
+ durationTicks: timing.duration,
1247
+ mediaTimescale: timing.timescale,
1248
+ authoredTiming: {
1249
+ decodeTime: timing.decodeTime,
1250
+ presentationTime: timing.presentationTime,
1251
+ durationTicks: timing.duration,
1252
+ mediaTimescale: timing.timescale,
1253
+ },
1254
+ keyframe,
1255
+ packetId,
1256
+ trackType: track?.type ?? null,
1257
+ packetSequenceNumber: seq,
1258
+ ...(assembled.packetSequenceNumbers === undefined
1259
+ ? {}
1260
+ : { packetSequenceNumbers: assembled.packetSequenceNumbers }),
1261
+ ...(assembled.repairedPacketSequenceNumbers === undefined
1262
+ ? {}
1263
+ : { repairedPacketSequenceNumbers: assembled.repairedPacketSequenceNumbers }),
1264
+ ...(assembled.catalogTrackName === undefined
1265
+ ? {}
1266
+ : { catalogTrackName: assembled.catalogTrackName }),
1267
+ mmtpTimestamp,
1268
+ mpuSequenceNumber: assembled.mpuSequenceNumber,
1269
+ ...(assembled.movieFragmentSequenceNumber === undefined
1270
+ ? {}
1271
+ : { movieFragmentSequenceNumber: assembled.movieFragmentSequenceNumber }),
1272
+ ...(assembled.sampleNumber === undefined ? {} : { sampleNumber: assembled.sampleNumber }),
1273
+ fragmentCount: assembled.fragmentCount,
1274
+ rapFlag,
1275
+ ...(priority === undefined ? {} : { priority }),
1276
+ ...(assembled.source === undefined ? {} : { source: assembled.source }),
1277
+ deliveryProvenance: assembled.deliveryProvenance,
1278
+ }
1279
+ if (recovered || (assembled.repairedPacketSequenceNumbers?.length ?? 0) > 0) {
1280
+ meta.recovered = true
1281
+ }
1282
+
1283
+ return meta
1284
+ }
1285
+
1286
+ #sequencerFor(
1287
+ packetId: number,
1288
+ _trackType: FrameMeta['trackType'],
1289
+ catalogTrackName?: string,
1290
+ ): FrameSequencer | null {
1291
+ const windowSize = this.#reassemblyPolicy
1292
+ ? mmtpFrameReorderWindowFrames(this.#reassemblyPolicy, this.#reorderWindow)
1293
+ : this.#reorderWindow
1294
+ if (windowSize <= 0) return null
1295
+ const streamKey = this.#streamKey(packetId, catalogTrackName)
1296
+ let sequencer = this.#sequencers.get(streamKey)?.sequencer
1297
+ if (!sequencer) {
1298
+ sequencer = new FrameSequencer(windowSize, (data, meta) => {
1299
+ this.#emitFrame(data, meta)
1300
+ }, (stat, count = 1) => {
1301
+ this.#reorderStats[stat] += count
1302
+ })
1303
+ this.#sequencers.set(streamKey, { packetId, sequencer })
1304
+ }
1305
+ return sequencer
1306
+ }
1307
+
1308
+ #flushSequencers(): void {
1309
+ for (const { sequencer } of this.#sequencers.values()) sequencer.flush()
1310
+ this.#sequencers.clear()
1311
+ }
1312
+
1313
+ /** Authored presentation timestamp of the last emitted frame, in microseconds. */
1314
+ lastFrameTimestamp: number | undefined = undefined
1315
+
1316
+ #emitFrame(data: Uint8Array, meta: FrameMeta): void {
1317
+ this.lastFrameTimestamp = meta.timestamp
1318
+ const trackCb = this.#trackFrameCbs.get(meta.packetId)
1319
+ trackCb?.(data, meta)
1320
+ this.#frameCb?.(data, meta)
1321
+ }
1322
+
1323
+ // ── Static utilities ──────────────────────────────────────────
1324
+
1325
+ static isKeyframe(data: Uint8Array, rapFlag: boolean, codec = ''): boolean {
1326
+ const payloadKeyframe = MmtpContainerParser.#scanForKeyframe(data, codec)
1327
+ if (payloadKeyframe !== undefined) return payloadKeyframe
1328
+ return rapFlag
1329
+ }
1330
+
1331
+ static #scanForKeyframe(data: Uint8Array, codec: string): boolean | undefined {
1332
+ const c = codec.toLowerCase()
1333
+ if (data.length === 0) return undefined
1334
+
1335
+ if (c.startsWith('av01')) {
1336
+ return scanAv1ObusForKeyframe(data)
1337
+ }
1338
+
1339
+ if (c.startsWith('vp09') || c.startsWith('vp8')) {
1340
+ return (data[0]! & 0x04) === 0
1341
+ }
1342
+
1343
+ const isHevc = c.startsWith('hev1') || c.startsWith('hvc1')
1344
+ if (isHevc) {
1345
+ if (data.length < 5) return undefined
1346
+ if (startsWithAnnexB(data)) {
1347
+ return MmtpContainerParser.#scanAnnexBForKeyframe(data, true)
1348
+ }
1349
+ return MmtpContainerParser.#scanLengthPrefixedHevcForKeyframe(data)
1350
+ }
1351
+
1352
+ if (c === '' || c.startsWith('avc')) {
1353
+ return classifyH264Keyframe(data, { allowPartialAvcc: true })
1354
+ }
1355
+
1356
+ return undefined
1357
+ }
1358
+
1359
+ static #scanLengthPrefixedHevcForKeyframe(data: Uint8Array): boolean | undefined {
1360
+ let sawNal = false
1361
+ let offset = 0
1362
+ while (offset + 4 < data.length) {
1363
+ const nalLen =
1364
+ (data[offset]! << 24) |
1365
+ (data[offset + 1]! << 16) |
1366
+ (data[offset + 2]! << 8) |
1367
+ data[offset + 3]!
1368
+ offset += 4
1369
+ if (nalLen <= 0) return sawNal ? false : undefined
1370
+ // The router's sync probe may see only the first fragment of a large
1371
+ // length-prefixed NAL. The length can exceed this packet, but the NAL
1372
+ // header is still enough to classify IRAP/IDR and open the sync gate.
1373
+ if (offset >= data.length) return sawNal ? false : undefined
1374
+ sawNal = true
1375
+ const hevcType = (data[offset]! >> 1) & 0x3f
1376
+ if (hevcType >= 16 && hevcType <= 21) return true
1377
+ if (offset + nalLen > data.length) break
1378
+ offset += nalLen
1379
+ }
1380
+ return sawNal ? false : undefined
1381
+ }
1382
+
1383
+ static #scanAnnexBForKeyframe(data: Uint8Array, isHevc: boolean): boolean | undefined {
1384
+ let offset = 0
1385
+ let sawNal = false
1386
+ while (offset < data.length) {
1387
+ const start = findAnnexBStart(data, offset)
1388
+ if (!start) return sawNal ? false : undefined
1389
+ const headerOffset = start.offset + start.length
1390
+ if (headerOffset >= data.length) return sawNal ? false : undefined
1391
+ sawNal = true
1392
+ if (isHevc) {
1393
+ if (headerOffset + 1 >= data.length) return sawNal ? false : undefined
1394
+ const hevcType = (data[headerOffset]! >> 1) & 0x3f
1395
+ if (hevcType >= 16 && hevcType <= 21) return true
1396
+ } else {
1397
+ const nalType = data[headerOffset]! & 0x1f
1398
+ if (nalType === 5) return true
1399
+ }
1400
+ offset = headerOffset + 1
1401
+ }
1402
+ return sawNal ? false : undefined
1403
+ }
1404
+ }
1405
+
1406
+ function scanAv1ObusForKeyframe(data: Uint8Array): boolean | undefined {
1407
+ let offset = 0
1408
+ let sawSequenceHeader = false
1409
+ let sawFrame = false
1410
+
1411
+ while (offset < data.length) {
1412
+ const obu = readAv1Obu(data, offset)
1413
+ if (!obu) return sawFrame ? false : undefined
1414
+ offset = obu.nextOffset
1415
+
1416
+ if (obu.type === 1) {
1417
+ sawSequenceHeader = true
1418
+ continue
1419
+ }
1420
+ if (obu.type !== 3 && obu.type !== 6) continue
1421
+
1422
+ sawFrame = true
1423
+ if (!sawSequenceHeader) {
1424
+ continue
1425
+ }
1426
+
1427
+ const keyframe = readAv1FrameHeaderKeyframe(data.subarray(obu.payloadOffset, obu.payloadEnd))
1428
+ if (keyframe !== undefined) return keyframe
1429
+ }
1430
+
1431
+ return sawFrame ? false : undefined
1432
+ }
1433
+
1434
+ function readAv1Obu(
1435
+ data: Uint8Array,
1436
+ offset: number,
1437
+ ): { type: number; payloadOffset: number; payloadEnd: number; nextOffset: number } | null {
1438
+ if (offset >= data.length) return null
1439
+ const header = data[offset]!
1440
+ if ((header & 0x80) !== 0 || (header & 0x01) !== 0) return null
1441
+
1442
+ const type = (header >> 3) & 0x0f
1443
+ const extensionFlag = (header & 0x04) !== 0
1444
+ const hasSizeField = (header & 0x02) !== 0
1445
+ let cursor = offset + 1
1446
+ if (extensionFlag) cursor += 1
1447
+ if (cursor > data.length) return null
1448
+
1449
+ let payloadSize = data.length - cursor
1450
+ if (hasSizeField) {
1451
+ const leb = readLeb128(data, cursor)
1452
+ if (!leb) return null
1453
+ payloadSize = leb.value
1454
+ cursor = leb.nextOffset
1455
+ }
1456
+
1457
+ const payloadEnd = cursor + payloadSize
1458
+ if (payloadEnd > data.length) return null
1459
+ return { type, payloadOffset: cursor, payloadEnd, nextOffset: payloadEnd }
1460
+ }
1461
+
1462
+ function readLeb128(data: Uint8Array, offset: number): { value: number; nextOffset: number } | null {
1463
+ let value = 0
1464
+ for (let i = 0; i < 8; i++) {
1465
+ if (offset + i >= data.length) return null
1466
+ const byte = data[offset + i]!
1467
+ value += (byte & 0x7f) * (2 ** (i * 7))
1468
+ if ((byte & 0x80) === 0) return { value, nextOffset: offset + i + 1 }
1469
+ }
1470
+ return null
1471
+ }
1472
+
1473
+ function readAv1FrameHeaderKeyframe(payload: Uint8Array): boolean | undefined {
1474
+ if (payload.length === 0) return undefined
1475
+ const first = payload[0]!
1476
+ const showExistingFrame = (first & 0x80) !== 0
1477
+ if (showExistingFrame) return false
1478
+ const frameType = (first >> 5) & 0x03
1479
+ return frameType === 0
1480
+ }
1481
+
1482
+ function startsWithAnnexB(data: Uint8Array): boolean {
1483
+ return (
1484
+ (data.length >= 4 && data[0] === 0 && data[1] === 0 && data[2] === 1) ||
1485
+ (data.length >= 5 && data[0] === 0 && data[1] === 0 && data[2] === 0 && data[3] === 1)
1486
+ )
1487
+ }
1488
+
1489
+ function findAnnexBStart(data: Uint8Array, offset: number): { offset: number; length: 3 | 4 } | null {
1490
+ for (let i = offset; i + 3 <= data.length; i++) {
1491
+ if (data[i] !== 0 || data[i + 1] !== 0) continue
1492
+ if (data[i + 2] === 1) return { offset: i, length: 3 }
1493
+ if (i + 3 < data.length && data[i + 2] === 0 && data[i + 3] === 1) {
1494
+ return { offset: i, length: 4 }
1495
+ }
1496
+ }
1497
+ return null
1498
+ }