@blockcast/mmt-container 0.1.0-main.09c5546569f1

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