@blockcast/mmt-container 0.1.0 → 0.3.0-main.288475b64b3e
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.
- package/README.md +538 -0
- package/dist/.build-stamp +0 -0
- package/dist/__tests__/test-utils.d.ts +71 -0
- package/dist/__tests__/test-utils.d.ts.map +1 -0
- package/dist/align.d.ts +152 -0
- package/dist/align.d.ts.map +1 -0
- package/dist/align.js +88 -0
- package/dist/align.js.map +7 -0
- package/dist/audio-init-parser.d.ts +66 -0
- package/dist/audio-init-parser.d.ts.map +1 -0
- package/dist/audio-init-parser.js +118 -0
- package/dist/audio-init-parser.js.map +7 -0
- package/dist/audio-ring-buffer.d.ts +50 -0
- package/dist/audio-ring-buffer.d.ts.map +1 -0
- package/dist/audio-ring-buffer.js +80 -0
- package/dist/audio-ring-buffer.js.map +7 -0
- package/dist/authored-timing.d.ts +32 -0
- package/dist/authored-timing.d.ts.map +1 -0
- package/dist/authored-timing.js +32 -0
- package/dist/authored-timing.js.map +7 -0
- package/dist/block-policy.d.ts +314 -0
- package/dist/block-policy.d.ts.map +1 -0
- package/dist/block-policy.js +466 -0
- package/dist/block-policy.js.map +7 -0
- package/dist/browser-runtime.js +26457 -0
- package/dist/browser-runtime.js.map +7 -0
- package/dist/carp-catalog.d.ts +445 -0
- package/dist/carp-catalog.d.ts.map +1 -0
- package/dist/carp-catalog.js +998 -0
- package/dist/carp-catalog.js.map +7 -0
- package/dist/catalog-fec-params.d.ts +315 -0
- package/dist/catalog-fec-params.d.ts.map +1 -0
- package/dist/catalog-fec-params.js +730 -0
- package/dist/catalog-fec-params.js.map +7 -0
- package/dist/catalog-network-source.d.ts +42 -0
- package/dist/catalog-network-source.d.ts.map +1 -0
- package/dist/catalog-network-source.js +46 -0
- package/dist/catalog-network-source.js.map +7 -0
- package/dist/catalog-validator.d.ts +18 -0
- package/dist/catalog-validator.d.ts.map +1 -0
- package/dist/catalog-validator.js +7747 -0
- package/dist/catalog-validator.js.map +7 -0
- package/dist/cmaf-block-assembler.d.ts +285 -0
- package/dist/cmaf-block-assembler.d.ts.map +1 -0
- package/dist/cmaf-block-assembler.js +1966 -0
- package/dist/cmaf-block-assembler.js.map +7 -0
- package/dist/cmaf-group-batcher.d.ts +38 -0
- package/dist/cmaf-group-batcher.d.ts.map +1 -0
- package/dist/cmaf-group-batcher.js +176 -0
- package/dist/cmaf-group-batcher.js.map +7 -0
- package/dist/cmcd-producer.d.ts +104 -0
- package/dist/cmcd-producer.d.ts.map +1 -0
- package/dist/cmcd-producer.js +183 -0
- package/dist/cmcd-producer.js.map +7 -0
- package/dist/codec-utils.d.ts +384 -0
- package/dist/codec-utils.d.ts.map +1 -0
- package/dist/codec-utils.js +1546 -0
- package/dist/codec-utils.js.map +7 -0
- package/dist/conceal-timeline.d.ts +146 -0
- package/dist/conceal-timeline.d.ts.map +1 -0
- package/dist/conceal-timeline.js +137 -0
- package/dist/conceal-timeline.js.map +7 -0
- package/dist/delivery-gap.d.ts +75 -0
- package/dist/delivery-gap.d.ts.map +1 -0
- package/dist/delivery-gap.js +88 -0
- package/dist/delivery-gap.js.map +7 -0
- package/dist/duplicate-ledger.d.ts +197 -0
- package/dist/duplicate-ledger.d.ts.map +1 -0
- package/dist/duplicate-ledger.js +226 -0
- package/dist/duplicate-ledger.js.map +7 -0
- package/dist/fec-codec.d.ts +32 -0
- package/dist/fec-codec.d.ts.map +1 -0
- package/dist/fec-codec.js +47 -0
- package/dist/fec-codec.js.map +7 -0
- package/dist/fec-manager.d.ts +589 -0
- package/dist/fec-manager.d.ts.map +1 -0
- package/dist/fec-manager.js +3840 -0
- package/dist/fec-manager.js.map +7 -0
- package/dist/fec-starvation-detector.d.ts +59 -0
- package/dist/fec-starvation-detector.d.ts.map +1 -0
- package/dist/fec-starvation-detector.js +60 -0
- package/dist/fec-starvation-detector.js.map +7 -0
- package/dist/fec-state.d.ts +140 -0
- package/dist/fec-state.d.ts.map +1 -0
- package/dist/fec-state.js +86 -0
- package/dist/fec-state.js.map +7 -0
- package/dist/fec-worker-setup.d.ts +100 -0
- package/dist/fec-worker-setup.d.ts.map +1 -0
- package/dist/fec-worker-setup.js +671 -0
- package/dist/fec-worker-setup.js.map +7 -0
- package/dist/fmp4-completion.d.ts +30 -0
- package/dist/fmp4-completion.d.ts.map +1 -0
- package/dist/fmp4-completion.js +21 -0
- package/dist/fmp4-completion.js.map +7 -0
- package/dist/fmp4-reader.d.ts +37 -0
- package/dist/fmp4-reader.d.ts.map +1 -0
- package/dist/fmp4-reader.js +100 -0
- package/dist/fmp4-reader.js.map +7 -0
- package/dist/index.d.ts +44 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +464 -14
- package/dist/index.js.map +7 -1
- package/dist/iso-block-locator.d.ts +118 -0
- package/dist/iso-block-locator.d.ts.map +1 -0
- package/dist/iso-block-locator.js +288 -0
- package/dist/iso-block-locator.js.map +7 -0
- package/dist/iso-bmff-timing.d.ts +78 -0
- package/dist/iso-bmff-timing.d.ts.map +1 -0
- package/dist/iso-bmff-timing.js +593 -0
- package/dist/iso-bmff-timing.js.map +7 -0
- package/dist/loc.d.ts +70 -75
- package/dist/loc.d.ts.map +1 -1
- package/dist/loc.js +106 -192
- package/dist/loc.js.map +7 -1
- package/dist/manifest-auth-plan.d.ts +30 -0
- package/dist/manifest-auth-plan.d.ts.map +1 -0
- package/dist/manifest-auth-plan.js +1266 -0
- package/dist/manifest-auth-plan.js.map +7 -0
- package/dist/media-router.d.ts +262 -0
- package/dist/media-router.d.ts.map +1 -0
- package/dist/media-router.js +584 -0
- package/dist/media-router.js.map +7 -0
- package/dist/mfu-reassembler.d.ts +313 -9
- package/dist/mfu-reassembler.d.ts.map +1 -1
- package/dist/mfu-reassembler.js +3050 -285
- package/dist/mfu-reassembler.js.map +7 -1
- package/dist/mfu-reassembly-policy.d.ts +24 -0
- package/dist/mfu-reassembly-policy.d.ts.map +1 -0
- package/dist/mfu-reassembly-policy.js +99 -0
- package/dist/mfu-reassembly-policy.js.map +7 -0
- package/dist/mmtp-container-parser.d.ts +573 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -0
- package/dist/mmtp-container-parser.js +6601 -0
- package/dist/mmtp-container-parser.js.map +7 -0
- package/dist/mmtp-container-parser.no-modules.js +9645 -0
- package/dist/mmtp-container-parser.no-modules.js.map +7 -0
- package/dist/mmtp-decode-pipeline.d.ts +138 -0
- package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
- package/dist/mmtp-decode-pipeline.js +6835 -0
- package/dist/mmtp-decode-pipeline.js.map +7 -0
- package/dist/mmtp-router.d.ts +702 -0
- package/dist/mmtp-router.d.ts.map +1 -0
- package/dist/mmtp-router.js +11193 -0
- package/dist/mmtp-router.js.map +7 -0
- package/dist/mmtp-to-cmaf.d.ts +187 -0
- package/dist/mmtp-to-cmaf.d.ts.map +1 -0
- package/dist/mmtp-to-cmaf.js +716 -0
- package/dist/mmtp-to-cmaf.js.map +7 -0
- package/dist/multicast-auth-catalog.d.ts +115 -0
- package/dist/multicast-auth-catalog.d.ts.map +1 -0
- package/dist/multicast-auth-catalog.js +202 -0
- package/dist/multicast-auth-catalog.js.map +7 -0
- package/dist/multicast-input.d.ts +141 -0
- package/dist/multicast-input.d.ts.map +1 -0
- package/dist/multicast-input.js +389 -0
- package/dist/multicast-input.js.map +7 -0
- package/dist/multicast-manager.d.ts +699 -0
- package/dist/multicast-manager.d.ts.map +1 -0
- package/dist/multicast-manager.js +19120 -0
- package/dist/multicast-manager.js.map +7 -0
- package/dist/multicast-transport.d.ts +271 -0
- package/dist/multicast-transport.d.ts.map +1 -0
- package/dist/multicast-transport.js +2262 -0
- package/dist/multicast-transport.js.map +7 -0
- package/dist/multicast-unwrap.d.ts +57 -0
- package/dist/multicast-unwrap.d.ts.map +1 -0
- package/dist/multicast-unwrap.js +417 -0
- package/dist/multicast-unwrap.js.map +7 -0
- package/dist/no-modules.d.ts +10 -0
- package/dist/no-modules.d.ts.map +1 -0
- package/dist/no-modules.js +9520 -0
- package/dist/no-modules.js.map +7 -0
- package/dist/phase14-catalog-override.d.ts +126 -0
- package/dist/phase14-catalog-override.d.ts.map +1 -0
- package/dist/phase14-catalog-override.js +155 -0
- package/dist/phase14-catalog-override.js.map +7 -0
- package/dist/repair-fetch.d.ts +281 -0
- package/dist/repair-fetch.d.ts.map +1 -0
- package/dist/repair-fetch.js +391 -0
- package/dist/repair-fetch.js.map +7 -0
- package/dist/repair-layers.d.ts +169 -0
- package/dist/repair-layers.d.ts.map +1 -0
- package/dist/repair-layers.js +351 -0
- package/dist/repair-layers.js.map +7 -0
- package/dist/sample-combiner.d.ts +212 -0
- package/dist/sample-combiner.d.ts.map +1 -0
- package/dist/sample-combiner.js +595 -0
- package/dist/sample-combiner.js.map +7 -0
- package/dist/signaling.d.ts +29 -0
- package/dist/signaling.d.ts.map +1 -0
- package/dist/signaling.js +568 -0
- package/dist/signaling.js.map +7 -0
- package/dist/source-auth.d.ts +2 -0
- package/dist/source-auth.d.ts.map +1 -0
- package/dist/source-auth.js +1209 -0
- package/dist/source-auth.js.map +7 -0
- package/dist/switching-set-alignment.d.ts +61 -0
- package/dist/switching-set-alignment.d.ts.map +1 -0
- package/dist/switching-set-alignment.js +70 -0
- package/dist/switching-set-alignment.js.map +7 -0
- package/dist/timestamp-codec.d.ts +34 -0
- package/dist/timestamp-codec.d.ts.map +1 -0
- package/dist/timestamp-codec.js +472 -0
- package/dist/timestamp-codec.js.map +7 -0
- package/dist/track-pipeline-setup.d.ts +83 -0
- package/dist/track-pipeline-setup.d.ts.map +1 -0
- package/dist/track-pipeline-setup.js +291 -0
- package/dist/track-pipeline-setup.js.map +7 -0
- package/dist/track-pipeline.d.ts +121 -0
- package/dist/track-pipeline.d.ts.map +1 -0
- package/dist/track-pipeline.js +209 -0
- package/dist/track-pipeline.js.map +7 -0
- package/dist/track-session.d.ts +414 -0
- package/dist/track-session.d.ts.map +1 -0
- package/dist/track-session.js +754 -0
- package/dist/track-session.js.map +7 -0
- package/dist/transport-pool.d.ts +69 -0
- package/dist/transport-pool.d.ts.map +1 -0
- package/dist/transport-pool.js +74 -0
- package/dist/transport-pool.js.map +7 -0
- package/dist/wasm-mfu-reassembler.d.ts +97 -0
- package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
- package/dist/wasm-mfu-reassembler.js +2132 -0
- package/dist/wasm-mfu-reassembler.js.map +7 -0
- package/dist/wasm-mmtp-container-parser.d.ts +10 -0
- package/dist/wasm-mmtp-container-parser.d.ts.map +1 -0
- package/dist/wasm-mmtp-container-parser.js +6855 -0
- package/dist/wasm-mmtp-container-parser.js.map +7 -0
- package/node_modules/@blockcast/fec-worker/README.md +223 -0
- package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts +22 -0
- package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/block-order.js +48 -0
- package/node_modules/@blockcast/fec-worker/dist/block-order.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +92 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +345 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +373 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +11 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +14 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +764 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +7 -0
- package/node_modules/@blockcast/fec-worker/dist/index.d.ts +10 -0
- package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/index.js +8 -0
- package/node_modules/@blockcast/fec-worker/dist/index.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +20 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +370 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +7 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +104 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +181 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +194 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +62 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +9 -0
- package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/transfer.js +13 -0
- package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/package.json +59 -0
- package/node_modules/@blockcast/fec-worker/src/block-order.ts +59 -0
- package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +391 -0
- package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +392 -0
- package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +1386 -0
- package/node_modules/@blockcast/fec-worker/src/index.ts +35 -0
- package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +589 -0
- package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +222 -0
- package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +209 -0
- package/node_modules/@blockcast/fec-worker/src/transfer.ts +12 -0
- package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
- package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
- package/node_modules/@blockcast/mmt-base/README.md +134 -0
- package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +55 -0
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +132 -0
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts +7 -0
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/index.js +7 -0
- package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +292 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js +612 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +18 -0
- package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/net-constants.js +18 -0
- package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +47 -0
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +54 -0
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +28 -0
- package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/serial-number.js +44 -0
- package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +314 -0
- package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/signalling.js +637 -0
- package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/package.json +49 -0
- package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +145 -0
- package/node_modules/@blockcast/mmt-base/src/index.ts +6 -0
- package/node_modules/@blockcast/mmt-base/src/mmtp.ts +892 -0
- package/node_modules/@blockcast/mmt-base/src/net-constants.ts +17 -0
- package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +67 -0
- package/node_modules/@blockcast/mmt-base/src/serial-number.ts +49 -0
- package/node_modules/@blockcast/mmt-base/src/signalling.ts +860 -0
- package/node_modules/@blockcast/mmt-fec/README.md +271 -0
- package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +82 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +296 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +105 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +222 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +46 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +96 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +33 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +57 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +149 -0
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +161 -0
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +28 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.js +28 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +20 -0
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +28 -0
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +258 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.js +382 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +41 -0
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +81 -0
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts +82 -0
- package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/repair-group.js +170 -0
- package/node_modules/@blockcast/mmt-fec/dist/repair-group.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +134 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +55 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +132 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +7 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +7 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +292 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +612 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +18 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js +18 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +47 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js +54 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +28 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js +44 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +314 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js +637 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +49 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +145 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +6 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +892 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/net-constants.ts +17 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/ntp-short.ts +67 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/serial-number.ts +49 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/signalling.ts +860 -0
- package/node_modules/@blockcast/mmt-fec/package.json +79 -0
- package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +371 -0
- package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +360 -0
- package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +127 -0
- package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +68 -0
- package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +225 -0
- package/node_modules/@blockcast/mmt-fec/src/index.ts +93 -0
- package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +34 -0
- package/node_modules/@blockcast/mmt-fec/src/oti.ts +463 -0
- package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +101 -0
- package/node_modules/@blockcast/mmt-fec/src/repair-group.ts +195 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +28 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +128 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +4 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +4 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +37 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +179 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +99 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +492 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/package.json +45 -0
- package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +171 -0
- package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +3 -0
- package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +230 -0
- package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +669 -0
- package/node_modules/@noble/hashes/LICENSE +21 -0
- package/node_modules/@noble/hashes/README.md +521 -0
- package/node_modules/@noble/hashes/_assert.d.ts +17 -0
- package/node_modules/@noble/hashes/_assert.d.ts.map +1 -0
- package/node_modules/@noble/hashes/_assert.js +18 -0
- package/node_modules/@noble/hashes/_assert.js.map +1 -0
- package/node_modules/@noble/hashes/_blake.d.ts +14 -0
- package/node_modules/@noble/hashes/_blake.d.ts.map +1 -0
- package/node_modules/@noble/hashes/_blake.js +50 -0
- package/node_modules/@noble/hashes/_blake.js.map +1 -0
- package/node_modules/@noble/hashes/_md.d.ts +51 -0
- package/node_modules/@noble/hashes/_md.d.ts.map +1 -0
- package/node_modules/@noble/hashes/_md.js +162 -0
- package/node_modules/@noble/hashes/_md.js.map +1 -0
- package/node_modules/@noble/hashes/_u64.d.ts +55 -0
- package/node_modules/@noble/hashes/_u64.d.ts.map +1 -0
- package/node_modules/@noble/hashes/_u64.js +90 -0
- package/node_modules/@noble/hashes/_u64.js.map +1 -0
- package/node_modules/@noble/hashes/argon2.d.ts +32 -0
- package/node_modules/@noble/hashes/argon2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/argon2.js +401 -0
- package/node_modules/@noble/hashes/argon2.js.map +1 -0
- package/node_modules/@noble/hashes/blake1.d.ts +106 -0
- package/node_modules/@noble/hashes/blake1.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake1.js +459 -0
- package/node_modules/@noble/hashes/blake1.js.map +1 -0
- package/node_modules/@noble/hashes/blake2.d.ts +116 -0
- package/node_modules/@noble/hashes/blake2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake2.js +420 -0
- package/node_modules/@noble/hashes/blake2.js.map +1 -0
- package/node_modules/@noble/hashes/blake2b.d.ts +11 -0
- package/node_modules/@noble/hashes/blake2b.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake2b.js +14 -0
- package/node_modules/@noble/hashes/blake2b.js.map +1 -0
- package/node_modules/@noble/hashes/blake2s.d.ts +20 -0
- package/node_modules/@noble/hashes/blake2s.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake2s.js +24 -0
- package/node_modules/@noble/hashes/blake2s.js.map +1 -0
- package/node_modules/@noble/hashes/blake3.d.ts +54 -0
- package/node_modules/@noble/hashes/blake3.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake3.js +255 -0
- package/node_modules/@noble/hashes/blake3.js.map +1 -0
- package/node_modules/@noble/hashes/crypto.d.ts +2 -0
- package/node_modules/@noble/hashes/crypto.d.ts.map +1 -0
- package/node_modules/@noble/hashes/crypto.js +5 -0
- package/node_modules/@noble/hashes/crypto.js.map +1 -0
- package/node_modules/@noble/hashes/cryptoNode.d.ts +2 -0
- package/node_modules/@noble/hashes/cryptoNode.d.ts.map +1 -0
- package/node_modules/@noble/hashes/cryptoNode.js +18 -0
- package/node_modules/@noble/hashes/cryptoNode.js.map +1 -0
- package/node_modules/@noble/hashes/eskdf.d.ts +47 -0
- package/node_modules/@noble/hashes/eskdf.d.ts.map +1 -0
- package/node_modules/@noble/hashes/eskdf.js +166 -0
- package/node_modules/@noble/hashes/eskdf.js.map +1 -0
- package/node_modules/@noble/hashes/esm/_assert.d.ts +17 -0
- package/node_modules/@noble/hashes/esm/_assert.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/_assert.js +15 -0
- package/node_modules/@noble/hashes/esm/_assert.js.map +1 -0
- package/node_modules/@noble/hashes/esm/_blake.d.ts +14 -0
- package/node_modules/@noble/hashes/esm/_blake.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/_blake.js +45 -0
- package/node_modules/@noble/hashes/esm/_blake.js.map +1 -0
- package/node_modules/@noble/hashes/esm/_md.d.ts +51 -0
- package/node_modules/@noble/hashes/esm/_md.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/_md.js +155 -0
- package/node_modules/@noble/hashes/esm/_md.js.map +1 -0
- package/node_modules/@noble/hashes/esm/_u64.d.ts +55 -0
- package/node_modules/@noble/hashes/esm/_u64.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/_u64.js +67 -0
- package/node_modules/@noble/hashes/esm/_u64.js.map +1 -0
- package/node_modules/@noble/hashes/esm/argon2.d.ts +32 -0
- package/node_modules/@noble/hashes/esm/argon2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/argon2.js +392 -0
- package/node_modules/@noble/hashes/esm/argon2.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake1.d.ts +106 -0
- package/node_modules/@noble/hashes/esm/blake1.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake1.js +452 -0
- package/node_modules/@noble/hashes/esm/blake1.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2.d.ts +116 -0
- package/node_modules/@noble/hashes/esm/blake2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2.js +413 -0
- package/node_modules/@noble/hashes/esm/blake2.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2b.d.ts +11 -0
- package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2b.js +11 -0
- package/node_modules/@noble/hashes/esm/blake2b.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2s.d.ts +20 -0
- package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2s.js +21 -0
- package/node_modules/@noble/hashes/esm/blake2s.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake3.d.ts +54 -0
- package/node_modules/@noble/hashes/esm/blake3.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake3.js +251 -0
- package/node_modules/@noble/hashes/esm/blake3.js.map +1 -0
- package/node_modules/@noble/hashes/esm/crypto.d.ts +2 -0
- package/node_modules/@noble/hashes/esm/crypto.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/crypto.js +2 -0
- package/node_modules/@noble/hashes/esm/crypto.js.map +1 -0
- package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +2 -0
- package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/cryptoNode.js +15 -0
- package/node_modules/@noble/hashes/esm/cryptoNode.js.map +1 -0
- package/node_modules/@noble/hashes/esm/eskdf.d.ts +47 -0
- package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/eskdf.js +160 -0
- package/node_modules/@noble/hashes/esm/eskdf.js.map +1 -0
- package/node_modules/@noble/hashes/esm/hkdf.d.ts +36 -0
- package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/hkdf.js +82 -0
- package/node_modules/@noble/hashes/esm/hkdf.js.map +1 -0
- package/node_modules/@noble/hashes/esm/hmac.d.ts +35 -0
- package/node_modules/@noble/hashes/esm/hmac.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/hmac.js +86 -0
- package/node_modules/@noble/hashes/esm/hmac.js.map +1 -0
- package/node_modules/@noble/hashes/esm/index.d.ts +2 -0
- package/node_modules/@noble/hashes/esm/index.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/index.js +33 -0
- package/node_modules/@noble/hashes/esm/index.js.map +1 -0
- package/node_modules/@noble/hashes/esm/legacy.d.ts +71 -0
- package/node_modules/@noble/hashes/esm/legacy.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/legacy.js +281 -0
- package/node_modules/@noble/hashes/esm/legacy.js.map +1 -0
- package/node_modules/@noble/hashes/esm/package.json +10 -0
- package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +23 -0
- package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/pbkdf2.js +97 -0
- package/node_modules/@noble/hashes/esm/pbkdf2.js.map +1 -0
- package/node_modules/@noble/hashes/esm/ripemd160.d.ts +13 -0
- package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/ripemd160.js +13 -0
- package/node_modules/@noble/hashes/esm/ripemd160.js.map +1 -0
- package/node_modules/@noble/hashes/esm/scrypt.d.ts +34 -0
- package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/scrypt.js +228 -0
- package/node_modules/@noble/hashes/esm/scrypt.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha1.d.ts +11 -0
- package/node_modules/@noble/hashes/esm/sha1.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha1.js +11 -0
- package/node_modules/@noble/hashes/esm/sha1.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha2.d.ts +159 -0
- package/node_modules/@noble/hashes/esm/sha2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha2.js +375 -0
- package/node_modules/@noble/hashes/esm/sha2.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha256.d.ts +20 -0
- package/node_modules/@noble/hashes/esm/sha256.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha256.js +20 -0
- package/node_modules/@noble/hashes/esm/sha256.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +142 -0
- package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha3-addons.js +393 -0
- package/node_modules/@noble/hashes/esm/sha3-addons.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha3.d.ts +53 -0
- package/node_modules/@noble/hashes/esm/sha3.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha3.js +234 -0
- package/node_modules/@noble/hashes/esm/sha3.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha512.d.ts +26 -0
- package/node_modules/@noble/hashes/esm/sha512.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha512.js +26 -0
- package/node_modules/@noble/hashes/esm/sha512.js.map +1 -0
- package/node_modules/@noble/hashes/esm/utils.d.ts +161 -0
- package/node_modules/@noble/hashes/esm/utils.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/utils.js +281 -0
- package/node_modules/@noble/hashes/esm/utils.js.map +1 -0
- package/node_modules/@noble/hashes/hkdf.d.ts +36 -0
- package/node_modules/@noble/hashes/hkdf.d.ts.map +1 -0
- package/node_modules/@noble/hashes/hkdf.js +88 -0
- package/node_modules/@noble/hashes/hkdf.js.map +1 -0
- package/node_modules/@noble/hashes/hmac.d.ts +35 -0
- package/node_modules/@noble/hashes/hmac.d.ts.map +1 -0
- package/node_modules/@noble/hashes/hmac.js +91 -0
- package/node_modules/@noble/hashes/hmac.js.map +1 -0
- package/node_modules/@noble/hashes/index.d.ts +1 -0
- package/node_modules/@noble/hashes/index.d.ts.map +1 -0
- package/node_modules/@noble/hashes/index.js +33 -0
- package/node_modules/@noble/hashes/index.js.map +1 -0
- package/node_modules/@noble/hashes/legacy.d.ts +71 -0
- package/node_modules/@noble/hashes/legacy.d.ts.map +1 -0
- package/node_modules/@noble/hashes/legacy.js +287 -0
- package/node_modules/@noble/hashes/legacy.js.map +1 -0
- package/node_modules/@noble/hashes/package.json +266 -0
- package/node_modules/@noble/hashes/pbkdf2.d.ts +23 -0
- package/node_modules/@noble/hashes/pbkdf2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/pbkdf2.js +101 -0
- package/node_modules/@noble/hashes/pbkdf2.js.map +1 -0
- package/node_modules/@noble/hashes/ripemd160.d.ts +13 -0
- package/node_modules/@noble/hashes/ripemd160.d.ts.map +1 -0
- package/node_modules/@noble/hashes/ripemd160.js +16 -0
- package/node_modules/@noble/hashes/ripemd160.js.map +1 -0
- package/node_modules/@noble/hashes/scrypt.d.ts +34 -0
- package/node_modules/@noble/hashes/scrypt.d.ts.map +1 -0
- package/node_modules/@noble/hashes/scrypt.js +232 -0
- package/node_modules/@noble/hashes/scrypt.js.map +1 -0
- package/node_modules/@noble/hashes/sha1.d.ts +11 -0
- package/node_modules/@noble/hashes/sha1.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha1.js +14 -0
- package/node_modules/@noble/hashes/sha1.js.map +1 -0
- package/node_modules/@noble/hashes/sha2.d.ts +159 -0
- package/node_modules/@noble/hashes/sha2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha2.js +384 -0
- package/node_modules/@noble/hashes/sha2.js.map +1 -0
- package/node_modules/@noble/hashes/sha256.d.ts +20 -0
- package/node_modules/@noble/hashes/sha256.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha256.js +23 -0
- package/node_modules/@noble/hashes/sha256.js.map +1 -0
- package/node_modules/@noble/hashes/sha3-addons.d.ts +142 -0
- package/node_modules/@noble/hashes/sha3-addons.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha3-addons.js +402 -0
- package/node_modules/@noble/hashes/sha3-addons.js.map +1 -0
- package/node_modules/@noble/hashes/sha3.d.ts +53 -0
- package/node_modules/@noble/hashes/sha3.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha3.js +239 -0
- package/node_modules/@noble/hashes/sha3.js.map +1 -0
- package/node_modules/@noble/hashes/sha512.d.ts +26 -0
- package/node_modules/@noble/hashes/sha512.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha512.js +29 -0
- package/node_modules/@noble/hashes/sha512.js.map +1 -0
- package/node_modules/@noble/hashes/src/_assert.ts +22 -0
- package/node_modules/@noble/hashes/src/_blake.ts +50 -0
- package/node_modules/@noble/hashes/src/_md.ts +176 -0
- package/node_modules/@noble/hashes/src/_u64.ts +91 -0
- package/node_modules/@noble/hashes/src/argon2.ts +497 -0
- package/node_modules/@noble/hashes/src/blake1.ts +534 -0
- package/node_modules/@noble/hashes/src/blake2.ts +486 -0
- package/node_modules/@noble/hashes/src/blake2b.ts +10 -0
- package/node_modules/@noble/hashes/src/blake2s.ts +20 -0
- package/node_modules/@noble/hashes/src/blake3.ts +272 -0
- package/node_modules/@noble/hashes/src/crypto.ts +9 -0
- package/node_modules/@noble/hashes/src/cryptoNode.ts +15 -0
- package/node_modules/@noble/hashes/src/eskdf.ts +187 -0
- package/node_modules/@noble/hashes/src/hkdf.ts +88 -0
- package/node_modules/@noble/hashes/src/hmac.ts +94 -0
- package/node_modules/@noble/hashes/src/index.ts +31 -0
- package/node_modules/@noble/hashes/src/legacy.ts +293 -0
- package/node_modules/@noble/hashes/src/pbkdf2.ts +122 -0
- package/node_modules/@noble/hashes/src/ripemd160.ts +12 -0
- package/node_modules/@noble/hashes/src/scrypt.ts +257 -0
- package/node_modules/@noble/hashes/src/sha1.ts +10 -0
- package/node_modules/@noble/hashes/src/sha2.ts +402 -0
- package/node_modules/@noble/hashes/src/sha256.ts +24 -0
- package/node_modules/@noble/hashes/src/sha3-addons.ts +499 -0
- package/node_modules/@noble/hashes/src/sha3.ts +258 -0
- package/node_modules/@noble/hashes/src/sha512.ts +34 -0
- package/node_modules/@noble/hashes/src/utils.ts +395 -0
- package/node_modules/@noble/hashes/utils.d.ts +161 -0
- package/node_modules/@noble/hashes/utils.d.ts.map +1 -0
- package/node_modules/@noble/hashes/utils.js +313 -0
- package/node_modules/@noble/hashes/utils.js.map +1 -0
- package/package.json +179 -8
- package/schemas/catalog.schema.json +357 -0
- package/src/align.ts +239 -0
- package/src/audio-init-parser.ts +189 -0
- package/src/audio-ring-buffer.ts +120 -0
- package/src/authored-timing.ts +63 -0
- package/src/block-policy.ts +903 -0
- package/src/carp-catalog.ts +1086 -0
- package/src/catalog-fec-params.ts +803 -0
- package/src/catalog-network-source.ts +74 -0
- package/src/catalog-validator.ts +34 -0
- package/src/cmaf-block-assembler.ts +1255 -0
- package/src/cmaf-group-batcher.ts +104 -0
- package/src/cmcd-producer.ts +329 -0
- package/src/codec-utils.ts +1984 -0
- package/src/conceal-timeline.ts +293 -0
- package/src/delivery-gap.ts +147 -0
- package/src/duplicate-ledger.ts +372 -0
- package/src/fec-codec.ts +61 -0
- package/src/fec-manager.ts +4241 -0
- package/src/fec-starvation-detector.ts +128 -0
- package/src/fec-state.ts +228 -0
- package/src/fec-worker-setup.ts +291 -0
- package/src/fmp4-completion.ts +47 -0
- package/src/fmp4-reader.ts +112 -0
- package/src/index.ts +592 -21
- package/src/iso-block-locator.ts +329 -0
- package/src/iso-bmff-timing.ts +813 -0
- package/src/loc.ts +128 -154
- package/src/manifest-auth-plan.ts +107 -0
- package/src/media-router.ts +841 -0
- package/src/mfu-reassembler.ts +3480 -155
- package/src/mfu-reassembly-policy.ts +134 -0
- package/src/mmtp-container-parser.ts +2406 -0
- package/src/mmtp-decode-pipeline.ts +406 -0
- package/src/mmtp-router.ts +4712 -0
- package/src/mmtp-to-cmaf.ts +977 -0
- package/src/multicast-auth-catalog.ts +340 -0
- package/src/multicast-input.ts +793 -0
- package/src/multicast-manager.ts +2895 -0
- package/src/multicast-transport.ts +1058 -0
- package/src/multicast-unwrap.ts +132 -0
- package/src/no-modules.ts +21 -0
- package/src/phase14-catalog-override.ts +336 -0
- package/src/repair-fetch.ts +786 -0
- package/src/repair-layers.ts +553 -0
- package/src/sample-combiner.ts +938 -0
- package/src/signaling.ts +78 -0
- package/src/source-auth.ts +1 -0
- package/src/switching-set-alignment.ts +109 -0
- package/src/timestamp-codec.ts +265 -0
- package/src/track-pipeline-setup.ts +205 -0
- package/src/track-pipeline.ts +332 -0
- package/src/track-session.ts +1010 -0
- package/src/transport-pool.ts +128 -0
- package/src/wasm-mfu-reassembler.ts +405 -0
- package/src/wasm-mmtp-container-parser.ts +23 -0
- package/test-vectors/align.json +199 -0
- package/test-vectors/catalog/README.md +123 -0
- package/test-vectors/catalog/negative/archive-origin-with-path.json +33 -0
- package/test-vectors/catalog/negative/archive-unknown-field.json +34 -0
- package/test-vectors/catalog/negative/fec-block-profile-missing.json +51 -0
- package/test-vectors/catalog/negative/fec-block-profile-retired.json +52 -0
- package/test-vectors/catalog/negative/fec-block-profile-unknown.json +52 -0
- package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +28 -0
- package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +33 -0
- package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +31 -0
- package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +32 -0
- package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +34 -0
- package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +24 -0
- package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +33 -0
- package/test-vectors/catalog/negative/legacy-selection-params.json +15 -0
- package/test-vectors/catalog/negative/media-track-repair-layer.json +16 -0
- package/test-vectors/catalog/negative/missing-mmtp-fields.json +12 -0
- package/test-vectors/catalog/negative/multicast-auth-padded-key.json +37 -0
- package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +53 -0
- package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +37 -0
- package/test-vectors/catalog/negative/multicast-endpoint-missing-protocol-and-source.json +24 -0
- package/test-vectors/catalog/negative/multicast-network-source-empty.json +44 -0
- package/test-vectors/catalog/negative/multicast-packet-id-signaling.json +26 -0
- package/test-vectors/catalog/negative/network-source-object.json +25 -0
- package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +24 -0
- package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +27 -0
- package/test-vectors/catalog/negative/repair-track-legacy-shape.json +19 -0
- package/test-vectors/catalog/negative/track-role-empty.json +16 -0
- package/test-vectors/catalog/negative/track-role-non-string.json +16 -0
- package/test-vectors/catalog/positive/archive-origin.json +34 -0
- package/test-vectors/catalog/positive/fec-iso-ssbg1.json +52 -0
- package/test-vectors/catalog/positive/fec-layered-repair.json +106 -0
- package/test-vectors/catalog/positive/fec-multicast.json +53 -0
- package/test-vectors/catalog/positive/flat-av.json +30 -0
- package/test-vectors/catalog/positive/multicast-auth-rotation.json +55 -0
- package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +44 -0
- package/test-vectors/catalog/positive/ticks-and-mixed-timescales.json +39 -0
- package/test-vectors/catalog/positive/track-role.json +49 -0
- package/test-vectors/catalog/releases/moqcast-native-v16-v1/catalog.json +65 -0
- package/test-vectors/catalog/releases/moqcast-native-v16-v1.archive.sha256 +1 -0
- package/test-vectors/catalog-multicast-auth-v1.json +26 -0
- package/test-vectors/catalog-validation.json +767 -0
- package/test-vectors/catalog-validator-public-api.mjs +25 -0
- package/test-vectors/generate-source-auth-fixture.mjs +114 -0
- package/test-vectors/hevc-x265-repeat-headers-v1.json +40 -0
- package/test-vectors/source-auth-v1.json +61 -0
- package/test-vectors/source-contiguity-v1.json +131 -0
- package/test-vectors/subsample-placement-v1.json +230 -0
- package/dist/mmtp.d.ts +0 -73
- package/dist/mmtp.d.ts.map +0 -1
- package/dist/mmtp.js +0 -210
- package/dist/mmtp.js.map +0 -1
- package/src/mmtp.ts +0 -277
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../mmt-alta-parse/src/index.ts", "../../mmt-base/src/mmtp.ts", "../../mmt-base/src/net-constants.ts", "../../mmt-base/src/ntp-short.ts", "../../mmt-base/src/serial-number.ts", "../../mmt-base/src/fec-block-profile.ts", "../src/track-session.ts", "../src/delivery-gap.ts", "../src/fec-manager.ts", "../src/iso-block-locator.ts", "../src/fec-state.ts", "../src/block-policy.ts", "../src/mfu-reassembler.ts", "../src/codec-utils.ts", "../src/authored-timing.ts", "../src/mmtp-router.ts", "../src/mmtp-to-cmaf.ts", "../src/conceal-timeline.ts", "../src/cmaf-block-assembler.ts", "../src/sample-combiner.ts", "../src/mfu-reassembly-policy.ts", "../src/iso-bmff-timing.ts", "../src/mmtp-container-parser.ts", "../src/duplicate-ledger.ts", "../node_modules/@blockcast/mmt-transport/src/types.ts", "../node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-alta-parse/src/index.ts", "../node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/src/mmtp.ts", "../node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/src/net-constants.ts", "../node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/src/ntp-short.ts", "../node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts", "../node_modules/@blockcast/mmt-transport/src/driad-discovery.ts", "../node_modules/@blockcast/mmt-transport/src/multicast-tier.ts", "../node_modules/@blockcast/mmt-transport/src/abr-controller.ts", "../node_modules/@blockcast/mmt-transport/src/multicast-orchestrator.ts", "../node_modules/@blockcast/mmt-transport/src/endpoint-config.ts", "../node_modules/@blockcast/mmt-transport/src/packet-batch.ts", "../node_modules/@blockcast/mmt-transport/src/multicast-subscription-pool.ts", "../node_modules/@blockcast/mmt-transport/src/transport-manager.ts", "../node_modules/@blockcast/mmt-transport/src/moqt-priority.ts", "../node_modules/@blockcast/mmt-transport/src/amt-gateway.ts", "../src/multicast-transport.ts", "../src/multicast-manager.ts", "../src/multicast-auth-catalog.ts", "../src/repair-layers.ts", "../src/carp-catalog.ts", "../src/catalog-fec-params.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Zero-dependency helper for extracting the detached ALTA authenticator trailer\n * from an MMTP source packet.\n *\n * Lives in its own leaf package so both `@blockcast/mmt-container` (wire-path\n * parsing) and `@blockcast/fec-worker` (FEC-recovered-source parsing) can share\n * one implementation without creating a package cycle between them.\n *\n * Spec: ISO/IEC 23008-1:2023 + draft-ramadan-moq-mmt \u00A712 (detached trailer, Path 2).\n */\n\n// --- Private MMTP/MPU/MFU constants (ISO/IEC 23008-1 \u00A79.2.2\u20139.2.3) ---\n// Duplicated here as immutable spec values so this package stays a leaf with\n// zero `@blockcast/*` dependencies. Re-deriving from `mmt-container` would\n// reintroduce the package cycle this split exists to avoid.\n\nconst MMTP_HEADER_SIZE = 12\nconst MPU_HEADER_SIZE = 8\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst TIMED_MFU_DU_HEADER_SIZE = 14\nconst NONTIMED_MFU_DU_HEADER_SIZE = 4\nconst PACKET_TYPE_MPU = 0x00\n\n// --- Public ALTA trailer constants ---\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\nexport const MMTP_EXT_TYPE_ALTA_AUTH = 0xf0a1\n\n/** Fixed size of the trailer's `[ext_type][ext_length]` prefix (2 + 2 BE). */\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = 4\n\n/** Result of extracting the detached ALTA authenticator from an MMTP packet. */\nexport interface AltaTrailer {\n /** Authenticator bytes (no ext_type/ext_length prefix, no trailing padding). */\n auth: Uint8Array\n /**\n * Wire-packet byte offset where the signed payload ends, i.e. where the\n * trailer begins. Caller uses this to produce\n * `payload = wirePacket.subarray(0, signedEnd)` for verify_detached.\n */\n signedEnd: number\n}\n\n/**\n * Extract the detached ALTA authenticator trailer from an MMTP source packet.\n *\n * Wire layout:\n * `[MMTP hdr][length(2)][remaining MPU hdr(6)][DU?][media][TRAILER][SS_ID?]`\n * `payload_length = 6 + DU bytes + media bytes`\n *\n * Returns `null` when:\n * - the packet is too small / malformed,\n * - `payload_length` cannot contain the remaining six MPU-header bytes,\n * - the declared payload overruns the packet,\n * - no bytes remain between `payload_length` and the SS_ID,\n * - the trailer's `ext_type` isn't {@link MMTP_EXT_TYPE_ALTA_AUTH},\n * - the declared `ext_length` would extend past the SS_ID boundary.\n *\n * Non-MPU packets (repair, signaling) always return `null`.\n *\n * @param options.skipFecId When true, do NOT reserve 4 trailing bytes for the\n * Source FEC Payload ID. Use on FEC-recovered symbols where the PID has\n * already been stripped upstream.\n */\nexport function extractAltaTrailer(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): AltaTrailer | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n // ISO/IEC 23008-1:2023 Figure 8 places V=0 packet_type in the low six bits.\n const packetType = mmtpPacket[1]! & 0x3f\n if (packetType !== PACKET_TYPE_MPU) return null\n\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n let headerLength = MMTP_HEADER_SIZE\n if (fecType < 2 && (mmtpPacket[0]! & 0x20) !== 0) headerLength += 4\n if (fecType < 2 && (mmtpPacket[0]! & 0x02) !== 0) {\n if (headerLength + 4 > mmtpPacket.length) return null\n const extensionLength = new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + headerLength + 2,\n 2,\n ).getUint16(0, false)\n headerLength += 4 + extensionLength\n }\n if (headerLength + MPU_HEADER_SIZE > mmtpPacket.length) return null\n\n const payload = mmtpPacket.subarray(headerLength)\n\n // Inline a minimal MPU-header read \u2014 only the fields extractAltaTrailer needs.\n // payloadLength: 16 BE at bytes 0-1.\n // Byte 2 packs FT(4) | T(1) | FI(2) | A(1).\n const payloadLength = (payload[0]! << 8) | payload[1]!\n const flagsByte = payload[2]!\n const fragmentType = (flagsByte >> 4) & 0x0f\n const timed = ((flagsByte >> 3) & 0x01) === 1\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n\n // Only MFU packets (FT=2) carry the detached trailer.\n if (fragmentType !== 2) return null\n\n // FI=0/1 structurally carries a DU header; FI=2/3 does not. Require at\n // least one media byte after that deterministic layout so the bytes at the\n // declared boundary cannot impersonate ALTA for a header-only fragment.\n const dataUnitHeaderSize = fragmentationIndicator <= 1\n ? (timed ? TIMED_MFU_DU_HEADER_SIZE : NONTIMED_MFU_DU_HEADER_SIZE)\n : 0\n if (payloadLength <= MPU_HEADER_REMAINDER_SIZE + dataUnitHeaderSize) return null\n\n if (fecType > 1) return null\n const hasFecId = !options?.skipFecId && fecType === 1\n const payloadEnd = hasFecId ? payload.length - 4 : payload.length\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (declaredEnd > payloadEnd) return null\n\n if (declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE > payloadEnd) return null\n const extType = (payload[declaredEnd]! << 8) | payload[declaredEnd + 1]!\n if (extType !== MMTP_EXT_TYPE_ALTA_AUTH) return null\n const extLen = (payload[declaredEnd + 2]! << 8) | payload[declaredEnd + 3]!\n\n const authStart = declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE\n if (authStart + extLen !== payloadEnd) return null\n\n return {\n auth: payload.subarray(authStart, payloadEnd),\n signedEnd: headerLength + declaredEnd,\n }\n}\n", "import {\n MMTP_EXT_TYPE_ALTA_AUTH as _LEAF_MMTP_EXT_TYPE_ALTA_AUTH,\n MMTP_EXT_TRAILER_HEADER_SIZE as _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE,\n extractAltaTrailer as _leafExtractAltaTrailer,\n type AltaTrailer as _LeafAltaTrailer,\n} from '@blockcast/mmt-alta-parse'\n\n/**\n * @mmt/container - MMTP Parser\n *\n * MMTP (MMT Protocol) packet parser based on ISO/IEC 23008-1:2023 Section 9.2\n *\n * MMTP header format (12 bytes):\n * - Byte 0: V(2) | C(1) | FEC_type(2) | r(1) | X(1) | R(1)\n * - Byte 1: reserved(2) | packet_type(6)\n * - Bytes 2-3: packet_id (big-endian)\n * - Bytes 4-7: timestamp (big-endian, 32-bit)\n * - Bytes 8-11: sequence_number (big-endian)\n *\n * FEC_type values:\n * - 0: No FEC / source packet\n * - 1: FEC mode 0 (source with FEC payload ID)\n * - 2: FEC repair packet mode 0\n * - 3: FEC repair packet mode 1\n *\n * @packageDocumentation\n */\n\nexport interface MMTPHeader {\n version: number\n /** Packet type: 0x00=MPU, 0x02=Signaling, 0x03=Repair */\n packetType: number\n packetId: number\n timestamp: bigint\n sequenceNumber: number\n payloadLength: number\n fecType: number\n /** C flag: header carries a 32-bit packet_counter. */\n packetCounterFlag: boolean\n packetCounter?: number\n /** X flag: header carries an extension header and value. */\n extensionFlag: boolean\n extension?: { type: number; data: Uint8Array }\n /** Byte offset at which the MMTP payload begins. */\n headerLength: number\n /** Random Access Point flag - indicates keyframe (ISO 23008-1, byte 0 bit 0) */\n rapFlag: boolean\n}\n\nexport interface MPUHeader {\n mpuSequenceNumber: number\n fragmentationIndicator: number // FI: 0=complete, 1=first, 2=middle, 3=last\n /** 8-bit MPU fragment_counter from byte 3. */\n fragmentCounter: number\n /** T flag: 1=timed media (14-byte MFU DU header), 0=non-timed (4-byte header) */\n timedFlag: number\n /** A flag: aggregated MPU payload. This strict profile requires A=0. */\n aggregationFlag: boolean\n /** MPU payload_length (bytes 0-1): bytes after the length field, including\n * the remaining six MPU-header bytes and all data-unit bytes. */\n payloadLength: number\n}\n\nexport interface MFUDuHeader {\n movieFragmentSequenceNumber: number\n sampleNumber: number\n offset: number\n priority: number\n dependencyCounter: number\n}\n\nexport interface MMTPPacket {\n header: MMTPHeader\n payload: Uint8Array\n}\n\nexport const MMTP_HEADER_SIZE = 12\nexport const MPU_HEADER_SIZE = 8\nexport const MFU_DU_HEADER_SIZE_TIMED = 14 // T=1: timed media (video/audio samples)\nexport const MFU_DU_HEADER_SIZE_NONTIMED = 4 // T=0: non-timed media (item_id only)\n\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst SOURCE_FEC_PAYLOAD_ID_SIZE = 4\n\n// MMTP Packet Types (ISO/IEC 23008-1:2023 Section 9.2.2)\nexport const PACKET_TYPE_MPU = 0x00\nexport const PACKET_TYPE_SIGNALING = 0x02\nexport const PACKET_TYPE_REPAIR = 0x03\n\n// AL-FEC Signaling Message ID (ISO/IEC 23008-1:2023 Amendment 1:2025)\nexport const ALFEC_MESSAGE_ID = 0x0203\n\n/**\n * MMTP `packet_id` of the signaling flow, which carries PACKET_TYPE_SIGNALING\n * packets such as the AL-FEC message (ALFEC_MESSAGE_ID). The FFmpeg `moq_mmt`\n * muxer sends that message on this packet_id into the same multicast group as\n * the media, and moqcast-draft (PR #44) makes advertising it for a track a\n * MUST NOT. A track's packet_id is therefore 1..MMTP_PACKET_ID_MAX.\n */\nexport const MMTP_SIGNALING_PACKET_ID = 0\n\n/** Largest value of the 16-bit MMTP `packet_id` header field. */\nexport const MMTP_PACKET_ID_MAX = 0xffff\n\n// FEC Code IDs\nexport const FEC_CODE_XOR = 1\nexport const FEC_CODE_RAPTORQ = 2\n\n/**\n * Parse MMTP header from buffer\n */\nexport function parseMMTPHeader(buffer: Uint8Array): MMTPHeader {\n if (buffer.length < MMTP_HEADER_SIZE) {\n throw new Error(`MMTP packet too small: ${buffer.length} bytes`)\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n\n const version = (buffer[0]! >> 6) & 0x03\n if (version !== 0) {\n throw new Error(`Unsupported MMTP version: ${version}`)\n }\n\n // Extract FEC type from byte 0, bits 3-4\n const fecType = (buffer[0]! >> 3) & 0x03\n const packetCounterFlag = (buffer[0]! & 0x20) !== 0\n const extensionFlag = (buffer[0]! & 0x02) !== 0\n\n // Extract RAP flag from byte 0, bit 0 (R bit - Random Access Point)\n const rapFlag = (buffer[0]! & 0x01) === 1\n\n // ISO/IEC 23008-1:2023 Figure 8 (V=0) places reserved(2) before\n // packet_type(6), so packet_type occupies the low six bits of byte 1.\n const packetType = buffer[1]! & 0x3F\n\n let offset = 2\n\n // Packet ID (2 bytes, big-endian)\n const packetId = view.getUint16(offset, false)\n offset += 2\n\n // Timestamp (4 bytes, big-endian)\n const timestamp = BigInt(view.getUint32(offset, false))\n offset += 4\n\n // Sequence number (4 bytes, big-endian)\n const sequenceNumber = view.getUint32(offset, false)\n offset += 4\n\n let packetCounter: number | undefined\n if (packetCounterFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in packet_counter')\n packetCounter = view.getUint32(offset, false)\n offset += 4\n }\n\n let extension: { type: number; data: Uint8Array } | undefined\n if (extensionFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in header extension')\n const type = view.getUint16(offset, false)\n const length = view.getUint16(offset + 2, false)\n offset += 4\n if (offset + length > buffer.length) throw new Error('MMTP packet truncated in header extension value')\n extension = { type, data: buffer.subarray(offset, offset + length) }\n offset += length\n }\n\n return {\n version,\n packetType,\n packetId,\n timestamp,\n sequenceNumber,\n payloadLength: buffer.length - offset,\n fecType,\n packetCounterFlag,\n packetCounter,\n extensionFlag,\n extension,\n headerLength: offset,\n rapFlag,\n }\n}\n\n/**\n * The MMTP `packet_id` of `buffer`, or null exactly when parseMMTPHeader would\n * throw for it (short buffer, V != 0, truncated packet_counter or header\n * extension).\n *\n * This is the per-packet filter a receiver runs before any other work, so it\n * builds no header object, DataView or BigInt timestamp (BLO-36599). Use\n * parseMMTPHeader when any other header field is needed.\n */\nexport function readMMTPPacketId(buffer: Uint8Array): number | null {\n if (buffer.length < MMTP_HEADER_SIZE) return null\n const flags = buffer[0]!\n if (((flags >> 6) & 0x03) !== 0) return null\n let offset = MMTP_HEADER_SIZE\n if ((flags & 0x20) !== 0) {\n if (offset + 4 > buffer.length) return null\n offset += 4\n }\n if ((flags & 0x02) !== 0) {\n if (offset + 4 > buffer.length) return null\n const length = (buffer[offset + 2]! << 8) | buffer[offset + 3]!\n if (offset + 4 + length > buffer.length) return null\n }\n return (buffer[2]! << 8) | buffer[3]!\n}\n\n/**\n * Unwrap MMTP packet to extract payload\n */\nexport function unwrapMMTP(buffer: Uint8Array): MMTPPacket {\n const header = parseMMTPHeader(buffer)\n const payload = buffer.subarray(header.headerLength)\n\n return { header, payload }\n}\n\n/**\n * Parse MPU header from payload\n * ISO/IEC 23008-1:2023 Section 9.2.3.3\n *\n * MPU Header format (8 bytes) as written by FFmpeg/libmmt:\n * - Bytes 0-1: payload_length (16 bits, big-endian)\n * - Byte 2: FT(4) | T(1) | FI(2) | A(1)\n * - Byte 3: fragment_counter (8 bits)\n * - Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n */\nexport function parseMPUHeader(payload: Uint8Array): MPUHeader {\n if (payload.length < MPU_HEADER_SIZE) {\n throw new Error(`Payload too small for MPU header: ${payload.length} bytes`)\n }\n\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)\n\n // Bytes 0-1: payload_length (16 bits, big-endian)\n const payloadLength = view.getUint16(0, false)\n\n // Byte 2: FT(4) | T(1) | FI(2) | A(1)\n const flagsByte = payload[2]!\n const timedFlag = (flagsByte >> 3) & 0x01\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n const aggregationFlag = (flagsByte & 0x01) !== 0\n\n // Byte 3: fragment_counter (8 bits). The high bit is part of the counter in\n // the current libmmt/FFmpeg writer ABI.\n const fragmentCounter = payload[3]!\n\n // Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n const mpuSequenceNumber = view.getUint32(4, false)\n\n return {\n mpuSequenceNumber,\n fragmentationIndicator,\n fragmentCounter,\n timedFlag,\n aggregationFlag,\n payloadLength,\n }\n}\n\nexport function parseTimedMfuDuHeader(payload: Uint8Array, offset: number): MFUDuHeader | null {\n if (offset < 0 || offset + MFU_DU_HEADER_SIZE_TIMED > payload.length) return null\n const view = new DataView(payload.buffer, payload.byteOffset + offset, MFU_DU_HEADER_SIZE_TIMED)\n return {\n movieFragmentSequenceNumber: view.getUint32(0, false),\n sampleNumber: view.getUint32(4, false),\n offset: view.getUint32(8, false),\n priority: payload[offset + 12]!,\n dependencyCounter: payload[offset + 13]!,\n }\n}\n\n/**\n * Why `extractMFUPayload` refused a packet.\n *\n * These identifiers are part of the public contract: callers surface them in\n * diagnostics, so renaming one is a breaking change.\n */\nexport type MFURejectionGate =\n | 'packet-shorter-than-mmtp-plus-mpu-header'\n | 'mmtp-header-unparseable'\n | 'not-an-mpu-packet'\n | 'payload-shorter-than-mpu-header'\n | 'aggregation-flag-set'\n | 'mpu-payload-length-below-header-remainder'\n | 'mpu-declared-end-exceeds-payload'\n | 'mfu-offset-at-or-past-declared-end'\n | 'source-fec-payload-id-width-mismatch'\n | 'declared-trailers-unresolved'\n\nexport interface MFURejection {\n gate: MFURejectionGate\n /** Human-readable detail, including the byte arithmetic that decided it. */\n detail: string\n}\n\ninterface MfuPayloadLayout {\n hasMfuDuHeader: boolean\n mfuOffset: number\n dataEnd: number\n}\n\nfunction resolveMfuPayloadLayout(\n mpu: MPUHeader,\n fragmentType: number,\n payload: Uint8Array,\n): { layout: MfuPayloadLayout } | { rejection: MFURejection } {\n if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE) {\n return {\n rejection: {\n gate: 'mpu-payload-length-below-header-remainder',\n detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE}`,\n },\n }\n }\n\n // ISO/IEC 23008-1 defines payload_length from immediately after its own\n // two-byte field. It therefore includes the remaining six MPU-header bytes.\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength\n if (declaredEnd > payload.length) {\n return {\n rejection: {\n gate: 'mpu-declared-end-exceeds-payload',\n detail:\n `payload_length=${mpu.payloadLength} implies end=${declaredEnd}, ` +\n `payload is ${payload.length} bytes`,\n },\n }\n }\n\n const hasHeader = fragmentType === 2 && mpu.fragmentationIndicator <= 1\n const mfuHeaderSize = mpu.timedFlag ? MFU_DU_HEADER_SIZE_TIMED : MFU_DU_HEADER_SIZE_NONTIMED\n const mfuOffset = MPU_HEADER_SIZE + (hasHeader ? mfuHeaderSize : 0)\n if (mfuOffset >= declaredEnd) {\n return {\n rejection: {\n gate: 'mfu-offset-at-or-past-declared-end',\n detail: `mfuOffset=${mfuOffset} >= declaredEnd=${declaredEnd}`,\n },\n }\n }\n\n return {\n layout: {\n hasMfuDuHeader: hasHeader,\n mfuOffset,\n dataEnd: declaredEnd,\n },\n }\n}\n\nfunction isEmptyOrExactAltaTrailer(payload: Uint8Array, start: number, end: number): boolean {\n if (start === end) return true\n if (start < 0 || end > payload.length || start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE > end) {\n return false\n }\n\n const view = new DataView(\n payload.buffer,\n payload.byteOffset + start,\n end - start,\n )\n if (view.getUint16(0, false) !== _LEAF_MMTP_EXT_TYPE_ALTA_AUTH) return false\n\n const authLength = view.getUint16(2, false)\n return start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE + authLength === end\n}\n\ninterface DeclaredTrailerLayout {\n hasFecId: boolean\n}\n\nfunction resolveDeclaredTrailers(\n payload: Uint8Array,\n declaredEnd: number,\n fecType: number,\n options?: { skipFecId?: boolean },\n): DeclaredTrailerLayout | null {\n if (declaredEnd > payload.length) return null\n\n if (options?.skipFecId) {\n return isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)\n ? { hasFecId: false }\n : null\n }\n\n if (fecType === 1) {\n const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n if (fecStart < declaredEnd || !isEmptyOrExactAltaTrailer(payload, declaredEnd, fecStart)) {\n return null\n }\n return { hasFecId: true }\n }\n if (fecType !== 0) return null\n\n if (isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)) {\n return { hasFecId: false }\n }\n\n return null\n}\n\n/**\n * Extract NAL unit payload from MMTP packet\n */\nexport function extractNALUnit(mmtpPacket: Uint8Array): Uint8Array {\n const extraction = extractMFUPayload(mmtpPacket)\n if (!extraction) throw new Error('Malformed MMTP MPU payload')\n return extraction.data\n}\n\n/** Result of extracting MFU payload from an MMTP packet */\nexport interface MFUExtraction {\n /** MMTP header fields */\n header: MMTPHeader\n /** MPU header fields */\n mpu: MPUHeader\n /** Fragment type (FT field, 4 bits): 0=metadata, 2=MFU data */\n fragmentType: number\n /** MFU payload data (video/audio bytes, headers stripped, FEC ID excluded) */\n data: Uint8Array\n /** Parsed timed MFU DU header when present, otherwise null */\n mfuDuHeader: MFUDuHeader | null\n /** Non-timed MFU Item_ID when the 4-byte DU header is present. */\n itemId: number | null\n /** Source FEC Payload ID if present (`FEC_type == 1`), or null */\n fecPayloadId: { ssId: number } | null\n}\n\n/**\n * Extract MFU payload from a raw MMTP packet.\n *\n * Handles all the byte arithmetic for:\n * - MMTP header (12 bytes)\n * - MPU sub-header (8 bytes, payloadLength field)\n * - MFU DU header (14 bytes timed / 4 bytes non-timed, only for FI \u2264 1)\n * - Source FEC Payload ID (4 bytes at end, only when `FEC_type == 1`)\n *\n * `payloadLength` is the ISO length-after-field value: six remaining MPU-header\n * bytes, then the MFU DU header when present, then media. ALTA and Source FEC\n * trailers are outside that declared span and must consume the exact suffix.\n *\n * @param mmtpPacket Raw MMTP packet (from multicast or MoQ)\n * @param options.skipFecId When true, do NOT subtract the 4-byte Source FEC Payload ID\n * from the payload boundary, even if fecType >= 1 in the MMTP header. Use this for\n * FEC-recovered symbols where the FEC PID was already stripped before WASM decoding\n * but the fecType bits in byte 0 still reflect the original packet (FEC Type=1 per\n * draft-ramadan-moq-mmt-00 \u00A73.1).\n * @returns Extracted MFU data + metadata, or null if packet is too small\n */\ninterface MfuPlan {\n header: MMTPHeader\n payload: Uint8Array\n mpu: MPUHeader\n fragmentType: number\n layout: MfuPayloadLayout\n trailerLayout: DeclaredTrailerLayout\n}\n\n/**\n * Single source of truth for whether a packet yields an MFU payload.\n *\n * `extractMFUPayload` and `explainMFURejection` both delegate here, so the\n * reason reported to operators can never drift from the decision the parser\n * actually made.\n */\nfunction planMfuExtraction(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n parsedHeader?: MMTPHeader,\n): { plan: MfuPlan } | { rejection: MFURejection } {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'packet-shorter-than-mmtp-plus-mpu-header',\n detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE + MPU_HEADER_SIZE}`,\n },\n }\n }\n\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch (error) {\n return {\n rejection: {\n gate: 'mmtp-header-unparseable',\n detail: error instanceof Error ? error.message : String(error),\n },\n }\n }\n }\n if (header.packetType !== PACKET_TYPE_MPU) {\n return {\n rejection: {\n gate: 'not-an-mpu-packet',\n detail: `packetType=0x${header.packetType.toString(16).padStart(2, '0')}`,\n },\n }\n }\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'payload-shorter-than-mpu-header',\n detail: `${payload.length} bytes < ${MPU_HEADER_SIZE}`,\n },\n }\n }\n\n const mpu = parseMPUHeader(payload)\n if (mpu.aggregationFlag) {\n return {\n rejection: { gate: 'aggregation-flag-set', detail: 'strict profile requires A=0' },\n }\n }\n\n // Fragment type from byte 2 upper nibble (not returned by parseMPUHeader)\n const fragmentType = (payload[2]! >> 4) & 0x0f\n\n const layoutResult = resolveMfuPayloadLayout(mpu, fragmentType, payload)\n if ('rejection' in layoutResult) return layoutResult\n const { layout } = layoutResult\n\n const trailerLayout = resolveDeclaredTrailers(\n payload,\n layout.dataEnd,\n header.fecType,\n options,\n )\n if (!trailerLayout) {\n // The most common cause is a producer whose Source FEC Payload ID width\n // disagrees with ISO/IEC 23008-1 \u00A7C.5.2. Name that explicitly rather than\n // leaving operators to infer it from a generic trailer error.\n if (!options?.skipFecId && header.fecType === 1) {\n const observed = payload.length - layout.dataEnd\n if (observed !== SOURCE_FEC_PAYLOAD_ID_SIZE) {\n return {\n rejection: {\n gate: 'source-fec-payload-id-width-mismatch',\n detail:\n `producer wrote a ${observed}-byte Source FEC Payload ID, ` +\n `ISO/IEC 23008-1 \u00A7C.5.2 requires ${SOURCE_FEC_PAYLOAD_ID_SIZE} ` +\n `(payload=${payload.length}, declaredEnd=${layout.dataEnd})`,\n },\n }\n }\n }\n return {\n rejection: {\n gate: 'declared-trailers-unresolved',\n detail:\n `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty ` +\n `nor an exact ALTA trailer (fecType=${header.fecType})`,\n },\n }\n }\n\n return { plan: { header, payload, mpu, fragmentType, layout, trailerLayout } }\n}\n\n/**\n * Report why `extractMFUPayload` would refuse this packet, or null if it would\n * accept it.\n *\n * `extractMFUPayload` returns bare `null` on ten distinct conditions, which\n * makes a producer/consumer framing disagreement indistinguishable from a\n * publisher that never started \u2014 both render as silence. Use this wherever\n * that distinction matters: diagnostics, contract tests, and operator-facing\n * logs.\n */\nexport function explainMFURejection(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFURejection | null {\n const result = planMfuExtraction(mmtpPacket, options)\n return 'rejection' in result ? result.rejection : null\n}\n\nexport function extractMFUPayload(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options))\n}\n\n/**\n * extractMFUPayload for a caller that already holds this packet's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet; the\n * result is then identical to `extractMFUPayload(mmtpPacket, options)`.\n */\nexport function extractMFUPayloadWithHeader(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options, header))\n}\n\nfunction extractPlannedMfu(\n mmtpPacket: Uint8Array,\n result: { plan: MfuPlan } | { rejection: MFURejection },\n): MFUExtraction | null {\n if ('rejection' in result) return null\n const { header, payload, mpu, fragmentType, layout, trailerLayout } = result.plan\n\n const mfuDuHeaderPresent = layout.hasMfuDuHeader\n const mfuOffset = layout.mfuOffset\n const dataEnd = layout.dataEnd\n\n const mfuDuHeader =\n mfuDuHeaderPresent && mpu.timedFlag === 1\n ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE)\n : null\n const itemId =\n mfuDuHeaderPresent && mpu.timedFlag === 0\n ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false)\n : null\n\n const data = payload.subarray(mfuOffset, dataEnd)\n\n const fecPayloadId = trailerLayout.hasFecId\n ? {\n ssId: new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE,\n SOURCE_FEC_PAYLOAD_ID_SIZE,\n ).getUint32(0, false),\n }\n : null\n\n return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId }\n}\n\n/**\n * Extract timestamp from MMTP packet\n */\nexport function extractTimestamp(mmtpPacket: Uint8Array): bigint {\n const { header } = unwrapMMTP(mmtpPacket)\n return header.timestamp\n}\n\n/**\n * Check if MMTP packet is an FEC repair packet\n * FEC_type 2 and 3 are repair packets\n */\nexport function isFecRepairPacket(mmtpPacket: Uint8Array): boolean {\n if (mmtpPacket.length < 1) return false\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n return fecType >= 2\n}\n\n/**\n * Extract Source FEC Payload ID (SS_ID) from the last 4 bytes of an MMTP\n * source packet per ISO/IEC 23008-1:2023 Section C.5.2 Figure C.10.\n *\n * SS_ID is a flat 32-bit monotonic counter (incremented by 1 per packet\n * for ssbg_mode0 and ssbg_mode1). Block membership and ESI use the\n * configured K; repair SS_Start identifies the first SS_ID of the block and\n * SSB_length records the authored source-symbol count:\n * SBN = floor(SS_ID / K), ESI = SS_ID % K\n * SS_Start = SBN * K (first SS_ID of the block)\n *\n * This matches FFmpeg moqenc_mmt.c, the drafts (draft-ramadan-moq-fec \u00A76.2),\n * and ATSC A/331 \u00A78.1.2.5 which defers to ISO 23008-1 Annex C.\n */\nexport function extractFecPayloadId(mmtpPacket: Uint8Array): { ssId: number } | null {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header ? sourceFecPayloadIdOf(mmtpPacket, header) : null\n}\n\n/**\n * Return true when FEC_type=1 and the exact suffix after the ISO-declared MPU\n * payload contains a four-byte Source FEC Payload ID.\n */\nexport function hasSourceFecPayloadId(mmtpPacket: Uint8Array): boolean {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header !== null && sourceFecPayloadIdOf(mmtpPacket, header) !== null\n}\n\nfunction parseHeaderForSourceFecId(mmtpPacket: Uint8Array): MMTPHeader | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n try {\n return parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n}\n\n/**\n * The Source FEC Payload ID of a packet whose MMTP header the caller already\n * parsed, or null when the packet carries none: present only when FEC_type=1\n * and the exact suffix after the ISO-declared MPU payload holds the four-byte\n * ID (ISO/IEC 23008-1 \u00A7C.5.2). Same answer as extractFecPayloadId, without a\n * second header parse (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet.\n */\nexport function sourceFecPayloadIdOf(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n): { ssId: number } | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n if (header.packetType !== PACKET_TYPE_MPU) return null\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) return null\n const payloadLength = (payload[0]! << 8) | payload[1]!\n if (payloadLength < MPU_HEADER_REMAINDER_SIZE) return null\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (!resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId) return null\n\n // Flat 4-byte SS_ID: encoder writes block_id * K + symbol_id\n const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n return {\n ssId:\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0,\n }\n}\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\n// Re-export the ALTA trailer helper from the zero-dep leaf package. The leaf\n// owns the implementation so container and FEC consumers share one parser\n// without creating a package cycle.\nexport const MMTP_EXT_TYPE_ALTA_AUTH = _LEAF_MMTP_EXT_TYPE_ALTA_AUTH\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE\nexport type AltaTrailer = _LeafAltaTrailer\nexport const extractAltaTrailer = _leafExtractAltaTrailer\n\n/** Size of Repair FEC Payload ID per ISO/IEC 23008-1 Section C.5.3 */\nexport const REPAIR_FEC_PID_SIZE = 13\n\n/**\n * Parse the 13-byte Repair FEC Payload ID after the complete dynamic MMTP header.\n * `parsedHeader`, when given, must be `parseMMTPHeader(mmtpPacket)` of this\n * same packet; it spares the second header parse (BLO-36599).\n */\nfunction readRepairFecPayloadIdFields(mmtpPacket: Uint8Array, parsedHeader?: MMTPHeader): {\n packetId: number; ssStart: number; rsbLength: number; rsId: number; ssbLength: number; dataOffset: number\n} | null {\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n }\n if (header.packetType !== PACKET_TYPE_REPAIR || header.fecType !== 2) return null\n\n const off = header.headerLength\n if (mmtpPacket.length < off + REPAIR_FEC_PID_SIZE) return null\n const ssStart =\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0 // 32 bits\n const rsbLength = (mmtpPacket[off + 4]! << 16) | (mmtpPacket[off + 5]! << 8) | mmtpPacket[off + 6]! // 24 bits\n const rsId = (mmtpPacket[off + 7]! << 16) | (mmtpPacket[off + 8]! << 8) | mmtpPacket[off + 9]! // 24 bits\n const ssbLength = (mmtpPacket[off + 10]! << 16) | (mmtpPacket[off + 11]! << 8) | mmtpPacket[off + 12]! // 24 bits\n return {\n packetId: header.packetId,\n ssStart,\n rsbLength,\n rsId,\n ssbLength,\n dataOffset: off + REPAIR_FEC_PID_SIZE,\n }\n}\n\n/**\n * Extract Repair FEC Payload ID from a raw-multicast MMTP repair packet.\n * Per ISO/IEC 23008-1:2023 Section C.5.3 (ssbg_mode0, one-stage, no FFSRP_TS):\n * [MMTP header][SS_Start:4][RSB_length:3][RS_ID:3][SSB_length:3][repair data]\n * OTI is delivered via AL-FEC signaling message (Table C.3), not per-packet.\n *\n * Byte 1 is reserved(2)|packet_type(6) for V=0, so repair type 0x03 is 0x03.\n * Pre-migration high-bits bytes (0x0c) are rejected \u2014 no compatibility decode.\n */\nexport function extractRepairFecPayloadId(mmtpPacket: Uint8Array): {\n ssStart: number; rsbLength: number; rsId: number; ssbLength: number\n} | null {\n const fields = readRepairFecPayloadIdFields(mmtpPacket)\n if (!fields) return null\n return {\n ssStart: fields.ssStart,\n rsbLength: fields.rsbLength,\n rsId: fields.rsId,\n ssbLength: fields.ssbLength,\n }\n}\n\n/** Parsed MoQ repair frame per ISO/IEC 23008-1 Section C.4.3 + C.5.3 */\nexport interface MoqRepairFrame {\n /** MMTP packetId (1=video, 2=audio) \u2014 for routing to correct FEC decoder */\n packetId: number\n /** SS_Start (32 bits): SS_ID of first source symbol in block */\n ssStart: number\n /** RSB_length (24 bits): number of repair symbols (P) */\n rsbLength: number\n /** RS_ID (24 bits): repair symbol index (0-based) */\n rsId: number\n /** SSB_length (24 bits): authored source-symbol count (1..=configured K) */\n ssbLength: number\n /** Repair symbol data after the dynamic MMTP header and 13-byte FEC payload ID. */\n symbolData: Uint8Array\n}\n\n/**\n * Parse a MoQ repair frame (ISO/IEC 23008-1 Annex C format).\n *\n * Wire format per ISO 23008-1 \u00A7C.4.3 + \u00A7C.5.3:\n * MMTP Header: fixed 12 bytes plus C/X optional fields\n * then SS_Start (4 bytes) \u2192 block identifier\n * then RSB_length (3 bytes) \u2192 P\n * then RS_ID (3 bytes) \u2192 ESI = K + RS_ID\n * then SSB_length (3 bytes) \u2192 authored source-symbol count (1..=K)\n * then Repair Symbol Data (T bytes)\n *\n * @param frame - Raw MoQ repair frame bytes\n * @returns Parsed frame or null if invalid\n */\nexport function parseMoqRepairFrame(frame: Uint8Array): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame))\n}\n\n/**\n * parseMoqRepairFrame for a caller that already holds this frame's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599). The router's\n * multicast repair branch uses it: one repair packet in every K + P otherwise\n * paid two header parses.\n *\n * `header` must be `parseMMTPHeader(frame)` of this same frame; the result is\n * then identical to `parseMoqRepairFrame(frame)`.\n */\nexport function parseMoqRepairFrameWithHeader(\n frame: Uint8Array,\n header: MMTPHeader,\n): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame, header))\n}\n\nfunction moqRepairFrameFrom(\n frame: Uint8Array,\n repairId: ReturnType<typeof readRepairFecPayloadIdFields>,\n): MoqRepairFrame | null {\n if (!repairId) return null\n\n const symbolData = frame.subarray(repairId.dataOffset)\n if (\n repairId.rsbLength === 0 ||\n repairId.ssbLength === 0 ||\n repairId.rsId >= repairId.rsbLength ||\n symbolData.length === 0\n ) return null\n\n return {\n packetId: repairId.packetId,\n ssStart: repairId.ssStart,\n rsbLength: repairId.rsbLength,\n rsId: repairId.rsId,\n ssbLength: repairId.ssbLength,\n symbolData,\n }\n}\n\n// The signalling-message parsers (9.3.4.2 payload header, AL-FEC message,\n// PA/MPT) live in ./signalling.ts.\n", "/**\n * AMT well-known UDP port (RFC 7450 \u00A74).\n *\n * The single definition for the whole repo. It lives here, in the one package\n * both `@blockcast/mmt-transport` and `@blockcast/mmt-container` already\n * depend on, because the two apply it on opposite sides of a boundary that\n * does not otherwise share code:\n *\n * - container resolves it where `networkSource` semantics live, so a catalog\n * relay that names no port is reachable instead of handing a join a\n * portless relay that fails.\n * - transport applies it when reading a catalog, dialling a gateway, and\n * answering DRIAD \u2014 but deliberately not at every boundary. Which call\n * sites do and do not take the default is transport's business; the\n * reasoning lives beside those call sites, not here.\n */\nexport const DEFAULT_AMT_RELAY_PORT = 2268;\n", "/**\n * NTP short format (RFC 5905 \u00A76): an unsigned 32-bit, 16.16 fixed-point count\n * of seconds. The MMTP header `timestamp` field (ISO/IEC 23008-1 \u00A79.2.2)\n * carries each packet's UTC sending instant in this format, so the value wraps\n * to 0 every 2^16 s (~18.2 h).\n *\n * Plain numeric order on these values breaks at every wrap: a retained\n * `Math.max` stays at the pre-wrap value and every later instant compares as\n * older. Order send times only with {@link compareNtpShort}, which applies\n * RFC 1982 serial-number arithmetic over the field's width.\n */\n\n/** Width in bits of the MMTP `timestamp` field (NTP short). */\nexport const NTP_SHORT_BITS = 32\n\n/** Number of distinct NTP-short values; the field wraps to 0 here. */\nexport const NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS\n\n/**\n * RFC 1982 \u00A73.2's `2^(SERIAL_BITS - 1)`. Two values whose forward distance is\n * below this are ordered; exactly this far apart they are not ordered at all.\n * Two instants must be less than this far apart (2^15 s, ~9.1 h of send time)\n * for the comparison to give their true order.\n */\nexport const NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1)\n\n/**\n * Serial order of one NTP-short value relative to another.\n *\n * `'unordered'` is RFC 1982's undefined case: the values are exactly\n * {@link NTP_SHORT_HALF_RANGE} apart, so neither precedes the other. It is\n * never folded into `'before'` or `'after'`; each caller decides what an\n * unorderable pair means for it.\n */\nexport type NtpShortOrder = 'before' | 'equal' | 'after' | 'unordered'\n\n/** True when `value` is an NTP-short value: an integer in [0, NTP_SHORT_MODULUS). */\nexport function isNtpShort(value: number): boolean {\n return Number.isInteger(value) && value >= 0 && value < NTP_SHORT_MODULUS\n}\n\n/**\n * RFC 1982 serial comparison of NTP-short `a` against `b`: `'after'` when `a`\n * is the later instant, `'before'` when it is the earlier one.\n *\n * The answer is the true order only while the two instants are less than\n * {@link NTP_SHORT_HALF_RANGE} apart. Past that a 32-bit field cannot tell\n * later from earlier, and the result names whichever direction is nearer.\n * This is not a total order (it is not transitive across the whole space), so\n * it must never be used to sort.\n *\n * @throws {RangeError} if either operand is not an NTP-short value\n */\nexport function compareNtpShort(a: number, b: number): NtpShortOrder {\n if (!isNtpShort(a) || !isNtpShort(b)) {\n throw new RangeError(\n `NTP-short values must be integers in [0, 2^${NTP_SHORT_BITS}), got ${String(a)} and ${String(b)}`,\n )\n }\n if (a === b) return 'equal'\n // Forward distance from b to a, reduced into [1, NTP_SHORT_MODULUS). Both\n // operands are below 2^32, so every step is exact in a double.\n const forward = (a - b + NTP_SHORT_MODULUS) % NTP_SHORT_MODULUS\n if (forward < NTP_SHORT_HALF_RANGE) return 'after'\n if (forward > NTP_SHORT_HALF_RANGE) return 'before'\n return 'unordered'\n}\n", "/**\n * RFC 1982 serial-number arithmetic for the 32-bit MMTP AL-FEC symbol\n * counters (ISO/IEC 23008-1:2023 C.5.2 SS_ID, C.5.3 SS_Start).\n *\n * SS_ID is a 32-bit field, so a stream that runs long enough wraps it to 0.\n * Ordering two SS_IDs (or two blocks by their first SS_ID) with `<` reads\n * every post-wrap value as older than every pre-wrap one. Serial order\n * agrees with `<` whenever the two values are less than 2^31 apart, so for\n * every stream that has not reached the wrap the result is unchanged; only\n * the order across the wrap point differs (BLO-35597 iso:5).\n */\n\n/** 2^SERIAL_BITS for SERIAL_BITS = 32. */\nexport const SERIAL32_SPAN = 0x1_0000_0000\n\n/** RFC 1982 \u00A73.2's 2^(SERIAL_BITS - 1). */\nexport const SERIAL32_HALF = 0x8000_0000\n\nfunction assertUint32(value: number, name: string): void {\n if (!Number.isInteger(value) || value < 0 || value >= SERIAL32_SPAN) {\n throw new RangeError(`${name} must be a uint32 (got ${value})`)\n }\n}\n\n/**\n * RFC 1982 \u00A73.1 addition, extended to negative `n` for the matching\n * subtraction: `(s + n) mod 2^32`. `n` must lie in (-2^31, 2^31).\n */\nexport function serial32Add(s: number, n: number): number {\n assertUint32(s, 's')\n if (!Number.isInteger(n) || n <= -SERIAL32_HALF || n >= SERIAL32_HALF) {\n throw new RangeError(`serial addend must lie in (-2^31, 2^31) (got ${n})`)\n }\n return (s + n + SERIAL32_SPAN) % SERIAL32_SPAN\n}\n\n/** Forward distance from `from` to `to`: `(to - from) mod 2^32`. */\nexport function serial32Distance(from: number, to: number): number {\n return (to - from) >>> 0\n}\n\n/**\n * RFC 1982 \u00A73.2: `a` precedes `b`. Values exactly 2^31 apart are unordered\n * (neither precedes the other).\n */\nexport function serial32Precedes(a: number, b: number): boolean {\n const distance = serial32Distance(a, b)\n return distance !== 0 && distance < SERIAL32_HALF\n}\n", "/**\n * FEC block profile (BLO-35597, ISO/IEC 23008-1 Annex C).\n *\n * Every catalog `fec` object that declares an FEC instance names its block\n * profile in `fec.blockProfile`. A receiver applies all of a profile's rules\n * or none of them, so the token is the one explicit per-stream discriminator:\n * it is never defaulted and never inferred from the wire.\n *\n * draft-ramadan-moq-fec \u00A75.1 defines exactly one value, and it is the only\n * profile this library implements:\n *\n * - `iso-ssbg1-v1`: ssbg_mode1 (C.3.3): a source symbol is\n * `u16 length || protected packet || zero padding` to T; a block is a\n * K' = SSB_length RaptorQ block (C.5.3) and repair ESI = SSB_length + RS_ID;\n * SS_ID increments by one per FEC source packet and wraps at 2^32 (C.5.2),\n * so a block is located by membership in `[SS_Start, SS_Start + SSB_length)`\n * under RFC 1982 serial arithmetic; the MoQ repair group id is the 62-bit\n * wrap-epoch-extended SS_Start. The AL-FEC coding byte for this profile is\n * 0x14 (one-stage, ssbg_mode1, fec_payload_id_mode 0, Amd 1:2025 Table C.3).\n *\n * A track whose `blockProfile` is absent or names any other token (the\n * retired Blockcast-private `k-padded-v1` included) has no FEC: a receiver\n * consumes its source without FEC and joins none of its repair tracks\n * (draft-ramadan-moq-fec \u00A75.1). `parseFecBlockProfile` throws for such a\n * token; the per-track catalog resolver turns the same rule into a typed\n * per-track \"FEC unavailable\" outcome instead of failing the session.\n */\nimport { serial32Add, serial32Distance, serial32Precedes } from './serial-number.js'\n\n/** The block profile a catalog `fec.blockProfile` may name: the one this library implements. */\nexport type FecBlockProfile = 'iso-ssbg1-v1'\n\n/** Every accepted `fec.blockProfile` token. */\nexport const FEC_BLOCK_PROFILES: readonly FecBlockProfile[] = Object.freeze([\n 'iso-ssbg1-v1',\n])\n\n/** True for a block-profile token this library implements. */\nexport function isFecBlockProfile(value: unknown): value is FecBlockProfile {\n return value === 'iso-ssbg1-v1'\n}\n\n/**\n * Read a required `fec.blockProfile`. Throws on an absent token and on any\n * token other than `iso-ssbg1-v1`: there is no default and nothing is\n * inferred from the wire (draft-ramadan-moq-fec \u00A75.1).\n *\n * @param where the catalog path of the field, for the error message.\n */\nexport function parseFecBlockProfile(value: unknown, where: string): FecBlockProfile {\n if (isFecBlockProfile(value)) return value\n if (value === undefined) {\n throw new Error(`${where} is required: name the FEC block profile (\"iso-ssbg1-v1\")`)\n }\n throw new Error(`${where} must be \"iso-ssbg1-v1\" (got ${JSON.stringify(value)})`)\n}\n\n/** ISO/IEC 23008-1 C.3.3 ssbg_mode1: the two-octet packet length field. */\nexport const SSBG1_LENGTH_FIELD_BYTES = 2\n\n/** The largest protected packet an ssbg_mode1 symbol of size T can carry. */\nexport function ssbg1MaxPacketLength(symbolSize: number): number {\n return symbolSize - SSBG1_LENGTH_FIELD_BYTES\n}\n\n/** Whether a protected packet of `packetLength` bytes fits one T-byte ssbg_mode1 symbol. */\nexport function ssbg1FitsSymbol(packetLength: number, symbolSize: number): boolean {\n return (\n Number.isSafeInteger(packetLength) &&\n packetLength >= 0 &&\n packetLength <= ssbg1MaxPacketLength(symbolSize) &&\n packetLength <= 0xffff\n )\n}\n\n/**\n * Build one ssbg_mode1 source symbol (C.3.3): the protected packet's length\n * in two octets, network byte order, then the packet, then zero octets up to\n * T. The protected packet is the MMTP packet before its Source FEC Payload ID\n * is appended (C.4.2).\n *\n * @throws when `length + 2 > T`: the packet cannot be protected under this\n * profile, and silently truncating it would corrupt every repair symbol.\n */\nexport function buildSsbg1SourceSymbol(packet: Uint8Array, symbolSize: number): Uint8Array {\n if (!Number.isSafeInteger(symbolSize) || symbolSize <= SSBG1_LENGTH_FIELD_BYTES) {\n throw new Error(`ssbg_mode1 symbol size must exceed ${SSBG1_LENGTH_FIELD_BYTES} (got ${symbolSize})`)\n }\n if (!ssbg1FitsSymbol(packet.byteLength, symbolSize)) {\n throw new Error(\n `ssbg_mode1 source symbol needs ${packet.byteLength} + ${SSBG1_LENGTH_FIELD_BYTES} bytes but T is ${symbolSize}`,\n )\n }\n const symbol = new Uint8Array(symbolSize)\n symbol[0] = (packet.byteLength >>> 8) & 0xff\n symbol[1] = packet.byteLength & 0xff\n symbol.set(packet, SSBG1_LENGTH_FIELD_BYTES)\n return symbol\n}\n\n/**\n * Recover the protected packet from one ssbg_mode1 source symbol: the bytes\n * the length field names. Returns null, never a guess, when the length field\n * points past the symbol or any padding octet is non-zero (C.3.3 padding is\n * all 00h), since either means the recovered symbol is not what was sent.\n */\nexport function trimSsbg1SourceSymbol(symbol: Uint8Array): Uint8Array | null {\n if (symbol.byteLength < SSBG1_LENGTH_FIELD_BYTES) return null\n const length = (symbol[0]! << 8) | symbol[1]!\n const end = SSBG1_LENGTH_FIELD_BYTES + length\n if (end > symbol.byteLength) return null\n for (let i = end; i < symbol.byteLength; i++) {\n if (symbol[i] !== 0) return null\n }\n return symbol.subarray(SSBG1_LENGTH_FIELD_BYTES, end)\n}\n\n/** The SS_ID after the last source symbol of `[ssStart, ssStart + ssbLength)`. */\nexport function isoBlockEnd(ssStart: number, ssbLength: number): number {\n return serial32Add(ssStart, ssbLength)\n}\n\n/**\n * Range membership (C.5.2, C.5.3): whether `ssId` is one of the\n * `ssbLength` consecutive SS_IDs starting at `ssStart`, modulo 2^32.\n */\nexport function isoBlockContains(ssStart: number, ssbLength: number, ssId: number): boolean {\n return serial32Distance(ssStart, ssId) < ssbLength\n}\n\n/**\n * A lower bound on how many blocks the block starting at `a` lies behind the\n * one starting at `b` (0 when `a` does not precede `b`). An iso-ssbg1-v1\n * block holds at most `kMax` source symbols (the catalog `sourceSymbols`, the\n * AL-FEC maximum_k_for_repair_flow), so `n * kMax` SS_IDs always span at least\n * `n` blocks. Every interleave horizon in the receiver (decoder retention,\n * worker retirement, ordered-relay settlement) uses this one bound, so the\n * manager's horizon can never fall inside the decoder's.\n */\nexport function isoBlocksBehindLowerBound(a: number, b: number, kMax: number): number {\n if (!Number.isSafeInteger(kMax) || kMax <= 0) {\n throw new Error(`iso-ssbg1-v1 block horizon needs a positive maximum K (got ${kMax})`)\n }\n return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0\n}\n", "/**\n * TrackSession \u2014 per-track transport state machine.\n * SessionManager \u2014 orchestrates all sessions + TransportPool.\n *\n * Implements Patent Claim 9 (draft-ramadan-moq-fec APPLICATION-1):\n * (a) subscribe unicast\n * (b) simultaneously join multicast\n * (c) render from unicast during join\n * (d) activate multicast on RAP, suppress unicast\n * (e) cross-transport FEC repair during transition\n * (f) hysteresis before switching away from multicast\n * (g) re-subscribe unicast on sustained failure\n *\n * PIM: manages transport state without inspecting packet contents.\n */\n\nimport {\n TransportPool,\n type Subscription,\n type SubscriptionMode,\n type AcquireOptions,\n type DirectSocketHealth,\n type TransportPoolOptions,\n} from './transport-pool.js'\nimport { fecBlockBudgetMs } from '@blockcast/mmt-fec'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TrackState =\n | 'idle'\n | 'unicast'\n | 'unicast-fallback'\n | 'joining'\n | 'multicast'\n | 'amt'\n | 'failing'\n\nexport interface TrackSessionConfig {\n trackName: string\n trackType: 'video' | 'audio'\n packetId: number\n /** FEC interleave depth from catalog (ms). Used for hysteresis timer. */\n fecInterleaveMs: number\n /** Explicit catalog jitter/reorder allowance in ms. */\n fecReorderToleranceMs?: number\n /** FEC K from catalog. Used for audio worklet latency calculation. */\n fecK: number\n /** FEC repair symbols per block (P). NOT symbol size. */\n fecRepairCount: number\n /**\n * Repair symbols per block this receiver actually joined: the sum of\n * `repairSymbols` over the joined BASE layers, not a count of layers\n * (draft-ramadan-moq-fec 6.3). A keyframe overlay is a separate FEC instance\n * and is not included.\n *\n * This is runtime join state, not catalog geometry: a receiver on a clean\n * path joins layer 0 only while a lossy one adds layers, so the catalog's\n * total P is not what this receiver is receiving. Diagnostics that report P\n * (the worker snapshot, CMCD `x-bc-fec-p`) must read this.\n *\n * Absent when the caller joins no layers explicitly, in which case P_0 is\n * what was joined. That is an identity on a single-layer track, not a\n * catalog default: with one layer, P_0 is the total.\n */\n joinedRepairCount?: number\n /** FEC symbol size in bytes (T). Required for WASM decoder OTI. */\n fecSymbolSize: number\n /** MoQ repair track name from catalog FEC config. */\n repairTrackName?: string\n /** Frame samples from initData parsing (audio-init-parser.ts). */\n frameSamples?: number\n /** Audio sample rate from catalog. */\n sampleRate?: number\n /** Ordered transport preference: best first. */\n preferredTransports: SubscriptionMode[]\n /** Subscription key for multicast/AMT: \"(S,G,port)\". */\n multicastKey?: string\n /** MoQ track name for unicast subscription. */\n moqTrackName?: string\n /** AMT relay address (if AMT is in preference chain). */\n amtRelay?: string\n /** MoQ relay URL (for WebTransport subscription). */\n moqRelayUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// PIM callbacks (protocol-independent \u2014 transport state only)\n// ---------------------------------------------------------------------------\n\nexport interface SessionManagerCallbacks {\n /** Fired on every transport state transition. */\n onStateChange?(trackName: string, from: TrackState, to: TrackState): void\n /** Fired to suppress/unsuppress unicast for this track (transport arbitration). */\n onSuppress?(trackName: string, suppressed: boolean): void\n /**\n * Fired when a track sat in `joining` for SessionManager.takeoverDeadlineMs\n * without a RAP (BLO-9413/D9). The session has already returned to `unicast`\n * (nothing was suppressed, so nothing to restore) and it keeps its FEC\n * decoder armed, because the multicast socket is still joined and the router\n * still admits the stream's symbols. The host should abandon the router-side\n * attempt (e.g. MediaRouter.failbackToMoq()) so the router stops waiting for\n * a keyframe that is never coming; it must not dispose the decoder here. The\n * decoder is released on the next transport edge (unsubscribe, failbackAll,\n * or transportFailed falling back to unicast) through fec-teardown.\n */\n onTakeoverTimeout?(trackName: string): void\n}\n\n// ---------------------------------------------------------------------------\n// MMTP application-layer extension (FEC lifecycle, media resets)\n// ---------------------------------------------------------------------------\n\n/**\n * MMTP track events (discriminated union).\n * Each protocol layer defines its own event union \u2014 MMTP events cover\n * media resets, latency, and FEC lifecycle.\n */\nexport type MmtpTrackEvent =\n | { type: 'reset' }\n | { type: 'latency'; latencyMs: number }\n | { type: 'fec-setup' }\n | { type: 'fec-teardown' }\n\n/**\n * MMTP application-layer extension of SessionManagerCallbacks.\n * Single onTrackEvent dispatches all MMTP-specific events.\n * PIM consumers use the base SessionManagerCallbacks (no onTrackEvent).\n *\n * IWA can define its own subinterface with its own TrackEvent union\n * (e.g., IwaTrackEvent for socket health, AMT state) using the same pattern.\n */\nexport interface MmtpSessionCallbacks extends SessionManagerCallbacks {\n onTrackEvent?(trackName: string, event: MmtpTrackEvent, config: TrackSessionConfig): void\n}\n\n/**\n * Loss-hysteresis window covering the signaled FEC block span plus jitter.\n *\n * `fecK` no longer feeds the math; it is still validated as a catalog\n * well-formedness guard and kept in the signature for positional API compat\n * (removal is a follow-up with the next cross-repo pin bump).\n */\nexport function computeFecLossWindowMs(fecInterleaveMs: number, fecK: number, jitterBufferMs?: number): number {\n return computeFecDeliveryWindowMs({\n interleaveDepthMs: fecInterleaveMs,\n sourceSymbols: fecK,\n jitterBufferMs,\n })\n}\n\nexport interface FecDeliveryWindowConfig {\n interleaveDepthMs: number\n sourceSymbols: number\n repairSymbols?: number\n symbolSize?: number\n mediaUnitsPerSecond?: number\n bandwidthBps?: number\n /**\n * Explicit jitter/reorder allowance in ms, added once to the complete span.\n * Callers pass their signaled catalog allowance, never a local default.\n */\n jitterBufferMs?: number\n}\n\n/**\n * The catalog interleaveDepthMs is the complete FEC block span, including the\n * repair tail. K, P, cadence, and bandwidth must not expand it again. Geometry\n * fields remain accepted for existing callers, but do not feed deadline math.\n */\nexport function computeFecDeliveryWindowMs(\n config: FecDeliveryWindowConfig,\n): number {\n const fecInterleaveMs = config.interleaveDepthMs\n const fecK = config.sourceSymbols\n if (!Number.isFinite(fecInterleaveMs) || fecInterleaveMs <= 0) {\n throw new Error(\n `[TrackSession] fecInterleaveMs=${fecInterleaveMs}. ` +\n `Derive a positive FEC interleave window from the stream catalog.`\n )\n }\n if (!Number.isFinite(fecK) || fecK <= 0) {\n throw new Error(\n `[TrackSession] fecK=${fecK}. ` +\n `Derive a positive FEC source-symbol count from the stream catalog.`\n )\n }\n\n // Absence stays absent; it does not authorize a synthesized reorder budget.\n if (config.jitterBufferMs === undefined) return Math.ceil(fecInterleaveMs)\n return fecBlockBudgetMs(fecInterleaveMs, config.jitterBufferMs)\n}\n\n/** Convert the shared FEC delivery deadline into an integer media timescale. */\nexport function computeFecDeliveryWindowMediaUnits(\n config: FecDeliveryWindowConfig,\n timestampTimescale: number,\n): number {\n if (!Number.isFinite(timestampTimescale) || timestampTimescale <= 0) {\n throw new Error(\n `[TrackSession] timestampTimescale=${timestampTimescale}. ` +\n 'Derive a positive media timescale from the container.',\n )\n }\n const deliveryWindow = Math.ceil(\n computeFecDeliveryWindowMs(config) * timestampTimescale / 1000,\n )\n if (!Number.isSafeInteger(deliveryWindow)) {\n throw new Error('[TrackSession] FEC delivery window exceeds the media timestamp range.')\n }\n return deliveryWindow\n}\n\n// ---------------------------------------------------------------------------\n// TrackSession\n// ---------------------------------------------------------------------------\n\nexport class TrackSession {\n readonly config: TrackSessionConfig\n #state: TrackState = 'idle'\n #subscription: Subscription | null = null\n #hysteresisTimer: ReturnType<typeof setTimeout> | null = null\n #joinDeadlineTimer: ReturnType<typeof setTimeout> | null = null\n #previousPrimary: SubscriptionMode | null = null\n #manager: SessionManager\n // Single suppression authority flag (BLO-9413 failback coherence): every\n // suppress this session fires is recorded here, and every path that leaves\n // a suppressed state unwinds through #unsuppress(). Before this flag,\n // unsubscribe() only un-suppressed from multicast/amt \u2014 a session torn down\n // from `failing` (exactly where a starving track sits) was re-subscribed to\n // unicast with the host's media still muted: MoQ playing into a dead sink.\n #suppressFired = false\n // Single FEC authority flag: set by the fec-setup edges (joinStarted,\n // enterUnicastFallback), cleared by the fec-teardown edge in #transition.\n // Setup and teardown are paired by construction, so a takeover timeout that\n // parks the session in `unicast` keeps the decoder the still-joined\n // transport is feeding, and the later unsubscribe still tears it down.\n #fecArmed = false\n\n constructor(config: TrackSessionConfig, manager: SessionManager) {\n this.config = config\n this.#manager = manager\n }\n\n // -----------------------------------------------------------------------\n // State\n // -----------------------------------------------------------------------\n\n get state(): TrackState { return this.#state }\n get subscription(): Subscription | null { return this.#subscription }\n\n /** True while this session's onSuppress(true) is outstanding (no matching\n * onSuppress(false) yet). Test/assertion surface for failback coherence. */\n get isSuppressed(): boolean { return this.#suppressFired }\n\n /** Current active transport mode, or null if idle. */\n get transport(): SubscriptionMode | null {\n switch (this.#state) {\n case 'multicast': return 'multicast'\n case 'amt': return 'amt'\n case 'unicast': return 'moq'\n case 'unicast-fallback': return 'moq' // SSM bridge absent \u2014 MoQ unicast is the only source\n case 'joining': return 'moq' // MoQ still primary during join\n case 'failing': return this.#previousPrimary\n default: return null\n }\n }\n\n // -----------------------------------------------------------------------\n // Computed (no magic numbers)\n // -----------------------------------------------------------------------\n\n /** Audio worklet latency: max(fecInterleaveMs, frameDurationMs * fecK).\n * Returns undefined if frame duration unknown (no initData). */\n get audioLatencyMs(): number | undefined {\n const fd = this.frameDurationMs\n if (fd === undefined) {\n return this.config.fecInterleaveMs > 0 ? this.config.fecInterleaveMs : undefined\n }\n return Math.max(this.config.fecInterleaveMs, fd * this.config.fecK)\n }\n\n /** Frame duration in ms, derived from frameSamples + sampleRate. */\n get frameDurationMs(): number | undefined {\n const { frameSamples, sampleRate } = this.config\n if (!frameSamples || !sampleRate || sampleRate <= 0) return undefined\n return (frameSamples / sampleRate) * 1000\n }\n\n // -----------------------------------------------------------------------\n // Transitions (Patent Claim 9)\n // -----------------------------------------------------------------------\n\n /** idle \u2192 unicast: player subscribes to this track. */\n subscribe(): void {\n if (this.#state !== 'idle') return\n this.#transition('unicast')\n\n // Emit audio latency on first subscribe\n if (this.config.trackType === 'audio') {\n const latency = this.audioLatencyMs\n if (latency !== undefined) {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'latency', latencyMs: latency }, this.config)\n }\n }\n }\n\n /** unicast \u2192 joining: multicast/AMT capability detected, join in progress. */\n joinStarted(): void {\n if (this.#state !== 'unicast') return\n this.#transition('joining')\n // FEC decoders must be ready before multicast data arrives (Claim 9e:\n // cross-transport repair). A session re-joining after a takeover timeout\n // still owns its decoder, so fec-setup fires once per FEC epoch.\n this.#armFec()\n }\n\n /**\n * any non-fallback state \u2192 unicast-fallback. Used when the SSM bridge is\n * absent (vanilla Chromium / no extension) and the host falls back to a\n * MoQ unicast subscription per track. Terminal-ish: activateMulticast,\n * failbackAll, and transportFailed are no-ops while in this state.\n *\n * Fires fec-setup so the per-track repair worker spins up \u2014 repair packets\n * still flow over the MoQ repair-track subscription, so FEC source+repair\n * can converge as in the multicast path.\n *\n * Audio latency is NOT re-emitted here: subscribe() already fires latency\n * once on the idle \u2192 unicast edge, and enterUnicastFallback is only ever\n * reached from a non-idle state (SessionManager skips idle sessions).\n */\n enterUnicastFallback(): void {\n if (this.#state === 'unicast-fallback') return\n this.#clearHysteresis()\n this.#transition('unicast-fallback')\n // Suppress the redundant unicast media decoder. On the no-bridge path the\n // host feeds MoQ-unicast source packets into MulticastManager (the render\n // consumer); a second base-media subscribe (the @moq/watch decoder) would\n // split the single connection's QUIC flow-control budget and stall delivery\n // (BLO-8969). Fire suppress here, on the fallback edge, so the decoder is\n // gated off before it subscribes rather than after the relay forward task\n // has already wedged.\n this.#suppress()\n this.#armFec()\n }\n\n /**\n * unicast-fallback \u2192 unicast. Called when the SSM bridge appears mid-session\n * (extension installed after fallback engaged). Host first aborts the fallback\n * drainers, then transitions sessions back to unicast so the regular\n * multicast/AMT preference chain can take over via joinStarted().\n */\n exitUnicastFallback(): void {\n if (this.#state !== 'unicast-fallback') return\n this.#transition('unicast')\n // Un-suppress the unicast decoder: the bridge appeared mid-session, so the\n // host tears down the fallback drainers and the regular preference chain\n // (joinStarted -> joining -> rapReceived) will re-suppress on multicast RAP.\n this.#unsuppress()\n }\n\n /**\n * joining \u2192 multicast or amt: RAP received on the joined transport (Claim 9d).\n * Resets audio worklet, then suppresses MoQ.\n */\n rapReceived(mode: 'multicast' | 'amt'): void {\n if (this.#state !== 'joining') return\n this.#previousPrimary = mode\n this.#transition(mode)\n if (this.config.trackType === 'audio') {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'reset' }, this.config)\n }\n this.#suppress()\n }\n\n /** multicast/amt \u2192 failing: loss detected, start hysteresis timer (Claim 9f). */\n lossDetected(): void {\n if (this.#state !== 'multicast' && this.#state !== 'amt') return\n this.#previousPrimary = this.#state as SubscriptionMode\n this.#transition('failing')\n\n // Hysteresis timer: sustained loss before failback (Claim 9f). Use the\n // same complete span plus explicit allowance as FECManager and workers.\n const hysteresisMs = computeFecLossWindowMs(\n this.config.fecInterleaveMs,\n this.config.fecK,\n this.config.fecReorderToleranceMs,\n )\n this.#hysteresisTimer = setTimeout(() => {\n this.hysteresisExpired()\n }, hysteresisMs)\n }\n\n /** failing \u2192 previous primary: loss recovered within hysteresis window. */\n lossRecovered(): void {\n if (this.#state !== 'failing') return\n this.#clearHysteresis()\n if (this.#previousPrimary) {\n this.#transition(this.#previousPrimary as TrackState)\n }\n }\n\n /**\n * Transport failed (AMT relay down, multicast interface gone).\n * Try next transport in preference chain.\n */\n transportFailed(): void {\n this.#clearHysteresis()\n // Unicast-fallback is terminal: the host owns the subscription and\n // SessionManager transport machinery does not arbitrate it.\n if (this.#state === 'unicast-fallback') return\n const current = this.#previousPrimary ?? this.transport\n const prefs = this.config.preferredTransports\n const currentIdx = current ? prefs.indexOf(current) : -1\n const nextMode = prefs[currentIdx + 1]\n\n if (nextMode !== undefined && nextMode !== 'moq') {\n // Next is multicast or AMT: start joining. `joining` means MoQ is\n // primary again (see transport getter), so if the failed transport had\n // suppressed unicast, restore it for the join window or the viewer\n // watches black video until the next RAP (which may never come).\n // The chain position moves with the attempt: a join that times out\n // parks the session in `unicast` with this transport recorded, so the\n // next failure continues past it rather than re-trying the transport\n // that already failed and re-firing onTakeoverTimeout on every lap.\n this.#previousPrimary = nextMode\n this.#transition('joining')\n this.#unsuppress()\n return\n }\n // MoQ is next, or the chain is exhausted: unicast either way (Claim 9g).\n // For a session parked in `unicast` with its decoder retained after a\n // takeover timeout, this transition is a self-edge and fires no teardown,\n // so the release is explicit. It is the \"transportFailed falling back to\n // unicast\" release path the onTakeoverTimeout doc names, and #disarmFec is\n // idempotent, so it is a no-op when the transition already fired the\n // teardown. The audio reset is not flag-guarded, so it is tied to the edge\n // itself: a session already resting in unicast changed no transport and\n // owes the host no reset, however many pool subscriptions fail for it.\n const from = this.#state\n this.#previousPrimary = null\n this.#transition('unicast')\n this.#disarmFec()\n this.#unsuppress()\n if (from !== 'unicast' && this.config.trackType === 'audio') {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'reset' }, this.config)\n }\n }\n\n /** Hysteresis expired \u2192 failback to next transport (Claim 9g). */\n hysteresisExpired(): void {\n if (this.#state !== 'failing') return\n this.#clearHysteresis()\n this.transportFailed()\n }\n\n /** \u2192 idle: player unsubscribes / tab close / cleanup. */\n unsubscribe(): void {\n this.#clearHysteresis()\n if (this.#state !== 'idle') {\n this.#transition('idle')\n // Unwind any outstanding suppression regardless of which state we left.\n // The old `wasActive` check (multicast/amt only) leaked suppression when\n // tearing down from `failing` or `unicast-fallback` \u2014 failbackAll()'s\n // unsubscribe\u2192subscribe cycle then re-entered unicast with the host's\n // media still muted (BLO-9413 freeze: \"failing back\" logged, video dead).\n this.#unsuppress()\n }\n }\n\n /** True when SessionManager.enterUnicastFallback put this track into the\n * host-driven unicast subscription mode. Host can iterate sessions to\n * decide whether to keep its fallback drainer alive across MediaRouter\n * arbitration events. */\n get isUnicastFallback(): boolean {\n return this.#state === 'unicast-fallback'\n }\n\n /**\n * True while this track owns a FEC decoder (fec-setup fired, fec-teardown\n * not yet). Stays true across a takeover timeout that parked the session in\n * `unicast`; SessionManager.failbackAll uses it to release such sessions.\n */\n get isFecArmed(): boolean {\n return this.#fecArmed\n }\n\n /**\n * Start the deadline a `joining` entry deferred while the join was inactive\n * (SessionManager.joinActive). The track gets the FULL deadline from here,\n * because that is when media can first arrive. No-op outside `joining` and\n * when a deadline is already running, so it never shortens or restarts one.\n *\n * The `joinActive` guard is what makes this safe to call from anywhere: a\n * track parked in `joining` with a deferred (null) timer satisfies the other\n * two, so without it a host calling this while the join is still inactive\n * would arm the exact deadline the gate exists to defer -- silently\n * reinstating BLO-34289. It is a no-op for the internal caller, which writes\n * `#joinActive = true` before iterating the sessions.\n */\n armDeferredJoinDeadline(): void {\n if (this.#state !== 'joining' || this.#joinDeadlineTimer !== null) return\n if (!this.#manager.joinActive) return\n this.#armJoinDeadline(this.#manager.takeoverDeadlineMs)\n }\n\n dispose(): void {\n this.#clearHysteresis()\n this.#clearJoinDeadline()\n this.#state = 'idle'\n this.#subscription = null\n // Release through the teardown edge, not a bare assignment: the\n // retained-FEC park makes `unicast` + isFecArmed a resting state, and\n // SessionManager.dispose() calls this directly (removeTrack() reaches it\n // via unsubscribe(), which has already disarmed). A bare assignment there\n // drops the host's decoder without a fec-teardown -- a leaked Worker.\n this.#disarmFec()\n }\n\n // -----------------------------------------------------------------------\n // Internal\n // -----------------------------------------------------------------------\n\n #transition(to: TrackState, opts?: { retainFec?: boolean }): void {\n const from = this.#state\n if (from === to) return\n // Takeover deadline (BLO-9413/D9): every entry into `joining` arms it\n // (joinStarted AND transportFailed-failover both route through here);\n // every exit cancels it. Without this, a transport that accepts the\n // subscribe but never delivers a RAP parks the session in `joining`\n // forever (observed live in the Phase-0 headless repro). The value is\n // resolved before any state write, so the one residual failure -- a\n // manager whose deadline was never set at all, which addTrack() refuses --\n // leaves the session in its previous state rather than half-transitioned.\n // Invalid values never reach here: SessionManager's setter refuses them.\n // The read is unconditional even when `joinActive` defers the arm, so a\n // manager without a deadline still fails closed on every joining edge.\n const joinDeadlineMs = to === 'joining' ? this.#manager.takeoverDeadlineMs : undefined\n this.#state = to\n if (from === 'joining') this.#clearJoinDeadline()\n if (joinDeadlineMs !== undefined && this.#manager.joinActive) {\n this.#armJoinDeadline(joinDeadlineMs)\n }\n // FEC teardown: entering a state without a FEC decoder releases the one\n // this track armed, unless the caller keeps it (takeover timeout: the\n // transport is still joined and still feeding the decoder).\n const fecActive = (s: TrackState) =>\n s === 'joining' ||\n s === 'multicast' ||\n s === 'amt' ||\n s === 'failing' ||\n s === 'unicast-fallback'\n if (!fecActive(to) && !opts?.retainFec) this.#disarmFec()\n this.#manager.callbacks?.onStateChange?.(this.config.trackName, from, to)\n }\n\n #clearHysteresis(): void {\n if (this.#hysteresisTimer !== null) {\n clearTimeout(this.#hysteresisTimer)\n this.#hysteresisTimer = null\n }\n }\n\n #armJoinDeadline(deadlineMs: number): void {\n this.#clearJoinDeadline()\n if (deadlineMs === 0) return // a declared 0 disables the deadline\n this.#joinDeadlineTimer = setTimeout(() => {\n this.#joinDeadlineTimer = null\n if (this.#state !== 'joining') return\n // No RAP within the deadline: return to unicast for arbitration only.\n // MoQ was never suppressed in `joining`, so there is nothing to restore\n // and this edge stays silent on onSuppress (asserted in tests). The\n // multicast socket is still joined and the router still admits this\n // stream's symbols, so the FEC decoder stays armed; the next transport\n // edge releases it.\n this.#transition('unicast', { retainFec: true })\n this.#manager.callbacks?.onTakeoverTimeout?.(this.config.trackName)\n }, deadlineMs)\n }\n\n #clearJoinDeadline(): void {\n if (this.#joinDeadlineTimer !== null) {\n clearTimeout(this.#joinDeadlineTimer)\n this.#joinDeadlineTimer = null\n }\n }\n\n /** Fire fec-setup exactly once per FEC epoch. */\n #armFec(): void {\n if (this.#fecArmed) return\n this.#fecArmed = true\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'fec-setup' }, this.config)\n }\n\n /** Fire fec-teardown for an armed decoder; no-op when none is armed. */\n #disarmFec(): void {\n if (!this.#fecArmed) return\n this.#fecArmed = false\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'fec-teardown' }, this.config)\n }\n\n /** Fire onSuppress(true) exactly once per suppression epoch. */\n #suppress(): void {\n if (this.#suppressFired) return\n this.#suppressFired = true\n this.#manager.callbacks?.onSuppress?.(this.config.trackName, true)\n }\n\n /** Unwind an outstanding suppression; no-op when none is outstanding. */\n #unsuppress(): void {\n if (!this.#suppressFired) return\n this.#suppressFired = false\n this.#manager.callbacks?.onSuppress?.(this.config.trackName, false)\n }\n}\n\n// ---------------------------------------------------------------------------\n// SessionManager\n// ---------------------------------------------------------------------------\n\n/**\n * Largest takeover deadline setTimeout represents faithfully. setTimeout\n * coerces its delay to a signed 32-bit int, so a larger value fires on the\n * next tick (Node also warns TimeoutOverflowWarning) instead of after the\n * window the host asked for -- silently inverting the feature. Rejecting it\n * keeps the classic units bug (seconds-as-ms, a wrong timescale) loud.\n */\nexport const MAX_TAKEOVER_DEADLINE_MS = 2_147_483_647\n\n/**\n * How long `joinActive` may stay false before the library says so. The deferral\n * is unbounded by construction -- that is the feature -- so a host that never\n * flips it back parks every track in `joining` with no timer at all: exactly the\n * forever-join the takeover deadline exists to prevent, only silent. MoQ stays\n * unsuppressed so the viewer sees video, but onTakeoverTimeout never fires, the\n * FEC decoder stays armed, and the preference chain never advances past the\n * transport that failed. It looks like a slow join. A host is one un-awaited\n * rejection between `false` and `true` away from it, so the library complains\n * rather than trusting every future host to get all of its exit paths right.\n *\n * Generous against a genuinely slow subscribe -- a bridge reconnect or AMT relay\n * handshake is seconds -- so anything still unsettled here is broken, not slow.\n */\nexport const JOIN_DEFER_WARN_MS = 30_000\n\nexport class SessionManager {\n readonly pool: TransportPool\n readonly sessions = new Map<string, TrackSession>()\n callbacks: SessionManagerCallbacks | null = null\n #takeoverDeadlineMs: number | undefined = undefined\n #joinActive = true\n #joinDeferWarnTimer: ReturnType<typeof setTimeout> | null = null\n\n /**\n * Takeover deadline (BLO-9413/D9): the time a session may sit in `joining`\n * without a RAP before returning to `unicast` and firing onTakeoverTimeout.\n * MoQ is never suppressed during `joining`, so expiry abandons the takeover\n * without a playback glitch. The host derives it from the catalog (RAP\n * cadence from groupDurationTicks/timescale plus the signaled FEC delivery\n * window or reorder tolerance) and sets it before addTrack(). There is no\n * default: an unset value fails addTrack() closed, and reading the getter\n * before assignment throws the same way, so no host ever holds a\n * `number | undefined` it could coalesce back into a default.\n * `hasTakeoverDeadline` answers whether one was admitted. A declared 0\n * disables the deadline. The value is an integer number of milliseconds:\n * setTimeout truncates fractions, so a host that divides ticks by a\n * timescale must round before assigning.\n *\n * Assignment validates, so the diagnostic lands at the host's own call site.\n * That is what bounds the blast radius: `joining` is entered from\n * joinStarted(), from the transportFailed() failover branch -- itself\n * reachable from a setTimeout via hysteresisExpired(), where no caller could\n * catch a throw -- and from the activateMulticast()/activateAmt()/pool-failure\n * loops, where a throw on one session would abandon its siblings mid-loop\n * (the split-brain those loops exist to prevent). Refusing a bad value at\n * assignment means no host mutation can make an already-admitted session\n * throw out of a transport edge.\n */\n get takeoverDeadlineMs(): number {\n return this.#requireTakeoverDeadlineMs()\n }\n\n set takeoverDeadlineMs(value: number) {\n if (!Number.isInteger(value) || value < 0 || value > MAX_TAKEOVER_DEADLINE_MS) {\n throw new Error(\n `[SessionManager] takeoverDeadlineMs must be an integer from 0 to ${MAX_TAKEOVER_DEADLINE_MS} (0 disables it, larger values overflow setTimeout); got ${String(value)}`,\n )\n }\n this.#takeoverDeadlineMs = value\n }\n\n /** True once the host has admitted a deadline; a declared 0 counts. */\n get hasTakeoverDeadline(): boolean {\n return this.#takeoverDeadlineMs !== undefined\n }\n\n /**\n * Whether the transport the joining tracks are waiting on is actually\n * subscribed. The takeover deadline is a media-arrival budget and media\n * cannot arrive before the join exists, so while this is false, entering\n * `joining` arms no deadline; flipping it true starts the FULL deadline for\n * every track already parked there.\n *\n * Default true, which is the arm-on-`joining` behaviour every host had\n * before: a host that only calls joinStarted() after its own join resolves\n * (moqtail-private's player, post-BLO-33339) is already at the right edge and\n * needs no gate. A host that arms joinStarted() up front \u2014 to have FEC\n * decoders ready before the first datagram \u2014 sets this false before addTrack()\n * and true when its subscribe settles. Charging a slow subscribe (bridge\n * reconnect, AMT relay handshake) against the media budget expired the\n * deadline before the subscription existed and abandoned the join on the\n * happy path. BLO-34289.\n *\n * The deadline is a media budget (group cadence plus FEC delivery window) and\n * budgets nothing for reaching the stream, so ideally it starts at\n * MulticastTransport.onDataPlaneLive, the join's first accepted packet. Over\n * AMT that is measurably later than a settled subscribe: the subscribe\n * settles once the gateway has sent its Membership Update, and the relay's\n * upstream join plus a path RTT (0.4-0.8 s measured to the prod relay) still\n * precede the first packet. But RFC 7450 gives a gateway no signal that the\n * relay has traffic for its (S,G), so a join whose source never sends never\n * reaches that edge. Starting the deadline there is therefore sound only when\n * the provider bounds the join phase itself, with a typed subscription error\n * for a join that delivers nothing (MulticastTransport forwards it to\n * onFailback). A host whose provider makes no such guarantee flips this true\n * when the subscribe settles and accepts that join latency is charged to the\n * media budget; holding it until the first packet would park a silent join\n * in `joining` forever.\n *\n * The flag tracks the host's primary join, not per-track transport state. A\n * track that fails over to the next transport in its own chain while this is\n * false (transportFailed's failover branch also enters `joining`) has that\n * deadline gated on the primary join activating, not on its own transport.\n * Under the documented usage -- false once at setup, true once the subscribe\n * settles -- the two coincide.\n *\n * The gate is on the ENTRY into `joining`, and false cannot take back a\n * deadline already running: the `true -> false` edge arms the warn below and\n * clears nothing. So set it false BEFORE the `joining` entry it is meant to\n * gate. A host that instead flipped it false after a `transportFailed()`\n * failover had already entered `joining` would leave the full deadline\n * counting against a subscribe that had not begun -- BLO-34289 again, on the\n * failover edge. Clearing running deadlines on that edge is deliberately NOT\n * done: this flag is manager-wide, so one transport's brief false would\n * cancel an unrelated track's live deadline and then re-arm it at FULL length\n * on the next activation (armDeferredJoinDeadline sees `joining` + a null\n * timer and cannot tell a cleared deadline from a deferred one). Every\n * unrelated transport blip would restart the whole budget, so under repeated\n * failover a track that should have timed out is extended indefinitely and\n * never advances. Only a false that never returns true loses the timer\n * outright, and JOIN_DEFER_WARN_MS already covers that.\n *\n * Leaving this false is not free: see JOIN_DEFER_WARN_MS.\n */\n get joinActive(): boolean {\n return this.#joinActive\n }\n\n set joinActive(active: boolean) {\n const wasActive = this.#joinActive\n this.#joinActive = active\n if (!active) {\n // Edge-triggered: a host re-declaring false has not restarted its\n // subscribe, and total deferral is what the warning is about.\n if (wasActive) this.#armJoinDeferWarn()\n return\n }\n this.#clearJoinDeferWarn()\n if (wasActive) return\n for (const [, session] of this.sessions) session.armDeferredJoinDeadline()\n }\n\n #armJoinDeferWarn(): void {\n this.#joinDeferWarnTimer = setTimeout(() => {\n this.#joinDeferWarnTimer = null\n const deferred = [...this.sessions.values()].filter(s => s.state === 'joining').length\n console.warn(\n `[SessionManager] joinActive has been false for ${JOIN_DEFER_WARN_MS} ms and ${deferred} track(s) sit in 'joining' with no takeover deadline. Set joinActive = true when the subscribe settles, on every exit path including failures, or these tracks never time out, never release their FEC decoders, and never advance to the next transport.`,\n )\n }, JOIN_DEFER_WARN_MS)\n }\n\n #clearJoinDeferWarn(): void {\n if (this.#joinDeferWarnTimer === null) return\n clearTimeout(this.#joinDeferWarnTimer)\n this.#joinDeferWarnTimer = null\n }\n\n /** Narrow to MMTP callbacks (onTrackEvent). Safe \u2014 optional chaining handles PIM-only consumers. */\n get mmtp(): MmtpSessionCallbacks | null {\n return this.callbacks as MmtpSessionCallbacks | null\n }\n\n constructor(poolOptions?: TransportPoolOptions) {\n this.pool = new TransportPool(poolOptions)\n\n // Wire pool failure \u2192 per-track failover\n this.pool.onSubscriptionFailed(sub => {\n for (const trackName of sub.consumers) {\n const session = this.sessions.get(trackName)\n if (session) session.transportFailed()\n }\n })\n }\n\n // -----------------------------------------------------------------------\n // Track management\n // -----------------------------------------------------------------------\n\n /**\n * The admitted takeover deadline. Throws only when the host never set one,\n * every other invalid value being refused by the setter, and returns a\n * declared 0 as 0. The getter and addTrack() both route through it, so a\n * session cannot exist without an admitted deadline, which is what makes\n * the #transition() read unable to throw on an already-admitted session.\n */\n #requireTakeoverDeadlineMs(): number {\n const value = this.#takeoverDeadlineMs\n if (value === undefined) {\n throw new Error(\n '[SessionManager] takeoverDeadlineMs is unset; set a catalog-derived takeover deadline (0 disables it) before adding tracks',\n )\n }\n return value\n }\n\n /** Add a track session from catalog config. */\n addTrack(config: TrackSessionConfig): TrackSession {\n if (this.sessions.has(config.trackName)) {\n throw new Error(`[SessionManager] Track \"${config.trackName}\" already exists`)\n }\n this.#requireTakeoverDeadlineMs()\n const session = new TrackSession(config, this)\n this.sessions.set(config.trackName, session)\n return session\n }\n\n /** Remove a track session. Releases subscription with grace period. */\n removeTrack(trackName: string): void {\n const session = this.sessions.get(trackName)\n if (!session) return\n session.unsubscribe()\n\n // Release pool subscription\n const subKey = session.config.multicastKey ?? session.config.moqTrackName\n if (subKey) this.pool.releaseSubscription(subKey, trackName)\n\n session.dispose()\n this.sessions.delete(trackName)\n }\n\n // -----------------------------------------------------------------------\n // Atomic multi-track operations\n // -----------------------------------------------------------------------\n\n /**\n * All tracks with multicast RAP \u2192 multicast state (atomic).\n * Called when MediaRouter fires onSourceSwitch('multicast').\n * Prevents split-brain between video and audio.\n *\n * A takeover deadline can return a track from `joining` to `unicast` while\n * the multicast socket keeps delivering packets and the track keeps its FEC\n * decoder armed. If a valid RAP arrives later, that late-RAP edge suppresses\n * unicast and promotes multicast without a second fec-setup.\n */\n activateMulticast(): void {\n for (const [, session] of this.sessions) {\n if (session.state === 'unicast') {\n session.joinStarted()\n }\n if (session.state === 'joining') {\n session.rapReceived('multicast')\n }\n }\n }\n\n /**\n * All tracks with AMT RAP \u2192 amt state (atomic).\n */\n activateAmt(): void {\n for (const [, session] of this.sessions) {\n if (session.state === 'unicast') {\n session.joinStarted()\n }\n if (session.state === 'joining') {\n session.rapReceived('amt')\n }\n }\n }\n\n /**\n * All tracks \u2192 unicast (failback, atomic).\n * Called when MulticastManager fires onFailback().\n * unicast-fallback sessions are skipped \u2014 the host owns those subscriptions\n * and they must drain uninterrupted across transport-state events.\n *\n * Coherence invariant (BLO-9413): after this returns, every non-fallback\n * session is `unicast` (or `idle`), unsuppressed, and without a FEC decoder.\n * unsubscribe() unwinds any outstanding suppression regardless of the state\n * it tears down from (`failing` included; see TrackSession.#suppressFired),\n * and a session that a takeover timeout parked in `unicast` with its decoder\n * still armed is cycled too, because this transport edge is what releases\n * that decoder (unsubscribe fires fec-teardown).\n */\n failbackAll(): void {\n for (const [, session] of this.sessions) {\n const parkedWithFec = session.state === 'unicast' && session.isFecArmed\n if (\n parkedWithFec ||\n (session.state !== 'idle' &&\n session.state !== 'unicast' &&\n session.state !== 'unicast-fallback')\n ) {\n session.unsubscribe()\n session.subscribe() // re-enter unicast\n }\n }\n }\n\n /**\n * All tracks \u2192 unicast-fallback (atomic).\n * Called by the host when the multicast gateway is absent and\n * the player will subscribe to source tracks over MoQ unicast and feed\n * packets via MulticastManager.feedUnicastSource() (or manager.router\n * directly). Sessions in this state stay terminal until the host calls\n * unsubscribe() / disposes the SessionManager.\n */\n enterUnicastFallback(): void {\n for (const [, session] of this.sessions) {\n if (session.state === 'idle') continue\n session.enterUnicastFallback()\n }\n }\n\n /**\n * True if any session is currently in unicast-fallback. Hosts use this to\n * gate `multicastTrack.close()` on failback events \u2014 closing the shared\n * broadcast would kill the host's fallback drainers.\n */\n hasActiveFallback(): boolean {\n for (const [, session] of this.sessions) {\n if (session.isUnicastFallback) return true\n }\n return false\n }\n\n /**\n * All fallback tracks \u2192 unicast (atomic).\n * Called when the SSM bridge appears mid-session and the host has aborted\n * the fallback drainers. The session re-enters the regular preference chain\n * via joinStarted() (issued by the host immediately after).\n */\n exitUnicastFallback(): void {\n for (const [, session] of this.sessions) {\n if (session.isUnicastFallback) session.exitUnicastFallback()\n }\n }\n\n // -----------------------------------------------------------------------\n // Cleanup lifecycle\n // -----------------------------------------------------------------------\n\n /** Tab closed \u2014 release all subscriptions for this tab (grace period). */\n tabClosed(tabId: string): void {\n // All tracks owned by this tab \u2192 idle\n for (const [trackName, session] of this.sessions) {\n session.unsubscribe()\n this.pool.releaseAllForConsumer(trackName)\n }\n }\n\n /** Bridge WebTransport connection dropped \u2014 same as tab close. */\n bridgeDisconnected(): void {\n this.tabClosed('bridge')\n }\n\n /**\n * Track selection changed (user or ABR).\n * Old track released (grace period), new track subscribed.\n */\n trackSelectionChanged(oldTrackName: string, newConfig: TrackSessionConfig): void {\n this.removeTrack(oldTrackName)\n const session = this.addTrack(newConfig)\n session.subscribe()\n }\n\n // -----------------------------------------------------------------------\n // Query\n // -----------------------------------------------------------------------\n\n /** Get a track session by name. */\n getTrack(trackName: string): TrackSession | undefined {\n return this.sessions.get(trackName)\n }\n\n // -----------------------------------------------------------------------\n // Cleanup\n // -----------------------------------------------------------------------\n\n dispose(): void {\n this.#clearJoinDeferWarn()\n for (const [, session] of this.sessions) {\n session.dispose()\n }\n this.sessions.clear()\n this.pool.dispose()\n this.callbacks = null\n }\n}\n", "/**\n * Receiver-measured multicast delivery gap (BLO-35597).\n *\n * The FEC layer gives a block's multicast symbols a late-symbol wait before it\n * declares the block over: the catalog block span plus an allowance for the\n * path's arrival jitter. The catalog can only signal a tolerance it knows in\n * advance (`fec.reorderToleranceMs`); the path the receiver actually sits on\n * is measured here, in-band, from the arrival times of the multicast\n * datagrams themselves.\n *\n * Which statistic, and why not RFC 3550 interarrival jitter\n * ---------------------------------------------------------\n * The wait is an inactivity bound: a block fails when no symbol reaches it for\n * the wait. What it has to outlast is therefore the longest silence the\n * delivery path produces, not the path's average variation.\n *\n * RFC 3550 \u00A76.4.1 `J` is a mean absolute transit-time difference smoothed with\n * gain 1/16. It is the right number for sizing a playout buffer against steady\n * jitter, and the wrong one for a bimodal path. On the path this was measured\n * on (iPhone Safari over Wi-Fi, prod AMT, 2026-09-28, 36,657 datagrams in 57 s)\n * arrivals were either back-to-back (p90 inter-arrival 2 ms) or stalled for\n * one 802.11 power-save beacon interval (102.4 ms; 135 stalls of 50-110 ms,\n * recurring every ~511 ms at the median). Applied to that trace, `J` has a p99\n * of 6 ms: a wait of span + J expires inside every stall, and the stall's\n * symbols then arrive at a block that has already been failed.\n *\n * So the estimator is the largest delivery gap observed over a trailing\n * window: the time between consecutive multicast datagram arrivals, measured\n * on the page thread where the FEC deadlines themselves run (a main-thread or\n * bridge-batching delay is as real to those timers as a radio stall). It is\n * receiver-wide rather than per stream because the stall is a property of the\n * link every stream shares; a quiet stream learns the gap from a busy one.\n *\n * Only a gap that ended in delivery counts. A silence is closed by the first\n * packet after it; if that packet is the in-sequence successor (MMTP\n * packet_sequence_number + 1) of the last one its packet_id delivered, nothing\n * of that flow was lost across the silence: its packets were held and then\n * delivered, which is exactly the lateness a longer wait turns from a failed\n * block into a completed one. If the sequence jumped, the silence was (at\n * least partly) loss -- a tunnel outage, a publisher pause -- and waiting\n * longer would not have delivered anything, so the gap is not taken as\n * jitter.\n *\n * The trailing window is supplied by the caller from the catalog. It must be\n * long enough to contain one recurrence of the stall it has to remember, and\n * nothing here can know that recurrence in advance. The window is also the\n * largest gap reported: a silence longer than the catalog horizon is outside\n * what the estimator models (a backgrounded tab, a long GC), and every bound\n * derived from the wait (block lifetime, terminal retention, the release\n * gate's hold ceiling) must stay a catalog quantity rather than a measurement.\n * `undefined` means no catalog horizon is known: nothing bounds the gap, so\n * none is reported and the catalog tolerance alone applies.\n */\nexport class DeliveryGapEstimator {\n /** Most recent arrival, or undefined before the first one since reset. */\n #lastArrivalMs: number | undefined\n /** Last MMTP packet_sequence_number seen per packet_id. */\n #lastSequence = new Map<number, number>()\n /**\n * Monotonic deque of (arrivalMs, gapMs): gaps strictly decrease from head to\n * tail, so the head is the window maximum. Amortized O(1) per arrival.\n */\n #times: number[] = []\n #gaps: number[] = []\n #head = 0\n #windowMs: number | undefined\n\n /**\n * @param windowMs trailing window in ms, which also caps the reported gap,\n * or undefined when the catalog gives none (no gap is reported).\n */\n setWindowMs(windowMs: number | undefined): void {\n if (windowMs !== undefined && !(Number.isFinite(windowMs) && windowMs > 0)) {\n throw new Error(\n `[DeliveryGap] window must be a finite positive ms value or undefined (got ${windowMs})`,\n )\n }\n this.#windowMs = windowMs\n }\n\n get windowMs(): number | undefined {\n return this.#windowMs\n }\n\n /**\n * Record one multicast datagram arrival at receiver time `nowMs`, carrying\n * MMTP `packetSequenceNumber` (32-bit, per packet_id) on `packetId`.\n */\n noteArrival(nowMs: number, packetId: number, packetSequenceNumber: number): void {\n const last = this.#lastArrivalMs\n const previousSequence = this.#lastSequence.get(packetId)\n this.#lastArrivalMs = nowMs\n this.#lastSequence.set(packetId, packetSequenceNumber)\n if (last === undefined || previousSequence === undefined) return\n if (this.#windowMs === undefined) return\n // MMTP packet_sequence_number is a 32-bit counter that wraps.\n if (((previousSequence + 1) >>> 0) !== (packetSequenceNumber >>> 0)) return\n const gap = nowMs - last\n this.#evict(nowMs)\n // A gap no larger than the current tail can never become the maximum\n // while the tail is still in the window, except as the newest entry.\n while (this.#gaps.length > this.#head && this.#gaps[this.#gaps.length - 1] <= gap) {\n this.#gaps.pop()\n this.#times.pop()\n }\n this.#gaps.push(gap)\n this.#times.push(nowMs)\n this.#compact()\n }\n\n /**\n * Largest gap observed within the window ending at `nowMs`, at most the\n * window itself; 0 if none or when no window is set.\n */\n peakGapMs(nowMs: number): number {\n const windowMs = this.#windowMs\n if (windowMs === undefined) return 0\n this.#evict(nowMs)\n return this.#gaps.length > this.#head ? Math.min(this.#gaps[this.#head], windowMs) : 0\n }\n\n /** Forget everything: a new delivery path starts with no measurement. */\n reset(): void {\n this.#lastArrivalMs = undefined\n this.#lastSequence.clear()\n this.#times = []\n this.#gaps = []\n this.#head = 0\n }\n\n #evict(nowMs: number): void {\n const windowMs = this.#windowMs\n if (windowMs === undefined) return\n while (this.#times.length > this.#head && this.#times[this.#head] < nowMs - windowMs) {\n this.#head++\n }\n }\n\n #compact(): void {\n // Drop consumed head slots once they outnumber live ones.\n if (this.#head > 0 && this.#head * 2 >= this.#times.length) {\n this.#times = this.#times.slice(this.#head)\n this.#gaps = this.#gaps.slice(this.#head)\n this.#head = 0\n }\n }\n}\n", "/**\n * FEC Manager \u2014 codec-agnostic FEC decode service.\n *\n * Receives (packetId, sbn, esi, symbolData) tuples from any transport\n * (MMTP, LOC, etc.) and manages FEC block lifecycle: source counting,\n * WASM RaptorQ decode, block timeout, and recovery/failure notification.\n *\n * Extracted from MulticastManager to decouple FEC block logic from\n * MMTP packet parsing and frame routing.\n */\n\nimport { computeFecDeliveryWindowMs } from './track-session.js'\nimport { DeliveryGapEstimator } from './delivery-gap.js'\nimport {\n deriveFecInterleaveDepth,\n deriveFecRetentionBlocks,\n tryDeriveFecRetentionBlocks,\n} from '@blockcast/mmt-fec'\nimport {\n isoBlockContains,\n isoBlocksBehindLowerBound,\n parseFecBlockProfile,\n serial32Distance,\n serial32Precedes,\n ssbg1FitsSymbol,\n type FecBlockProfile,\n} from '@blockcast/mmt-base'\nimport { IsoBlockLocator, type IsoRepairPlacement } from './iso-block-locator.js'\nimport { MIN_INTERLEAVE_DEPTH_MS } from './fec-state.js'\nimport {\n BlockClassifier,\n type BlockClass,\n type BlockClassifierGeometry,\n type ClassifierChange,\n type ClassifierMode,\n type ClassifierReason,\n type ReclassificationEvidence,\n type SourceSymbolEvidence,\n type TrackKind,\n} from './block-policy.js'\n\nimport type { MmtpDeliveryProvenance } from './mfu-reassembler.js'\nimport type {\n FecWorkerClient,\n FecTrackStats,\n FecBlockSnapshot,\n FrameMeta,\n FecWorkerErrorCode,\n} from '@blockcast/fec-worker'\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n// MIN_INTERLEAVE_DEPTH_MS lives with the catalog FEC predicate that also\n// needs it (fec-state.ts); re-exported here for existing importers.\nexport { MIN_INTERLEAVE_DEPTH_MS }\nconst WORKER_ERROR_LOG_CAP = 5\nconst WORKER_ERROR_KIND_CAP = 32\nconst WORKER_ERROR_OVERFLOW_KIND = 'other'\nconst STARTUP_REPAIR_WAIT_SOURCE_BOUNDARIES = 2\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Algorithm-agnostic FEC decoder backend interface.\n *\n * Implementations:\n * - RaptorQ WASM (mmt_wasm.js) \u2014 RFC 6330\n * - Reed-Solomon WASM \u2014 future\n * - XOR \u2014 future\n *\n * Source/repair symbol IDs follow ISO 23008-1 \u00A7C.5.2/C.5.3:\n * source: SS_ID (flat 32-bit symbol identifier)\n * repair: SS_Start + SSB_length + RS_ID (block-scoped repair coords)\n */\nexport type WasmFecDecoder = {\n add_source(ss_id: number, data: Uint8Array, ts: number): Uint8Array | null\n add_repair(ss_start: number, ssb_length: number, rs_id: number, data: Uint8Array, ts: number): Uint8Array | null\n configure(oti: Uint8Array): void\n cleanup(current_time: number): Uint8Array\n /** Retire the named blocks from the decoder (last-chance decode + stats). */\n cleanup_blocks_detailed(ss_starts: Uint32Array): unknown\n /**\n * iso-ssbg1-v1 only: retire the source symbols the decoder holds before a\n * repair states their block, once no repair can place them any more, by the\n * decoder's own horizon behind `current`, the newest SS_ID seen. Retires no\n * block: the manager retires blocks itself, by their deadlines. Returns how\n * many it retired. The manager calls it after every feed that grows the\n * decoder and on block retirement. (`MmtFecDecoder.retire_unplaced`; the\n * worker path uses `cleanup_by_ss_start`, which applies the same rule.)\n */\n retire_unplaced(current: number): number\n flush?(): void\n reset_stats?(): void\n}\n\n/** Algorithm-agnostic FEC stats snapshot. */\nexport interface FecStats {\n sourcePackets: number\n repairPackets: number\n duplicateSymbols: number\n blocksClean: number\n blocksRecovered: number\n blocksFailed: number\n blocksComplete: number\n mixedPathBlocks: number\n recentBlockPathCounts: FecBlockPathCount[]\n k: number\n /** Of blocks with loss, what percentage were recovered (0-100). */\n recoveryRate: number\n /** What percentage of blocks had any loss (0-100). */\n packetLossRate: number\n /** Per-MMTP-packet-ID counters for the synchronous WASM decoder path. */\n tracks: FecPacketStats[]\n /** Latest Worker/WASM snapshot for each packet ID. */\n workers: Array<FecTrackStats & { packetId: number }>\n /** Main-thread repair symbols rejected before dispatch because their size did not match T. */\n repairSymbolSizeMismatches: number\n /** Bounded Worker error counts keyed by stable code, or message text for legacy errors. */\n workerErrors: Record<string, number>\n}\n\nexport interface FecPacketStats {\n packetId: number\n sourcePackets: number\n repairPackets: number\n duplicateSymbols: number\n blocksClean: number\n blocksRecovered: number\n blocksFailed: number\n blocksComplete: number\n k: number\n recoveryRate: number\n packetLossRate: number\n}\n\nexport type FecSymbolSource = 'multicast' | 'moq'\n\n/**\n * One iso-ssbg1-v1 first-arrival source symbol MmtpRouter holds until a repair\n * symbol states its block: the exact protected packet (no SS_ID, unpadded),\n * the transport that delivered it and its classification evidence.\n */\nexport interface IsoHeldSource {\n readonly symbolData: Uint8Array\n readonly source: FecSymbolSource\n readonly evidence: SourceSymbolEvidence\n /** Larger than one ssbg_mode1 symbol: counted, never decoded. */\n readonly oversized: boolean\n}\n\n/**\n * How the multicast leg hands its symbols to this manager.\n *\n * - `'datagram'`: every symbol is an independent datagram from the receiving\n * socket. A pause in arrivals is the only loss signal the receiver has, so a\n * block that stays idle past the catalog-derived inactivity deadline fails.\n * - `'ordered-relay'`: the provider relays one receiving socket over a\n * reliable, ordered stream (the BCDS bridge's WebTransport uni stream). The\n * delivered sequence is a subsequence of the socket's receive order, so a\n * pause carries no loss information at all: the relay's own scheduling\n * decides when bytes arrive. What does carry it is order. Once the stream\n * has advanced more than the decoder's interleave horizon past a block, no\n * symbol of that block can still be in flight, and the block's fate follows\n * from the symbols it holds (in-band SS_ID/SS_Start and SSB_length), not\n * from a wall clock tuned for raw datagrams.\n *\n * Only the provider can know which of the two it is delivering; an undeclared\n * provider promises no ordering and keeps datagram semantics.\n */\nexport type MulticastSymbolDelivery = 'datagram' | 'ordered-relay'\n\nfunction isMulticastSymbolDelivery(value: unknown): value is MulticastSymbolDelivery {\n return value === 'datagram' || value === 'ordered-relay'\n}\n\nexport interface FecBlockPathCount {\n packetId: number\n sbn: number\n catalogTrackName?: string\n multicastSymbols: number\n moqSymbols: number\n complete: boolean\n}\n\ntype FecLifecycleTrace = {\n atMs: number\n event:\n | 'repair-requested'\n | 'repair-arrived'\n | 'timeout-queued'\n | 'horizon-settled'\n | 'cleanup-ack'\n | 'cleanup-ack-timeout'\n | 'recovered'\n | 'failed'\n | 'decoder-unavailable'\n packetId: number\n sbn: number\n catalogTrackName?: string\n sourceCount: number\n sourceEsis: number[]\n repairRsIds: number[]\n decoderPath: 'worker' | 'sync' | 'none'\n deadlineRemainingMs?: number\n pendingCleanup: boolean\n rsId?: number\n}\n\nexport interface FECManagerConfig {\n /**\n * The catalog `fec.blockProfile` of the track (BLO-35597). Required for every\n * configured track: there is no default, and nothing infers it. The only\n * profile is `iso-ssbg1-v1`: ISO/IEC 23008-1 ssbg_mode1 with K' = SSB_length\n * blocks keyed by SS_Start and located by `(SS_Start, SSB_length)` range\n * membership, `k` being only their maximum. The retired `k-padded-v1` token\n * is refused.\n */\n blockProfile?: FecBlockProfile\n k?: number\n /** Symbol size in bytes (T). NOT repair count. */\n symbolSize?: number\n interleaveDepthMs?: number\n fps?: number\n /** Guarded block inactivity deadline derived from the full delivery geometry. */\n timeoutMs?: number\n /** Media kind of the track (block class `audio` vs video classification). */\n trackKind?: TrackKind\n /** Catalog codec string. */\n codec?: string\n /**\n * Block classifier geometry derived from raw catalog timing\n * (`deriveBlockClassifierGeometry`). Absent geometry classifies every video\n * block `unknown` (fail-closed: protected).\n */\n geometry?: BlockClassifierGeometry\n}\n\ninterface ResolvedFECTrackConfig {\n blockProfile?: FecBlockProfile\n k: number\n symbolSize: number\n interleaveDepthMs: number\n timeoutMs: number\n fps?: number\n trackKind?: TrackKind\n codec?: string\n geometry?: BlockClassifierGeometry\n}\n\n/** Which symbol path completed a short block. */\nexport type RepairVia = 'inband' | 'quic'\n\n/** Lifecycle resolution of one FEC block, for in-order frame release. */\nexport type FecBlockResolution = 'pending' | 'complete' | 'failed' | 'untracked'\n\ninterface MutableFecCounters {\n sourcePackets: number\n repairPackets: number\n duplicateSymbols: number\n blocksClean: number\n blocksRecovered: number\n blocksFailed: number\n}\n\nfunction newFecCounters(): MutableFecCounters {\n return {\n sourcePackets: 0,\n repairPackets: 0,\n duplicateSymbols: 0,\n blocksClean: 0,\n blocksRecovered: 0,\n blocksFailed: 0,\n }\n}\n\n/**\n * The one timer armed for a block deadline (BLO-36599).\n *\n * Symbols move a block's deadline later many times per block. Re-arming a\n * browser timer for each of them cost ~41 us per clear+set pair on a Kirin 659,\n * so the deadline itself is stored as a performance.now() timestamp and this\n * timer only guarantees a wake-up no later than it. When the timer fires it\n * re-checks the stored deadline and re-arms for the remainder if activity\n * moved it. `fireAtMs` is what the armed timer was scheduled for; `handle` is\n * null once it fired without re-arming.\n */\ninterface DeadlineTimer {\n handle: ReturnType<typeof setTimeout> | null\n fireAtMs: number\n}\n\n/**\n * Delete the `count` oldest (first-inserted) keys of `collection` in place\n * and return them.\n */\nfunction dropOldest(\n collection: Set<string> | Map<string, unknown>,\n count: number,\n): string[] {\n const dropped: string[] = []\n for (const key of collection.keys()) {\n if (dropped.length >= count) break\n dropped.push(key)\n }\n for (const key of dropped) collection.delete(key)\n return dropped\n}\n\n/** A block key whose terminal state expires at `expiresAtMs`. */\ninterface TerminalRecord {\n readonly key: string\n readonly expiresAtMs: number\n}\n\n/** FIFO of terminal records in the order blocks became terminal. */\nclass TerminalQueue {\n #records: TerminalRecord[] = []\n #head = 0\n\n push(record: TerminalRecord): void {\n this.#records.push(record)\n }\n\n peek(): TerminalRecord | undefined {\n return this.#records[this.#head]\n }\n\n shift(): void {\n this.#head++\n // Compact once the consumed prefix outweighs the live tail, so the array\n // never holds more than twice the records still in the window.\n if (this.#head * 2 >= this.#records.length) {\n this.#records = this.#records.slice(this.#head)\n this.#head = 0\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// FECManager\n// ---------------------------------------------------------------------------\n\n/** Why a stream's Worker decoder stopped being routable. */\nexport type FecDecoderUnavailableReason = 'cleared' | 'disposed'\n\n/**\n * What still decodes a stream after its Worker left routing: the synchronous\n * WASM decoder registered for the same stream (the Worker path took precedence\n * over it while both were live), or nothing.\n */\nexport type FecFallbackDecoder = 'sync' | 'none'\n\nexport interface FecDecoderUnavailableEvent {\n readonly packetId: number\n readonly catalogTrackName?: string\n readonly reason: FecDecoderUnavailableReason\n /**\n * What decodes the stream at the moment the Worker left. A synchronous\n * decoder discarded later (a resetTrack flush failure drops it without an\n * event) is not reported here; a consumer that keeps a stream alive on\n * 'sync' owns that decoder's lifecycle itself.\n */\n readonly fallbackDecoder: FecFallbackDecoder\n}\n\nexport class FECManager {\n // Per-track WASM RaptorQ decoders, keyed by physical stream identity.\n #fecDecoders = new Map<string, { client: WasmFecDecoder; k: number }>()\n /** @deprecated Use setFecDecoder(packetId, ...). Backwards-compat shim. */\n\n // Per-track Worker-backed FEC decoders, keyed by physical stream identity.\n // Worker-backed tracks use async decode \u2014 recovered frames arrive via onFrame callback,\n // not synchronous return from feedSourceSymbol/feedRepairSymbol.\n #workerClients = new Map<string, FecWorkerClient>()\n // Worker clients whose streams releasePacket ended. A Worker posts\n // asynchronously, so a message it sent before its reset can still arrive;\n // the stream it describes is gone, and letting it in would rebuild state\n // (block ledgers, statistics, lifecycle events) for a retired packet ID.\n #releasedWorkerClients = new WeakSet<FecWorkerClient>()\n // Worker setup can finish while an interleaved block is already collecting\n // on the sync decoder. Keep that block on its original decoder so later\n // source/repair symbols do not split across two RaptorQ states.\n #syncDecoderBlockOwners = new Map<string, WasmFecDecoder>()\n #decoderBlockOwners = new WeakMap<object, Map<number, {\n catalogTrackName?: string\n conflicted: boolean\n }>>()\n\n #fecK = 4\n #fecSymbolSize = 0\n #fecTimeoutMs = 500 // default 500ms; refined by setFramerate() when fps known\n #trackConfigs = new Map<number, ResolvedFECTrackConfig>()\n #fecStats = newFecCounters()\n #fecTrackStats = new Map<number, MutableFecCounters>()\n #fecWorkerStats = new Map<number, FecTrackStats>()\n #fecStatsObservers = new Set<(packetId: number, stats: FecTrackStats) => void>()\n\n // Block inactivity tracking: (packetId, SBN) -> the one armed timer. The\n // deadline is derived from catalog geometry and refreshed on progress; only\n // an idle block fails. #activeBlockDeadlinesMs is the authoritative\n // deadline; the timer re-checks it when it fires (see DeadlineTimer).\n #activeBlocks = new Map<string, DeadlineTimer>()\n #activeBlockDeadlinesMs = new Map<string, number>()\n // The two terms #activeBlockDeadlinesMs is the later of (BLO-35597):\n // - the multicast late-symbol wait, refreshed by every accepted symbol;\n // - a D-28 repair hold, set by the repair scheduler only while a repair\n // FETCH it admitted can still land before the block's presentation\n // deadline. The multicast span elapsing never ends a fetch the scheduler\n // judged able to arrive in time.\n #blockWaitDeadlinesMs = new Map<string, number>()\n #repairHoldUntilMs = new Map<string, number>()\n // Receiver-measured multicast delivery gap: the late-symbol wait's jitter\n // term (see DeliveryGapEstimator for the statistic and why).\n #deliveryGaps = new DeliveryGapEstimator()\n #lifecycleTrace: FecLifecycleTrace[] = []\n // Worker-backed timeouts enqueue cleanup behind all prior symbol messages.\n // Failure remains pending until cleanupComplete acknowledges that queue drain.\n #pendingWorkerCleanups = new Set<string>()\n #pendingWorkerCleanupClients = new Map<string, FecWorkerClient>()\n // One bound per owed cleanup: the acknowledgement is awaited for at most one\n // catalog-derived block lifetime (fail bound plus repair grace).\n // A Worker that hangs or drops one message must not leave the block\n // 'pending' forever, which would park every later frame of its stream in\n // the in-order release gate. Each entry owns its handle; the callback\n // checks it still owns the key, so a settled or reset key is never touched.\n #pendingWorkerCleanupBounds = new Map<string, { handle: ReturnType<typeof setTimeout> }>()\n // Source symbol count per track-scoped block \u2014 marks complete when K symbols arrive.\n #blockSourceCounts = new Map<string, number>()\n #blockSourceEsis = new Map<string, Set<number>>()\n // Authored SSB_length per block (ISO/IEC 23008-1 \u00A7C.5.3), learned from the\n // first repair symbol. The FFmpeg moq_mmt publisher closes a block every D\n // media units, usually before it holds K symbols; under iso-ssbg1-v1\n // (ssbg_mode1) that block is K' = SSB_length symbols, its repair ESIs start\n // at SSB_length, and K is only the maximum. Such a block is clean at\n // SSB_length source arrivals and recoverable from SSB_length received\n // symbols; waiting for K fails every one of them.\n #blockAuthoredLengths = new Map<string, number>()\n #ssbLengthConflictCount = 0\n #blockRepairRsIds = new Map<string, Set<number>>()\n // Blocks already recovered \u2014 prevents late source symbols from restarting timeouts.\n // WASM removes blocks after recovery; late symbols create empty blocks returning null,\n // which would falsely start new timeouts without this guard.\n //\n // Every insertion goes through #markTerminal, which retires the key once its\n // catalog-derived retention window (terminalRetentionMsForPacket) passes. The\n // previous bound (trim to 500 at 1000) lived in cancelBlockTimeout alone, so\n // a stream whose blocks only failed grew this set without limit (BLO-36599).\n #completedBlocks = new Set<string>()\n #terminalExpiryMs = new Map<string, number>()\n #terminalQueues = new Map<number, TerminalQueue>()\n // sbn -> (block key -> packetId) for every key held by #activeBlocks,\n // #blockSourceCounts, #blockCatalogTrackNames or #completedBlocks. A lookup\n // without a catalog name used to string-parse every key of all of those on\n // each source packet, which is O(retained blocks) per packet (BLO-36599).\n #blockKeysBySbn = new Map<number, Map<string, number>>()\n // Stable per-name encodings for stream and block keys, bounded by the\n // catalog's track names; #blockKey is built several times per packet.\n #encodedTrackNames = new Map<string, string>()\n // The last block and stream key built. One packet asks for the same block's\n // key from every FECManager entry point it passes through (~6 per source\n // symbol), so each key is built once per packet rather than once per call\n // (BLO-36599). Both keys are pure functions of their arguments.\n #lastBlockKeySbn = -1\n #lastBlockKeyPacketId = -1\n #lastBlockKeyTrackName: string | undefined = undefined\n #lastBlockKey = ''\n #lastStreamKeyPacketId = -1\n #lastStreamKeyTrackName: string | undefined = undefined\n #lastStreamKey = ''\n #seenSymbols = new Set<string>()\n #blockPathCounts = new Map<string, FecBlockPathCount>()\n // Receiver-local first-accepted-symbol time per block, bounded like\n // #blockPathCounts (see blockAnchorAtMs).\n #blockAnchorsMs = new Map<string, number>()\n // Exact catalog identity observed on accepted symbols for each FEC block.\n // Conflicting identities collapse to null so recovery fails closed.\n #blockCatalogTrackNames = new Map<string, string | null>()\n // A receiver joins an already-running live stream in the middle of D\n // interleaved source blocks. This applies to both multicast and reliable MoQ:\n // a MoQ group boundary is not an FEC ESI=0 boundary. Those blocks are not\n // packet loss because their earlier ESIs predate the subscription. Ignore\n // each such block until ESI=0 establishes a complete boundary for the track.\n // After that boundary, a first observed ESI>0 is real delivery loss and must\n // continue through normal FEC recovery/failure accounting.\n #startupAlignedPackets = new Set<string>()\n #startupPartialBlocks = new Set<string>()\n // A live multicast join can observe an ESI=0 source boundary before the\n // repair lane catches up. Returning false here also drops that media packet\n // at MmtpRouter, so the readiness probe is multicast-only and bounded: an\n // actual multicast repair unlocks the next boundary, while a second source\n // boundary fails open. Reliable MoQ media continues through normal startup\n // alignment and retained MoQ repair cannot prove multicast readiness.\n #startupRequireRepairReady = false\n #startupRepairReadyPackets = new Set<string>()\n #startupRepairBoundaryCounts = new Map<string, number>()\n #startupRepairFailOpenPackets = new Set<string>()\n // SBNs that reached K source-symbol arrivals via trackBlock. Used by the\n // worker recovery suppression check (see onFrame handler below) so we only\n // suppress `recovered=true` emits for blocks that ALSO completed cleanly\n // via source symbols. Separating this from #completedBlocks (which is also\n // populated by every worker frame emit, by failed timeouts, and by external\n // callers) was the BLO-3323 L15 fix: the prior predicate caused blocksRecovered\n // to stay at 0 because the SBN was always in #completedBlocks by the time the\n // worker fired recovered=true.\n #cleanlyCompletedBlocks = new Set<string>()\n // D-28: blocks whose repair deadline already fired onRepairNeeded. At most\n // one repair request per block; entries are removed when the block\n // completes, recovers, or fails.\n #repairRequestedBlocks = new Set<string>()\n // D-28: repair deadlines refreshed by accepted source progress until the\n // first repair request. A `null` value marks a deadline that already fired\n // and requested repair (never re-armed). At most K accepted source symbols\n // can refresh the horizon, so browser scheduling delay cannot create an\n // early loss signal and repair traffic cannot keep a block alive\n // indefinitely. #repairDeadlineAtMs is the authoritative deadline; the one\n // armed timer re-checks it when it fires (see DeadlineTimer).\n #repairDeadlines = new Map<string, DeadlineTimer | null>()\n #repairDeadlineAtMs = new Map<string, number>()\n // Worker recovery emits all K source symbols. Track recovery once per block\n // while routing only ESI slots that were absent from the direct source path.\n #workerRecoveredBlocks = new Set<string>()\n\n // How the multicast leg delivers symbols (see MulticastSymbolDelivery). The\n // provider declares it per transport join; absence keeps datagram semantics.\n #multicastSymbolDelivery: MulticastSymbolDelivery = 'datagram'\n // ordered-relay: per stream, the highest SBN any multicast symbol carried.\n // The relay preserves the receiving socket's order, so every block more than\n // the decoder's interleave horizon behind this frontier has received every\n // symbol it will ever receive over multicast.\n #orderedFrontiers = new Map<string, number>()\n // ordered-relay: blocks with multicast symbols that are neither terminal nor\n // handed to a decoder cleanup, per stream, keyed by SBN. They carry no\n // timer; the frontier settles them. At most one interleave horizon of blocks\n // per stream is ever here.\n #orderedPendingBlocks = new Map<string, Map<number, string>>()\n\n // \u2500\u2500 Keyframe-protected repair policy (block-policy.ts) \u2500\u2500\n // One classifier per physical stream, built from that stream's geometry.\n #classifiers = new Map<string, BlockClassifier>()\n // Geometry a stream-scoped configuration gave one stream (streamKey). ABR\n // rungs can share a packetId with a different framerate or GOP, so the\n // packet-level geometry is only the geometry of a packet whose streams\n // were never configured apart (see #geometryFor).\n #streamGeometries = new Map<string, BlockClassifierGeometry>()\n // Terminal outcomes retained for in-order frame release: a failed block is\n // resolved-with-loss, a completed one resolved-clean.\n #failedBlocks = new Set<string>()\n // A block's class at resolution, so events emitted after the classifier\n // forgot the block (the router counts a recovery after cancelling the\n // block) still carry it.\n #resolvedBlockClasses = new Map<string, BlockClass>()\n // Blocks already reported through onBlockRepaired (once per block).\n #repairedBlocks = new Set<string>()\n // Blocks created before any symbol of theirs was tracked: the repair-less\n // spans placeIsoRepair materializes (onBlockVanished).\n #materializedBlocks = new Set<string>()\n // packetIds whose ordered-relay horizon became underivable and fell back to\n // the datagram deadline. Bounds the warn in #derivableOrderedHorizon to once\n // per stream. Not only throws: the ordinary out-of-range case is detected by\n // arithmetic and never throws at all.\n #orderedHorizonWarnedFor = new Set<number>()\n // Newest block start (SS_Start) seen per stream and when: the frontier for\n // source-discontinuity detection and isoSpanResolution.\n #highestSbn = new Map<string, { sbn: number; atMs: number }>()\n // iso-ssbg1-v1 block location per stream (streamKey). A source belongs to a\n // block only once a repair symbol stated the block's (SS_Start, SSB_length).\n #isoLocators = new Map<string, IsoBlockLocator<IsoHeldSource>>()\n #isoRepairRefusedCount = 0\n #isoSourceOversizeCount = 0\n // Held sources a locator's capacity cap evicted (IsoBlockLocator.heldCapacity):\n // only a stream whose SS_IDs are not consecutive reaches it. Cumulative,\n // unlike the locators, which a reset discards.\n #isoHeldEvictedCount = 0\n // Sources a synchronous decoder held unplaced and retired at its horizon\n // (WasmFecDecoder.retire_unplaced): placed sources whose block's repair\n // never reached that decoder. Cumulative.\n #isoDecoderUnplacedRetired = 0\n // Failed retire_unplaced calls. The sweep runs on every feed, so its warn is\n // rate-limited like the other per-symbol warns here. Cumulative.\n #isoDecoderUnplacedRetireFailed = 0\n // The latest source symbol per stream that carried its MMTP\n // packet_sequence_number: the anchor that bounds how many blocks the\n // producer can have started before a later source symbol.\n #sourceAnchors = new Map<string, { sbn: number; psn: number; atMs: number }>()\n // Catalog/reset epoch per packetId. Gap decisions carry it for fencing.\n #packetEpochs = new Map<number, number>()\n\n // Framerate detection state\n #fpsDetected = false\n #fecFps?: number\n #fecInterleaveDepthMs = 0\n\n // Worker error log rate-limiter \u2014 bounds console.warn output and exported\n // error cardinality when the FEC Worker emits the same failure repeatedly\n // with volatile packet details (e.g. ssStart) in the message text.\n // Stable Worker codes aggregate those messages; uncoded/legacy messages are\n // capped into an `other` bucket. This also covers wasm-bindgen\n // \"recursive use of an object\" bubbled from add_source/add_repair during\n // overlapping message processing). Without this, Point 5 of the 7-point\n // validation harness \u2014 which caps headless warnings at 500 \u2014 fails even\n // though the errors are duplicates of a single underlying event.\n #workerErrorCounts = new Map<string, number>()\n #decoderUnavailableEvents = 0\n #repairSymbolSizeMismatchCount = 0\n\n // Event callbacks \u2014 block lifecycle notifications for MmtpRouter wiring.\n // NOTE: For sync WASM path, recovery routing is handled by MmtpRouter from\n // feedSourceSymbol/feedRepairSymbol return values. For Worker path, recovery\n // arrives via onBlockRecovered callback.\n onBlockStarted:\n | ((sbn: number, packetId: number, catalogTrackName?: string) => void)\n | null = null\n onBlockComplete:\n | ((sbn: number, packetId: number, catalogTrackName?: string) => void)\n | null = null\n /** A block reached its fail bound. `blockClass` is its class at failure. */\n onBlockFailed:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => void)\n | null = null\n /**\n * Fired once per stream when its Worker decoder stops being routable.\n * `reason` is 'cleared' when clearFecWorker() removed it and 'disposed' when\n * a symbol or reset found the registered client already disposed and\n * evicted it. `fallbackDecoder` is 'sync' when a synchronous WASM decoder\n * registered for the same stream keeps decoding its symbols, and 'none'\n * when nothing does. Only 'none' leaves the stream unprotected: the host\n * must then register a decoder again or leave the stream, because counting\n * its symbols as handled would hide the loss.\n */\n onDecoderUnavailable: ((event: FecDecoderUnavailableEvent) => void) | null = null\n\n // D-28 on-demand repair callbacks. When onRepairNeeded is registered, each\n // block arms a one-shot repair deadline after the derived K-symbol source\n // horizon; a block still short of K when it fires requests a per-SBN MoQ\n // repair fetch upstream. The inactivity fail bound is unchanged either way \u2014\n // repair and failure are orthogonal timers, so wiring D-28 never delays\n // failure accounting. onRepairResolved fires when a repair-requested block\n // leaves the active set, with the outcome (\"completed\" after clean completion\n // or recovery, \"failed\" at the fail bound) so the upstream in-flight slot\n // (MulticastManager.clearRepairForBlock) can be released and logged.\n onRepairNeeded:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => void)\n | null = null\n onRepairResolved:\n | ((\n sbn: number,\n packetId: number,\n outcome: 'completed' | 'failed',\n catalogTrackName: string | undefined,\n via: RepairVia | null,\n ) => void)\n | null = null\n\n /**\n * A block that could not complete from its first-arrival source symbols\n * alone was completed anyway: by FEC decode (in-band or QUIC repair\n * symbols) or, after a D-28 request, by late symbols. Fired once per block.\n */\n onBlockRepaired:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n via: RepairVia,\n ) => void)\n | null = null\n /** A block's class changed on new evidence or when classification was disabled. */\n onBlockReclassified:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n from: BlockClass,\n to: BlockClass,\n evidence: ReclassificationEvidence,\n ) => void)\n | null = null\n /** A video stream's classifier mode was established or changed. */\n onClassifierMode:\n | ((\n packetId: number,\n catalogTrackName: string | undefined,\n mode: ClassifierMode,\n reason: ClassifierReason | null,\n ) => void)\n | null = null\n /** A block every symbol of which vanished was created by SBN evidence. */\n onBlockVanished:\n | ((sbn: number, packetId: number, catalogTrackName: string | undefined) => void)\n | null = null\n /** A block reached a terminal state (completed, recovered, or failed). */\n onBlockResolved:\n | ((sbn: number, packetId: number, catalogTrackName: string | undefined) => void)\n | null = null\n /** A stream's SBN went backwards (or jumped implausibly): the source restarted. */\n onSourceDiscontinuity:\n | ((packetId: number, catalogTrackName: string | undefined) => void)\n | null = null\n\n // Worker-path callbacks \u2014 async recovery and stats from FecWorkerClient.\n onBlockRecovered: ((sbn: number, packetId: number, data: ArrayBuffer, meta: FrameMeta, catalogTrackName?: string) => void) | null = null\n onFecStatsUpdate: ((packetId: number, stats: FecTrackStats) => void) | null = null\n\n #encodeTrackName(catalogTrackName: string): string {\n let encoded = this.#encodedTrackNames.get(catalogTrackName)\n if (encoded === undefined) {\n encoded = encodeURIComponent(catalogTrackName)\n this.#encodedTrackNames.set(catalogTrackName, encoded)\n }\n return encoded\n }\n\n #streamKey(packetId: number, catalogTrackName?: string): string {\n if (\n packetId === this.#lastStreamKeyPacketId &&\n catalogTrackName === this.#lastStreamKeyTrackName\n ) {\n return this.#lastStreamKey\n }\n const key = catalogTrackName === undefined\n ? `${packetId}`\n : `${packetId}:${this.#encodeTrackName(catalogTrackName)}`\n this.#lastStreamKeyPacketId = packetId\n this.#lastStreamKeyTrackName = catalogTrackName\n this.#lastStreamKey = key\n return key\n }\n\n #belongsToPacket(key: string, packetId: number): boolean {\n return Number(key.split(':', 1)[0]) === packetId\n }\n\n #belongsToStream(key: string, packetId: number, catalogTrackName: string): boolean {\n if (!this.#belongsToPacket(key, packetId)) return false\n const parts = key.split(':')\n const encodedTrackName = this.#encodeTrackName(catalogTrackName)\n if (parts.length === 2) return parts[1] === encodedTrackName\n if (parts.length >= 3) return parts[2] === encodedTrackName\n return false\n }\n\n #packetIdsForStreamKeys(keys: Set<string>): number[] {\n return [...new Set([...keys].map((key) => Number(key.split(':', 1)[0])))]\n }\n\n #parseStreamKey(key: string): { packetId: number; catalogTrackName?: string } {\n const separator = key.indexOf(':')\n if (separator < 0) return { packetId: Number(key) || 0 }\n return {\n packetId: Number(key.slice(0, separator)) || 0,\n catalogTrackName: decodeURIComponent(key.slice(separator + 1)),\n }\n }\n\n #blockKey(sbn: number, packetId: number, catalogTrackName?: string): string {\n if (\n sbn === this.#lastBlockKeySbn &&\n packetId === this.#lastBlockKeyPacketId &&\n catalogTrackName === this.#lastBlockKeyTrackName\n ) {\n return this.#lastBlockKey\n }\n const base = `${packetId}:${sbn}`\n const key = catalogTrackName === undefined\n ? base\n : `${base}:${this.#encodeTrackName(catalogTrackName)}`\n this.#lastBlockKeySbn = sbn\n this.#lastBlockKeyPacketId = packetId\n this.#lastBlockKeyTrackName = catalogTrackName\n this.#lastBlockKey = key\n return key\n }\n\n /** Record that `key` (block `sbn` on `packetId`) is held by a lookup set. */\n #indexBlockKey(key: string, sbn: number, packetId: number): void {\n let keys = this.#blockKeysBySbn.get(sbn)\n if (!keys) {\n keys = new Map()\n this.#blockKeysBySbn.set(sbn, keys)\n }\n keys.set(key, packetId)\n }\n\n /** True while any set a catalog-name-less lookup consults still holds `key`. */\n #isBlockKeyHeld(key: string): boolean {\n return this.#activeBlocks.has(key) ||\n this.#blockSourceCounts.has(key) ||\n this.#blockCatalogTrackNames.has(key) ||\n this.#completedBlocks.has(key)\n }\n\n /** Drop `key` from the SBN index once no lookup set holds it any more. */\n #unindexBlockKey(key: string): void {\n if (this.#isBlockKeyHeld(key)) return\n const sbn = this.#parseBlockKey(key).sbn\n const keys = this.#blockKeysBySbn.get(sbn)\n if (!keys) return\n keys.delete(key)\n if (keys.size === 0) this.#blockKeysBySbn.delete(sbn)\n }\n\n /**\n * Make `key` terminal: late symbols for it are duplicates from now on. The\n * key is retired terminalRetentionMsForPacket later, when no copy of any of\n * its symbols can still be in flight on either transport and no frame can\n * still be held behind it. Retirement is swept lazily here, on the stream's\n * own terminal events, so it needs no timer.\n */\n #markTerminal(key: string, sbn: number, packetId: number, clean: boolean): void {\n const nowMs = performance.now()\n this.#retireTerminalBlocks(packetId, nowMs)\n this.#forgetOrderedPending(key)\n this.#completedBlocks.add(key)\n if (clean) this.#cleanlyCompletedBlocks.add(key)\n this.#indexBlockKey(key, sbn, packetId)\n const expiresAtMs = nowMs + this.terminalRetentionMsForPacket(packetId)\n this.#terminalExpiryMs.set(key, expiresAtMs)\n let queue = this.#terminalQueues.get(packetId)\n if (!queue) {\n queue = new TerminalQueue()\n this.#terminalQueues.set(packetId, queue)\n }\n queue.push({ key, expiresAtMs })\n }\n\n #retireTerminalBlocks(packetId: number, nowMs: number): void {\n const queue = this.#terminalQueues.get(packetId)\n if (!queue) return\n for (let record = queue.peek(); record !== undefined; record = queue.peek()) {\n if (record.expiresAtMs > nowMs) return\n // A key made terminal again later (or reset and reused) owns a newer\n // record; this one no longer describes it.\n const current = this.#terminalExpiryMs.get(record.key) === record.expiresAtMs\n // An ordered relay can still hand over the rest of a terminal block's\n // symbols (a clean block's remaining repair symbols) for as long as the\n // relay pauses. Until the stream passes the block's interleave horizon\n // those symbols are late copies, not a new block, so the key stays.\n if (current && this.#withinOrderedHorizon(record.key)) return\n queue.shift()\n if (!current) continue\n this.#terminalExpiryMs.delete(record.key)\n this.#completedBlocks.delete(record.key)\n this.#cleanlyCompletedBlocks.delete(record.key)\n this.#workerRecoveredBlocks.delete(record.key)\n // The policy's terminal ledgers describe the same terminal state and\n // retire with it: a failed block must never outlive its failure record\n // as 'complete' (that would hide a loss from the release gate).\n this.#failedBlocks.delete(record.key)\n this.#resolvedBlockClasses.delete(record.key)\n this.#repairedBlocks.delete(record.key)\n this.#unindexBlockKey(record.key)\n }\n }\n\n // -----------------------------------------------------------------------\n // Ordered-relay settlement (MulticastSymbolDelivery 'ordered-relay')\n // -----------------------------------------------------------------------\n\n /**\n * The decoder's interleave horizon for one stream, in blocks: the same\n * retention the Worker and the WASM decoder are configured with, because it\n * comes from the same function they do (`deriveFecRetentionBlocks`, over the\n * block's delivery window and its span). The decoder retires every block\n * more than this many blocks behind the newest SS_Start it saw, counted\n * from below by `isoBlocksBehindLowerBound` (`cleanup_by_ss_start`), so\n * settling earlier than this would decide a block the decoder would still\n * have recovered.\n *\n * This used to call `deriveFecInterleaveDepth`, which returns D -- MEDIA\n * UNITS per block, not a block count -- and a comment here asserted the two\n * matched. They did, only while the decoder's retention was also (wrongly)\n * sized in D. Once BLO-35597 sized retention as a real block count this\n * settled 2-5 blocks early on the live ISS catalog at reorderToleranceMs=100\n * (720p 1 vs 6, audio 2 vs 5), making the manager the binding constraint and\n * the decoder fix inert on ordered relays. Both sides now consume one\n * derivation; do not re-derive a horizon at either call site.\n */\n #orderedHorizonBlocksFor(packetId: number, config: ResolvedFECTrackConfig): number {\n if (!FECManager.#hasOrderedGeometry(config)) {\n throw new Error(\n `[FECManager] packetId=${packetId}: ordered-relay settlement derives the ` +\n `decoder's interleave horizon from the catalog interleaveDepthMs ` +\n `(>= ${MIN_INTERLEAVE_DEPTH_MS}ms), the media-unit rate and the block ` +\n `delivery window; got interleaveDepthMs=${config.interleaveDepthMs} ` +\n `fps=${String(config.fps)} timeoutMs=${config.timeoutMs}.`,\n )\n }\n // Config time is where failing closed is right, so a > 255 geometry throws\n // from here with the message oti.ts wrote: it names interleaveDepthMs and\n // the catalog fix. #derivableOrderedHorizon deliberately does not.\n return deriveFecRetentionBlocks(config.timeoutMs, config.interleaveDepthMs)\n }\n\n /**\n * Whether a stream's geometry can define an ordered-relay horizon at all.\n * One definition, shared by the config-time and packet-feed callers, so the\n * two can never disagree about which streams settle by stream order.\n */\n static #hasOrderedGeometry(config: ResolvedFECTrackConfig): boolean {\n return (\n config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS &&\n // `fps` is no longer an input to the horizon, but it still gates WHICH\n // streams settle by stream order at all: a track with no usable\n // media-unit rate fell back to datagram semantics before this and must\n // keep doing so. Removing this widens ordered-relay settlement to streams\n // that never had it. Load-bearing \u2014 do not drop it as unused.\n config.fps !== undefined &&\n Number.isFinite(config.fps) &&\n config.fps > 0 &&\n Number.isFinite(config.timeoutMs) &&\n config.timeoutMs > 0\n )\n }\n\n /**\n * The horizon for one stream, or undefined when its geometry cannot define\n * one.\n *\n * `config.timeoutMs` is the block's delivery window. On the live path\n * multicast-manager computes it with `jitterBufferMs: fec.reorderToleranceMs`\n * and passes it to configure(), which is the same `deliveryWindowMs`\n * fec-worker-setup sizes decoder retention from, so the two horizons are\n * equal there.\n *\n * They are NOT equal on the fallback path, and the skew runs in the\n * direction this file's invariant calls unsafe. `jitterBufferMs` reaches\n * fec-manager only inside an explicit configure({timeoutMs}); all four of\n * its own computeFecDeliveryWindowMs calls (one in #computeTimeout, one in\n * configure(), two in setFramerate) omit it, so a manager-derived\n * timeoutMs is tolerance-free and yields a\n * horizon NARROWER than the decoder's retention \u2014 the direction that settles\n * a block the decoder would still have recovered. At 720p/33ms the decoder\n * retains ceil(133/33)+1 = 6 while a tolerance-free 33ms window gives\n * ceil(33/33)+1 = 2. Two writes can also raise a validated timeoutMs\n * afterwards without re-validating (configure()'s widening loop and\n * setFramerate).\n *\n * Not fixed here: the real fix is to thread the tolerance into those four\n * calls so both sides derive from the same quantity, which is a change to\n * inputs fec-manager does not currently receive. Tracked in BLO-38250. The\n * live ISS path does not hit it \u2014 multicast-manager always supplies both\n * `timeoutMs` and `fps`, and setFramerate skips tracks that already carry\n * `fps` \u2014 so this is a latent skew on the fallback, not a live defect. Do\n * not read the old \"can only skew narrower, never wider\" note as a safety\n * argument: narrower IS the failing direction.\n */\n #derivableOrderedHorizon(config: ResolvedFECTrackConfig, packetId: number): number | undefined {\n if (!FECManager.#hasOrderedGeometry(config)) return undefined\n try {\n // `tryDeriveFecRetentionBlocks`, not the throwing variant: this is the\n // per-symbol path (#settlesByOrder -> #orderedHorizonBlocks, from\n // noteBlockActivity on every source and repair symbol), and a geometry\n // past the 255-block ceiling is STEADY STATE, not a transient --\n // `timeoutMs` does not narrow back on its own. Throwing here would\n // construct an Error and capture a stack in oti.ts, then unwind it,\n // for every symbol on the stream, and #advanceOrderedFrontier's\n // `blocks === undefined` branch re-enters noteBlockActivity per pending\n // SBN and would multiply it again. The ceiling itself stays owned by\n // oti.ts -- open-coding the comparison here would re-create the second\n // derivation this change exists to remove.\n const blocks = tryDeriveFecRetentionBlocks(config.timeoutMs, config.interleaveDepthMs)\n if (blocks === undefined) {\n // Falls back to datagram semantics through the `blocks === undefined`\n // branch already written for it in #advanceOrderedFrontier. Two writes\n // can raise a track's timeoutMs after #orderedHorizonBlocksFor\n // validated it (configure()'s widening loop and setFramerate's), and\n // neither re-validates, so this is reachable without a second\n // config-time check.\n this.#noteLeftOrderedSettlement(\n packetId,\n `geometry cannot define a retention horizon within the 255-block ceiling ` +\n `(timeoutMs=${config.timeoutMs}ms over interleaveDepthMs=${config.interleaveDepthMs}ms)`,\n )\n }\n return blocks\n } catch (error) {\n // Backstop only -- nothing above throws today. Deliberately unnarrowed:\n // if the callee grows a new throw, this is what makes it visible rather\n // than silently swallowed. The contract of this path\n // (#orderedHorizonBlocks) is that it must not throw.\n this.#noteLeftOrderedSettlement(packetId, String(error))\n return undefined\n }\n }\n\n /**\n * Warn once per stream that it dropped from ordered-relay settlement to the\n * datagram deadline. Swallowing is right for the packet-feed contract, but a\n * configured stream changing settlement mid-flight is a behavioural change,\n * and it is otherwise indistinguishable from a stream that never had\n * geometry.\n */\n #noteLeftOrderedSettlement(packetId: number, reason: string): void {\n if (this.#orderedHorizonWarnedFor.has(packetId)) return\n this.#orderedHorizonWarnedFor.add(packetId)\n console.warn(\n `[FECManager] packetId=${packetId} left ordered-relay settlement for the datagram deadline: ${reason}`,\n )\n }\n\n /**\n * The stream's interleave horizon, or undefined when its geometry cannot\n * define one. Two different streams reach that case, with different causes:\n * a packetId that was never configured, and so resolves to the manager\n * defaults (explicitly configured tracks are refused an underivable geometry\n * at config time \u2014 #upsertTrackConfig, setMulticastSymbolDelivery); or a\n * configured track whose timeoutMs was later widened past the 255-block\n * ceiling without re-validating, which #derivableOrderedHorizon catches\n * rather than throwing from the packet-feed path. Either way the stream\n * keeps datagram semantics instead of throwing. The manager defaults can\n * also change under a stream that already holds blocks, so a caller cannot\n * assume a horizon that was defined when a block was held is still defined\n * (see #advanceOrderedFrontier).\n */\n #orderedHorizonBlocks(packetId: number): number | undefined {\n return this.#derivableOrderedHorizon(this.#resolveTrackConfig(packetId), packetId)\n }\n\n /** True while a block of an ordered-relay stream can still receive symbols. */\n #withinOrderedHorizon(key: string): boolean {\n if (this.#multicastSymbolDelivery !== 'ordered-relay') return false\n const block = this.#parseBlockKey(key)\n const frontier = this.#orderedFrontiers.get(\n this.#streamKey(block.packetId, block.catalogTrackName),\n )\n if (frontier === undefined) return false\n const horizon = this.#orderedHorizonBlocks(block.packetId)\n if (horizon === undefined) return false\n return this.#blocksBehind(block.sbn, frontier, block.packetId) <= horizon\n }\n\n /** Whether a block's fate is decided by stream order rather than a timer. */\n #settlesByOrder(key: string): boolean {\n return this.#multicastSymbolDelivery === 'ordered-relay' &&\n (this.#blockPathCounts.get(key)?.multicastSymbols ?? 0) > 0 &&\n this.#orderedHorizonBlocks(this.#parseBlockKey(key).packetId) !== undefined\n }\n\n #forgetOrderedPending(key: string): void {\n if (this.#orderedPendingBlocks.size === 0) return\n const block = this.#parseBlockKey(key)\n const streamKey = this.#streamKey(block.packetId, block.catalogTrackName)\n const pending = this.#orderedPendingBlocks.get(streamKey)\n if (pending?.get(block.sbn) !== key) return\n pending.delete(block.sbn)\n if (pending.size === 0) this.#orderedPendingBlocks.delete(streamKey)\n }\n\n /**\n * Record the stream position of a multicast symbol and settle every pending\n * block it proves final: a block more than the interleave horizon behind the\n * frontier has already been handed every symbol the relay will deliver.\n */\n #advanceOrderedFrontier(\n streamKey: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n const previous = this.#orderedFrontiers.get(streamKey)\n const frontier = previous === undefined || this.#blockPrecedes(previous, sbn)\n ? sbn\n : previous\n if (frontier !== previous) this.#orderedFrontiers.set(streamKey, frontier)\n const pending = this.#orderedPendingBlocks.get(streamKey)\n if (!pending) return\n const blocks = this.#orderedHorizonBlocks(packetId)\n if (blocks === undefined) {\n // A configured stream whose timeoutMs was widened past the 255-block\n // ceiling after validation (#derivableOrderedHorizon); an unconfigured\n // one never gets here (feedSymbolFromSource refuses it). Stream\n // order can no longer settle its held blocks, so hand them to the\n // datagram deadline -- as setMulticastSymbolDelivery('datagram') does --\n // rather than settle them a horizon early or hold them with no timer.\n this.#orderedPendingBlocks.delete(streamKey)\n for (const pendingSbn of pending.keys()) {\n this.noteBlockActivity(pendingSbn, packetId, catalogTrackName)\n }\n return\n }\n for (const [pendingSbn, key] of pending) {\n if (this.#blocksBehind(pendingSbn, frontier, packetId) <= blocks) continue\n pending.delete(pendingSbn)\n this.#settleOrderedBlock(key, pendingSbn, packetId, catalogTrackName)\n }\n if (pending.size === 0 && this.#orderedPendingBlocks.get(streamKey) === pending) {\n this.#orderedPendingBlocks.delete(streamKey)\n }\n }\n\n /**\n * Decide a block the ordered relay has finished delivering. Its accounting\n * is final: SSB_length (from any repair symbol) against the source and\n * repair symbols it holds says whether the decoder can still recover it.\n * A block short of that asks for repair when D-28 is wired, under the same\n * post-request fetch budget the datagram path grants; otherwise, and for a\n * block the decoder holds enough symbols for, the decoder finalizes it\n * exactly as a datagram deadline would (a Worker acknowledges the cleanup\n * only after every earlier symbol, so an in-flight recovery still wins).\n */\n #settleOrderedBlock(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n if (\n this.#completedBlocks.has(key) ||\n this.#pendingWorkerCleanups.has(key) ||\n this.#repairRequestedBlocks.has(key)\n ) return\n const authoredLength = this.#blockAuthoredLengths.get(key)\n const received =\n (this.#blockSourceCounts.get(key) ?? 0) +\n (this.#blockRepairRsIds.get(key)?.size ?? 0)\n if (\n this.onRepairNeeded &&\n (authoredLength === undefined || received < authoredLength)\n ) {\n this.#requestRepair(sbn, packetId, catalogTrackName, true)\n return\n }\n this.#traceLifecycle('horizon-settled', sbn, packetId, catalogTrackName)\n this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName)\n }\n\n /** Register a block whose multicast symbols arrive over an ordered relay. */\n #holdForOrderedSettlement(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n // A block that started on the other transport may already hold a timer.\n // Its multicast symbols now decide it by order; a wall clock cannot.\n this.#clearRepairDeadline(key)\n if (this.#activeBlocks.has(key)) {\n this.#clearBlockTimer(key)\n this.#unindexBlockKey(key)\n }\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n let pending = this.#orderedPendingBlocks.get(streamKey)\n if (!pending) {\n pending = new Map()\n this.#orderedPendingBlocks.set(streamKey, pending)\n }\n pending.set(sbn, key)\n }\n\n /** Cancel and forget a block's inactivity timer and deadline. */\n #clearBlockTimer(key: string): void {\n const timer = this.#activeBlocks.get(key)\n if (timer?.handle != null) clearTimeout(timer.handle)\n this.#activeBlocks.delete(key)\n this.#activeBlockDeadlinesMs.delete(key)\n this.#blockWaitDeadlinesMs.delete(key)\n this.#repairHoldUntilMs.delete(key)\n }\n\n #parseBlockKey(key: string): { packetId: number; sbn: number; catalogTrackName?: string } {\n const [packetIdText, sbnText, ...catalogParts] = key.split(':')\n const catalogTrackName = catalogParts.length > 0\n ? decodeURIComponent(catalogParts.join(':'))\n : undefined\n return {\n packetId: Number(packetIdText) || 0,\n sbn: Number(sbnText) || 0,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n }\n }\n\n #decoderForPacket(packetId: number, catalogTrackName?: string) {\n return this.#fecDecoders.get(this.#streamKey(packetId, catalogTrackName))\n ?? this.#fecDecoders.get(this.#streamKey(packetId))\n }\n\n #workerForPacket(packetId: number, catalogTrackName?: string) {\n return this.#workerClients.get(this.#streamKey(packetId, catalogTrackName))\n ?? this.#workerClients.get(this.#streamKey(packetId))\n }\n\n /**\n * The stream's Worker client for routing, or undefined after evicting a\n * registered client that was already disposed. A disposed client throws on\n * every command, so it leaves routing here before a symbol reaches it and\n * the stream reports its decoder unavailable instead.\n */\n #routableWorkerForPacket(packetId: number, catalogTrackName?: string): FecWorkerClient | undefined {\n const client = this.#workerForPacket(packetId, catalogTrackName)\n if (client === undefined || client.disposed !== true) return client\n this.#evictDisposedWorker(client)\n return undefined\n }\n\n #claimDecoderBlock(\n client: object,\n sbn: number,\n catalogTrackName?: string,\n ): boolean {\n let blocks = this.#decoderBlockOwners.get(client)\n if (!blocks) {\n blocks = new Map()\n this.#decoderBlockOwners.set(client, blocks)\n }\n const owner = blocks.get(sbn)\n if (!owner) {\n blocks.set(sbn, { catalogTrackName, conflicted: false })\n if (blocks.size > 512) {\n const stale = [...blocks.keys()].slice(0, 256)\n for (const key of stale) blocks.delete(key)\n }\n return true\n }\n if (owner.conflicted) return false\n if (owner.catalogTrackName === catalogTrackName) return true\n owner.conflicted = true\n return false\n }\n\n #decoderBlockCatalogTrackName(client: object, sbn: number): string | undefined {\n const owner = this.#decoderBlockOwners.get(client)?.get(sbn)\n return owner?.conflicted ? undefined : owner?.catalogTrackName\n }\n\n #resetDecoderBlockOwners(client: object, catalogTrackName: string): void {\n const blocks = this.#decoderBlockOwners.get(client)\n if (!blocks) return\n for (const [sbn, owner] of blocks) {\n if (owner.catalogTrackName === catalogTrackName || owner.conflicted) {\n blocks.delete(sbn)\n }\n }\n if (blocks.size === 0) this.#decoderBlockOwners.delete(client)\n }\n\n #discardFecDecoderClient(client: WasmFecDecoder): void {\n for (const [key, entry] of this.#fecDecoders) {\n if (entry.client === client) this.#fecDecoders.delete(key)\n }\n this.#decoderBlockOwners.delete(client)\n }\n\n #assertExclusiveDecoderClient(\n streamKey: string,\n client: object,\n registrations: Iterable<readonly [string, object]>,\n kind: 'decoder' | 'worker',\n ): void {\n for (const [registeredKey, registeredClient] of registrations) {\n if (registeredClient !== client || registeredKey === streamKey) continue\n throw new Error(\n `[FECManager] ${kind} client is already registered for stream ${registeredKey}; ` +\n `create one client per physical stream`,\n )\n }\n }\n\n #statsForPacket(packetId: number): MutableFecCounters {\n let stats = this.#fecTrackStats.get(packetId)\n if (!stats) {\n stats = newFecCounters()\n this.#fecTrackStats.set(packetId, stats)\n }\n return stats\n }\n\n #markPathBlockComplete(sbn: number, packetId: number, catalogTrackName?: string): void {\n const block = this.#blockPathCounts.get(this.#blockKey(sbn, packetId, catalogTrackName))\n if (block) block.complete = true\n }\n\n /** Cancel and forget a block's D-28 repair-deadline timer. */\n #clearRepairDeadline(key: string): void {\n const timer = this.#repairDeadlines.get(key)\n if (timer?.handle != null) clearTimeout(timer.handle)\n this.#repairDeadlines.delete(key)\n this.#repairDeadlineAtMs.delete(key)\n }\n\n /**\n * Move a block's D-28 repair deadline to `delayMs` from now. The timer\n * already armed for an earlier moment fires first and re-arms for the\n * remainder, so repeated source progress costs no timer operation.\n */\n #armRepairDeadline(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n delayMs: number,\n ): void {\n const deadlineMs = performance.now() + delayMs\n this.#repairDeadlineAtMs.set(key, deadlineMs)\n const armed = this.#repairDeadlines.get(key)\n if (armed?.handle != null) {\n if (armed.fireAtMs <= deadlineMs) return\n clearTimeout(armed.handle)\n }\n const timer: DeadlineTimer = { handle: null, fireAtMs: deadlineMs }\n const onFire = (): void => {\n if (this.#repairDeadlines.get(key) !== timer) return\n const currentDeadlineMs = this.#repairDeadlineAtMs.get(key)\n const nowMs = performance.now()\n if (currentDeadlineMs !== undefined && currentDeadlineMs > nowMs) {\n timer.fireAtMs = currentDeadlineMs\n timer.handle = setTimeout(onFire, Math.ceil(currentDeadlineMs - nowMs))\n return\n }\n timer.handle = null\n this.#requestRepair(sbn, packetId, catalogTrackName)\n }\n timer.handle = setTimeout(onFire, delayMs)\n this.#repairDeadlines.set(key, timer)\n }\n\n #requestRepair(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n /**\n * Ordered-relay settlement only: the stream has proved the block final,\n * so its multicast wait ends now. A datagram block keeps the wait its\n * accepted symbols armed.\n */\n settledByOrder = false,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n if (\n this.#completedBlocks.has(key) ||\n !this.onRepairNeeded ||\n this.#repairRequestedBlocks.has(key)\n ) return\n\n const deadline = this.#repairDeadlines.get(key)\n if (deadline?.handle != null) clearTimeout(deadline.handle)\n this.#repairDeadlines.set(key, null)\n this.#repairDeadlineAtMs.delete(key)\n this.#repairRequestedBlocks.add(key)\n\n // The request itself grants no time (BLO-35597). The multicast wait keeps\n // running, extended by any late symbol; the repair scheduler extends the\n // block past it (holdBlockForRepair) only for a FETCH it admits, i.e. one\n // the measured reliable-path RTT says can land before the block's\n // presentation deadline. A request it declines leaves the block on the\n // multicast wait alone, so no doomed FETCH is sent and none is aborted\n // for want of a grace that could never have covered the round trip.\n if (settledByOrder) this.#armBlockTimeout(sbn, packetId, catalogTrackName, 0)\n this.#traceLifecycle('repair-requested', sbn, packetId, catalogTrackName)\n this.onRepairNeeded(\n sbn,\n packetId,\n catalogTrackName,\n this.blockClassFor(sbn, packetId, catalogTrackName),\n )\n }\n\n // \u2500\u2500 Keyframe-protected repair policy \u2500\u2500\n\n /**\n * The classifier geometry of one stream. A stream-scoped configuration\n * wins. Once any stream of a packetId was configured with its own geometry,\n * a sibling stream without one has none that provably belongs to it: the\n * packet-level geometry may be another rung's (a different framerate or\n * GOP), so it runs all-protected (`stream-geometry-absent`).\n */\n #geometryFor(packetId: number, catalogTrackName?: string): BlockClassifierGeometry {\n const own = this.#streamGeometries.get(this.#streamKey(packetId, catalogTrackName))\n if (own) return own\n const config = this.#trackConfigs.get(packetId)\n if (config?.trackKind === 'audio') return { trackKind: 'audio' }\n for (const key of this.#streamGeometries.keys()) {\n if (this.#belongsToPacket(key, packetId)) {\n return { trackKind: 'video', mode: 'all-protected', reason: 'stream-geometry-absent' }\n }\n }\n if (config?.geometry) return config.geometry\n // No catalog cadence reached this track: every video block is protected.\n return { trackKind: 'video', mode: 'all-protected', reason: 'keyframe-cadence-absent' }\n }\n\n #classifierFor(packetId: number, catalogTrackName?: string): BlockClassifier {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n let classifier = this.#classifiers.get(streamKey)\n if (!classifier) {\n classifier = new BlockClassifier(this.#geometryFor(packetId, catalogTrackName))\n this.#classifiers.set(streamKey, classifier)\n if (classifier.geometry.trackKind === 'video') {\n this.onClassifierMode?.(packetId, catalogTrackName, classifier.mode, classifier.reason)\n }\n }\n return classifier\n }\n\n #applyClassifierChanges(\n packetId: number,\n catalogTrackName: string | undefined,\n changes: readonly ClassifierChange[],\n ): void {\n for (const change of changes) {\n if (change.kind === 'mode') {\n this.onClassifierMode?.(packetId, catalogTrackName, change.mode, change.reason)\n } else {\n this.onBlockReclassified?.(\n change.sbn,\n packetId,\n catalogTrackName,\n change.from,\n change.to,\n change.evidence,\n )\n }\n }\n }\n\n /**\n * Freeze a block's class at resolution and drop its classifier state. The\n * frozen class answers every later query (a recovery counted after the\n * block was cancelled, a failure event) exactly.\n */\n #retireBlockClass(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): BlockClass {\n const frozen = this.#resolvedBlockClasses.get(key)\n if (frozen !== undefined) return frozen\n const blockClass = this.#peekClass(sbn, packetId, catalogTrackName)\n this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))?.forget(sbn)\n this.#resolvedBlockClasses.set(key, blockClass)\n this.#materializedBlocks.delete(key)\n return blockClass\n }\n\n /** A block's live class without creating classifier state for the stream. */\n #peekClass(sbn: number, packetId: number, catalogTrackName?: string): BlockClass {\n const classifier = this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))\n if (classifier) return classifier.classOf(sbn)\n return this.#geometryFor(packetId, catalogTrackName).trackKind === 'audio' ? 'audio' : 'unknown'\n }\n\n #viaFor(key: string): RepairVia {\n return (this.#blockPathCounts.get(key)?.moqSymbols ?? 0) > 0 ? 'quic' : 'inband'\n }\n\n #noteBlockRepaired(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n if (this.#repairedBlocks.has(key)) return\n this.#repairedBlocks.add(key)\n this.onBlockRepaired?.(\n sbn,\n packetId,\n catalogTrackName,\n this.blockClassFor(sbn, packetId, catalogTrackName),\n this.#viaFor(key),\n )\n }\n\n // Deliberately one block tighter than deriveFecRetentionBlocks over the same\n // inputs (that function's `+ 1`). Different question: the retention horizon\n // asks how long the decoder still ACCEPTS symbols for a block, this asks how\n // many blocks a stream can live behind (or ahead of) its frontier before a\n // source symbol there is a discontinuity rather than loss (see #noteSbnSeen).\n // The extra block of retention is slack for a block still being fed; a block\n // nothing has been heard from does not get it. Not the units defect \u2014 but it\n // is the next place the two can drift, so change both or neither.\n #liveWindowBlocks(config: ResolvedFECTrackConfig): number | null {\n if (config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS || config.timeoutMs <= 0) return null\n return Math.ceil(config.timeoutMs / config.interleaveDepthMs)\n }\n\n /**\n * Record that a symbol for block `sbn` (the block's SS_Start) arrived and\n * advance the stream's frontier, the newest block start seen.\n *\n * A SOURCE symbol whose block goes back further than one live window, or\n * jumps further than the stream could have produced since its last symbol,\n * is a source discontinuity, not loss. A repair symbol can only advance the\n * frontier: a D-28 fetch legitimately answers for an old block, so it never\n * signals a discontinuity.\n *\n * The producer closes a block at its Dth media-unit boundary OR when K\n * source symbols filled it, so one interleave span can start several blocks\n * (every large keyframe does). What bounds a forward jump is therefore\n * packet evidence, not a block rate: SS_ID advances by one per FEC source\n * packet (ISO/IEC 23008-1 C.5.2) and the MMTP packet_sequence_number by one\n * per packet of the packet_id, so with both source symbols'\n * packet_sequence_number a block start can move at most the packets sent\n * between them plus the anchor's own offset into its block (< K). Without\n * it, only the D-close rate is known.\n *\n * Vanished blocks are not enumerated here: their starts are unknown until a\n * repair states them. The SS_IDs between two stated blocks become one\n * repair-less span when the later block's repair arrives (placeIsoRepair).\n */\n #noteSbnSeen(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n symbol: 'source' | 'repair',\n packetSequenceNumber?: number,\n ): void {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n const now = performance.now()\n const previous = this.#highestSbn.get(streamKey)\n const anchor = this.#sourceAnchors.get(streamKey)\n const psn = symbol === 'source' &&\n packetSequenceNumber !== undefined &&\n Number.isSafeInteger(packetSequenceNumber) &&\n packetSequenceNumber >= 0 &&\n packetSequenceNumber <= 0xffff_ffff\n ? packetSequenceNumber\n : undefined\n const anchorHere = (): void => {\n if (psn !== undefined && (anchor === undefined || !this.#blockPrecedes(sbn, anchor.sbn))) {\n this.#sourceAnchors.set(streamKey, { sbn, psn, atMs: now })\n }\n }\n const discontinuity = (): void => {\n // The classifier compares authored order with SBN order only within\n // one continuous SBN sequence.\n this.#classifiers.get(streamKey)?.noteSourceDiscontinuity()\n this.#highestSbn.set(streamKey, { sbn, atMs: now })\n if (psn === undefined) this.#sourceAnchors.delete(streamKey)\n else this.#sourceAnchors.set(streamKey, { sbn, psn, atMs: now })\n this.onSourceDiscontinuity?.(packetId, catalogTrackName)\n }\n if (previous === undefined) {\n this.#highestSbn.set(streamKey, { sbn, atMs: now })\n anchorHere()\n return\n }\n const config = this.#resolveTrackConfig(packetId)\n const window = this.#liveWindowBlocks(config)\n if (!this.#blockPrecedes(previous.sbn, sbn)) {\n if (symbol === 'source' && window !== null &&\n this.#blocksBehind(sbn, previous.sbn, packetId) > window) {\n discontinuity()\n return\n }\n anchorHere()\n return\n }\n const missing = this.#blocksBehind(previous.sbn, sbn, packetId) - 1\n let plausible = true\n if (window !== null) {\n // A packet_sequence_number that did not advance says nothing (a\n // producer that does not maintain it); only then is the D-close rate\n // the whole bound.\n if (psn !== undefined && anchor !== undefined &&\n this.#blockPrecedes(anchor.sbn, sbn) && psn !== anchor.psn) {\n // A block start can move at most the packets sent since the anchor,\n // plus the anchor's own offset into its block (< K). Packets sent\n // from the anchor packet to this one, serial arithmetic (RFC 1982):\n // more than half the space means it went backwards.\n const sent = (psn - anchor.psn) >>> 0\n plausible =\n sent <= 0x7fff_ffff &&\n serial32Distance(anchor.sbn, sbn) <= sent + config.k\n } else {\n plausible = missing <= window + Math.ceil((now - previous.atMs) / config.interleaveDepthMs)\n }\n }\n if (!plausible) {\n if (symbol === 'repair') return\n discontinuity()\n return\n }\n this.#highestSbn.set(streamKey, { sbn, atMs: now })\n anchorHere()\n }\n\n /*\n * Block order for one stream (BLO-35597 iso:5). A block is keyed by its\n * SS_Start and SS_ID is a 32-bit serial number (ISO/IEC 23008-1 C.5.2), so\n * blocks are ordered by RFC 1982 comparison of their starts. Only configured\n * streams hold blocks: every entry point refuses an unconfigured packetId.\n */\n #blockPrecedes(a: number, b: number): boolean {\n return serial32Precedes(a, b)\n }\n\n /**\n * Blocks `a` lies behind `b` (0 when it does not precede it). Blocks vary\n * in length (K' = SSB_length <= K), so only a lower bound is known: the one\n * metric the decoder and the worker retire by too.\n */\n #blocksBehind(a: number, b: number, packetId: number): number {\n return isoBlocksBehindLowerBound(a, b, this.kForPacket(packetId))\n }\n\n #materializeBlock(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n repairable: boolean,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n if (this.#blockResolutionFor(key) !== 'untracked') return\n this.#materializedBlocks.add(key)\n this.#classifierFor(packetId, catalogTrackName).touch(sbn)\n this.onBlockVanished?.(sbn, packetId, catalogTrackName)\n if (repairable) {\n this.noteBlockActivity(sbn, packetId, catalogTrackName, true)\n } else {\n this.#failBlock(sbn, packetId, catalogTrackName)\n }\n }\n\n #blockResolutionFor(key: string): FecBlockResolution {\n if (this.#failedBlocks.has(key)) return 'failed'\n if (this.#completedBlocks.has(key)) return 'complete'\n if (\n this.#activeBlocks.has(key) ||\n this.#pendingWorkerCleanups.has(key) ||\n this.#repairDeadlines.has(key) ||\n this.#blockSourceCounts.has(key) ||\n this.#blockRepairRsIds.has(key) ||\n this.#materializedBlocks.has(key)\n ) {\n return 'pending'\n }\n return 'untracked'\n }\n\n #bumpEpoch(packetId: number): void {\n this.#packetEpochs.set(packetId, (this.#packetEpochs.get(packetId) ?? 0) + 1)\n }\n\n #traceLifecycle(\n event: FecLifecycleTrace['event'],\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n rsId?: number,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n const deadline = this.#activeBlockDeadlinesMs.get(key)\n const decoderPath = this.#syncDecoderBlockOwners.has(key)\n ? 'sync'\n : this.#workerForPacket(packetId, catalogTrackName)\n ? 'worker'\n : this.#decoderForPacket(packetId, catalogTrackName)\n ? 'sync'\n : 'none'\n this.#lifecycleTrace.push({\n atMs: Date.now(),\n event,\n packetId,\n sbn,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n sourceCount: this.#blockSourceCounts.get(key) ?? 0,\n sourceEsis: [...(this.#blockSourceEsis.get(key) ?? [])].sort((a, b) => a - b),\n repairRsIds: [...(this.#blockRepairRsIds.get(key) ?? [])].sort((a, b) => a - b),\n decoderPath,\n ...(deadline === undefined\n ? {}\n : { deadlineRemainingMs: Math.max(0, deadline - performance.now()) }),\n pendingCleanup: this.#pendingWorkerCleanups.has(key),\n ...(rsId === undefined ? {} : { rsId }),\n })\n if (this.#lifecycleTrace.length > 64) this.#lifecycleTrace.splice(0, 32)\n }\n\n #defaultTrackConfig(): ResolvedFECTrackConfig {\n return {\n k: this.#fecK,\n symbolSize: this.#fecSymbolSize,\n interleaveDepthMs: this.#fecInterleaveDepthMs,\n fps: this.#fecFps,\n timeoutMs: this.#fecTimeoutMs,\n }\n }\n\n #resolveTrackConfig(packetId: number): ResolvedFECTrackConfig {\n return this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig()\n }\n\n #computeTimeout(config: {\n k: number\n interleaveDepthMs: number\n fps?: number\n timeoutMs?: number\n }): number {\n if (config.timeoutMs && config.timeoutMs > 0) return config.timeoutMs\n // Only a valid \u00A75.1 ms interleave (>= MIN_INTERLEAVE_DEPTH_MS) drives the\n // timeout. A sub-floor value is a raw frame count (no fps to convert with);\n // fall back to the established #fecTimeoutMs rather than collapse to ~0.\n if (config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {\n return Math.max(\n this.#fecTimeoutMs,\n computeFecDeliveryWindowMs({\n interleaveDepthMs: config.interleaveDepthMs,\n sourceSymbols: config.k,\n mediaUnitsPerSecond: config.fps,\n }),\n )\n }\n return this.#fecTimeoutMs\n }\n\n /**\n * The delivery-gap window is the longest catalog keyframe interval among\n * the configured video streams. A failed video block costs every frame up\n * to the next keyframe, so a receiver must remember a stall for at least\n * that long: forgetting it sooner would let the next recurrence fail a\n * block whose damage outlives the memory of the stall that caused it. It\n * comes from the catalog's `keyframeIntervalTicks`/`keyframeIntervalMs`\n * (via the classifier geometry), never from a constant. It also caps the\n * gap the wait may absorb; with no catalog cadence no gap is reported and\n * the catalog tolerance alone applies (see DeliveryGapEstimator).\n */\n #refreshDeliveryGapWindow(): void {\n let windowMs: number | undefined\n const consider = (geometry: BlockClassifierGeometry | undefined): void => {\n if (geometry?.trackKind !== 'video' || geometry.mode !== 'classifying') return\n const { ticks, timescale } = geometry.keyframeInterval\n const ms = (ticks / timescale) * 1000\n if (Number.isFinite(ms) && ms > 0 && (windowMs === undefined || ms > windowMs)) windowMs = ms\n }\n for (const geometry of this.#streamGeometries.values()) consider(geometry)\n for (const config of this.#trackConfigs.values()) consider(config.geometry)\n this.#deliveryGaps.setWindowMs(windowMs)\n }\n\n /**\n * The multicast late-symbol wait: the block span plus the larger of the\n * catalog's signaled tolerance and the receiver-measured delivery gap.\n *\n * `config.timeoutMs` already is span + `fec.reorderToleranceMs` (or the\n * wider envelope configure() applies), so it stays the floor; the measured\n * gap widens it only when the path stalls for longer than the catalog\n * allowed for. Datagram delivery only: an ordered relay settles blocks by\n * stream order and never feeds the estimator.\n */\n #multicastWaitMs(config: ResolvedFECTrackConfig): number {\n return this.#waitForGapMs(config, this.#deliveryGaps.peakGapMs(performance.now()))\n }\n\n /**\n * The widest #multicastWaitMs can become: the estimator reports at most its\n * catalog window, so this is a catalog quantity that no measurement moves.\n */\n #maxMulticastWaitMs(config: ResolvedFECTrackConfig): number {\n return this.#waitForGapMs(config, this.#deliveryGaps.windowMs ?? 0)\n }\n\n #waitForGapMs(config: ResolvedFECTrackConfig, gapMs: number): number {\n if (!(gapMs > 0) || !(config.interleaveDepthMs > 0)) return config.timeoutMs\n return Math.max(config.timeoutMs, Math.ceil(config.interleaveDepthMs + gapMs))\n }\n\n #repairDeadlineMs(\n config: ResolvedFECTrackConfig,\n waitMs = this.#multicastWaitMs(config),\n ): number {\n const halfFailBoundMs = Math.ceil(waitMs / 2)\n if (\n config.k > 0 &&\n config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS &&\n config.fps !== undefined &&\n Number.isFinite(config.fps) &&\n config.fps > 0\n ) {\n const mediaUnitMs = 1000 / config.fps\n const interleaveUnits = deriveFecInterleaveDepth(\n config.interleaveDepthMs,\n mediaUnitMs,\n )\n const sourceHorizonMs = Math.ceil(config.k * interleaveUnits * mediaUnitMs)\n if (sourceHorizonMs < waitMs) return sourceHorizonMs\n }\n return halfFailBoundMs\n }\n\n /**\n * One block lifetime: the inactivity fail bound plus the D-28 repair grace,\n * at the widest wait the catalog allows (#maxMulticastWaitMs). Everything\n * derived from it (terminal retention, the release gate's hold ceiling, the\n * duplicate ledgers, the owed-cleanup bound) is compared across time, so it\n * must not follow the live measurement.\n */\n #blockLifetimeMs(config: ResolvedFECTrackConfig): number {\n const waitMs = this.#maxMulticastWaitMs(config)\n return waitMs + this.#repairGraceMs(config, waitMs)\n }\n\n /**\n * The lifetime at the live wait, capped at #blockLifetimeMs: lifetime is not\n * monotone in the wait (the repair deadline switches from half the wait to\n * the source horizon), so the cap is what keeps it inside the retention.\n */\n #liveBlockLifetimeMs(config: ResolvedFECTrackConfig): number {\n const waitMs = this.#multicastWaitMs(config)\n return Math.min(waitMs + this.#repairGraceMs(config, waitMs), this.#blockLifetimeMs(config))\n }\n\n #repairGraceMs(config: ResolvedFECTrackConfig, waitMs: number): number {\n return Math.max(1, waitMs - this.#repairDeadlineMs(config, waitMs))\n }\n\n #armBlockTimeout(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n delayMs: number,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n const waitAtMs = performance.now() + Math.max(1, Math.ceil(delayMs))\n // A held block's symbols (its own FETCH's repair symbols included) never\n // carry its wait past the hold: the hold is its presentation deadline.\n const holdAtMs = this.#repairHoldUntilMs.get(key)\n this.#blockWaitDeadlinesMs.set(key, holdAtMs === undefined ? waitAtMs : Math.min(waitAtMs, holdAtMs))\n this.#applyBlockDeadline(key, sbn, packetId, catalogTrackName)\n }\n\n /**\n * Arm (or move) a block's one fail timer at the later of its multicast\n * wait and its repair hold. Moving it later costs no timer operation: the\n * armed timer wakes no later than the new deadline and re-arms for the\n * remainder.\n */\n #applyBlockDeadline(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n ): void {\n const nowMs = performance.now()\n const deadlineMs = Math.max(\n this.#blockWaitDeadlinesMs.get(key) ?? nowMs + 1,\n this.#repairHoldUntilMs.get(key) ?? -Infinity,\n )\n this.#activeBlockDeadlinesMs.set(key, deadlineMs)\n const armed = this.#activeBlocks.get(key)\n if (armed?.handle != null) {\n if (armed.fireAtMs <= deadlineMs) return\n clearTimeout(armed.handle)\n }\n const timer: DeadlineTimer = { handle: null, fireAtMs: deadlineMs }\n const onFire = (): void => {\n // clearTimeout() cannot retract a callback that the browser already\n // moved onto its task queue. Only the timer that still owns this\n // block's deadline may fail it or enqueue Worker cleanup.\n if (this.#activeBlocks.get(key) !== timer) return\n const currentDeadlineMs = this.#activeBlockDeadlinesMs.get(key)\n const firedAtMs = performance.now()\n if (currentDeadlineMs !== undefined && currentDeadlineMs > firedAtMs) {\n timer.fireAtMs = currentDeadlineMs\n timer.handle = setTimeout(onFire, Math.ceil(currentDeadlineMs - firedAtMs))\n return\n }\n this.#activeBlocks.delete(key)\n this.#activeBlockDeadlinesMs.delete(key)\n this.#blockWaitDeadlinesMs.delete(key)\n this.#repairHoldUntilMs.delete(key)\n this.#traceLifecycle('timeout-queued', sbn, packetId, catalogTrackName)\n this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName)\n }\n timer.handle = setTimeout(onFire, Math.max(1, Math.ceil(deadlineMs - nowMs)))\n this.#activeBlocks.set(key, timer)\n this.#indexBlockKey(key, sbn, packetId)\n }\n\n /**\n * Hand a block whose delivery is over to its decoder for finalization: the\n * datagram deadline fired, or the ordered relay moved past it.\n */\n #finalizeExpiredBlock(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n // A vanished block that never received a symbol holds no decoder state\n // to clean up; waiting on a Worker acknowledgement would never settle.\n if (this.#materializedBlocks.has(key) && !this.#blockPathCounts.has(key)) {\n this.#failBlock(sbn, packetId, catalogTrackName)\n return\n }\n\n // A block that began on the synchronous decoder remains owned by that\n // decoder even if Worker setup completed later. Cleanup must follow the\n // same ownership rule as source and repair symbol dispatch.\n const syncOwner = this.#syncDecoderBlockOwners.get(key)\n if (syncOwner) {\n this.#routeSyncCleanup(syncOwner, sbn, packetId, catalogTrackName)\n this.#failBlock(sbn, packetId, catalogTrackName)\n return\n }\n\n const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName)\n if (workerClient) {\n this.#pendingWorkerCleanups.add(key)\n this.#pendingWorkerCleanupClients.set(key, workerClient)\n this.#boundOwedCleanup(key, packetId, workerClient)\n workerClient.cleanup(sbn)\n } else {\n const decoder = this.#decoderForPacket(packetId, catalogTrackName)?.client\n if (decoder) {\n this.#routeSyncCleanup(decoder, sbn, packetId, catalogTrackName)\n this.#failBlock(sbn, packetId, catalogTrackName)\n } else {\n this.#failBlock(sbn, packetId, catalogTrackName)\n }\n }\n }\n\n /**\n * Upsert one packetId's track configuration. With `catalogTrackName`, the\n * classifier geometry in `config` is that stream's own and is kept per\n * stream; every other field stays per packetId.\n */\n #upsertTrackConfig(\n packetId: number,\n config: FECManagerConfig,\n catalogTrackName?: string,\n ): ResolvedFECTrackConfig {\n const previous = this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig()\n const streamScopedGeometry =\n catalogTrackName !== undefined && config.geometry !== undefined ? config.geometry : undefined\n if (streamScopedGeometry !== undefined) {\n const { geometry: _streamGeometry, ...packetLevel } = config\n config = packetLevel\n }\n const timingChanged =\n config.k !== undefined ||\n config.interleaveDepthMs !== undefined ||\n config.fps !== undefined\n if (config.blockProfile !== undefined) {\n parseFecBlockProfile(config.blockProfile, `packetId=${packetId} fec.blockProfile`)\n }\n const blockProfile = config.blockProfile ?? previous.blockProfile\n if (blockProfile === undefined) {\n throw new Error(\n `[FECManager] packetId=${packetId}: fec.blockProfile is required ` +\n '(\"iso-ssbg1-v1\"); configure the track from its catalog',\n )\n }\n // iso-ssbg1-v1 block location keeps what it holds for a horizon derived\n // from this packet's catalog block span (#isoRetentionBlocks): without\n // one the locator would grow for the life of the session. The span is\n // this packet's own; the manager-wide default is never borrowed.\n const ownInterleaveDepthMs =\n config.interleaveDepthMs ?? this.#trackConfigs.get(packetId)?.interleaveDepthMs\n if (\n ownInterleaveDepthMs === undefined ||\n !Number.isFinite(ownInterleaveDepthMs) ||\n ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS\n ) {\n throw new Error(\n `[FECManager] packetId=${packetId}: iso-ssbg1-v1 requires the catalog fec.interleaveDepthMs ` +\n `(>= ${MIN_INTERLEAVE_DEPTH_MS} ms; got ${String(ownInterleaveDepthMs)}): it bounds how long ` +\n 'block location keeps stated blocks and held sources',\n )\n }\n const next: ResolvedFECTrackConfig = {\n blockProfile,\n k: config.k ?? previous.k,\n symbolSize:\n config.symbolSize != null && config.symbolSize > 0\n ? config.symbolSize\n : previous.symbolSize,\n interleaveDepthMs: config.interleaveDepthMs ?? previous.interleaveDepthMs,\n fps: config.fps ?? previous.fps,\n timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs),\n ...((config.trackKind ?? previous.trackKind) === undefined\n ? {}\n : { trackKind: config.trackKind ?? previous.trackKind }),\n ...((config.codec ?? previous.codec) === undefined\n ? {}\n : { codec: config.codec ?? previous.codec }),\n ...((config.geometry ?? previous.geometry) === undefined\n ? {}\n : { geometry: config.geometry ?? previous.geometry }),\n }\n next.timeoutMs = this.#computeTimeout(next)\n if (this.#multicastSymbolDelivery === 'ordered-relay') {\n // Error on bad init: an ordered-relay stream settles its blocks by the\n // decoder's interleave horizon, which this geometry must define.\n this.#orderedHorizonBlocksFor(packetId, next)\n }\n if (previous.blockProfile !== undefined && previous.k !== next.k) {\n // A different maximum K is a different block geometry: the stream's\n // blocks must not be read under the old one's rules.\n this.#dropIsoLocators(packetId)\n }\n this.#trackConfigs.set(packetId, next)\n this.#refreshDeliveryGapWindow()\n let policyChanged =\n (config.geometry !== undefined &&\n JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry)) ||\n (config.trackKind !== undefined && config.trackKind !== previous.trackKind)\n if (streamScopedGeometry !== undefined) {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n if (JSON.stringify(streamScopedGeometry) !== JSON.stringify(this.#streamGeometries.get(streamKey))) {\n this.#streamGeometries.set(streamKey, streamScopedGeometry)\n this.#refreshDeliveryGapWindow()\n policyChanged = true\n }\n }\n if (policyChanged) {\n // A new catalog epoch for this packet: every classifier whose geometry\n // changed rebuilds from the new one on its next symbol and announces\n // its mode again. A stream with its own geometry is untouched by a\n // sibling's change, but the first stream-scoped geometry on a packet\n // does change the siblings that have none (stream-geometry-absent).\n this.#bumpEpoch(packetId)\n for (const [key, classifier] of [...this.#classifiers]) {\n if (!this.#belongsToPacket(key, packetId)) continue\n const stream = this.#parseStreamKey(key)\n const geometry = this.#geometryFor(stream.packetId, stream.catalogTrackName)\n if (JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue\n this.#classifiers.delete(key)\n // Blocks still unresolved lose the evidence their class rested on:\n // under the fresh classifier they read `unknown`. Say so, so a queued\n // or running P2 repair for a former delta block is promoted to P0\n // instead of silently keeping a priority its class no longer has.\n this.#applyClassifierChanges(stream.packetId, stream.catalogTrackName, classifier.retire())\n }\n }\n return next\n }\n\n #blockKeysFor(sbn: number, packetId?: number, catalogTrackName?: string): string[] {\n if (packetId != null && catalogTrackName !== undefined) {\n return [this.#blockKey(sbn, packetId, catalogTrackName)]\n }\n\n // Every key held by a block-keyed lookup set is indexed under its SBN\n // (#indexBlockKey), so this visits only the handful of streams sharing the\n // SBN instead of every retained block. #cleanlyCompletedBlocks is a subset\n // of #completedBlocks and needs no separate check.\n const keys: string[] = []\n const candidates = this.#blockKeysBySbn.get(sbn)\n if (candidates) {\n for (const [key, keyPacketId] of candidates) {\n if (packetId !== undefined && keyPacketId !== packetId) continue\n if (!this.#isBlockKeyHeld(key)) {\n candidates.delete(key)\n continue\n }\n keys.push(key)\n }\n if (candidates.size === 0) this.#blockKeysBySbn.delete(sbn)\n }\n\n if (packetId !== undefined) {\n if (keys.length === 0) return [this.#blockKey(sbn, packetId)]\n return keys.length === 1 ? keys : []\n }\n return keys.length > 0 ? keys : [this.#blockKey(sbn, 0, catalogTrackName)]\n }\n\n // -----------------------------------------------------------------------\n // Accessors\n // -----------------------------------------------------------------------\n\n /** Internal state for diagnostics \u2014 not part of the public stats API. */\n get diagnostics() {\n return {\n timeoutMs: this.#fecTimeoutMs,\n activeBlocks: this.#activeBlocks.size,\n completedBlocks: this.#completedBlocks.size,\n cleanlyCompletedBlocks: this.#cleanlyCompletedBlocks.size,\n pendingCounts: Object.fromEntries(this.#blockSourceCounts),\n fpsDetected: this.#fpsDetected,\n interleaveDepthMs: this.#fecInterleaveDepthMs,\n startupAlignedPackets: this.#packetIdsForStreamKeys(this.#startupAlignedPackets),\n startupPartialBlocks: this.#startupPartialBlocks.size,\n startupRepairReadyPackets: this.#packetIdsForStreamKeys(this.#startupRepairReadyPackets),\n startupRepairFailOpenPackets: this.#packetIdsForStreamKeys(this.#startupRepairFailOpenPackets),\n pendingWorkerCleanups: this.#pendingWorkerCleanups.size,\n decoderUnavailableEvents: this.#decoderUnavailableEvents,\n repairTrackedBlocks: this.#blockRepairRsIds.size,\n multicastSymbolDelivery: this.#multicastSymbolDelivery,\n orderedFrontiers: Object.fromEntries(this.#orderedFrontiers),\n orderedPendingBlocks: [...this.#orderedPendingBlocks.values()]\n .reduce((total, pending) => total + pending.size, 0),\n lifecycleTrace: this.#lifecycleTrace.map((entry) => ({ ...entry })),\n }\n }\n\n get fecStats(): FecStats {\n const s = this.#fecStats\n const affected = s.blocksRecovered + s.blocksFailed\n const total = s.blocksClean + affected\n const tracks = [...this.#fecTrackStats.entries()]\n .sort(([left], [right]) => left - right)\n .map(([packetId, stats]) => {\n const trackAffected = stats.blocksRecovered + stats.blocksFailed\n const trackTotal = stats.blocksClean + trackAffected\n return {\n ...stats,\n packetId,\n blocksComplete: trackTotal,\n k: this.#resolveTrackConfig(packetId).k,\n recoveryRate: trackAffected > 0\n ? (stats.blocksRecovered / trackAffected) * 100\n : 100,\n packetLossRate: trackTotal > 0 ? (trackAffected / trackTotal) * 100 : 0,\n }\n })\n return {\n ...s,\n blocksComplete: total,\n mixedPathBlocks: Array.from(this.#blockPathCounts.values()).filter(\n (block) => block.multicastSymbols > 0 && block.moqSymbols > 0,\n ).length,\n recentBlockPathCounts: Array.from(this.#blockPathCounts.values()).slice(-24),\n k: this.#fecK,\n // True recovery rate: of blocks that HAD loss, how many were recovered?\n recoveryRate: affected > 0 ? (s.blocksRecovered / affected) * 100 : 100,\n packetLossRate: total > 0 ? (affected / total) * 100 : 0,\n tracks,\n workers: [...this.#fecWorkerStats.entries()]\n .sort(([left], [right]) => left - right)\n .map(([packetId, stats]) => ({\n ...stats,\n config: {\n ...stats.config,\n resolution: stats.config.resolution ? { ...stats.config.resolution } : undefined,\n },\n blocks: stats.blocks.map((block) => ({ ...block })),\n decoder: stats.decoder ? { ...stats.decoder } : undefined,\n altaFailedDetails: stats.altaFailedDetails?.map((detail) => ({ ...detail })),\n packetId,\n })),\n repairSymbolSizeMismatches: this.#repairSymbolSizeMismatchCount,\n workerErrors: Object.fromEntries(this.#workerErrorCounts),\n }\n }\n\n /**\n * Observe Worker-backed per-track snapshots without taking ownership of the\n * legacy single-callback slot. Infrastructure diagnostics and a player may\n * therefore consume the same source event without wrapping or replacing one\n * another's callback.\n */\n addFecStatsObserver(\n observer: (packetId: number, stats: FecTrackStats) => void,\n ): () => void {\n this.#fecStatsObservers.add(observer)\n return () => this.#fecStatsObservers.delete(observer)\n }\n\n /** Start a new multicast transport join without discarding track/decoder state. */\n beginMulticastJoin(): void {\n // A new subscription is a new relay: stream order is only defined within\n // one. Blocks still pending from the last join settle once the new\n // stream's own frontier passes them.\n this.#orderedFrontiers.clear()\n this.#startupAlignedPackets.clear()\n this.#startupPartialBlocks.clear()\n this.#startupRequireRepairReady = true\n this.#startupRepairReadyPackets.clear()\n this.#startupRepairBoundaryCounts.clear()\n this.#startupRepairFailOpenPackets.clear()\n }\n\n /** How the multicast leg currently delivers symbols. */\n get multicastSymbolDelivery(): MulticastSymbolDelivery {\n return this.#multicastSymbolDelivery\n }\n\n /**\n * Declare how the multicast leg delivers symbols (see\n * MulticastSymbolDelivery). The multicast provider owns this fact:\n * MulticastManager applies what the window.multicast subscription declares\n * on every join, and a consumer feeding packets itself declares its own leg.\n *\n * Switching to 'ordered-relay' requires every configured track to define\n * its interleave horizon and throws otherwise. Switching back to 'datagram'\n * gives every block awaiting ordered settlement the datagram inactivity\n * deadline from now.\n */\n setMulticastSymbolDelivery(delivery: MulticastSymbolDelivery): void {\n if (!isMulticastSymbolDelivery(delivery)) {\n throw new Error(\n `[FECManager] multicast symbol delivery must be 'datagram' or ` +\n `'ordered-relay', got ${String(delivery)}`,\n )\n }\n if (delivery === this.#multicastSymbolDelivery) return\n // A different delivery path: gaps measured on the old one say nothing\n // about it (and ordered-relay never feeds the estimator).\n this.#deliveryGaps.reset()\n if (delivery === 'ordered-relay') {\n for (const [packetId, config] of this.#trackConfigs) {\n this.#orderedHorizonBlocksFor(packetId, config)\n }\n this.#multicastSymbolDelivery = delivery\n return\n }\n this.#multicastSymbolDelivery = delivery\n const pending = [...this.#orderedPendingBlocks.values()]\n .flatMap((blocks) => [...blocks.values()])\n this.#orderedPendingBlocks.clear()\n this.#orderedFrontiers.clear()\n for (const key of pending) {\n const block = this.#parseBlockKey(key)\n this.noteBlockActivity(block.sbn, block.packetId, block.catalogTrackName)\n }\n }\n\n /** Begin a fresh decode epoch for one physical stream. */\n resetTrack(packetId: number, catalogTrackName: string): void {\n if (!Number.isInteger(packetId) || packetId < 0) {\n throw new Error(`[FECManager] invalid packetId for reset: ${packetId}`)\n }\n if (!catalogTrackName) {\n throw new Error('[FECManager] catalogTrackName is required for track reset')\n }\n const resetMap = <T>(map: Map<string, T>): void => {\n for (const key of map.keys()) {\n if (!this.#belongsToStream(key, packetId, catalogTrackName)) continue\n map.delete(key)\n }\n }\n const resetTimerMap = (map: Map<string, DeadlineTimer | null>): void => {\n for (const [key, timer] of map) {\n if (!this.#belongsToStream(key, packetId, catalogTrackName)) continue\n if (timer?.handle != null) clearTimeout(timer.handle)\n map.delete(key)\n }\n }\n const resetSet = (set: Set<string>): void => {\n for (const key of set) {\n if (this.#belongsToStream(key, packetId, catalogTrackName)) set.delete(key)\n }\n }\n\n resetTimerMap(this.#activeBlocks)\n resetTimerMap(this.#repairDeadlines)\n resetMap(this.#activeBlockDeadlinesMs)\n resetMap(this.#blockWaitDeadlinesMs)\n resetMap(this.#repairHoldUntilMs)\n resetMap(this.#repairDeadlineAtMs)\n // Queued terminal records of this stream no longer match an expiry entry,\n // so the retirement sweep skips them and cannot retire a reused key early.\n resetMap(this.#terminalExpiryMs)\n resetMap(this.#pendingWorkerCleanupClients)\n for (const key of [...this.#pendingWorkerCleanupBounds.keys()]) {\n if (this.#belongsToStream(key, packetId, catalogTrackName)) this.#clearOwedCleanupBound(key)\n }\n resetMap(this.#blockSourceCounts)\n resetMap(this.#blockSourceEsis)\n resetMap(this.#blockAuthoredLengths)\n resetMap(this.#blockRepairRsIds)\n resetMap(this.#blockPathCounts)\n resetMap(this.#blockAnchorsMs)\n resetMap(this.#blockCatalogTrackNames)\n resetMap(this.#syncDecoderBlockOwners)\n resetMap(this.#startupRepairBoundaryCounts)\n resetMap(this.#orderedFrontiers)\n resetMap(this.#orderedPendingBlocks)\n resetSet(this.#pendingWorkerCleanups)\n resetSet(this.#completedBlocks)\n resetSet(this.#cleanlyCompletedBlocks)\n resetSet(this.#repairRequestedBlocks)\n resetSet(this.#workerRecoveredBlocks)\n resetSet(this.#startupAlignedPackets)\n resetSet(this.#startupPartialBlocks)\n resetSet(this.#startupRepairReadyPackets)\n resetSet(this.#startupRepairFailOpenPackets)\n resetSet(this.#seenSymbols)\n resetSet(this.#failedBlocks)\n resetSet(this.#repairedBlocks)\n resetSet(this.#materializedBlocks)\n resetMap(this.#resolvedBlockClasses)\n resetMap(this.#highestSbn)\n resetMap(this.#isoLocators)\n resetMap(this.#sourceAnchors)\n this.#classifiers.delete(this.#streamKey(packetId, catalogTrackName))\n this.#bumpEpoch(packetId)\n for (const [sbn, keys] of this.#blockKeysBySbn) {\n for (const key of keys.keys()) {\n if (this.#belongsToStream(key, packetId, catalogTrackName)) keys.delete(key)\n }\n if (keys.size === 0) this.#blockKeysBySbn.delete(sbn)\n }\n\n const decoderClients = new Set<WasmFecDecoder>()\n for (const [key, entry] of this.#fecDecoders) {\n if (this.#belongsToStream(key, packetId, catalogTrackName)) {\n decoderClients.add(entry.client)\n }\n }\n const clients = new Set<object>()\n for (const client of decoderClients) {\n if (\n typeof client.flush === 'function' &&\n typeof client.reset_stats === 'function'\n ) {\n try {\n client.flush()\n client.reset_stats()\n clients.add(client)\n } catch {\n this.#discardFecDecoderClient(client)\n }\n } else {\n this.#discardFecDecoderClient(client)\n }\n }\n for (const [key, client] of [...this.#workerClients]) {\n if (!this.#belongsToStream(key, packetId, catalogTrackName)) continue\n if (client.disposed === true) {\n // reset() throws on a disposed client; evict it (reporting the\n // decoder unavailable) instead of resetting a Worker that is gone.\n this.#evictDisposedWorker(client)\n continue\n }\n client.reset()\n clients.add(client)\n }\n for (const client of clients) this.#resetDecoderBlockOwners(client, catalogTrackName)\n }\n\n /**\n * Retire one packet ID, drop-only: forget every stream on it (block\n * ledgers, deadlines and their timers, repair holds, terminal records,\n * classifiers, stream geometry), its per-packet configuration and\n * statistics, and take its decoders out of routing.\n *\n * This is the end of the packet ID's streams, not a new epoch of them (that\n * is resetTrack): afterwards isTrackConfigured() is false and nothing about\n * it remains until it is configured again. Nothing is reported, because\n * nothing happened to the media: no block fails, recovers or resolves, no\n * repair resolves, and no decoder becomes \"unavailable\" (a retired stream\n * has no symbols left to decode, so a decoder-unavailable event would\n * describe lost protection that no stream suffers). The epoch still\n * advances, so a gap decision made before the retirement never matches a\n * stream later configured on the same packet ID.\n *\n * Decoder and Worker clients stay caller-owned: each is flushed or reset so\n * it holds no block of the retired streams, then released from routing;\n * dispose it after this returns. Cumulative totals in fecStats keep what\n * the retired streams contributed.\n */\n releasePacket(packetId: number): void {\n if (!Number.isInteger(packetId) || packetId < 0) {\n throw new Error(`[FECManager] invalid packetId for release: ${packetId}`)\n }\n const onPacket = (key: string): boolean => this.#belongsToPacket(key, packetId)\n const dropMap = <T>(map: Map<string, T>): void => {\n for (const key of [...map.keys()]) if (onPacket(key)) map.delete(key)\n }\n const dropTimerMap = (map: Map<string, DeadlineTimer | null>): void => {\n for (const [key, timer] of [...map]) {\n if (!onPacket(key)) continue\n if (timer?.handle != null) clearTimeout(timer.handle)\n map.delete(key)\n }\n }\n const dropSet = (set: Set<string>): void => {\n for (const key of [...set]) if (onPacket(key)) set.delete(key)\n }\n\n dropTimerMap(this.#activeBlocks)\n dropTimerMap(this.#repairDeadlines)\n for (const key of [...this.#pendingWorkerCleanupBounds.keys()]) {\n if (onPacket(key)) this.#clearOwedCleanupBound(key)\n }\n dropMap(this.#activeBlockDeadlinesMs)\n dropMap(this.#blockWaitDeadlinesMs)\n dropMap(this.#repairHoldUntilMs)\n dropMap(this.#repairDeadlineAtMs)\n dropMap(this.#terminalExpiryMs)\n dropMap(this.#pendingWorkerCleanupClients)\n dropMap(this.#blockSourceCounts)\n dropMap(this.#blockSourceEsis)\n dropMap(this.#blockAuthoredLengths)\n dropMap(this.#blockRepairRsIds)\n dropMap(this.#blockPathCounts)\n dropMap(this.#blockAnchorsMs)\n dropMap(this.#blockCatalogTrackNames)\n dropMap(this.#syncDecoderBlockOwners)\n dropMap(this.#startupRepairBoundaryCounts)\n dropMap(this.#orderedFrontiers)\n dropMap(this.#orderedPendingBlocks)\n dropMap(this.#resolvedBlockClasses)\n dropMap(this.#highestSbn)\n dropMap(this.#isoLocators)\n dropMap(this.#sourceAnchors)\n dropMap(this.#classifiers)\n dropMap(this.#streamGeometries)\n dropSet(this.#pendingWorkerCleanups)\n dropSet(this.#completedBlocks)\n dropSet(this.#cleanlyCompletedBlocks)\n dropSet(this.#repairRequestedBlocks)\n dropSet(this.#workerRecoveredBlocks)\n dropSet(this.#startupAlignedPackets)\n dropSet(this.#startupPartialBlocks)\n dropSet(this.#startupRepairReadyPackets)\n dropSet(this.#startupRepairFailOpenPackets)\n dropSet(this.#seenSymbols)\n dropSet(this.#failedBlocks)\n dropSet(this.#repairedBlocks)\n dropSet(this.#materializedBlocks)\n for (const [sbn, keys] of [...this.#blockKeysBySbn]) {\n for (const [key, keyPacketId] of [...keys]) {\n if (keyPacketId === packetId) keys.delete(key)\n }\n if (keys.size === 0) this.#blockKeysBySbn.delete(sbn)\n }\n this.#terminalQueues.delete(packetId)\n\n // Bookkeeping first: a decoder or Worker client whose teardown throws must\n // not leave the packet ID configured or its epoch unbumped, or a gap\n // decision made before the release could match a stream later configured\n // on the same packet ID.\n this.#trackConfigs.delete(packetId)\n this.#fecTrackStats.delete(packetId)\n this.#fecWorkerStats.delete(packetId)\n this.#bumpEpoch(packetId)\n // The retired stream's keyframe cadence no longer widens the gap window.\n // The delivery-gap estimator's per-packet sequence is kept: it measures\n // the link, which the retired packet ID's datagrams still describe while\n // a rendition-stable allow-list keeps delivering them.\n this.#refreshDeliveryGapWindow()\n\n for (const [key, entry] of [...this.#fecDecoders]) {\n if (!onPacket(key)) continue\n this.#fecDecoders.delete(key)\n this.#decoderBlockOwners.delete(entry.client)\n try {\n entry.client.flush?.()\n } catch {\n // The client is leaving routing either way; a decoder that cannot\n // flush holds nothing this manager will ask it for again.\n }\n }\n for (const [key, client] of [...this.#workerClients]) {\n if (!onPacket(key)) continue\n this.#workerClients.delete(key)\n this.#decoderBlockOwners.delete(client)\n this.#releasedWorkerClients.add(client)\n try {\n if (client.disposed !== true) client.reset()\n } catch {\n // Already out of routing, and its late messages are ignored\n // (#releasedWorkerClients); a Worker that cannot reset holds nothing\n // this manager will ask it for again.\n }\n }\n }\n\n /**\n * Record a transport symbol and reject later duplicates of the same\n * packetId/SBN/ESI. `sbn` is the block's SS_Start. A packetId with no\n * configured block profile has no block geometry: its symbol is refused\n * (false) and no block state is created for it.\n */\n feedSymbolFromSource(\n packetId: number,\n sbn: number,\n esi: number,\n _data: Uint8Array,\n source: FecSymbolSource,\n catalogTrackName?: string,\n ): boolean {\n if (!this.#hasBlockProfile(packetId)) return false\n const blockKey = this.#blockKey(sbn, packetId, catalogTrackName)\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n if (source === 'multicast' && this.#multicastSymbolDelivery === 'ordered-relay') {\n // Every multicast symbol is a stream position on an ordered relay, even\n // one refused below as a join partial or a duplicate.\n this.#advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName)\n }\n // Repair ESIs start at the block's own SSB_length (K'), which its locator\n // states once a repair placed it; before that, K is the only bound.\n const sourceSymbols =\n this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? this.kForPacket(packetId)\n const isSourceSymbol = esi >= 0 && esi < sourceSymbols\n const isStartupPartial = this.#startupPartialBlocks.has(blockKey)\n const isReadyMulticastRepair =\n this.#startupRequireRepairReady &&\n source === 'multicast' &&\n !isSourceSymbol &&\n this.#startupRepairReadyPackets.has(streamKey)\n const awaitingMulticastRepair =\n this.#startupRequireRepairReady &&\n source === 'multicast' &&\n !this.#startupRepairReadyPackets.has(streamKey) &&\n !this.#startupRepairFailOpenPackets.has(streamKey)\n if (awaitingMulticastRepair) {\n if (!isSourceSymbol) {\n this.#startupRepairReadyPackets.add(streamKey)\n this.#startupRepairBoundaryCounts.delete(streamKey)\n this.#markStartupPartial(blockKey)\n return false\n }\n\n if (esi === 0) {\n const boundaries = (this.#startupRepairBoundaryCounts.get(streamKey) ?? 0) + 1\n this.#startupRepairBoundaryCounts.set(streamKey, boundaries)\n if (boundaries >= STARTUP_REPAIR_WAIT_SOURCE_BOUNDARIES) {\n this.#startupRepairBoundaryCounts.delete(streamKey)\n this.#startupRepairFailOpenPackets.add(streamKey)\n } else {\n this.#markStartupPartial(blockKey)\n return false\n }\n } else {\n this.#markStartupPartial(blockKey)\n return false\n }\n }\n if (isSourceSymbol && esi === 0) {\n // ESI=0 establishes a complete source boundary for this SBN. Remove\n // only this block: other interleaved SBNs may still be join partials.\n this.#startupPartialBlocks.delete(blockKey)\n this.#startupAlignedPackets.add(streamKey)\n } else if (\n (isStartupPartial && !isReadyMulticastRepair) ||\n (!this.#startupAlignedPackets.has(streamKey) && !isReadyMulticastRepair)\n ) {\n this.#markStartupPartial(blockKey)\n return false\n }\n\n // A completed block remains authoritative after its individual symbol\n // identities age out of the bounded #seenSymbols cache. Without this\n // guard, a delayed copy from the other transport can recreate the block\n // inside a Worker decoder; that orphan never receives the full source and\n // repair set, so cleanup later reports a false failure/green fill.\n if (this.#completedBlocks.has(blockKey)) {\n this.#fecStats.duplicateSymbols++\n this.#statsForPacket(packetId).duplicateSymbols++\n return false\n }\n\n const symbolKey = `${blockKey}:${esi}`\n if (this.#seenSymbols.has(symbolKey)) {\n this.#fecStats.duplicateSymbols++\n this.#statsForPacket(packetId).duplicateSymbols++\n return false\n }\n this.#seenSymbols.add(symbolKey)\n\n const counts = this.#blockPathCounts.get(blockKey) ?? {\n packetId,\n sbn,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n multicastSymbols: 0,\n moqSymbols: 0,\n complete: false,\n }\n // The block's absolute anchor (fecBlockDeadlineMs): the receiver-local\n // time of its first accepted symbol, from which its presentation deadline\n // is measured.\n if (!this.#blockAnchorsMs.has(blockKey)) this.#blockAnchorsMs.set(blockKey, performance.now())\n if (source === 'multicast') counts.multicastSymbols++\n else counts.moqSymbols++\n this.#blockPathCounts.set(blockKey, counts)\n this.#observeBlockCatalogTrackName(blockKey, sbn, packetId, catalogTrackName)\n\n // Keep the newest entries of each bounded cache. Deleting the oldest in\n // place keeps exactly the entries a slice(-n) rebuild would, without\n // copying the whole collection into an array on the page thread.\n if (this.#seenSymbols.size > 4096) {\n dropOldest(this.#seenSymbols, this.#seenSymbols.size - 2048)\n }\n if (this.#blockPathCounts.size > 512) {\n dropOldest(this.#blockPathCounts, this.#blockPathCounts.size - 256)\n }\n if (this.#blockAnchorsMs.size > 512) {\n dropOldest(this.#blockAnchorsMs, this.#blockAnchorsMs.size - 256)\n }\n if (this.#blockCatalogTrackNames.size > 512) {\n for (const key of dropOldest(\n this.#blockCatalogTrackNames,\n this.#blockCatalogTrackNames.size - 256,\n )) {\n this.#unindexBlockKey(key)\n }\n }\n\n return true\n }\n\n #observeBlockCatalogTrackName(\n blockKey: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n if (catalogTrackName === undefined) return\n if (!this.#blockCatalogTrackNames.has(blockKey)) {\n this.#blockCatalogTrackNames.set(blockKey, catalogTrackName)\n this.#indexBlockKey(blockKey, sbn, packetId)\n return\n }\n if (this.#blockCatalogTrackNames.get(blockKey) !== catalogTrackName) {\n this.#blockCatalogTrackNames.set(blockKey, null)\n }\n }\n\n /** Exact catalog identity retained for async recovery, if unambiguous. */\n catalogTrackNameForBlock(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): string | undefined {\n if (catalogTrackName !== undefined) {\n return this.#blockCatalogTrackNames.get(\n this.#blockKey(sbn, packetId, catalogTrackName),\n ) ?? undefined\n }\n const names = new Set<string>()\n for (const [key, name] of this.#blockCatalogTrackNames) {\n const block = this.#parseBlockKey(key)\n if (block.packetId === packetId && block.sbn === sbn && name !== null) names.add(name)\n }\n return names.size === 1 ? [...names][0] : undefined\n }\n\n #markStartupPartial(blockKey: string): void {\n this.#startupPartialBlocks.add(blockKey)\n if (this.#startupPartialBlocks.size > 128) {\n this.#startupPartialBlocks = new Set(\n Array.from(this.#startupPartialBlocks).slice(-64),\n )\n }\n }\n\n /** Per-block state snapshot for FecBlockGrid visualization.\n * Returns the most recent blocks (active + recently completed). */\n getBlockStates(): Array<{\n sbn: number;\n packetId?: number;\n state: 'collecting' | 'complete' | 'failed';\n sourceCount: number;\n k: number;\n progress: number;\n }> {\n const blocks: Array<{ sbn: number; packetId?: number; state: 'collecting' | 'complete' | 'failed'; sourceCount: number; k: number; progress: number }> = [];\n // Active blocks (still collecting symbols)\n for (const [key] of this.#activeBlocks) {\n const { packetId, sbn } = this.#parseBlockKey(key);\n const count = this.#blockSourceCounts.get(key) ?? 0;\n const k = this.#blockAuthoredLengths.get(key) ?? this.kForPacket(packetId);\n blocks.push({ sbn, packetId, state: 'collecting', sourceCount: count, k, progress: k > 0 ? Math.min(count / k, 1) : 0 });\n }\n // Recently completed blocks (last 16)\n const completed = [...this.#completedBlocks].slice(-16);\n for (const key of completed) {\n const { packetId, sbn } = this.#parseBlockKey(key);\n if (!this.#activeBlocks.has(key)) {\n const k = this.kForPacket(packetId);\n blocks.push({ sbn, packetId, state: 'complete', sourceCount: k, k, progress: 1 });\n }\n }\n blocks.sort((a, b) => a.sbn - b.sbn || (a.packetId ?? 0) - (b.packetId ?? 0));\n return blocks.slice(-24); // cap at 24 most recent\n }\n\n /** The raw WASM decoder for stats access (first registered decoder) */\n get wasmDecoder(): WasmFecDecoder | null {\n return this.#fecDecoders.values().next().value?.client ?? null\n }\n\n get k(): number { return this.#fecK }\n kForPacket(packetId: number): number { return this.#resolveTrackConfig(packetId).k }\n /** FEC symbol size in bytes. */\n get symbolSize(): number { return this.#fecSymbolSize }\n symbolSizeForPacket(packetId: number): number { return this.#resolveTrackConfig(packetId).symbolSize }\n /** FEC block span in milliseconds (0 = no interleaving). */\n get interleaveDepthMs(): number { return this.#fecInterleaveDepthMs }\n interleaveDepthMsForPacket(packetId: number): number { return this.#resolveTrackConfig(packetId).interleaveDepthMs }\n /**\n * Record one multicast MMTP packet arrival (every packet of the leg, FEC\n * symbol or not) for the delivery-gap measurement. `packetSequenceNumber`\n * is the MMTP packet_sequence_number, which increments by one per packet of\n * a packet_id: it is what separates a stall from a loss (see\n * DeliveryGapEstimator). Datagram delivery only.\n */\n noteMulticastArrival(packetId: number, packetSequenceNumber: number): void {\n if (this.#multicastSymbolDelivery !== 'datagram') return\n this.#deliveryGaps.noteArrival(performance.now(), packetId, packetSequenceNumber)\n }\n /**\n * Resolved per-track multicast late-symbol wait in milliseconds: the span\n * plus the larger of the catalog tolerance and the measured delivery gap.\n */\n timeoutMsForPacket(packetId: number): number {\n return this.#multicastWaitMs(this.#resolveTrackConfig(packetId))\n }\n /**\n * Largest multicast delivery gap measured over the current window (ms), and\n * that window (undefined: held since the last reset). Diagnostics.\n */\n multicastDeliveryGap(): { peakGapMs: number; windowMs: number | undefined } {\n return {\n peakGapMs: this.#deliveryGaps.peakGapMs(performance.now()),\n windowMs: this.#deliveryGaps.windowMs,\n }\n }\n /**\n * Receiver-local time of a block's first accepted symbol, or undefined for\n * a block no symbol reached (one materialized because a later SBN arrived).\n */\n blockAnchorAtMs(sbn: number, packetId: number, catalogTrackName?: string): number | undefined {\n return this.#blockAnchorsMs.get(this.#blockKey(sbn, packetId, catalogTrackName))\n }\n /** Absolute fail deadline of a still-open block, or undefined. */\n blockDeadlineAtMs(sbn: number, packetId: number, catalogTrackName?: string): number | undefined {\n return this.#activeBlockDeadlinesMs.get(this.#blockKey(sbn, packetId, catalogTrackName))\n }\n /**\n * Keep a block open until `untilAtMs` while a repair FETCH the scheduler\n * admitted is in flight. Never shortens the multicast wait already armed;\n * while set, the hold is the block's whole deadline, so no later symbol\n * extends it. Returns false when the block is no longer open (resolved, or\n * not yet tracked).\n */\n holdBlockForRepair(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n untilAtMs: number,\n ): boolean {\n if (!Number.isFinite(untilAtMs)) {\n throw new Error(`[FECManager] repair hold must be a finite time (got ${untilAtMs})`)\n }\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n if (!this.#activeBlocks.has(key)) return false\n const previous = this.#repairHoldUntilMs.get(key)\n if (previous !== undefined && previous >= untilAtMs) return true\n // The hold absorbs the wait already armed, so it never shortens it.\n this.#repairHoldUntilMs.set(\n key,\n Math.max(untilAtMs, this.#blockWaitDeadlinesMs.get(key) ?? -Infinity),\n )\n this.#applyBlockDeadline(key, sbn, packetId, catalogTrackName)\n return true\n }\n /**\n * Drop a block's repair hold: its FETCH ended without delivering. The block\n * falls back to its multicast wait, which may already have passed.\n */\n releaseRepairHold(sbn: number, packetId: number, catalogTrackName?: string): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n if (!this.#repairHoldUntilMs.delete(key) || !this.#activeBlocks.has(key)) return\n this.#applyBlockDeadline(key, sbn, packetId, catalogTrackName)\n }\n /** Resolved per-track D-28 repair request deadline in milliseconds. */\n repairDeadlineMsForPacket(packetId: number): number {\n return this.#repairDeadlineMs(this.#resolveTrackConfig(packetId))\n }\n /**\n * One block lifetime: the longest a block stays open after its last symbol\n * (inactivity fail bound plus D-28 repair grace).\n */\n blockLifetimeMsForPacket(packetId: number): number {\n return this.#blockLifetimeMs(this.#resolveTrackConfig(packetId))\n }\n /**\n * How long a terminal (clean, recovered or failed) block stays authoritative,\n * for late symbols and for the in-order release gate. It is also the gate's\n * hold ceiling (MmtpRouter's holdLimitMs): a retired block reads back as\n * untracked, which the gate counts as lost, so no frame may still be waiting\n * on a block that can retire. The window is one interleave depth for a gap's\n * last block to finish arriving, one block lifetime to fail it, and one more\n * for an owed Worker cleanup that can keep a failing block pending. Every\n * term comes from the track's catalog geometry; there is no separate cap.\n */\n terminalRetentionMsForPacket(packetId: number): number {\n const config = this.#resolveTrackConfig(packetId)\n return config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config)\n }\n /** Remaining fetch budget for a specific repair-requested block. */\n repairFetchTimeoutMsForBlock(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): number {\n const deadlineMs = this.#activeBlockDeadlinesMs.get(\n this.#blockKey(sbn, packetId, catalogTrackName),\n )\n if (deadlineMs !== undefined) {\n return Math.max(1, Math.ceil(deadlineMs - performance.now()))\n }\n const trackConfig = this.#resolveTrackConfig(packetId)\n return Math.max(1, trackConfig.timeoutMs - this.#repairDeadlineMs(trackConfig))\n }\n\n /** True once configureTrack/setFecDecoder/setFecWorker configured this packetId. */\n // -----------------------------------------------------------------------\n // iso-ssbg1-v1 block location (BLO-35597 iso:2/iso:3)\n // -----------------------------------------------------------------------\n\n /** The configured `fec.blockProfile` of a packetId (undefined until configured). */\n blockProfileForPacket(packetId: number): FecBlockProfile | undefined {\n return this.#trackConfigs.get(packetId)?.blockProfile\n }\n\n /**\n * Whether a packetId has a configured block geometry. iso-ssbg1-v1 is the\n * only profile, so every configured track has it; a packetId never\n * configured (or released) has none and gets no FEC block tracking.\n */\n #hasBlockProfile(packetId: number): boolean {\n return this.#trackConfigs.get(packetId)?.blockProfile !== undefined\n }\n\n #isoLocatorFor(packetId: number, catalogTrackName?: string): IsoBlockLocator<IsoHeldSource> {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n let locator = this.#isoLocators.get(streamKey)\n if (!locator) {\n locator = new IsoBlockLocator<IsoHeldSource>(this.kForPacket(packetId), this.#isoRetentionBlocks(packetId))\n this.#isoLocators.set(streamKey, locator)\n }\n return locator\n }\n\n /**\n * The locator a call is about to grow, with its retention brought up to\n * date: the horizon follows catalog timing, which a later configuration\n * (this track's or another video track's keyframe cadence) can change.\n */\n #growingIsoLocatorFor(packetId: number, catalogTrackName?: string): IsoBlockLocator<IsoHeldSource> {\n const locator = this.#isoLocatorFor(packetId, catalogTrackName)\n locator.retentionBlocks = this.#isoRetentionBlocks(packetId)\n return locator\n }\n\n #dropIsoLocators(packetId: number): void {\n for (const key of [...this.#isoLocators.keys()]) {\n if (this.#belongsToPacket(key, packetId)) this.#isoLocators.delete(key)\n }\n }\n\n /**\n * The iso-ssbg1-v1 locator horizon in blocks: every block the manager can\n * still hold terminal state for (`terminalRetentionMsForPacket`) over the\n * block span, plus the block being filled, measured with the shared\n * lower-bound metric so a retained block is never forgotten early. Every\n * term is the packet's catalog geometry, which configuration validated\n * (#upsertTrackConfig), so this never falls back: an underivable horizon\n * throws rather than leave the locator without retention.\n */\n #isoRetentionBlocks(packetId: number): number {\n const config = this.#trackConfigs.get(packetId)\n if (config?.blockProfile === undefined || !(config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS)) {\n throw new Error(\n `[FECManager] packetId=${packetId} has no iso-ssbg1-v1 block span to derive block-location retention from`,\n )\n }\n const blocks = Math.ceil(this.terminalRetentionMsForPacket(packetId) / config.interleaveDepthMs) + 1\n if (!Number.isSafeInteger(blocks) || blocks <= 0) {\n throw new Error(`[FECManager] packetId=${packetId} iso-ssbg1-v1 retention of ${blocks} blocks is not derivable`)\n }\n return blocks\n }\n\n /** Whether an iso-ssbg1-v1 protected packet fits one ssbg_mode1 symbol (len + 2 <= T). */\n isoSourceFits(packetId: number, packetLength: number): boolean {\n return ssbg1FitsSymbol(packetLength, this.symbolSizeForPacket(packetId))\n }\n\n /**\n * Locate an iso-ssbg1-v1 source by range membership in a block a repair\n * already stated. Null while its block is unknown: hold it (holdIsoSource).\n */\n locateIsoSource(\n packetId: number,\n catalogTrackName: string | undefined,\n ssId: number,\n ): { sbn: number; esi: number } | null {\n const located = this.#isoLocatorFor(packetId, catalogTrackName).locate(ssId)\n return located === null ? null : { sbn: located.block.start, esi: located.esi }\n }\n\n /**\n * Hold a first-arrival iso-ssbg1-v1 source whose block no repair has stated\n * yet. Returns false for a later copy of an SS_ID already held.\n */\n holdIsoSource(\n packetId: number,\n catalogTrackName: string | undefined,\n ssId: number,\n source: IsoHeldSource,\n ): boolean {\n const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName)\n const evictedBefore = locator.heldEvicted\n const held = locator.hold(ssId, source)\n const evicted = locator.heldEvicted - evictedBefore\n if (evicted > 0) {\n const before = this.#isoHeldEvictedCount\n this.#isoHeldEvictedCount += evicted\n if (before < 5 || Math.floor(before / 100) !== Math.floor(this.#isoHeldEvictedCount / 100)) {\n console.warn(\n `[FECManager] packetId=${packetId} iso-ssbg1-v1 held-source cap (${locator.heldCapacity}) ` +\n `evicted ${evicted} unplaced source(s); SS_IDs are not consecutive (C.5.2)`,\n )\n }\n }\n return held\n }\n\n /**\n * Apply an iso-ssbg1-v1 repair symbol's `(SS_Start, SSB_length)`: states the\n * block, and hands back the held sources it (and any repair-less span in\n * front of it) now places. The caller feeds them as first-arrival sources,\n * then the repair symbol, the order they had on the wire. A repair that\n * contradicts a known block is refused (counted) and must be dropped.\n */\n placeIsoRepair(\n packetId: number,\n catalogTrackName: string | undefined,\n ssStart: number,\n ssbLength: number,\n ): IsoRepairPlacement<IsoHeldSource> {\n const placement = this.#growingIsoLocatorFor(packetId, catalogTrackName).placeRepair(ssStart, ssbLength)\n if (placement.status !== 'placed') {\n this.#isoRepairRefusedCount++\n if (this.#isoRepairRefusedCount <= 5 || this.#isoRepairRefusedCount % 100 === 0) {\n console.warn(`[FECManager] packetId=${packetId} iso-ssbg1-v1 repair refused (${placement.status}): ${placement.reason}`)\n }\n return placement\n }\n // A repair-less span has no repair to state its length, so it is known\n // here: its sources make it clean exactly when all of them arrived. A\n // stated block learns its SSB_length from its own repair symbol, fed\n // after its placed sources exactly as on the wire (feedRepairSymbol).\n if (placement.span !== undefined) {\n const key = this.#blockKey(placement.span.block.start, packetId, catalogTrackName)\n if (!this.#blockAuthoredLengths.has(key)) this.#blockAuthoredLengths.set(key, placement.span.block.length)\n }\n\n if (placement.span !== undefined && placement.span.placed.length === 0) {\n // Every SS_ID of the span vanished, repair included: it resolves\n // through its deadline like any vanished block.\n this.#materializeBlock(placement.span.block.start, packetId, catalogTrackName, true)\n }\n return placement\n }\n\n /**\n * Resolution of the SS_IDs strictly between two released frames' SS_IDs on\n * an iso-ssbg1-v1 stream, for in-order frame release: null while a block\n * that could hold them is pending or not yet stated by a repair, else the\n * starts of the blocks that failed or vanished.\n *\n * `untracked` reads as the release gate reads it (MmtpRouter\n * #createFecReleaseGate): a block holding either released frame's SS_ID\n * (`fromSsId` or `toSsId`) delivered that frame, so untracked there means it\n * resolved and retired; a block starting after the stream's frontier\n * (`highestSbn`, an SS_Start) may still be heard from; a block at or behind the\n * frontier that no symbol of this manager ever reached vanished, and its\n * media is lost.\n */\n isoSpanResolution(\n packetId: number,\n catalogTrackName: string | undefined,\n fromSsId: number,\n toSsId: number,\n ): readonly number[] | null {\n if (!serial32Precedes(fromSsId, toSsId) || serial32Distance(fromSsId, toSsId) <= 1) return []\n const locator = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))\n if (!locator || !locator.coversBetween(fromSsId, toSsId)) return null\n const highest = this.highestSbn(packetId, catalogTrackName)\n const lost: number[] = []\n for (const block of locator.blocksBetween(fromSsId, toSsId)) {\n const resolution = this.blockResolution(block.start, packetId, catalogTrackName)\n if (resolution === 'pending') return null\n if (resolution === 'failed') {\n lost.push(block.start)\n } else if (resolution === 'untracked') {\n if (\n isoBlockContains(block.start, block.length, fromSsId) ||\n isoBlockContains(block.start, block.length, toSsId)\n ) {\n continue\n }\n if (highest === undefined || serial32Precedes(highest, block.start)) return null\n lost.push(block.start)\n }\n }\n return lost\n }\n\n /**\n * iso-ssbg1-v1 block location: repair symbols it refused, oversize sources,\n * the sources held now for a block no repair has stated yet, and the held\n * sources the capacity cap evicted (cumulative), and the sources a\n * synchronous decoder held unplaced and retired at its horizon (cumulative).\n */\n get isoDiagnostics(): {\n repairRefused: number\n sourceOversize: number\n heldSources: number\n heldEvicted: number\n decoderUnplacedRetired: number\n decoderUnplacedRetireFailed: number\n } {\n let heldSources = 0\n for (const locator of this.#isoLocators.values()) heldSources += locator.heldCount\n return {\n repairRefused: this.#isoRepairRefusedCount,\n sourceOversize: this.#isoSourceOversizeCount,\n heldSources,\n heldEvicted: this.#isoHeldEvictedCount,\n decoderUnplacedRetired: this.#isoDecoderUnplacedRetired,\n decoderUnplacedRetireFailed: this.#isoDecoderUnplacedRetireFailed,\n }\n }\n\n isTrackConfigured(packetId: number): boolean {\n return this.#trackConfigs.has(packetId)\n }\n\n /** Catalog/reset epoch of a packet's streams; gap decisions carry it. */\n epochFor(packetId: number): number {\n return this.#packetEpochs.get(packetId) ?? 0\n }\n\n /** Configured media kind of a packetId, when known. */\n trackKindFor(packetId: number): TrackKind | undefined {\n return this.#trackConfigs.get(packetId)?.trackKind\n }\n\n /** Configured codec of a packetId, when known. */\n codecFor(packetId: number): string | undefined {\n return this.#trackConfigs.get(packetId)?.codec\n }\n\n /**\n * Block classifier geometry of one stream: its own stream-scoped geometry,\n * else the packet's (fail-closed when absent, or when a sibling stream of\n * the packet has its own geometry and this one has none).\n */\n geometryFor(packetId: number, catalogTrackName?: string): BlockClassifierGeometry {\n return this.#geometryFor(packetId, catalogTrackName)\n }\n\n /** Current classifier mode of a stream. */\n classifierModeFor(\n packetId: number,\n catalogTrackName?: string,\n ): { mode: ClassifierMode; reason: ClassifierReason | null } {\n const classifier = this.#classifierFor(packetId, catalogTrackName)\n return { mode: classifier.mode, reason: classifier.reason }\n }\n\n /**\n * Whether an MPU boundary still bounds this stream's RAPs (see\n * `BlockClassifier.mpuBoundsRaps`). Gap decisions drop MPU evidence once\n * it does not. A stream with no classifier yet has shown nothing against it.\n */\n mpuBoundsRapsFor(packetId: number, catalogTrackName?: string): boolean {\n return this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))?.mpuBoundsRaps ?? true\n }\n\n /** A block's class now, or frozen at its resolution. */\n blockClassFor(sbn: number, packetId: number, catalogTrackName?: string): BlockClass {\n const frozen = this.#resolvedBlockClasses.get(this.#blockKey(sbn, packetId, catalogTrackName))\n if (frozen !== undefined) return frozen\n return this.#peekClass(sbn, packetId, catalogTrackName)\n }\n\n /**\n * Lifecycle resolution of one block for in-order frame release: `pending`\n * while it can still complete, `complete`/`failed` once terminal, and\n * `untracked` when no symbol of it was ever accepted.\n */\n blockResolution(sbn: number, packetId: number, catalogTrackName?: string): FecBlockResolution {\n return this.#blockResolutionFor(this.#blockKey(sbn, packetId, catalogTrackName))\n }\n\n /** Highest SBN any symbol of this stream carried in the current epoch. */\n highestSbn(packetId: number, catalogTrackName?: string): number | undefined {\n return this.#highestSbn.get(this.#streamKey(packetId, catalogTrackName))?.sbn\n }\n\n /**\n * Record a RAP frame released in order, for the classifier's RAP-spacing\n * invariant (consecutive MPUs exactly one keyframe interval apart).\n */\n noteReleasedRap(\n packetId: number,\n catalogTrackName: string | undefined,\n mpu: number,\n presentationTime: bigint,\n mediaTimescale: number,\n ): void {\n if (!this.#trackConfigs.has(packetId)) return\n this.#applyClassifierChanges(\n packetId,\n catalogTrackName,\n this.#classifierFor(packetId, catalogTrackName).noteReleasedRap(\n mpu,\n presentationTime,\n mediaTimescale,\n ),\n )\n }\n\n // -----------------------------------------------------------------------\n // Configuration\n // -----------------------------------------------------------------------\n\n /** Configure FEC parameters. */\n configure(config: FECManagerConfig): void {\n if (config.k != null) this.#fecK = config.k\n if (config.symbolSize != null && config.symbolSize > 0) this.#fecSymbolSize = config.symbolSize\n if (config.interleaveDepthMs != null) this.#fecInterleaveDepthMs = config.interleaveDepthMs\n if (config.fps != null && Number.isFinite(config.fps) && config.fps > 0) {\n this.#fecFps = config.fps\n }\n // interleaveDepthMs is in milliseconds per spec \u00A75.1.\n // Valid range: 33ms (interactive/gaming) to 10000ms (broadcast ATSC 3.0).\n // Warn if value looks like frame count instead of ms (common misconfiguration).\n if (config.interleaveDepthMs != null && config.interleaveDepthMs > 0 && config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {\n console.warn(`[FECManager] interleaveDepthMs=${config.interleaveDepthMs} looks like frame count, not milliseconds (\u00A75.1 requires ms). Expected range: ${MIN_INTERLEAVE_DEPTH_MS}-10000ms.`)\n }\n if (config.timeoutMs != null && config.timeoutMs > 0) {\n this.#fecTimeoutMs = config.timeoutMs\n } else if (\n config.interleaveDepthMs != null &&\n config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS\n ) {\n this.#fecTimeoutMs = computeFecDeliveryWindowMs({\n interleaveDepthMs: config.interleaveDepthMs,\n sourceSymbols: this.#fecK,\n symbolSize: this.#fecSymbolSize,\n mediaUnitsPerSecond: config.fps,\n })\n }\n // A shared multicast parser receives every packetId. Keep existing track\n // deadlines at least as wide as the selected video delivery envelope so a\n // short audio interleave cannot expire during browser/AMT scheduling gaps.\n for (const [packetId, track] of this.#trackConfigs) {\n if (track.timeoutMs < this.#fecTimeoutMs) {\n this.#trackConfigs.set(packetId, { ...track, timeoutMs: this.#fecTimeoutMs })\n }\n }\n }\n\n /**\n * Configure FEC parameters for one packetId without changing legacy\n * defaults. With `catalogTrackName`, `config.geometry` is that stream's\n * own classifier geometry (ABR rungs that share a packetId each get\n * theirs); K, T, timing, kind and codec stay per packetId.\n */\n configureTrack(packetId: number, config: FECManagerConfig, catalogTrackName?: string): void {\n this.#upsertTrackConfig(packetId, config, catalogTrackName)\n }\n\n /** Register a FEC decoder for a specific packetId (track). */\n setFecDecoder(\n packetId: number,\n client: WasmFecDecoder,\n k: number,\n config?: FECManagerConfig,\n catalogTrackName?: string,\n ): void {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n this.#assertExclusiveDecoderClient(\n streamKey,\n client,\n [...this.#fecDecoders].map(([key, entry]) => [key, entry.client] as const),\n 'decoder',\n )\n const trackConfig = this.#upsertTrackConfig(packetId, { ...config, k }, catalogTrackName)\n this.#fecDecoders.set(streamKey, { client, k: trackConfig.k })\n }\n\n /**\n * Register a Worker-backed FEC decoder for a specific packetId (track).\n *\n * Worker-backed tracks decode asynchronously \u2014 feedSourceSymbol/feedRepairSymbol\n * return null immediately, and recovered frames arrive via the onBlockRecovered callback.\n *\n * Takes precedence over sync WASM decoders for the same packetId.\n */\n setFecWorker(\n packetId: number,\n client: FecWorkerClient,\n config: FECManagerConfig,\n catalogTrackName?: string,\n ): void {\n const k = config?.k\n if (k === undefined || !Number.isFinite(k) || k <= 0) {\n throw new Error(`[FECManager] packetId=${packetId} Worker requires an explicit positive k`)\n }\n const symbolSize = config.symbolSize\n if (symbolSize === undefined || !Number.isFinite(symbolSize) || symbolSize <= 0) {\n throw new Error(\n `[FECManager] packetId=${packetId} Worker requires an explicit positive symbolSize`,\n )\n }\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n this.#assertExclusiveDecoderClient(\n streamKey,\n client,\n this.#workerClients,\n 'worker',\n )\n // Every Worker leaves routing through #retireWorker, which fails the\n // blocks it still owed and reports the stream's decoder unavailable.\n // Overwriting a registration would skip that path and strand those\n // blocks, so a second Worker under a routed stream is refused.\n const incumbent = this.#workerClients.get(streamKey)\n if (incumbent !== undefined && incumbent !== client) {\n throw new Error(\n `[FECManager] stream ${streamKey} already routes to a Worker; ` +\n `call clearFecWorker before registering another`,\n )\n }\n if (config) this.#upsertTrackConfig(packetId, config, catalogTrackName)\n const workerK = this.#resolveTrackConfig(packetId).k\n this.#workerClients.set(streamKey, client)\n this.#releasedWorkerClients.delete(client)\n\n client.onFrame((data: ArrayBuffer, meta: FrameMeta) => {\n if (this.#releasedWorkerClients.has(client)) return\n if (meta.recovered) {\n // Recovered Worker events represent one exact source-symbol position.\n // Block-completion accounting needs that exact ESI, and counting an\n // ESI-less event as recovered would report healthy FEC while dropping\n // the media. Enforce the Worker contract before any recovery metric.\n // The Worker names the recovered block's authored SSB_length; ESIs\n // at or past it are not source symbols of the block and are never\n // emitted.\n if (\n !Number.isInteger(meta.ssbLength) ||\n meta.ssbLength! <= 0 ||\n meta.ssbLength! > workerK\n ) {\n const kind: FecWorkerErrorCode = 'recovered-symbol-ssb-length-invalid'\n const seen = this.#workerErrorCounts.get(kind) ?? 0\n this.#workerErrorCounts.set(kind, seen + 1)\n if (seen < WORKER_ERROR_LOG_CAP) {\n console.warn(\n `[FECManager] Dropping recovered symbol with invalid SSB_length: pid=${packetId} sbn=${meta.sbn} ssbLength=${String(meta.ssbLength)}`,\n )\n }\n return\n }\n if (\n !Number.isInteger(meta.esi) ||\n meta.esi! < 0 ||\n meta.esi! >= meta.ssbLength!\n ) {\n const kind = 'recovered-symbol-missing-esi'\n const seen = this.#workerErrorCounts.get(kind) ?? 0\n this.#workerErrorCounts.set(kind, seen + 1)\n if (seen < WORKER_ERROR_LOG_CAP) {\n console.warn(\n `[FECManager] Dropping recovered symbol without valid ESI: pid=${packetId} sbn=${meta.sbn} esi=${String(meta.esi)} ssbLength=${String(meta.ssbLength)}`,\n )\n }\n return\n }\n const esi = meta.esi!\n\n // Suppress duplicate routing when the worker fires recovered=true\n // for a block ALREADY CLEANLY COMPLETED by K source symbols.\n //\n // BLO-3323 L15: predicate was previously `#completedBlocks.has(sbn)`,\n // but `#completedBlocks` is populated by 5 paths (clean K-arrival,\n // failed timeout, every worker frame emit via markBlockCompleted below,\n // external markBlockCompleted, and cancelBlockTimeout). The first\n // worker frame for a block adds the SBN, so subsequent recovered=true\n // emits for the same block were always suppressed \u2192 blocksRecovered\n // stayed at 0 even when recovery actually fired. Switching to the\n // narrower #cleanlyCompletedBlocks set (populated only by trackBlock\n // when count >= K) preserves the original dedup intent (BLO-3035\n // layer 4 \u2014 don't double-route a block already received via source)\n // while letting genuinely-recovered blocks bump the counter.\n const recoveredCatalogTrackName = catalogTrackName\n ?? this.#decoderBlockCatalogTrackName(client, meta.sbn)\n if (!this.#claimDecoderBlock(client, meta.sbn, recoveredCatalogTrackName)) return\n const blockKey = this.#blockKey(meta.sbn, packetId, recoveredCatalogTrackName)\n const cleanlyCompleted = this.#cleanlyCompletedBlocks.has(blockKey)\n if (!cleanlyCompleted) {\n if (!this.#workerRecoveredBlocks.has(blockKey)) {\n this.#traceLifecycle(\n 'recovered',\n meta.sbn,\n packetId,\n recoveredCatalogTrackName,\n )\n this.#workerRecoveredBlocks.add(blockKey)\n this.#fecStats.blocksRecovered++\n this.#statsForPacket(packetId).blocksRecovered++\n if (this.#workerRecoveredBlocks.size > 1000) {\n dropOldest(this.#workerRecoveredBlocks, 500)\n }\n this.#noteBlockRepaired(blockKey, meta.sbn, packetId, recoveredCatalogTrackName)\n }\n\n // RaptorQ returns the full K-symbol block, not only erased symbols.\n // The direct path already parsed every source ESI in #seenSymbols;\n // routing those symbols again injects duplicate MFU fragments and can\n // leave a real FI=3 tail followed by duplicate FI=2 fragments.\n const symbolKey = `${blockKey}:${esi}`\n if (!this.#seenSymbols.has(symbolKey)) {\n this.#seenSymbols.add(symbolKey)\n this.onBlockRecovered?.(\n meta.sbn,\n packetId,\n data,\n meta,\n recoveredCatalogTrackName,\n )\n }\n }\n\n // The worker emits recovered source symbols in ESI order. Complete the\n // block only after its final authored slot so assemblers receive every\n // missing symbol before their completion callback flushes buffered media.\n if (\n esi >= meta.ssbLength! - 1 &&\n !this.#completedBlocks.has(blockKey)\n ) {\n this.cancelBlockTimeout(meta.sbn, packetId, recoveredCatalogTrackName)\n if (recoveredCatalogTrackName === undefined) {\n this.onBlockComplete?.(meta.sbn, packetId)\n } else {\n this.onBlockComplete?.(\n meta.sbn,\n packetId,\n recoveredCatalogTrackName,\n )\n }\n }\n } else {\n const completedCatalogTrackName = catalogTrackName\n ?? this.#decoderBlockCatalogTrackName(client, meta.sbn)\n this.markBlockCompleted(meta.sbn, packetId, completedCatalogTrackName)\n }\n })\n\n client.onSnapshot((stats: FecTrackStats) => {\n if (this.#releasedWorkerClients.has(client)) return\n this.#fecWorkerStats.set(packetId, stats)\n try {\n this.onFecStatsUpdate?.(packetId, stats)\n } catch (error) {\n console.warn('[FECManager] FEC stats callback failed:', error)\n }\n for (const observer of this.#fecStatsObservers) {\n try {\n observer(packetId, stats)\n } catch (error) {\n console.warn('[FECManager] FEC stats observer failed:', error)\n }\n }\n })\n\n client.onCleanupComplete((sbn: number) => {\n if (this.#releasedWorkerClients.has(client)) return\n const cleanupKeys = [...this.#pendingWorkerCleanups].filter((key) => {\n const block = this.#parseBlockKey(key)\n return block.packetId === packetId &&\n block.sbn === sbn &&\n this.#pendingWorkerCleanupClients.get(key) === client\n })\n for (const key of cleanupKeys) this.#settleOwedCleanup(key, 'cleanup-ack')\n })\n\n client.onError((msg: string, code?: FecWorkerErrorCode) => {\n const requestedKind = code ?? msg\n const kind =\n this.#workerErrorCounts.has(requestedKind) ||\n this.#workerErrorCounts.size < WORKER_ERROR_KIND_CAP - 1\n ? requestedKind\n : WORKER_ERROR_OVERFLOW_KIND\n const seen = this.#workerErrorCounts.get(kind) ?? 0\n this.#workerErrorCounts.set(kind, seen + 1)\n if (seen < WORKER_ERROR_LOG_CAP) {\n console.warn('[FECManager] Worker error:', msg)\n } else if (seen === WORKER_ERROR_LOG_CAP) {\n console.warn(\n `[FECManager] Worker error (suppressing further copies of ${kind}): ${msg}`,\n )\n }\n })\n }\n\n /**\n * Remove a stream's Worker decoder from routing.\n *\n * Returns false when no Worker is registered under that exact stream key.\n * On success every cleanup acknowledgement still owed by the client is\n * resolved by failing its block now (a Worker that is going away cannot\n * post it), the client's block ownership is released, and\n * onDecoderUnavailable fires once with reason 'cleared' and the decoder\n * that remains for the stream ('sync' or 'none'). Call this before\n * disposing the client; a client disposed while still registered is\n * evicted on its next symbol or reset with reason 'disposed'.\n */\n clearFecWorker(packetId: number, catalogTrackName?: string): boolean {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n const client = this.#workerClients.get(streamKey)\n if (!client) return false\n this.#retireWorker(streamKey, client, 'cleared')\n return true\n }\n\n /**\n * Take a Worker client out of routing under one stream key, fail the blocks\n * whose completion it still owed, and report the stream's decoder\n * unavailable together with whatever still decodes the stream.\n */\n #retireWorker(streamKey: string, client: FecWorkerClient, reason: FecDecoderUnavailableReason): void {\n this.#workerClients.delete(streamKey)\n this.#releaseWorkerClient(client)\n this.#decoderUnavailableEvents++\n const stream = this.#parseStreamKey(streamKey)\n const fallbackDecoder: FecFallbackDecoder =\n this.#decoderForPacket(stream.packetId, stream.catalogTrackName) ? 'sync' : 'none'\n this.onDecoderUnavailable?.({\n packetId: stream.packetId,\n ...(stream.catalogTrackName === undefined ? {} : { catalogTrackName: stream.catalogTrackName }),\n reason,\n fallbackDecoder,\n })\n }\n\n /**\n * Fail every block whose Worker cleanup acknowledgement is owed by `client`\n * and drop its block ownership, so no block waits on a message the Worker\n * will never post.\n */\n #releaseWorkerClient(client: FecWorkerClient): void {\n const owed = [...this.#pendingWorkerCleanupClients]\n .filter(([, owner]) => owner === client)\n .map(([key]) => key)\n for (const key of owed) this.#settleOwedCleanup(key, 'decoder-unavailable')\n this.#decoderBlockOwners.delete(client)\n }\n\n /**\n * Bound the wait for one Worker cleanup acknowledgement by the block's\n * catalog-derived terminal lifetime. When it elapses with the cleanup still\n * owed to the same client, the block fails as if the ack had arrived: its\n * fail deadline already passed, so failing it is the decision that deadline\n * made. A late ack then finds nothing owed and is a no-op.\n */\n #boundOwedCleanup(key: string, packetId: number, client: FecWorkerClient): void {\n this.#clearOwedCleanupBound(key)\n const bound = {\n handle: setTimeout(() => {\n if (this.#pendingWorkerCleanupBounds.get(key) !== bound) return\n this.#pendingWorkerCleanupBounds.delete(key)\n if (\n !this.#pendingWorkerCleanups.has(key) ||\n this.#pendingWorkerCleanupClients.get(key) !== client\n ) {\n return\n }\n this.#settleOwedCleanup(key, 'cleanup-ack-timeout')\n }, Math.ceil(this.#liveBlockLifetimeMs(this.#resolveTrackConfig(packetId)))),\n }\n this.#pendingWorkerCleanupBounds.set(key, bound)\n }\n\n #clearOwedCleanupBound(key: string): void {\n const bound = this.#pendingWorkerCleanupBounds.get(key)\n if (!bound) return\n clearTimeout(bound.handle)\n this.#pendingWorkerCleanupBounds.delete(key)\n }\n\n /**\n * Resolve one owed Worker cleanup: drop the pending entry and fail the block\n * unless it already completed. Shared by the cleanup acknowledgement and by\n * Worker retirement, so the two paths cannot drift in their bookkeeping.\n */\n #settleOwedCleanup(\n key: string,\n event: 'cleanup-ack' | 'cleanup-ack-timeout' | 'decoder-unavailable',\n ): void {\n this.#clearOwedCleanupBound(key)\n this.#pendingWorkerCleanups.delete(key)\n this.#pendingWorkerCleanupClients.delete(key)\n if (this.#completedBlocks.has(key)) return\n const block = this.#parseBlockKey(key)\n this.#traceLifecycle(event, block.sbn, block.packetId, block.catalogTrackName)\n this.#failBlock(block.sbn, block.packetId, block.catalogTrackName)\n }\n\n /** Evict a disposed client under every stream key that still maps to it. */\n #evictDisposedWorker(client: FecWorkerClient): void {\n for (const [key, registered] of [...this.#workerClients]) {\n if (registered !== client) continue\n const stream = this.#parseStreamKey(key)\n const track = stream.catalogTrackName === undefined ? '' : ` track=${stream.catalogTrackName}`\n console.warn(\n `[FECManager] packetId=${stream.packetId}${track} Worker decoder was disposed while still registered; evicting it`,\n )\n this.#retireWorker(key, client, 'disposed')\n }\n }\n\n /** Called by player when framerate is known (from decoder output). Refines FEC block timeout. */\n setFramerate(fps: number): void {\n if (this.#fpsDetected || fps <= 0) return\n this.#fpsDetected = true\n this.#fecFps = fps\n // Only a valid \u00A75.1 ms interleave refines the timeout; a sub-floor value is\n // a raw frame count and would collapse it (see MIN_INTERLEAVE_DEPTH_MS).\n if (this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {\n this.#fecTimeoutMs = computeFecDeliveryWindowMs({\n interleaveDepthMs: this.#fecInterleaveDepthMs,\n sourceSymbols: this.#fecK,\n symbolSize: this.#fecSymbolSize,\n mediaUnitsPerSecond: fps,\n })\n }\n for (const [packetId, config] of this.#trackConfigs) {\n if (config.fps !== undefined || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue\n this.#trackConfigs.set(packetId, {\n ...config,\n fps,\n timeoutMs: Math.max(\n this.#fecTimeoutMs,\n computeFecDeliveryWindowMs({\n interleaveDepthMs: config.interleaveDepthMs,\n sourceSymbols: config.k,\n symbolSize: config.symbolSize,\n mediaUnitsPerSecond: fps,\n }),\n ),\n })\n }\n }\n\n // -----------------------------------------------------------------------\n // Symbol feeding (codec-agnostic)\n // -----------------------------------------------------------------------\n\n /**\n * Feed a source symbol to the WASM FEC decoder.\n *\n * Does NOT track block source counts or fire lifecycle events \u2014 call\n * `trackBlock()` separately BEFORE this method (MmtpRouter does this).\n *\n * @returns Recovered block data if WASM completed the block, else null.\n * Caller is responsible for routing recovered data and updating\n * block completion state via `markBlockCompleted()`.\n */\n feedSourceSymbol(\n packetId: number,\n ssId: number,\n symbolData: Uint8Array,\n alta?: { auth: Uint8Array; payloadEnd: number },\n catalogTrackName?: string,\n ): Uint8Array | null {\n this.#fecStats.sourcePackets++\n this.#statsForPacket(packetId).sourcePackets++\n // A packetId with no configured block profile has no block geometry and\n // no decoder (setFecDecoder/setFecWorker configure the track): nothing to\n // decode.\n if (!this.#hasBlockProfile(packetId)) return null\n // The decoder builds the ssbg_mode1 symbol from the exact protected\n // packet, so the packet travels unpadded. Its block must already be\n // stated (MmtpRouter holds a source until a repair places it).\n const locator = this.#isoLocatorFor(packetId, catalogTrackName)\n const located = locator.locate(ssId)\n if (located === null) {\n throw new Error(\n `[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; ` +\n 'hold it until a repair places it (placeIsoRepair)',\n )\n }\n locator.observe(ssId)\n if (!ssbg1FitsSymbol(symbolData.byteLength, this.symbolSizeForPacket(packetId))) {\n this.#isoSourceOversizeCount++\n return null\n }\n const sbn = located.block.start\n const blockKey = this.#blockKey(sbn, packetId, catalogTrackName)\n const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey)\n\n // Worker-backed track: dispatch async, return null (recovery via onFrame callback)\n const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName)\n if (workerClient && !syncBlockOwner) {\n if (!this.#claimDecoderBlock(workerClient, sbn, catalogTrackName)) return null\n // Transfer an ArrayBuffer nothing else references. The source travels\n // unpadded, and a caller's buffer may be a view into a larger packet,\n // so it is copied first (T-03-06).\n const buf = symbolData.buffer.slice(\n symbolData.byteOffset,\n symbolData.byteOffset + symbolData.byteLength,\n ) as ArrayBuffer\n // Pass the flat SS_ID through to the worker. The WASM decoder places\n // the source in its block by SS_ID (ISO 23008-1 \u00A7C.5.2); the WASM add_source\n // signature is (ss_id, data, timestamp) \u2014 pre-computing sbn/esi on this\n // side produces wrong WASM argument alignment and silently breaks\n // block assembly.\n if (alta) {\n // Copy auth into a fresh ArrayBuffer to transfer zero-copy. The\n // underlying buffer of the parsed trailer view belongs to the wire\n // packet, which may be transferred separately or reused.\n const authBuf = alta.auth.buffer.slice(\n alta.auth.byteOffset,\n alta.auth.byteOffset + alta.auth.byteLength,\n ) as ArrayBuffer\n workerClient.feedSource(ssId, buf, performance.now(), {\n auth: authBuf,\n payloadEnd: alta.payloadEnd,\n })\n } else {\n workerClient.feedSource(ssId, buf, performance.now())\n }\n return null\n }\n\n // Synchronous WASM path.\n const entry = this.#decoderForPacket(packetId, catalogTrackName)\n const client = syncBlockOwner ?? entry?.client\n if (!client) return null\n if (!this.#claimDecoderBlock(client, sbn, catalogTrackName)) return null\n this.#syncDecoderBlockOwners.set(blockKey, client)\n\n let recovered: Uint8Array | null = null\n try {\n recovered = client.add_source(ssId, symbolData, performance.now())\n } catch {\n // WASM RefCell panic \u2014 decoder instance shared with multicast repair path\n }\n this.#retireIsoUnplaced(client, packetId, catalogTrackName)\n if (recovered || this.#completedBlocks.has(blockKey)) {\n this.#syncDecoderBlockOwners.delete(blockKey)\n }\n\n return recovered\n }\n\n /**\n * Feed a repair symbol to the WASM FEC decoder.\n *\n * @returns Recovered block data if repair completed the block, else null.\n * Caller is responsible for routing recovered data and updating\n * block completion state via `markBlockCompleted()`.\n */\n feedRepairSymbol(\n packetId: number,\n ssStart: number,\n ssbLength: number,\n rsId: number,\n repairData: Uint8Array,\n catalogTrackName?: string,\n ): Uint8Array | null {\n this.#fecStats.repairPackets++\n this.#statsForPacket(packetId).repairPackets++\n // A packetId with no configured block profile has no block geometry: its\n // repair names no block this manager can track or decode.\n if (!this.#hasBlockProfile(packetId)) return null\n const symbolSize = this.symbolSizeForPacket(packetId)\n if (symbolSize > 0 && repairData.byteLength !== symbolSize) {\n this.#repairSymbolSizeMismatchCount++\n if (this.#repairSymbolSizeMismatchCount <= 5 || this.#repairSymbolSizeMismatchCount % 100 === 0) {\n console.warn(\n `[FECManager] Dropping repair symbol with invalid size: got ${repairData.byteLength}B, expected ${symbolSize}B (packetId=${packetId}, ssStart=${ssStart}, ssbLength=${ssbLength}, rsId=${rsId})`,\n )\n }\n return null\n }\n const fecData = repairData\n\n // ISO/IEC 23008-1 C.5.3: the block is [SS_Start, SS_Start + SSB_length),\n // K' = SSB_length, and the catalog K is only its maximum. The block is\n // keyed by its SS_Start. MmtpRouter placed the block before feeding its\n // repair (placeIsoRepair). Keep the wire tuple unchanged when calling the\n // decoder below.\n const k = this.kForPacket(packetId)\n const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart)\n if (ssbLength <= 0 || ssbLength > k || block === undefined || block.span || block.length !== ssbLength) {\n return null\n }\n const sbn = ssStart\n this.#noteSbnSeen(sbn, packetId, catalogTrackName, 'repair')\n\n // Repair symbols are block activity: refresh the inactivity fail bound so\n // a block being actively repaired over MoQ is not failed mid-recovery.\n // The multicast in-band path already does this at the router; this covers\n // the MoQ backfill path, which bypasses the router's packet accounting.\n this.noteBlockActivity(sbn, packetId, catalogTrackName)\n const blockKey = this.#blockKey(sbn, packetId, catalogTrackName)\n if (this.#completedBlocks.has(blockKey)) return null\n if (!this.#noteAuthoredLength(blockKey, sbn, packetId, ssbLength, catalogTrackName)) {\n return null\n }\n let repairIds = this.#blockRepairRsIds.get(blockKey)\n if (!repairIds) {\n repairIds = new Set()\n this.#blockRepairRsIds.set(blockKey, repairIds)\n }\n repairIds.add(rsId)\n if (this.#repairRequestedBlocks.has(blockKey)) {\n this.#traceLifecycle('repair-arrived', sbn, packetId, catalogTrackName, rsId)\n }\n\n // Every authored source symbol already arrived: the block is clean now\n // that its SSB_length is known. The decoder still receives the repair so\n // it retires its own copy of the block, but whatever it returns is the\n // source the direct path already routed, not a recovery.\n const cleanOnArrival =\n (this.#blockSourceCounts.get(blockKey) ?? 0) >= ssbLength\n const recovered = this.#dispatchRepairSymbol(\n blockKey,\n sbn,\n packetId,\n ssStart,\n ssbLength,\n rsId,\n fecData,\n catalogTrackName,\n )\n if (cleanOnArrival) {\n this.#completeCleanBlock(blockKey, sbn, packetId, catalogTrackName)\n return null\n }\n return recovered\n }\n\n /**\n * Record a block's authored SSB_length from a repair symbol. Returns false,\n * leaving the block unchanged, when the symbol contradicts the block: a\n * different SSB_length than an earlier repair symbol declared is dropped,\n * and an SSB_length that places an already-received source ESI at or past\n * the block's end fails the block, since its geometry can no longer be\n * trusted.\n */\n #noteAuthoredLength(\n blockKey: string,\n sbn: number,\n packetId: number,\n ssbLength: number,\n catalogTrackName?: string,\n ): boolean {\n const known = this.#blockAuthoredLengths.get(blockKey)\n if (known === ssbLength) return true\n if (known !== undefined) {\n this.#ssbLengthConflictCount++\n if (this.#ssbLengthConflictCount <= 5 || this.#ssbLengthConflictCount % 100 === 0) {\n console.warn(\n `[FECManager] Dropping repair symbol whose SSB_length ${ssbLength} conflicts with ${known} for packetId=${packetId} sbn=${sbn}`,\n )\n }\n return false\n }\n const sourceEsis = this.#blockSourceEsis.get(blockKey)\n if (sourceEsis && [...sourceEsis].some((esi) => esi >= ssbLength)) {\n this.#failBlock(sbn, packetId, catalogTrackName)\n return false\n }\n this.#blockAuthoredLengths.set(blockKey, ssbLength)\n return true\n }\n\n #dispatchRepairSymbol(\n blockKey: string,\n sbn: number,\n packetId: number,\n ssStart: number,\n ssbLength: number,\n rsId: number,\n fecData: Uint8Array,\n catalogTrackName?: string,\n ): Uint8Array | null {\n const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey)\n // Worker-backed track: dispatch async, return null (recovery via onFrame callback)\n const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName)\n if (workerClient && !syncBlockOwner) {\n if (!this.#claimDecoderBlock(workerClient, sbn, catalogTrackName)) return null\n // Create clean ArrayBuffer copy (T-03-06)\n const buf = fecData.buffer.slice(\n fecData.byteOffset,\n fecData.byteOffset + fecData.byteLength,\n ) as ArrayBuffer\n // Forward the wire-level Repair FEC Payload ID (ISO 23008-1 \u00A7C.5.3)\n // directly. WASM add_repair signature is\n // (ss_start, ssb_length, rs_id, data, timestamp) \u2014 pre-computing\n // (sbn, esi) on this side misaligns WASM args and produces 0% recovery.\n workerClient.feedRepair(ssStart, ssbLength, rsId, buf, performance.now())\n return null\n }\n\n // Synchronous WASM path.\n const entry = this.#decoderForPacket(packetId, catalogTrackName)\n const client = syncBlockOwner ?? entry?.client\n if (!client) return null\n if (!this.#claimDecoderBlock(client, sbn, catalogTrackName)) return null\n this.#syncDecoderBlockOwners.set(blockKey, client)\n\n let recovered: Uint8Array | null = null\n try {\n recovered = client.add_repair(ssStart, ssbLength, rsId, fecData, performance.now())\n } catch {\n // WASM add_repair failure \u2014 block will timeout and be marked failed\n }\n this.#retireIsoUnplaced(client, packetId, catalogTrackName)\n if (recovered) this.#syncDecoderBlockOwners.delete(blockKey)\n\n return recovered\n }\n\n #routeSyncCleanup(\n decoder: WasmFecDecoder,\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n ): void {\n // Retire the block from the decoder. Concealment of a lost block is the\n // sink's job (hold last good frame), so nothing is routed from here.\n try {\n decoder.cleanup_blocks_detailed(Uint32Array.of(sbn))\n } catch (error) {\n console.warn(`[FECManager] Scoped cleanup failed for SBN ${sbn}:`, error)\n }\n this.#retireIsoUnplaced(decoder, packetId, catalogTrackName)\n }\n\n /**\n * iso-ssbg1-v1: a placed source the decoder took before its block's repair\n * (fed in wire order, sources first) waits unplaced in the decoder; one\n * whose repair never reaches it, such as a source of a repair-less span,\n * would stay there. Retire those by the decoder's horizon behind the newest\n * SS_ID the stream's locator has seen. Run after every feed that grows the\n * decoder and when a block is retired, as the fec-worker sweeps its own\n * pool: a stream that loses only repair symbols never expires a block, so\n * a sweep tied to expiry alone would never run there.\n */\n #retireIsoUnplaced(\n decoder: WasmFecDecoder,\n packetId: number,\n catalogTrackName: string | undefined,\n ): void {\n if (!this.#hasBlockProfile(packetId)) return\n const newest = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newest\n if (newest === undefined) return\n try {\n this.#isoDecoderUnplacedRetired += decoder.retire_unplaced(newest)\n } catch (error) {\n this.#isoDecoderUnplacedRetireFailed++\n const failed = this.#isoDecoderUnplacedRetireFailed\n if (failed <= 5 || failed % 100 === 0) {\n console.warn(\n `[FECManager] iso-ssbg1-v1 unplaced retirement failed for packetId=${packetId} (${failed} total):`,\n error,\n )\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Block lifecycle\n // -----------------------------------------------------------------------\n\n /**\n * Track a first-arrival network source symbol for an FEC block. Refreshes\n * the inactivity deadline on each symbol, records the symbol's in-band\n * classification evidence, and marks the block clean when all of its\n * authored source symbols arrived: the SSB_length a repair symbol declared,\n * otherwise K.\n *\n * Throws for a packetId that was never configured, so a classified block\n * never reads the manager-global K or timeout.\n */\n trackBlock(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n esi: number,\n evidence: SourceSymbolEvidence,\n ): void {\n const trackConfig = this.#trackConfigs.get(packetId)\n if (trackConfig === undefined) {\n throw new Error(\n `[FECManager] trackBlock for packetId=${packetId}, which was never configured; ` +\n 'configure the track from its catalog (configureTrack/setFecWorker/setFecDecoder) first',\n )\n }\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n const k = trackConfig.k\n\n // Skip blocks already recovered \u2014 late source symbols must not restart timeouts\n if (this.#completedBlocks.has(key)) return\n\n this.#noteSbnSeen(sbn, packetId, catalogTrackName, 'source', evidence.packetSequenceNumber)\n\n // A source ESI at or past the block's authored SSB_length contradicts it;\n // its geometry can no longer be trusted, so fail it closed.\n const authoredLength = this.#blockAuthoredLengths.get(key) ?? k\n if (Number.isInteger(esi) && esi! >= authoredLength) {\n this.#failBlock(sbn, packetId, catalogTrackName)\n return\n }\n\n this.#applyClassifierChanges(\n packetId,\n catalogTrackName,\n this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence),\n )\n\n // Count source symbols per block\n const count = (this.#blockSourceCounts.get(key) ?? 0) + 1\n this.#blockSourceCounts.set(key, count)\n if (count === 1) this.#indexBlockKey(key, sbn, packetId)\n if (Number.isInteger(esi) && esi! >= 0 && esi! < k) {\n let sourceEsis = this.#blockSourceEsis.get(key)\n if (!sourceEsis) {\n sourceEsis = new Set()\n this.#blockSourceEsis.set(key, sourceEsis)\n }\n sourceEsis.add(esi!)\n }\n if (count === 1) {\n if (catalogTrackName === undefined) {\n this.onBlockStarted?.(sbn, packetId)\n } else {\n this.onBlockStarted?.(sbn, packetId, catalogTrackName)\n }\n }\n\n // When every authored source symbol arrived (SSB_length once a repair\n // symbol declared it, otherwise K), the block is clean: no loss, no FEC.\n if (k > 0 && count >= authoredLength) {\n this.#completeCleanBlock(key, sbn, packetId, catalogTrackName)\n return\n }\n\n this.noteBlockActivity(sbn, packetId, catalogTrackName, true)\n\n // Source ESI is intentionally not a repair trigger. Multicast delivery is\n // unordered, so K-1 may precede an interior symbol in a clean block. The\n // progress-refreshed source horizon above is the earliest loss signal.\n }\n\n #completeCleanBlock(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n this.#fecStats.blocksClean++\n this.#statsForPacket(packetId).blocksClean++\n this.cancelBlockTimeout(sbn, packetId, catalogTrackName)\n this.#blockSourceCounts.delete(key)\n this.#blockSourceEsis.delete(key)\n this.#blockAuthoredLengths.delete(key)\n this.#blockRepairRsIds.delete(key)\n // BLO-3323 L15: track which SBNs reached K source-symbol arrivals so the\n // worker recovery suppression can distinguish \"block completed by source\"\n // (legitimately suppress duplicate recovery) from \"block touched by any\n // prior emit / timeout / external mark\" (do NOT suppress recovery).\n // #markTerminal retires both memberships together (BLO-36599).\n this.#markTerminal(key, sbn, packetId, true)\n this.#markPathBlockComplete(sbn, packetId, catalogTrackName)\n if (catalogTrackName === undefined) {\n this.onBlockComplete?.(sbn, packetId)\n } else {\n this.onBlockComplete?.(sbn, packetId, catalogTrackName)\n }\n // MmtpRouter feeds the K-th source immediately after this callback.\n // Defer cleanup so that synchronous feed selects the same decoder; this\n // also clears blocks whose K-th packet was too large for decoder input.\n queueMicrotask(() => this.#syncDecoderBlockOwners.delete(key))\n }\n\n /** Refresh deadlines after accepted block activity. */\n noteBlockActivity(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n sourceProgress = false,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n\n // Late symbols must not resurrect a block that already completed or failed.\n if (this.#completedBlocks.has(key)) return\n // The worker cleanup command is already queued after all symbols that made\n // the inactivity deadline. Symbols arriving after that boundary are late\n // and must not arm a second timer before the acknowledgement resolves it.\n if (this.#pendingWorkerCleanups.has(key)) return\n\n const repairRequested = this.#repairRequestedBlocks.has(key)\n // An ordered relay already decided a requested block by stream order; its\n // fate is the scheduler's hold (or its absence), never a re-hold.\n if (repairRequested && this.#settlesByOrder(key)) return\n\n // An ordered relay's pauses say nothing about loss; stream order settles\n // the block (see MulticastSymbolDelivery). No timer is armed for it.\n if (this.#settlesByOrder(key)) {\n this.#holdForOrderedSettlement(key, sbn, packetId, catalogTrackName)\n return\n }\n\n const trackConfig = this.#resolveTrackConfig(packetId)\n const timeoutMs = this.#multicastWaitMs(trackConfig)\n // #computeTimeout never yields <= 0 (computeFecDeliveryWindowMs throws on\n // a non-positive interleave or K). Returning here would leave a block\n // with source counted but no fail deadline, 'pending' forever, so a\n // violated invariant must fail loudly instead.\n if (!(timeoutMs > 0)) {\n throw new Error(`[FECManager] packetId=${packetId} has no positive fail bound (timeoutMs=${timeoutMs})`)\n }\n\n // D-28: accepted source progress refreshes the pre-request horizon. There\n // are at most K such arrivals; repair traffic never refreshes this timer.\n // A requested block's accepted symbols keep extending its multicast wait\n // (a late symbol can still complete it) but never re-arm the request: at\n // most K+P accepted symbols can move the wait, so the block stays bounded.\n const repairDeadline = this.#repairDeadlines.get(key)\n if (\n this.onRepairNeeded &&\n !repairRequested &&\n (repairDeadline === undefined || sourceProgress)\n ) {\n this.#armRepairDeadline(\n key,\n sbn,\n packetId,\n catalogTrackName,\n this.#repairDeadlineMs(trackConfig),\n )\n }\n\n this.#armBlockTimeout(sbn, packetId, catalogTrackName, timeoutMs)\n }\n\n #failBlock(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n this.#traceLifecycle('failed', sbn, packetId, catalogTrackName)\n this.#clearRepairDeadline(key)\n this.#clearBlockTimer(key)\n const blockClass = this.#retireBlockClass(key, sbn, packetId, catalogTrackName)\n if (this.#repairRequestedBlocks.delete(key)) {\n this.onRepairResolved?.(sbn, packetId, 'failed', catalogTrackName, null)\n }\n this.#blockSourceCounts.delete(key)\n this.#blockSourceEsis.delete(key)\n this.#blockAuthoredLengths.delete(key)\n this.#blockRepairRsIds.delete(key)\n this.#syncDecoderBlockOwners.delete(key)\n this.#markTerminal(key, sbn, packetId, false)\n this.#failedBlocks.add(key)\n this.#markPathBlockComplete(sbn, packetId, catalogTrackName)\n this.#fecStats.blocksFailed++\n this.#statsForPacket(packetId).blocksFailed++\n this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass)\n this.onBlockResolved?.(sbn, packetId, catalogTrackName)\n }\n\n /**\n * Resolve a block as completed (clean, recovered, or externally marked).\n * A block that had a D-28 repair outstanding counts as repaired: it was\n * short of its source at the repair deadline and completed anyway.\n */\n #resolveCompleted(key: string): void {\n const block = this.#parseBlockKey(key)\n this.#markTerminal(key, block.sbn, block.packetId, false)\n this.#clearOwedCleanupBound(key)\n this.#pendingWorkerCleanups.delete(key)\n this.#pendingWorkerCleanupClients.delete(key)\n this.#markPathBlockComplete(block.sbn, block.packetId, block.catalogTrackName)\n this.#clearRepairDeadline(key)\n if (this.#repairRequestedBlocks.delete(key)) {\n this.#noteBlockRepaired(key, block.sbn, block.packetId, block.catalogTrackName)\n this.onRepairResolved?.(\n block.sbn,\n block.packetId,\n 'completed',\n block.catalogTrackName,\n this.#viaFor(key),\n )\n }\n this.#retireBlockClass(key, block.sbn, block.packetId, block.catalogTrackName)\n this.#blockSourceCounts.delete(key)\n this.#blockSourceEsis.delete(key)\n this.#blockAuthoredLengths.delete(key)\n this.#blockRepairRsIds.delete(key)\n this.#clearBlockTimer(key)\n this.onBlockResolved?.(block.sbn, block.packetId, block.catalogTrackName)\n }\n\n /** Check whether a block has already been completed (clean or recovered). */\n isBlockCompleted(sbn: number, packetId?: number, catalogTrackName?: string): boolean {\n return this.#blockKeysFor(sbn, packetId, catalogTrackName).some((key) => this.#completedBlocks.has(key))\n }\n\n /** Mark a block as completed externally (e.g., after routing recovered data). */\n markBlockCompleted(sbn: number, packetId?: number, catalogTrackName?: string): void {\n for (const key of this.#blockKeysFor(sbn, packetId, catalogTrackName)) {\n this.#resolveCompleted(key)\n }\n }\n\n /**\n * Increment blocksRecovered (called by MmtpRouter after routing a\n * synchronous recovery). With the block's coordinates the recovery is also\n * reported through onBlockRepaired, once per block.\n */\n countRecovery(packetId?: number, sbn?: number, catalogTrackName?: string): void {\n this.#fecStats.blocksRecovered++\n if (packetId != null) this.#statsForPacket(packetId).blocksRecovered++\n if (packetId != null && sbn != null) {\n this.#noteBlockRepaired(\n this.#blockKey(sbn, packetId, catalogTrackName),\n sbn,\n packetId,\n catalogTrackName,\n )\n }\n }\n\n /**\n * Strict delivery provenance for one FEC block.\n *\n * A recovered symbol is a function of every symbol that entered its block,\n * so it can only be attributed to multicast when NO symbol in that block\n * arrived over MoQ. Repair symbols routinely arrive over reliable QUIC\n * (`MmtpRouter.feedRepairPacket`), so a block that recovered with MoQ help\n * produced bytes that never crossed a multicast socket.\n *\n * An untracked block returns 'unknown' rather than a guess: absence of\n * evidence is not evidence of multicast.\n */\n blockDeliveryProvenance(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): MmtpDeliveryProvenance {\n const counts = this.blockPathCount(sbn, packetId, catalogTrackName)\n if (!counts) return 'unknown'\n if (counts.moqSymbols > 0) return 'mixed'\n if (counts.multicastSymbols > 0) return 'multicast'\n return 'unknown'\n }\n\n /** Per-path symbol counts for one block (D-28 gate logging), if tracked. */\n blockPathCount(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): FecBlockPathCount | null {\n return this.#blockPathCounts.get(\n this.#blockKey(sbn, packetId, catalogTrackName),\n ) ?? null\n }\n\n /** Cancel block timeout (block completed via clean reception or FEC recovery). */\n cancelBlockTimeout(sbn: number, packetId?: number, catalogTrackName?: string): void {\n for (const key of this.#blockKeysFor(sbn, packetId, catalogTrackName)) {\n this.#resolveCompleted(key)\n }\n }\n\n // -----------------------------------------------------------------------\n // Cleanup\n // -----------------------------------------------------------------------\n\n dispose(): void {\n for (const timer of this.#activeBlocks.values()) {\n if (timer.handle != null) clearTimeout(timer.handle)\n }\n this.#activeBlocks.clear()\n this.#activeBlockDeadlinesMs.clear()\n this.#blockWaitDeadlinesMs.clear()\n this.#repairHoldUntilMs.clear()\n this.#deliveryGaps.reset()\n this.#pendingWorkerCleanups.clear()\n this.#pendingWorkerCleanupClients.clear()\n for (const bound of this.#pendingWorkerCleanupBounds.values()) clearTimeout(bound.handle)\n this.#pendingWorkerCleanupBounds.clear()\n for (const timer of this.#repairDeadlines.values()) {\n if (timer?.handle != null) clearTimeout(timer.handle)\n }\n this.#repairDeadlines.clear()\n this.#repairDeadlineAtMs.clear()\n this.#terminalExpiryMs.clear()\n this.#terminalQueues.clear()\n this.#blockKeysBySbn.clear()\n this.#blockSourceCounts.clear()\n this.#blockSourceEsis.clear()\n this.#blockAuthoredLengths.clear()\n this.#blockRepairRsIds.clear()\n this.#completedBlocks.clear()\n this.#cleanlyCompletedBlocks.clear()\n this.#repairRequestedBlocks.clear()\n this.#workerRecoveredBlocks.clear()\n this.#syncDecoderBlockOwners.clear()\n this.#startupAlignedPackets.clear()\n this.#startupPartialBlocks.clear()\n this.#startupRequireRepairReady = false\n this.#startupRepairReadyPackets.clear()\n this.#startupRepairBoundaryCounts.clear()\n this.#startupRepairFailOpenPackets.clear()\n this.#orderedFrontiers.clear()\n this.#orderedPendingBlocks.clear()\n this.#seenSymbols.clear()\n this.#blockPathCounts.clear()\n this.#blockAnchorsMs.clear()\n this.#blockCatalogTrackNames.clear()\n this.#classifiers.clear()\n this.#streamGeometries.clear()\n this.#failedBlocks.clear()\n this.#resolvedBlockClasses.clear()\n this.#repairedBlocks.clear()\n this.#materializedBlocks.clear()\n this.#highestSbn.clear()\n this.#isoLocators.clear()\n this.#sourceAnchors.clear()\n this.#trackConfigs.clear()\n this.#fecDecoders.clear()\n // Dispose Worker clients (T-03-07: prevent Worker leak)\n for (const client of new Set(this.#workerClients.values())) client.dispose()\n this.#workerClients.clear()\n }\n}\n", "/**\n * iso-ssbg1-v1 block location for one FEC stream (BLO-35597 iso:2/iso:3/iso:5).\n *\n * Under ISO/IEC 23008-1 C.5.2 the SS_ID increments by one per FEC source\n * packet and wraps at 2^32, and a block is the SS_ID range\n * `[SS_Start, SS_Start + SSB_length)` its Repair FEC Payload ID states (C.5.3).\n * Block lengths vary, so `floor(SS_ID / K)` locates nothing: a source symbol\n * belongs to a block only once a repair symbol has stated that block's range.\n * A source that arrives first is held, unplaced, and handed back when a repair\n * places it. Nothing here guesses a boundary.\n *\n * Coverage grows contiguously from the first block a repair states. When a\n * repair states a block that starts after the end of known coverage, the SS_IDs\n * between are one or more blocks whose every repair symbol was lost: they are\n * kept as one repair-less span, which completes clean once all of its SS_IDs\n * arrived (C.5.2 makes a gap in consecutive SS_IDs directly visible) and\n * otherwise resolves through its catalog-derived deadline. That is the only\n * way a block without repair completes: on the next block's SS_Start, never on\n * a count of K source symbols.\n */\nimport {\n isoBlockContains,\n isoBlockEnd,\n isoBlocksBehindLowerBound,\n serial32Add,\n serial32Distance,\n serial32Precedes,\n} from '@blockcast/mmt-base'\n\n/** One block (or repair-less span) the locator knows the exact range of. */\nexport interface IsoBlockRange {\n readonly start: number\n readonly length: number\n /** True for SS_IDs between two stated blocks whose own repair never came. */\n readonly span: boolean\n}\n\n/** A held source symbol handed back with its block coordinate. */\nexport interface IsoPlacedSource<T> {\n readonly ssId: number\n readonly esi: number\n readonly payload: T\n}\n\n/** What one repair symbol's `(SS_Start, SSB_length)` did to the locator. */\nexport type IsoRepairPlacement<T> =\n | {\n readonly status: 'placed'\n readonly block: IsoBlockRange\n /** Sources this repair placed into `block`, in SS_ID order. */\n readonly placed: readonly IsoPlacedSource<T>[]\n /** The repair-less span this repair closed in front of `block`, if any. */\n readonly span?: { readonly block: IsoBlockRange; readonly placed: readonly IsoPlacedSource<T>[] }\n }\n /** Not a block this stream can use; the repair symbol is dropped. */\n | { readonly status: 'conflict' | 'overlap' | 'stale'; readonly reason: string }\n\n/**\n * Retention: the locator is never without one. Every call that can grow it\n * (`hold`, `placeRepair`) retires, behind the position it just saw, every\n * block, span and held source more than `retentionBlocks` blocks back, by the\n * one ISO horizon metric (`isoBlocksBehindLowerBound`). A block is measured\n * from its last SS_ID, so it stays while any SS_ID of it is inside the\n * horizon: a repair-less span has no length bound, and one measured from its\n * start could be retired the moment it is created, before the sources it\n * placed are fed. Held sources are additionally capped at the SS_IDs the\n * horizon spans (`(retentionBlocks + 1) * kMax`): consecutive SS_IDs (C.5.2)\n * cannot hold more inside it, so the cap only ever bites on a stream whose\n * SS_IDs are not consecutive, and then the oldest arrival goes first.\n */\nexport class IsoBlockLocator<T> {\n /** Known blocks and spans, in serial order of their start. */\n readonly #blocks: IsoBlockRange[] = []\n /** Held first-arrival sources whose block no repair has stated yet. */\n readonly #held = new Map<number, T>()\n /** End of contiguous coverage: the SS_ID the next unknown block starts at. */\n #coverageEnd: number | undefined\n readonly #kMax: number\n #retentionBlocks: number\n #heldEvicted = 0\n #newest: number | undefined\n\n /**\n * @param kMax the catalog maximum K (`sourceSymbols`), the bound on SSB_length.\n * @param retentionBlocks how many blocks behind the newest position the\n * locator keeps, derived by its owner from catalog timing; required, so\n * no locator exists without retention.\n */\n constructor(kMax: number, retentionBlocks: number) {\n if (!Number.isSafeInteger(kMax) || kMax <= 0) {\n throw new Error(`[IsoBlockLocator] maximum K must be a positive integer (got ${kMax})`)\n }\n this.#kMax = kMax\n this.#retentionBlocks = IsoBlockLocator.#checkedRetention(retentionBlocks)\n }\n\n static #checkedRetention(retentionBlocks: number): number {\n if (!Number.isSafeInteger(retentionBlocks) || retentionBlocks <= 0) {\n throw new Error(\n `[IsoBlockLocator] retention must be a positive integer number of blocks (got ${retentionBlocks})`,\n )\n }\n return retentionBlocks\n }\n\n get kMax(): number {\n return this.#kMax\n }\n\n /** Blocks behind the newest position the locator keeps. */\n get retentionBlocks(): number {\n return this.#retentionBlocks\n }\n\n /** Update the retention when the catalog timing it derives from changes. */\n set retentionBlocks(retentionBlocks: number) {\n this.#retentionBlocks = IsoBlockLocator.#checkedRetention(retentionBlocks)\n }\n\n /** The most sources held at once: the SS_IDs the retention horizon spans. */\n get heldCapacity(): number {\n return (this.#retentionBlocks + 1) * this.#kMax\n }\n\n /**\n * The newest position the stream has reached (RFC 1982 order): the newest\n * SS_ID of a source held, or fed once located (`observe`), or SS_Start of a\n * placed repair, so no source the stream has fed follows it. Undefined\n * before the first one.\n */\n get newest(): number | undefined {\n return this.#newest\n }\n\n /** Advance `newest` to `position` if it is newer; retires nothing. */\n observe(position: number): void {\n if (this.#newest === undefined || serial32Precedes(this.#newest, position)) this.#newest = position\n }\n\n /** Held sources evicted by `heldCapacity` (never by the horizon itself). */\n get heldEvicted(): number {\n return this.#heldEvicted\n }\n\n /** Number of known blocks and spans. */\n get blockCount(): number {\n return this.#blocks.length\n }\n\n /** End of contiguous coverage, or undefined before the first repair. */\n get coverageEnd(): number | undefined {\n return this.#coverageEnd\n }\n\n /** Number of sources held for a block no repair has stated yet. */\n get heldCount(): number {\n return this.#held.size\n }\n\n /** The known block or span containing `ssId`, with its ESI. */\n locate(ssId: number): { block: IsoBlockRange; esi: number } | null {\n for (let i = this.#blocks.length - 1; i >= 0; i--) {\n const block = this.#blocks[i]!\n if (isoBlockContains(block.start, block.length, ssId)) {\n return { block, esi: serial32Distance(block.start, ssId) }\n }\n }\n return null\n }\n\n /** The known block or span starting exactly at `start`. */\n blockAt(start: number): IsoBlockRange | undefined {\n return this.#blocks.find((block) => block.start === start)\n }\n\n /** Known blocks and spans overlapping the SS_IDs strictly between `a` and `b`. */\n blocksBetween(a: number, b: number): IsoBlockRange[] {\n const first = (a + 1) >>> 0\n const count = serial32Distance(a, b) - 1\n if (!serial32Precedes(a, b) || count <= 0) return []\n return this.#blocks.filter((block) => {\n const toBlock = serial32Distance(first, block.start)\n const fromBlock = serial32Distance(block.start, first)\n return toBlock < count || fromBlock < block.length\n })\n }\n\n /**\n * Whether every SS_ID strictly between `a` and `b` lies in known coverage,\n * so no block that could hold them is still to be stated by a repair.\n */\n coversBetween(a: number, b: number): boolean {\n if (this.#coverageEnd === undefined) return false\n if (!serial32Precedes(a, b) || serial32Distance(a, b) <= 1) return true\n // The last SS_ID strictly before b must precede the coverage end.\n return serial32Precedes((b - 1) >>> 0, this.#coverageEnd)\n }\n\n /**\n * Hold a first-arrival source symbol whose block is not yet known. Returns\n * false for a second copy of an SS_ID already held, so the first copy wins.\n * Retires behind `ssId` (see the class note).\n */\n hold(ssId: number, payload: T): boolean {\n if (this.#held.has(ssId)) return false\n this.#held.set(ssId, payload)\n this.retireBehind(ssId)\n for (const oldest of this.#held.keys()) {\n if (this.#held.size <= this.heldCapacity) break\n this.#held.delete(oldest)\n this.#heldEvicted++\n }\n return true\n }\n\n /** Whether `ssId` is held unplaced. */\n isHeld(ssId: number): boolean {\n return this.#held.has(ssId)\n }\n\n /**\n * Apply one repair symbol's `(SS_Start, SSB_length)`. The first repair of a\n * block states its range and places every held source in it; later repair\n * symbols of the same block change nothing. A range that contradicts a\n * known block (a different length, or an overlap with another block or a\n * span) fails closed: the repair symbol is dropped and no block changes.\n * A newly stated block retires behind its SS_Start (see the class note).\n */\n placeRepair(start: number, length: number): IsoRepairPlacement<T> {\n if (!Number.isSafeInteger(length) || length <= 0 || length > this.#kMax) {\n return { status: 'conflict', reason: `SSB_length ${length} is outside 1..${this.#kMax}` }\n }\n const known = this.blockAt(start)\n if (known !== undefined) {\n if (known.span) {\n return { status: 'overlap', reason: `repair block ${start}+${length} falls inside a repair-less span` }\n }\n if (known.length !== length) {\n return {\n status: 'conflict',\n reason: `repair SSB_length ${length} conflicts with ${known.length} for SS_Start ${start}`,\n }\n }\n return { status: 'placed', block: known, placed: [] }\n }\n for (const block of this.#blocks) {\n if (\n isoBlockContains(block.start, block.length, start) ||\n isoBlockContains(start, length, block.start)\n ) {\n return {\n status: 'overlap',\n reason: `repair block ${start}+${length} overlaps known block ${block.start}+${block.length}`,\n }\n }\n }\n let span: { block: IsoBlockRange; placed: IsoPlacedSource<T>[] } | undefined\n if (this.#coverageEnd === undefined) {\n // Reception starts at the first stated block. Held sources before it\n // are the tail of blocks that began before the join: their range is\n // never stated, so they are not FEC symbols of this receiver.\n for (const ssId of this.#held.keys()) {\n if (serial32Precedes(ssId, start)) this.#held.delete(ssId)\n }\n this.#coverageEnd = start\n } else if (serial32Precedes(start, this.#coverageEnd)) {\n return { status: 'stale', reason: `repair block ${start}+${length} precedes known coverage` }\n } else if (start !== this.#coverageEnd) {\n const spanBlock: IsoBlockRange = {\n start: this.#coverageEnd,\n length: serial32Distance(this.#coverageEnd, start),\n span: true,\n }\n span = { block: spanBlock, placed: this.#claim(spanBlock) }\n this.#blocks.push(spanBlock)\n }\n const block: IsoBlockRange = { start, length, span: false }\n const placed = this.#claim(block)\n this.#blocks.push(block)\n this.#coverageEnd = isoBlockEnd(start, length)\n this.retireBehind(start)\n return span === undefined\n ? { status: 'placed', block, placed }\n : { status: 'placed', block, placed, span }\n }\n\n /**\n * Retire every block and span whose last SS_ID, and every held source,\n * lies more than `retentionBlocks` blocks behind `position`, with the shared\n * lower-bound metric (`isoBlocksBehindLowerBound`) every ISO interleave\n * horizon uses. Returns the retired block starts.\n */\n retireBehind(position: number): number[] {\n this.observe(position)\n const horizon = this.#retentionBlocks\n const retired: number[] = []\n for (let i = this.#blocks.length - 1; i >= 0; i--) {\n const block = this.#blocks[i]!\n const last = serial32Add(block.start, block.length - 1)\n if (isoBlocksBehindLowerBound(last, position, this.#kMax) > horizon) {\n retired.push(block.start)\n this.#blocks.splice(i, 1)\n }\n }\n for (const ssId of [...this.#held.keys()]) {\n if (isoBlocksBehindLowerBound(ssId, position, this.#kMax) > horizon) this.#held.delete(ssId)\n }\n return retired\n }\n\n /** Forget everything (publisher restart, track reset). */\n reset(): void {\n this.#blocks.length = 0\n this.#held.clear()\n this.#coverageEnd = undefined\n this.#newest = undefined\n }\n\n #claim(block: IsoBlockRange): IsoPlacedSource<T>[] {\n const placed: IsoPlacedSource<T>[] = []\n for (const [ssId, payload] of this.#held) {\n if (!isoBlockContains(block.start, block.length, ssId)) continue\n placed.push({ ssId, esi: serial32Distance(block.start, ssId), payload })\n this.#held.delete(ssId)\n }\n placed.sort((x, y) => x.esi - y.esi)\n return placed\n }\n}\n", "// Typed FEC state shared by every player (hang/moq-lib, moqtail, Shaka MSF).\n//\n// A player's FEC counters are `null` in three unrelated situations: the\n// session is on reliable MoQ, where FEC has nothing to protect (FEC protects\n// only lossy multicast/AMT datagrams); no multicast bridge exists, so no lossy\n// leg can be joined; or the catalog declares no FEC for the tracks the player\n// selected. `null` also meant \"nothing decoded yet\" and \"the pipeline broke\",\n// so an idle session and a broken one looked identical.\n//\n// `resolveFecState` names which case holds. Each reason is read from a fact the\n// player already owns -- its catalog, its transport policy, its bridge probe\n// and its physical subscription -- and never from counters, console text,\n// timers or elapsed time. The rules live here so the three players cannot\n// disagree about precedence.\n\nimport { isFecBlockProfile } from '@blockcast/mmt-base'\n\n/**\n * Minimum valid FEC interleave depth in milliseconds, per spec \u00A75.1\n * (the interactive/gaming floor; broadcast/ATSC 3.0 ranges up to 10000ms).\n *\n * A protocol constant, NOT a tuning knob. Values below this floor are almost\n * certainly a raw frame count (e.g. `2`) that a caller passed when no framerate\n * was available to convert to ms (common on audio tracks). Feeding such a value\n * into computeFecTimeout treats it as `2ms`, yielding a near-zero FEC block\n * timeout that abandons startup FEC blocks before their repair symbols arrive\n * (measured: moqtail startupFailed=4, shaka dropped=5.8% on startup). Timing\n * derivations MUST reject sub-floor values and fall back to the established\n * timeout instead of collapsing.\n */\nexport const MIN_INTERLEAVE_DEPTH_MS = 33\n\n/** Why FEC is not protecting this session. */\nexport type FecInactiveReason = 'no-catalog-fec' | 'reliable-moq' | 'no-multicast-bridge'\n\n/** Every inactive reason, in resolution order. */\nexport const FEC_INACTIVE_REASONS: readonly FecInactiveReason[] = Object.freeze([\n 'no-catalog-fec',\n 'reliable-moq',\n 'no-multicast-bridge',\n] as const)\n\n/** The transport policy vocabulary every player accepts. */\nexport type FecTransportPolicy = 'auto' | 'moq-only' | 'multicast-only'\n\nconst TRANSPORT_POLICIES: ReadonlySet<string> = new Set<FecTransportPolicy>(['auto', 'moq-only', 'multicast-only'])\n\n/** FEC is not protecting this session; `reason` says why. Carries no counters. */\nexport interface FecInactiveState {\n readonly state: 'inactive'\n readonly reason: FecInactiveReason\n}\n\n/**\n * FEC is the protection in force for this session's delivery path.\n *\n * Flat on purpose: the player's existing counters stay at the paths every\n * consumer already reads (`fec.blocksRecovered`, `fec.tracks`, `fec.blocks`),\n * with `state` added beside them. A consumer that ignores `state` reads an\n * active report exactly as before and an inactive one as \"no counters\", which\n * is what `null` meant to it.\n */\nexport type FecActiveState<S extends object> = S & { readonly state: 'active' }\n\nexport type FecState<S extends object> = FecActiveState<S> | FecInactiveState\n\n/** An active state whose decoder has not reported any counter yet. */\nexport type NoFecCounters = Record<never, never>\n\n/** The authoritative facts `resolveFecState` reads. */\nexport interface FecStateEvidence<S extends object> {\n /**\n * The counters the player reports today, or null/undefined when it has none.\n * Payload only: it never decides the state, because moqtail and Shaka report\n * a zeroed snapshot as soon as a MulticastManager exists, which is before any\n * bridge or subscription does.\n */\n readonly stats: S | null | undefined\n /**\n * Whether the catalog declares FEC (see `catalogDeclaresFec`) for at least\n * one media track the player selected.\n */\n readonly catalogFec: boolean\n /** The player's configured transport policy. */\n readonly transportPolicy: FecTransportPolicy\n /** Whether a multicast bridge (`window.multicast`) is available to the page. */\n readonly bridgeAvailable: boolean\n /**\n * Whether a physical multicast/AMT subscription is live: the bridge accepted\n * it and it has not been closed. A requested leg is not a joined one.\n */\n readonly multicastLegJoined: boolean\n}\n\nconst INACTIVE: Readonly<Record<FecInactiveReason, FecInactiveState>> = Object.freeze({\n 'no-catalog-fec': Object.freeze({ state: 'inactive', reason: 'no-catalog-fec' }),\n 'reliable-moq': Object.freeze({ state: 'inactive', reason: 'reliable-moq' }),\n 'no-multicast-bridge': Object.freeze({ state: 'inactive', reason: 'no-multicast-bridge' }),\n})\n\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n}\n\nfunction isPositiveSafeInteger(value: unknown): boolean {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0\n}\n\n/**\n * Whether one catalog `fec` block (a hang rendition's `fec` or an MSF/CMSF\n * track's `fec`) declares an FEC instance a receiver can configure: source\n * symbols per block (K) and a symbol size (T) are both positive integers, the\n * algorithm is not `none`, `blockProfile` names the implemented profile\n * `iso-ssbg1-v1` (BLO-35597), and the block carries that profile's usable\n * span (`isoBlockSpanUsable`). Total: it never throws, and answers false for\n * any other value.\n *\n * `resolveCatalogTrackFec` reads this predicate, so the players' parameters and\n * this state never disagree. Of the blocks this rejects, it returns no FEC\n * geometry only for `algorithm: \"none\"`. One with any other algorithm is a\n * malformed catalog (`FecCatalogConfig` marks both fields REQUIRED; the MSF\n * schema and `validateCmsfMmtpCatalog` enforce them on `tracks[]`, but the\n * hang dialect's `renditions` schema is permissive, so this is where a hang\n * block fails closed), and `resolveCatalogTrackFec` throws a `RepairContractError` for\n * it (`track_fec` unless another repair-contract violation fires first),\n * whether or not the source has repair layers, rather than silently dropping\n * the track's repair subscription.\n *\n * The block profile is part of this predicate because the profile selects the\n * block math and is never defaulted: draft-ramadan-moq-fec \u00A75.1 defines one,\n * `iso-ssbg1-v1`, and a receiver MUST NOT apply FEC under any other token. The\n * retired `k-padded-v1` is simply not a known profile. `resolveCatalogTrackFec`\n * and `MulticastManager.configureMultiTrack` resolve such a track to no FEC\n * with a typed `TrackFecUnavailable` reason (`catalogFecUnavailable`: code\n * `track_fec_block_profile_unimplemented` for an unimplemented token,\n * `track_fec_block_profile` for an absent one), scoped to that track, so their\n * parameters and this state still agree. The CMSF conformance validator\n * (`validateCmsfMmtpCatalog`) rejects the catalog with the same code. An\n * `iso-ssbg1-v1` instance without a usable block span (`isoBlockSpanUsable`)\n * is likewise no FEC for its track alone, on its `interleaveDepthMs`: code\n * `track_fec_block_span` when the span is absent, `track_fec` when the declared\n * one is unusable.\n *\n * The repair-contract resolvers (`resolveRepairLayers`) gate a `depends` target\n * on `algorithm` alone and never read a source's `symbolSize`. They check its\n * `sourceSymbols` for the RaptorQ K bound, and require a positive integer\n * `sourceSymbols` and `repairSymbols` only when the source has repair layers or\n * an overlay beyond layer 0.\n */\nexport function catalogDeclaresFec(fec: unknown): boolean {\n const block = recordOf(fec)\n if (block === undefined || block['algorithm'] === 'none') return false\n return (\n isPositiveSafeInteger(block['sourceSymbols']) &&\n isPositiveSafeInteger(block['symbolSize']) &&\n isFecBlockProfile(block['blockProfile']) &&\n isoBlockSpanUsable(block)\n )\n}\n\n/**\n * Whether a `fec` block that names `iso-ssbg1-v1` carries the block span its\n * block location is bounded by: `interleaveDepthMs`, a finite number of\n * milliseconds no smaller than `MIN_INTERLEAVE_DEPTH_MS`. Without it a receiver\n * cannot bound the sources it holds before a repair states their block, so the\n * instance cannot be configured (BLO-35597). A block that names no\n * `iso-ssbg1-v1` profile answers true: it has no FEC on account of its profile\n * (see `catalogDeclaresFec`), so the span is not what disqualifies it.\n */\nexport function isoBlockSpanUsable(fec: unknown): boolean {\n const block = recordOf(fec)\n if (block === undefined || block['blockProfile'] !== 'iso-ssbg1-v1') return true\n const ms = block['interleaveDepthMs']\n return typeof ms === 'number' && Number.isFinite(ms) && ms >= MIN_INTERLEAVE_DEPTH_MS\n}\n\nfunction requireBoolean(value: unknown, field: string): boolean {\n if (typeof value !== 'boolean') {\n throw new TypeError(`resolveFecState: ${field} must be a boolean, got ${typeof value}`)\n }\n return value\n}\n\n/**\n * Resolve a player's FEC state. First match wins:\n *\n * 1. The catalog declares no FEC for a selected track: `no-catalog-fec`. FEC\n * geometry comes only from the catalog, so there is nothing to configure on\n * any path.\n * 2. The policy is `moq-only`: `reliable-moq`. Media travels only over QUIC,\n * which is reliable, so FEC has no loss to repair, bridge or not.\n * 3. No multicast bridge: `no-multicast-bridge`. No lossy leg can exist, and on\n * `auto` this is the reason the session is on reliable MoQ.\n * 4. The policy is `multicast-only`, or a lossy subscription is live: active.\n * `multicast-only` commits the session to the lossy leg, so FEC is in force\n * before the join completes; whether the join succeeded is the physical\n * attestation's question, not this one. The counters may be absent until\n * the decoder reports.\n * 5. Otherwise (`auto`, a bridge, no live subscription): `reliable-moq`.\n *\n * Throws on malformed evidence rather than picking a reason for it.\n */\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S> & { readonly stats: S }): FecState<S>\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S>): FecState<S | NoFecCounters>\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S>): FecState<S | NoFecCounters> {\n const catalogFec = requireBoolean(evidence.catalogFec, 'catalogFec')\n const bridgeAvailable = requireBoolean(evidence.bridgeAvailable, 'bridgeAvailable')\n const multicastLegJoined = requireBoolean(evidence.multicastLegJoined, 'multicastLegJoined')\n const { transportPolicy, stats } = evidence\n if (typeof transportPolicy !== 'string' || !TRANSPORT_POLICIES.has(transportPolicy)) {\n throw new TypeError(`resolveFecState: unsupported transportPolicy ${JSON.stringify(transportPolicy)}`)\n }\n if (stats !== null && stats !== undefined && recordOf(stats) === undefined) {\n throw new TypeError('resolveFecState: stats must be a plain object, null or undefined')\n }\n\n if (!catalogFec) return INACTIVE['no-catalog-fec']\n if (transportPolicy === 'moq-only') return INACTIVE['reliable-moq']\n if (!bridgeAvailable) return INACTIVE['no-multicast-bridge']\n if (transportPolicy === 'multicast-only' || multicastLegJoined) {\n // The discriminant is written last so a stats field of the same name can\n // never masquerade as a state.\n return { ...(stats ?? {}), state: 'active' } as FecActiveState<S | NoFecCounters>\n }\n return INACTIVE['reliable-moq']\n}\n", "/**\n * Keyframe-protected FEC repair policy.\n *\n * Every FEC block still gets in-band FEC decode. What this module decides is\n * (1) which blocks may hold a keyframe (RAP), so a block short of K at its\n * repair deadline can be repaired over QUIC with the right priority, and\n * (2) what a sink must do with the first frame released after a gap: stall\n * until the next RAP, or conceal and keep playing.\n *\n * The two decisions are deliberately independent. Classification only sets\n * repair priority. The stall/conceal decision is made again per released gap\n * from the media that is actually missing, so a wrong classification can only\n * lower a block's repair priority; it can never make playback wrong.\n *\n * Everything here is derived from catalog-authored timing and in-band MMTP\n * evidence. There are no timers, no cadence heuristics, and no defaults: a\n * track whose geometry cannot be derived exactly runs in the defined\n * fail-closed mode `all-protected`, where every block is treated as possibly\n * keyframe-bearing.\n *\n * All three players (moqtail, hang, Shaka) consume this one implementation.\n */\n\nimport { deriveFecInterleaveDepth } from '@blockcast/mmt-fec'\n\n// \u2500\u2500\u2500 Classes and priorities \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Repair class of one FEC block.\n *\n * - `keyframe`: the block holds (or held) a RAP / MPU start.\n * - `delta`: the block provably holds only non-RAP video frames.\n * - `unknown`: neither could be proven; treated exactly like `keyframe`.\n * - `audio`: an audio block.\n */\nexport type BlockClass = 'keyframe' | 'delta' | 'unknown' | 'audio'\n\n/** Repair priority. Lower is more urgent. */\nexport type RepairPriority = 0 | 1 | 2\n\nexport type TrackKind = 'video' | 'audio'\n\n/** P0 = keyframe/unknown (stall-preventing), P1 = audio, P2 = delta. */\nexport function repairPriorityForClass(blockClass: BlockClass): RepairPriority {\n switch (blockClass) {\n case 'keyframe':\n case 'unknown':\n return 0\n case 'audio':\n return 1\n case 'delta':\n return 2\n }\n}\n\n// \u2500\u2500\u2500 Geometry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type ClassifierMode = 'classifying' | 'all-protected'\n\n/** Why a track runs `all-protected`. */\nexport type ClassifierReason =\n // Geometry (catalog) reasons.\n | 'framerate-absent'\n | 'keyframe-cadence-absent'\n | 'keyframe-interval-not-integral'\n | 'block-spans-gop'\n /**\n * The catalog keyframe interval is not an exact integer number of ticks\n * in the media track's own timescale (ISO-BMFF `mdhd`), so released RAP\n * spacing cannot be verified and hold-last cannot predict the next RAP.\n * Nothing is wrong with the stream; the cadence is simply unprovable.\n */\n | 'keyframe-interval-not-media-integral'\n /**\n * Another stream on this packetId was configured with its own geometry\n * and this one was not, so no geometry provably belongs to it (ABR rungs\n * sharing a packetId).\n */\n | 'stream-geometry-absent'\n // In-band invariant violations (positive evidence only).\n | 'rap-not-mpu-start'\n | 'mpu-longer-than-gop'\n | 'block-not-contiguous'\n | 'rap-spacing-mismatch'\n | 'mpu-multiple-fragments'\n | 'block-wider-than-interleave'\n\n/** Raw catalog timing for one track. Never a resolved cadence. */\nexport interface BlockClassifierGeometryInput {\n readonly trackKind: TrackKind\n /** Catalog `framerate` (frames per second). */\n readonly framerate?: number\n /** Catalog `fec.interleaveDepthMs`: the full FEC block span (\u00A75.1). */\n readonly interleaveDepthMs: number\n /** Catalog track `timescale` for `keyframeIntervalTicks`. */\n readonly timescale?: number\n /** Catalog `keyframeIntervalTicks` (preferred when the timescale is present). */\n readonly keyframeIntervalTicks?: number\n /** Catalog `keyframeIntervalMs`. */\n readonly keyframeIntervalMs?: number\n}\n\n/** Exact keyframe interval as a rational number of seconds: ticks / timescale. */\nexport interface KeyframeInterval {\n readonly ticks: number\n readonly timescale: number\n}\n\nexport type BlockClassifierGeometry =\n | { readonly trackKind: 'audio' }\n | {\n readonly trackKind: 'video'\n readonly mode: 'classifying'\n /** F: one frame's duration in milliseconds (1000 / framerate). */\n readonly frameDurationMs: number\n /** D: media units per FEC block (deriveFecInterleaveDepth). */\n readonly interleaveUnits: number\n /** G: frames per GOP. */\n readonly gopFrames: number\n readonly keyframeInterval: KeyframeInterval\n }\n | {\n readonly trackKind: 'video'\n readonly mode: 'all-protected'\n readonly reason: ClassifierReason\n }\n\n/**\n * Integer value of `numerator / denominator` when it is one, else null.\n *\n * Catalog framerates are JSON numbers, so an NTSC rate arrives as the double\n * nearest 30000/1001. One multiply and one divide each add at most half an\n * ulp of relative error (IEEE-754), so a quotient within two ulps of an\n * integer IS that integer; anything further is genuinely non-integral.\n */\nfunction exactInteger(numerator: number, denominator: number): number | null {\n if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator === 0) {\n return null\n }\n const quotient = numerator / denominator\n const rounded = Math.round(quotient)\n if (rounded < 1) return null\n const representationError = 2 * Number.EPSILON * Math.abs(quotient)\n return Math.abs(quotient - rounded) <= representationError ? rounded : null\n}\n\nfunction isPositiveFinite(value: number | undefined): value is number {\n return value !== undefined && Number.isFinite(value) && value > 0\n}\n\n/**\n * Derive the per-track classifier geometry from raw catalog fields.\n *\n * `all-protected` is the policy's defined fail-closed state, not a default:\n * it is returned with the exact reason. The only throw is for a value the\n * catalog validator already rejects (a non-positive block span).\n */\nexport function deriveBlockClassifierGeometry(\n input: BlockClassifierGeometryInput,\n): BlockClassifierGeometry {\n if (!isPositiveFinite(input.interleaveDepthMs)) {\n throw new Error(\n `[BlockPolicy] interleaveDepthMs=${input.interleaveDepthMs} is not a positive block span; ` +\n 'the catalog validator rejects this',\n )\n }\n if (input.trackKind === 'audio') return { trackKind: 'audio' }\n\n if (!isPositiveFinite(input.framerate)) {\n return { trackKind: 'video', mode: 'all-protected', reason: 'framerate-absent' }\n }\n const framerate = input.framerate\n\n let keyframeInterval: KeyframeInterval\n if (isPositiveFinite(input.keyframeIntervalTicks) && isPositiveFinite(input.timescale)) {\n keyframeInterval = { ticks: input.keyframeIntervalTicks, timescale: input.timescale }\n } else if (isPositiveFinite(input.keyframeIntervalMs)) {\n keyframeInterval = { ticks: input.keyframeIntervalMs, timescale: 1000 }\n } else {\n return { trackKind: 'video', mode: 'all-protected', reason: 'keyframe-cadence-absent' }\n }\n\n const gopFrames = exactInteger(keyframeInterval.ticks * framerate, keyframeInterval.timescale)\n if (gopFrames === null) {\n return {\n trackKind: 'video',\n mode: 'all-protected',\n reason: 'keyframe-interval-not-integral',\n }\n }\n\n const frameDurationMs = 1000 / framerate\n const interleaveUnits = deriveFecInterleaveDepth(input.interleaveDepthMs, frameDurationMs)\n if (interleaveUnits >= gopFrames) {\n return { trackKind: 'video', mode: 'all-protected', reason: 'block-spans-gop' }\n }\n\n return {\n trackKind: 'video',\n mode: 'classifying',\n frameDurationMs,\n interleaveUnits,\n gopFrames,\n keyframeInterval,\n }\n}\n\n/**\n * The keyframe interval expressed in a media track's timescale, or null when\n * the geometry is not `classifying` or the interval is not an exact integer\n * number of media ticks.\n */\nexport function keyframeIntervalMediaTicks(\n geometry: BlockClassifierGeometry,\n mediaTimescale: number,\n): bigint | null {\n if (geometry.trackKind !== 'video' || geometry.mode !== 'classifying') return null\n if (!Number.isSafeInteger(mediaTimescale) || mediaTimescale <= 0) return null\n const { ticks, timescale } = geometry.keyframeInterval\n if (!Number.isSafeInteger(ticks) || !Number.isSafeInteger(timescale)) return null\n const scaled = BigInt(ticks) * BigInt(mediaTimescale)\n const divisor = BigInt(timescale)\n if (scaled % divisor !== 0n) return null\n return scaled / divisor\n}\n\n// \u2500\u2500\u2500 Evidence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * In-band evidence from ONE first-arrival source symbol received from the\n * network. Recovered symbols and repair symbols never contribute, and the\n * MFU `priority > 0x80` hint is not evidence.\n */\nexport interface SourceSymbolEvidence {\n /** The MMTP header R (RAP) bit. */\n readonly rap: boolean\n /** MPU_sequence_number, when parsed from this packet. */\n readonly mpu?: number\n /**\n * 0-based sample position within the MPU: the MFU DU-header\n * `sample_number` minus one (ISO/IEC 23008-1 numbers samples from 1, and\n * the parser rejects `sample_number` 0). Present only when this packet\n * carried a DU header.\n */\n readonly idx?: number\n /** movie_fragment_sequence_number from the DU header, when present. */\n readonly movieFragment?: number\n /**\n * DU-header `offset`: the data unit's authored byte position within its\n * sample (0 for a whole-sample data unit, the partition boundary for a\n * subsample data unit). Present only with `idx`; absent reads as 0.\n */\n readonly offset?: number\n /**\n * The MMTP header packet_sequence_number (contiguous per packet_id). Not\n * classification evidence: FECManager uses it to bound how many blocks the\n * producer can have started between two source symbols, so an outage is\n * never mistaken for a source restart.\n */\n readonly packetSequenceNumber?: number\n}\n\nexport type ReclassificationEvidence = 'rap' | 'mpu-span' | 'window' | 'classifier-disabled'\n\nexport type ClassifierChange =\n | {\n readonly kind: 'reclassified'\n readonly sbn: number\n readonly from: BlockClass\n readonly to: BlockClass\n readonly evidence: ReclassificationEvidence\n }\n | {\n readonly kind: 'mode'\n readonly mode: 'all-protected'\n readonly reason: ClassifierReason\n }\n\ninterface BlockEvidence {\n rapSeen: boolean\n mpus: Set<number>\n idxMin?: number\n idxMax?: number\n cls: BlockClass\n}\n\n/** A data-unit start (a packet carrying a DU header) and the block it is in. */\ninterface DataUnitStart {\n readonly movieFragment: number\n readonly idx: number\n readonly offset: number\n readonly sbn: number\n}\n\n/** Authored order of two data-unit starts of one MPU. */\nfunction compareDataUnitStarts(\n a: Omit<DataUnitStart, 'sbn'>,\n b: Omit<DataUnitStart, 'sbn'>,\n): number {\n return a.movieFragment - b.movieFragment || a.idx - b.idx || a.offset - b.offset\n}\n\n/**\n * Per-stream block classifier.\n *\n * Classes only move toward more protection or narrower evidence:\n * `unknown\u2192delta` and `unknown\u2192keyframe` / `delta\u2192keyframe` on evidence, and\n * `delta\u2192unknown` when an invariant disables classification. `keyframe` is\n * terminal.\n *\n * The delta window rule rests on three premises, each guarded by an\n * invariant that fires on positive evidence only:\n *\n * - P1: the source packets of one block are one contiguous run of the\n * stream's authored order, and blocks follow each other in SBN order\n * (`block-not-contiguous`). The producer assigns SS_IDs in authored order\n * and a block is the SS_ID range [SBN*K, SBN*K + K), so this holds for a\n * block that closes at K symbols in the middle of a frame: that frame's\n * fragments then land in two ADJACENT blocks, which is still one run each.\n * A frame split across adjacent blocks is not a violation.\n * - P2: that run touches at most D media units, counting a frame continued\n * from the previous block (`block-wider-than-interleave`). The producer\n * counts a media-unit boundary only once the open block holds a symbol.\n * - P3: an MPU starts with its only RAP and holds at most G frames\n * (`rap-not-mpu-start`, `mpu-longer-than-gop`, `rap-spacing-mismatch`), in\n * one movie fragment (`mpu-multiple-fragments`).\n *\n * P1 and P2 are what make a block's frames contiguous and lie within\n * [idxMax \u2212 D + 1, idxMin + D \u2212 1] of its MPU. What refutes P1 is a pair of\n * source packets whose wire-stated authored order and SBN order disagree:\n * a later MPU in an earlier block, a later data unit of one MPU (by\n * movie_fragment_sequence_number, sample_number, then DU offset) in an\n * earlier block, or one data unit in two blocks. That is the signature of a\n * round-robin interleave, which scatters one run over several concurrent\n * blocks. Non-adjacency alone is not: a sample larger than K packets\n * legitimately spans blocks SBN..SBN+n.\n */\nexport class BlockClassifier {\n #geometry: BlockClassifierGeometry\n #mode: ClassifierMode\n #reason: ClassifierReason | null\n /**\n * False once the stream showed that an MPU boundary does not bound its\n * RAPs (a RAP inside an MPU, or an MPU longer than a GOP). Recorded in\n * every mode: the stall/conceal decision relies on it even when the\n * catalog geometry already runs the classifier `all-protected`.\n */\n #mpuBoundsRaps = true\n #blocks = new Map<number, BlockEvidence>()\n /**\n * P1 ledger, kept for the MPUs within the classification horizon (the\n * highest MPU seen and the one before it): the SBN span of every source\n * packet of each MPU, and each MPU's data-unit starts in authored order.\n * Valid only within one continuous SBN sequence; a source discontinuity\n * clears it (`noteSourceDiscontinuity`).\n */\n #mpuSbnSpans = new Map<number, { min: number; max: number }>()\n #dataUnitStarts = new Map<number, DataUnitStart[]>()\n #highestMpu: number | null = null\n /** MPU \u2192 movie_fragment_sequence_number observed in it. */\n #mpuFragments = new Map<number, number>()\n #lastRap: { mpu: number; presentationTime: bigint; mediaTimescale: number } | null = null\n\n constructor(geometry: BlockClassifierGeometry) {\n this.#geometry = geometry\n this.#mode = 'classifying'\n this.#reason = null\n this.reset(geometry)\n }\n\n get geometry(): BlockClassifierGeometry {\n return this.#geometry\n }\n\n get mode(): ClassifierMode {\n return this.#mode\n }\n\n get reason(): ClassifierReason | null {\n return this.#reason\n }\n\n /**\n * Whether \"same MPU on both sides of a gap\" still proves the gap held no\n * RAP. It stops holding for the rest of the epoch once a RAP was received\n * inside an MPU or an MPU ran longer than a GOP.\n */\n get mpuBoundsRaps(): boolean {\n return this.#mpuBoundsRaps\n }\n\n /** Begin a fresh epoch (catalog change or track reset). */\n reset(geometry: BlockClassifierGeometry = this.#geometry): void {\n this.#geometry = geometry\n this.#blocks.clear()\n this.#clearContiguityLedger()\n this.#mpuFragments.clear()\n this.#lastRap = null\n this.#mpuBoundsRaps = true\n if (geometry.trackKind === 'video' && geometry.mode === 'all-protected') {\n this.#mode = 'all-protected'\n this.#reason = geometry.reason\n } else {\n this.#mode = 'classifying'\n this.#reason = null\n }\n }\n\n /**\n * Retire this classifier (its stream moves to a new catalog epoch). Every\n * block it had classified `delta` loses the evidence behind that class and\n * reads `unknown` from now on; the returned changes say so.\n */\n retire(): ClassifierChange[] {\n const changes: ClassifierChange[] = []\n for (const [sbn, block] of this.#blocks) {\n if (block.cls !== 'delta') continue\n block.cls = 'unknown'\n changes.push({\n kind: 'reclassified',\n sbn,\n from: 'delta',\n to: 'unknown',\n evidence: 'classifier-disabled',\n })\n }\n return changes\n }\n\n /**\n * The stream's SBN sequence restarted (FECManager source discontinuity:\n * a publisher restart, or the SS_ID space wrapped). Authored order and SBN\n * order are comparable only within one continuous sequence, so the P1\n * ledger starts again.\n */\n noteSourceDiscontinuity(): void {\n this.#clearContiguityLedger()\n }\n\n /** Register a block that exists without evidence (a vanished block). */\n touch(sbn: number): void {\n this.#blockFor(sbn)\n }\n\n /** Forget a resolved block. */\n forget(sbn: number): void {\n this.#blocks.delete(sbn)\n }\n\n classOf(sbn: number): BlockClass {\n if (this.#geometry.trackKind === 'audio') return 'audio'\n return this.#blocks.get(sbn)?.cls ?? 'unknown'\n }\n\n /** Record one first-arrival source symbol's evidence for block `sbn`. */\n observe(sbn: number, evidence: SourceSymbolEvidence): ClassifierChange[] {\n if (this.#geometry.trackKind === 'audio') return []\n const changes: ClassifierChange[] = []\n const block = this.#blockFor(sbn)\n\n if (evidence.rap) block.rapSeen = true\n if (evidence.mpu !== undefined) block.mpus.add(evidence.mpu)\n if (evidence.idx !== undefined) {\n block.idxMin = block.idxMin === undefined ? evidence.idx : Math.min(block.idxMin, evidence.idx)\n block.idxMax = block.idxMax === undefined ? evidence.idx : Math.max(block.idxMax, evidence.idx)\n }\n\n // A RAP inside an MPU refutes \"an MPU boundary bounds RAPs\" whatever the\n // classifier mode, because gap decisions rely on it in every mode.\n if (evidence.rap && evidence.idx !== undefined && evidence.idx !== 0) {\n this.#mpuBoundsRaps = false\n }\n\n const violation = this.#checkInvariants(sbn, block, evidence)\n if (violation !== null) changes.push(...this.#disable(violation))\n\n const next = this.#classify(block)\n if (next !== block.cls && this.#allowed(block.cls, next)) {\n changes.push({\n kind: 'reclassified',\n sbn,\n from: block.cls,\n to: next,\n evidence: this.#evidenceFor(block, next),\n })\n block.cls = next\n }\n return changes\n }\n\n /**\n * Record a RAP frame released in order. Released MPU-start RAPs of MPUs m\n * and m+n must be exactly n keyframe intervals apart in the media\n * timescale, compared as integers.\n *\n * Comparing only consecutive MPUs would miss exactly the case the window\n * rule cannot survive: a short MPU whose successor's RAP was lost (that\n * RAP-bearing block is the one a wrong G calls delta), because the next\n * released RAP then belongs to m+2. Every MPU is one interval long, so the\n * span to any later released RAP proves or refutes all MPUs in between.\n */\n noteReleasedRap(\n mpu: number,\n presentationTime: bigint,\n mediaTimescale: number,\n ): ClassifierChange[] {\n if (this.#geometry.trackKind === 'audio' || this.#mode === 'all-protected') {\n return []\n }\n // Verification compares integers in the media timescale. When the\n // catalog interval is not an exact tick count there, spacing is\n // unprovable: fail closed, naming the cause, rather than blaming the\n // encoder with rap-spacing-mismatch on a perfectly spaced RAP.\n const interval = keyframeIntervalMediaTicks(this.#geometry, mediaTimescale)\n if (interval === null) return this.#disable('keyframe-interval-not-media-integral')\n const previous = this.#lastRap\n // A further RAP inside an already-anchored MPU is not an MPU start; the\n // MPU keeps its first RAP as the spacing anchor (rap-not-mpu-start owns\n // mid-MPU RAPs).\n if (previous !== null && previous.mpu === mpu && previous.mediaTimescale === mediaTimescale) {\n return []\n }\n this.#lastRap = { mpu, presentationTime, mediaTimescale }\n if (\n previous === null ||\n mpu < previous.mpu ||\n previous.mediaTimescale !== mediaTimescale\n ) {\n return []\n }\n if (\n presentationTime - previous.presentationTime === interval * BigInt(mpu - previous.mpu)\n ) {\n return []\n }\n return this.#disable('rap-spacing-mismatch')\n }\n\n #blockFor(sbn: number): BlockEvidence {\n let block = this.#blocks.get(sbn)\n if (!block) {\n block = { rapSeen: false, mpus: new Set(), cls: 'unknown' }\n this.#blocks.set(sbn, block)\n }\n return block\n }\n\n #checkInvariants(\n sbn: number,\n block: BlockEvidence,\n evidence: SourceSymbolEvidence,\n ): ClassifierReason | null {\n if (this.#mode === 'all-protected' || this.#geometry.trackKind !== 'video') return null\n const geometry = this.#geometry\n if (geometry.mode !== 'classifying') return null\n if (evidence.rap && evidence.idx !== undefined && evidence.idx !== 0) {\n return 'rap-not-mpu-start'\n }\n if (evidence.idx !== undefined && evidence.idx >= geometry.gopFrames) {\n return 'mpu-longer-than-gop'\n }\n // A block closes at the latest after D media units, so the DU headers it\n // carries from one MPU lie within D consecutive positions. A wider span\n // means the producer closes blocks some other way (at K symbols, or\n // round-robin across concurrent blocks) and the window rule is void.\n if (\n block.mpus.size === 1 &&\n block.idxMin !== undefined &&\n block.idxMax !== undefined &&\n block.idxMax - block.idxMin >= geometry.interleaveUnits\n ) {\n return 'block-wider-than-interleave'\n }\n if (evidence.mpu !== undefined && evidence.movieFragment !== undefined) {\n const fragment = this.#mpuFragments.get(evidence.mpu)\n if (fragment === undefined) {\n this.#mpuFragments.set(evidence.mpu, evidence.movieFragment)\n // One entry per MPU still inside the classification horizon.\n for (const mpu of this.#mpuFragments.keys()) {\n if (mpu + 1 < evidence.mpu) this.#mpuFragments.delete(mpu)\n }\n } else if (fragment !== evidence.movieFragment) {\n // sample_number is then a position within a movie fragment, not\n // within the MPU, and the window rule's geometry does not hold.\n return 'mpu-multiple-fragments'\n }\n }\n if (evidence.mpu !== undefined && this.#contradictsAuthoredOrder(sbn, evidence.mpu, evidence)) {\n return 'block-not-contiguous'\n }\n return null\n }\n\n #clearContiguityLedger(): void {\n this.#mpuSbnSpans.clear()\n this.#dataUnitStarts.clear()\n this.#highestMpu = null\n }\n\n /**\n * P1 check for one source packet of MPU `mpu` in block `sbn`, then record\n * it. True when some recorded packet's authored order and SBN order\n * disagree with this one's (see the class comment). Only positions the\n * wire states are compared: MPU order for every packet, and data-unit\n * order for packets that carry a DU header. A packet of one data unit\n * that continues into the next block carries no DU header and so only\n * takes part through its MPU, which is exactly what makes an adjacent\n * straddle consistent.\n */\n #contradictsAuthoredOrder(sbn: number, mpu: number, evidence: SourceSymbolEvidence): boolean {\n // Every packet of MPU m precedes every packet of MPU m' > m.\n for (const [other, span] of this.#mpuSbnSpans) {\n if (other < mpu && span.max > sbn) return true\n if (other > mpu && span.min < sbn) return true\n }\n const inHorizon = this.#highestMpu === null || mpu + 1 >= this.#highestMpu\n let insertAt = -1\n let starts: DataUnitStart[] | undefined\n let start: DataUnitStart | undefined\n if (evidence.idx !== undefined) {\n start = {\n movieFragment: evidence.movieFragment ?? 0,\n idx: evidence.idx,\n offset: evidence.offset ?? 0,\n sbn,\n }\n starts = this.#dataUnitStarts.get(mpu)\n if (starts !== undefined) {\n let lo = 0\n let hi = starts.length\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (compareDataUnitStarts(starts[mid]!, start) < 0) lo = mid + 1\n else hi = mid\n }\n const at = starts[lo]\n if (at !== undefined && compareDataUnitStarts(at, start) === 0) {\n // One data unit has one first packet: seen in another block, the\n // SBN-to-authored-order map is not a function.\n if (at.sbn !== sbn) return true\n } else {\n const before = starts[lo - 1]\n if (before !== undefined && before.sbn > sbn) return true\n if (at !== undefined && at.sbn < sbn) return true\n insertAt = lo\n }\n }\n }\n if (!inHorizon) return false\n const span = this.#mpuSbnSpans.get(mpu)\n if (span === undefined) this.#mpuSbnSpans.set(mpu, { min: sbn, max: sbn })\n else {\n if (sbn < span.min) span.min = sbn\n if (sbn > span.max) span.max = sbn\n }\n if (start !== undefined) {\n if (starts === undefined) this.#dataUnitStarts.set(mpu, [start])\n else if (insertAt >= 0) starts.splice(insertAt, 0, start)\n }\n if (this.#highestMpu === null || mpu > this.#highestMpu) {\n this.#highestMpu = mpu\n for (const other of [...this.#mpuSbnSpans.keys()]) {\n if (other + 1 < mpu) {\n this.#mpuSbnSpans.delete(other)\n this.#dataUnitStarts.delete(other)\n }\n }\n }\n return false\n }\n\n #disable(reason: ClassifierReason): ClassifierChange[] {\n if (reason === 'rap-not-mpu-start' || reason === 'mpu-longer-than-gop') {\n this.#mpuBoundsRaps = false\n }\n if (this.#mode === 'all-protected') return []\n this.#mode = 'all-protected'\n this.#reason = reason\n const changes: ClassifierChange[] = [{ kind: 'mode', mode: 'all-protected', reason }]\n for (const [sbn, block] of this.#blocks) {\n if (block.cls !== 'delta') continue\n block.cls = 'unknown'\n changes.push({\n kind: 'reclassified',\n sbn,\n from: 'delta',\n to: 'unknown',\n evidence: 'classifier-disabled',\n })\n }\n return changes\n }\n\n #classify(block: BlockEvidence): BlockClass {\n if (block.rapSeen || block.mpus.size >= 2) return 'keyframe'\n if (this.#mode === 'all-protected') return 'unknown'\n const geometry = this.#geometry\n if (geometry.trackKind !== 'video' || geometry.mode !== 'classifying') return 'unknown'\n if (\n block.mpus.size === 1 &&\n block.idxMin !== undefined &&\n block.idxMax !== undefined\n ) {\n // A block closes every D media units, so its frames are contiguous and\n // lie in [idxMax \u2212 D + 1, idxMin + D \u2212 1] of MPU m. Index 0 is the only\n // RAP of an MPU and G bounds its length.\n const span = geometry.interleaveUnits - 1\n if (block.idxMax - span >= 1 && block.idxMin + span <= geometry.gopFrames - 1) {\n return 'delta'\n }\n }\n return 'unknown'\n }\n\n #allowed(from: BlockClass, to: BlockClass): boolean {\n if (from === 'keyframe') return false\n if (from === 'unknown') return to === 'delta' || to === 'keyframe'\n if (from === 'delta') return to === 'keyframe'\n return false\n }\n\n #evidenceFor(block: BlockEvidence, to: BlockClass): ReclassificationEvidence {\n if (to === 'delta') return 'window'\n return block.rapSeen ? 'rap' : 'mpu-span'\n }\n}\n\n// \u2500\u2500\u2500 Stall vs conceal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface GapStreamIdentity {\n readonly packetId: number\n /** Non-empty catalog identity, or null when the producer has no track name. */\n readonly catalogTrackName: string | null\n}\n\n/** What the sink knows about one side of a released gap. */\nexport interface GapFrameEvidence {\n /**\n * Codec-aware RAP check: `MmtpContainerParser.isKeyframe(data, rapFlag,\n * codec)`, bitstream-only for recovered frames. Never the priority hint.\n */\n readonly rap: boolean\n /**\n * MPU_sequence_number of the frame. Omit it once the stream showed that\n * an MPU boundary does not bound its RAPs (`BlockClassifier.mpuBoundsRaps`):\n * same-MPU then no longer proves a gap lost only deltas.\n */\n readonly mpu?: number\n /** 0-based sample position within the MPU, when the geometry verifies it. */\n readonly idx?: number\n /** Authored presentation time in `timescale` ticks. */\n readonly pts?: bigint\n /** Authored sample duration in `timescale` ticks. */\n readonly durationTicks?: number\n /** The media timescale of `pts` and `durationTicks`. */\n readonly timescale?: number\n}\n\nexport type FrameGapAction = 'stall' | 'conceal'\n\nexport type FrameGapReason =\n | 'rap-lost'\n | 'rap-unknown'\n | 'discontinuity'\n | 'delta-lost'\n | 'rap-next'\n | 'audio-gap'\n | 'audio-discontinuity'\n\n/**\n * The decision attached to the first frame released after a gap\n * (`RoutedFrameMeta.gapBefore`). It is the same frozen object the manager\n * emits as its `gap` repair lifecycle event, so a sink that acts on the frame\n * and an observer that counts events see one fact.\n */\nexport interface FrameGapDecision {\n readonly version: 2\n readonly type: 'gap'\n readonly stream: GapStreamIdentity\n readonly trackKind: TrackKind\n readonly action: FrameGapAction\n readonly reason: FrameGapReason\n /** FEC blocks in the gap whose media was lost (failed or vanished). */\n readonly blocks: readonly number[]\n /** Missing video frames or audio access units, when exactly known. */\n readonly missingFrames: number | null\n /** Authored duration of the missing media in milliseconds, when known. */\n readonly missingMs: number | null\n /** Manager epoch of the stream when the decision was made. */\n readonly epoch: number\n}\n\nexport interface DecideGapInput {\n readonly stream: GapStreamIdentity\n readonly trackKind: TrackKind\n /** Last frame released before the gap. */\n readonly before: GapFrameEvidence\n /** First frame released after the gap. */\n readonly after: GapFrameEvidence\n /** The stream restarted (FEC SBN went backwards or the publisher reset). */\n readonly discontinuity: boolean\n readonly blocks: readonly number[]\n /**\n * The most media units the gap can hold: the FEC blocks spanned from the\n * frame before to the frame after, times D (media units per block,\n * ceil(interleaveDepthMs / unit duration), \u00A75.1). A block touches at most D\n * units whether it closes at D or at K symbols. Null when the span or D is\n * unknown. An audio PTS delta that implies more AUs than this is a clock\n * jump, not loss, and re-anchors instead of concealing every slot.\n */\n readonly maxMissingUnits: number | null\n readonly epoch: number\n}\n\nfunction durationMs(units: number, before: GapFrameEvidence): number | null {\n if (\n before.durationTicks === undefined ||\n before.timescale === undefined ||\n before.timescale <= 0\n ) {\n return null\n }\n return (units * before.durationTicks * 1000) / before.timescale\n}\n\n/**\n * Pure stall/conceal decision for the first frame released after a gap.\n *\n * Video stalls only when a RAP may be inside the gap: the frame after it is\n * not a RAP and either sits in another MPU (`rap-lost`) or the MPU cannot be\n * proven on both sides (`rap-unknown`), or the stream restarted\n * (`discontinuity`). A gap inside one MPU lost only deltas (`delta-lost`) and\n * a RAP after the gap resynchronises by itself (`rap-next`). Audio never\n * stalls: an exact gap is concealed per access unit (`audio-gap`), anything\n * else re-anchors at the next frame (`audio-discontinuity`).\n */\nexport function decideGap(input: DecideGapInput): FrameGapDecision {\n const { before, after } = input\n let action: FrameGapAction\n let reason: FrameGapReason\n let missingFrames: number | null = null\n\n if (input.trackKind === 'audio') {\n action = 'conceal'\n reason = 'audio-discontinuity'\n if (\n !input.discontinuity &&\n before.pts !== undefined &&\n after.pts !== undefined &&\n before.durationTicks !== undefined &&\n Number.isSafeInteger(before.durationTicks) &&\n before.durationTicks > 0\n ) {\n const auDuration = BigInt(before.durationTicks)\n const missingTicks = after.pts - before.pts - auDuration\n const bound = input.maxMissingUnits\n if (\n missingTicks > 0n &&\n missingTicks % auDuration === 0n &&\n bound !== null &&\n missingTicks / auDuration <= BigInt(bound)\n ) {\n reason = 'audio-gap'\n missingFrames = Number(missingTicks / auDuration)\n }\n }\n } else if (input.discontinuity) {\n action = 'stall'\n reason = 'discontinuity'\n } else if (after.rap) {\n action = 'conceal'\n reason = 'rap-next'\n } else if (before.mpu === undefined || after.mpu === undefined) {\n action = 'stall'\n reason = 'rap-unknown'\n } else if (before.mpu !== after.mpu) {\n action = 'stall'\n reason = 'rap-lost'\n } else {\n action = 'conceal'\n reason = 'delta-lost'\n if (before.idx !== undefined && after.idx !== undefined) {\n const missing = after.idx - before.idx - 1\n missingFrames = missing > 0 ? missing : null\n }\n }\n\n return Object.freeze({\n version: 2 as const,\n type: 'gap' as const,\n stream: Object.freeze({\n packetId: input.stream.packetId,\n catalogTrackName: input.stream.catalogTrackName,\n }),\n trackKind: input.trackKind,\n action,\n reason,\n blocks: Object.freeze([...input.blocks]),\n missingFrames,\n missingMs: missingFrames === null ? null : durationMs(missingFrames, before),\n epoch: input.epoch,\n })\n}\n", "/**\n * @mmt/container - MFU Fragment Reassembler\n *\n * Handles authored fragmented MFUs (Media Fragment Units) according to\n * ISO/IEC 23008-1:2023 Section 9.2.3.3. Fragmented runs are admitted in the\n * exact FI/fragment_counter countdown carried on the wire. FI=1 supplies the\n * intrinsic data-unit key. Every fragmented fragment is addressed by\n * (stream scope, MPU, packetSequenceNumber + fragmentCounter mod 2^32); a\n * continuation joins, parks under, or is stale against the one run holding\n * its address, and other runs are never consulted.\n *\n * Fragmentation Indicator (FI) values:\n * - 0: Complete MFU (single packet, no fragmentation)\n * - 1: First fragment\n * - 2: Middle fragment\n * - 3: Last fragment\n *\n * @packageDocumentation\n */\n\nimport { extractMFUPayload, isFecRepairPacket } from '@blockcast/mmt-base'\n\n/** Source transport admitted by the MMTP router. */\nexport type MmtpSourcePath = 'multicast' | 'moq'\n\n/**\n * Strict whole-frame delivery provenance, folded over EVERY contributing\n * fragment rather than the head fragment alone.\n *\n * This is deliberately NOT `source`. `source` answers a routing question \u2014\n * \"is this the reliable-MoQ copy that must not confirm multicast takeover?\" \u2014\n * and head-fragment-wins is the documented, deterministic answer to it.\n * Provenance answers an evidence question: \"did every byte of this frame\n * physically arrive over multicast?\" A frame with a multicast head and a\n * QUIC-delivered tail is a legitimate routing win for `source: 'multicast'`\n * and is emphatically NOT multicast-exclusive delivery.\n *\n * - 'multicast' \u2014 every contributing fragment was explicitly multicast.\n * The ONLY value that may be counted as physical-delivery evidence.\n * - 'mixed' \u2014 at least one contributing fragment arrived over MoQ.\n * - 'unknown' \u2014 no fragment arrived over MoQ, but at least one carried no\n * attribution at all. Absence is preserved as its own state rather than\n * defaulted, so a consumer cannot mistake \"never recorded\" for \"proven\".\n */\nexport type MmtpDeliveryProvenance = 'multicast' | 'mixed' | 'unknown'\n\n/**\n * Fold one fragment's `source` into a running provenance verdict.\n *\n * Fail-closed, order-independent and associative: 'mixed' absorbs everything,\n * a missing attribution degrades 'multicast' to 'unknown', and nothing ever\n * upgrades a verdict. Fold over an empty set is 'unknown' \u2014 no fragment ever\n * proved anything.\n */\nexport function foldDeliveryProvenance(\n verdict: MmtpDeliveryProvenance,\n source: MmtpSourcePath | undefined\n): MmtpDeliveryProvenance {\n if (verdict === 'mixed') return 'mixed'\n if (source === 'moq') return 'mixed'\n if (source === undefined) return 'unknown'\n return verdict\n}\n\n/**\n * One fragment's own provenance.\n *\n * An explicit `deliveryProvenance` wins: FEC-recovered fragments carry no\n * `source` (they were never delivered as themselves) but their block's symbol\n * provenance IS known, and the router supplies it. Falling back to `source`\n * keeps directly-delivered fragments working unchanged.\n */\nfunction fragmentProvenance(fragment: {\n source?: MmtpSourcePath\n deliveryProvenance?: MmtpDeliveryProvenance\n}): MmtpDeliveryProvenance {\n if (fragment.deliveryProvenance !== undefined) return fragment.deliveryProvenance\n if (fragment.source === 'moq') return 'mixed'\n if (fragment.source === 'multicast') return 'multicast'\n return 'unknown'\n}\n\n/** Strict provenance for a complete fragment set. */\nexport function deliveryProvenanceOf(\n fragments: readonly {\n source?: MmtpSourcePath\n deliveryProvenance?: MmtpDeliveryProvenance\n }[]\n): MmtpDeliveryProvenance {\n if (fragments.length === 0) return 'unknown'\n let verdict: MmtpDeliveryProvenance = 'multicast'\n for (const fragment of fragments) {\n const own = fragmentProvenance(fragment)\n verdict =\n verdict === 'mixed' || own === 'mixed'\n ? 'mixed'\n : verdict === 'unknown' || own === 'unknown'\n ? 'unknown'\n : 'multicast'\n }\n return verdict\n}\n\n/**\n * MFU Fragment for reassembly\n */\nexport interface MFUFragment {\n fragmentationIndicator: number\n mpuSequenceNumber: number\n fragmentCounter: number\n data: Uint8Array\n timestamp: bigint\n /** Random Access Point flag from MMTP header - indicates keyframe */\n rapFlag: boolean\n /** MFU DU priority. FFmpeg marks random-access samples with high priority. */\n priority?: number\n sampleNumber?: number\n movieFragmentSequenceNumber?: number\n sampleOffset?: number\n /** Non-timed MFU Item_ID from the 4-byte DU header. */\n itemId?: number\n /** Authored u32 MMTP packet_sequence_number, including on recovered packets. */\n packetSequenceNumber?: number\n /** True when this accepted source packet was reconstructed by FEC. */\n recovered?: boolean\n /** Transport that supplied this accepted fragment. Absent for recovered data. */\n source?: MmtpSourcePath\n /**\n * Explicit provenance for this fragment, overriding what `source` implies.\n * Set by the router for FEC-recovered fragments, which have no `source` of\n * their own but whose FEC block's symbol provenance is known.\n */\n deliveryProvenance?: MmtpDeliveryProvenance\n /** Immutable catalog identity supplied by the owning ingress subscription. */\n catalogTrackName?: string\n /** Physical MMTP stream identity when no catalog identity is available. */\n packetId?: number\n}\n\n/**\n * Reassembled MFU result\n */\nexport interface ReassembledMFU {\n mpuSequenceNumber: number\n /** MMTP packet_sequence_number of the first fragment in delivery order. */\n packetSequenceNumber?: number\n /** Every authoritative MMTP source-packet sequence that formed this MFU. */\n packetSequenceNumbers?: number[]\n /** Subset of packetSequenceNumbers reconstructed by FEC. */\n repairedPacketSequenceNumbers?: number[]\n data: Uint8Array\n timestamp: bigint\n fragmentCount: number\n /** Random Access Point flag - indicates keyframe (from first fragment) */\n rapFlag: boolean\n /** MFU DU priority from the first fragment that carried it. */\n priority?: number\n sampleNumber?: number\n movieFragmentSequenceNumber?: number\n /**\n * This DU's authored base byte offset within its sample (moqcast #37). 0\n * for a whole-sample DU; nonzero identifies one subsample DU among\n * siblings partitioning an oversized sample. Constant across the DU's own\n * fragments \u2014 feed into a sample combiner to reassemble the full sample.\n */\n sampleOffset?: number\n /**\n * Transport that supplied the accepted FI=1 head fragment (or the complete\n * FI=0 frame). This is the deterministic winner for mixed-path assembly.\n *\n * ROUTING ONLY. It describes one fragment, so it is not evidence about the\n * whole frame \u2014 read {@link ReassembledMFU.deliveryProvenance} for that.\n */\n source?: MmtpSourcePath\n /**\n * Strict provenance folded over every contributing fragment. Only\n * 'multicast' proves multicast-exclusive delivery of this frame.\n */\n deliveryProvenance: MmtpDeliveryProvenance\n /** Catalog identity from the accepted FI=1 head fragment, or FI=0 frame. */\n catalogTrackName?: string\n}\n\n/**\n * Reassembler statistics\n */\nexport interface ReassemblerStats {\n totalFragments: number\n reassembledMFUs: number\n /** Fragments rejected by strict authored DU-state validation. */\n rejectedFragments: number\n /** Incomplete MFUs discarded after their configured deadline. */\n timedOutMFUs: number\n /** Incomplete MFUs discarded to enforce the configured memory bound. */\n evictedMemoryLimit: number\n outOfOrderFragments: number\n}\n\nexport const MFU_REASSEMBLY_ERROR_CODES = [\n 'invalid-fragment-state',\n 'missing-data-unit-identity',\n 'missing-packet-sequence',\n 'conflicting-data-unit-identity',\n 'overlapping-data-unit',\n 'fragment-counter-mismatch',\n 'data-unit-identity-mismatch',\n 'offset-overflow',\n 'active-data-unit-limit',\n 'incomplete-terminal-fragment',\n 'ambiguous-continuation',\n] as const\n\nexport type MfuReassemblyErrorCode = (typeof MFU_REASSEMBLY_ERROR_CODES)[number]\n\nconst mfuReassemblyErrorCodes = new Set<string>(MFU_REASSEMBLY_ERROR_CODES)\n\nexport function isMfuReassemblyErrorCode(value: unknown): value is MfuReassemblyErrorCode {\n return typeof value === 'string' && mfuReassemblyErrorCodes.has(value)\n}\n\nexport class MfuReassemblyError extends Error {\n readonly name = 'MfuReassemblyError'\n\n constructor(\n readonly code: MfuReassemblyErrorCode,\n message: string\n ) {\n super(message)\n }\n}\n\n/**\n * Identity-free test for a reassembly rejection.\n *\n * Consumers that inline `@blockcast/mmt-container` more than once (a component\n * bundle whose `@blockcast/*` dependencies each carry their own copy) end up\n * with several distinct `MfuReassemblyError` classes. An `instanceof` check\n * against one copy then misses rejections raised by another, and a per-fragment\n * rejection escapes as an uncaught throw that aborts the rest of the packet's\n * processing. The wire contract of a rejection is its name plus one of the\n * closed error codes, so that is what this guard checks.\n */\nexport function isMfuReassemblyError(error: unknown): error is MfuReassemblyError {\n if (error instanceof MfuReassemblyError) return true\n if (typeof error !== 'object' || error === null) return false\n return (\n Reflect.get(error, 'name') === 'MfuReassemblyError' &&\n isMfuReassemblyErrorCode(Reflect.get(error, 'code'))\n )\n}\n\nfunction assembledPacketSequences(fragments: readonly MFUFragment[]): {\n packetSequenceNumbers?: number[]\n repairedPacketSequenceNumbers?: number[]\n} {\n const packetSequenceNumbers: number[] = []\n const repairedPacketSequenceNumbers: number[] = []\n const seen = new Set<number>()\n const repaired = new Set<number>()\n for (const fragment of fragments) {\n const sequence = fragment.packetSequenceNumber\n if (typeof sequence !== 'number' || !Number.isSafeInteger(sequence) || sequence < 0 || sequence > 0xffffffff) {\n return {}\n }\n if (!seen.has(sequence)) {\n seen.add(sequence)\n packetSequenceNumbers.push(sequence)\n }\n if (fragment.recovered === true && !repaired.has(sequence)) {\n repaired.add(sequence)\n repairedPacketSequenceNumbers.push(sequence)\n }\n }\n return { packetSequenceNumbers, repairedPacketSequenceNumbers }\n}\n\nexport interface PendingMfuDebug {\n key: string\n fragments: number\n fi0: number\n fi1: number\n fi2: number\n fi3: number\n firstFi: number\n lastFi: number\n firstOrder: number\n lastOrder: number\n minCounter: number\n maxCounter: number\n minOffset?: number\n maxOffset?: number\n movieFragmentSequenceNumber?: number\n sampleNumber?: number\n mpuSequenceNumber?: number\n sortedHead: Array<{\n fi: number\n counter: number\n order: number\n seq?: number\n offset?: number\n }>\n sortedTail: Array<{\n fi: number\n counter: number\n order: number\n seq?: number\n offset?: number\n }>\n}\n\nexport interface MFUReassemblerOptions {\n /** Timeout (ms) for incomplete MFUs. Derive from frame duration or FEC interleaveDepthMs.\n * Pass null for live clock-free operation; memory remains bounded by maxBuffered. */\n timeoutMs: number | null\n /** Max concurrent incomplete MFUs, including continuations awaiting FI=1. */\n maxBuffered?: number\n}\n\n/** Reassembly surface consumed by MmtpContainerParser and MmtpRouter.\n * Implemented by both the TypeScript engine and the shared\n * Rust/WASM IFC adapter. */\nexport interface MfuReassemblerBackend {\n timeoutMs: number | null\n readonly stats: ReassemblerStats\n onDeferredAssembly?: (result: ReassembledMFU) => void\n /** Returns null only for a valid incomplete DU; strict rejection throws\n * `MfuReassemblyError`. */\n addFragment(fragment: MFUFragment): ReassembledMFU | null\n flush(): void\n resetStream(catalogTrackName: string): void\n reset(): void\n readonly pendingCount: number\n getStatus(): {\n bufferedMFUs: number\n pendingFragments: number\n pendingSamples: PendingMfuDebug[]\n stats: ReassemblerStats\n }\n}\n\nfunction streamScope(fragment: MFUFragment): readonly [kind: string, identity: string | number | null] {\n if (fragment.catalogTrackName !== undefined) return ['catalog', fragment.catalogTrackName]\n if (fragment.packetId !== undefined) return ['packet', fragment.packetId]\n return ['unspecified', null]\n}\n\nfunction isU32(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 0xffffffff\n}\n\n/**\n * Wire address of one fragmented data unit: the physical stream scope, the\n * MPU, and `packetSequenceNumber + fragmentCounter` mod 2^32. Under the\n * contiguous-source producer profile every fragment of one data unit occupies\n * consecutive packet sequence numbers on its packet ID while\n * `fragmentCounter` counts the payloads that follow, so the sum is the\n * sequence number of the data unit's last fragment: equal for every fragment\n * of one data unit and distinct between data units of one MPU. Transport\n * association only; objectKey remains the intrinsic DU identity.\n */\nfunction dataUnitAddress(fragment: MFUFragment, packetSequenceNumber: number): string {\n return JSON.stringify([\n ...streamScope(fragment),\n fragment.mpuSequenceNumber,\n (packetSequenceNumber + fragment.fragmentCounter) >>> 0,\n ])\n}\n\nfunction hasAnySampleIdentityField(fragment: MFUFragment): boolean {\n return (\n fragment.movieFragmentSequenceNumber !== undefined ||\n fragment.sampleNumber !== undefined ||\n fragment.sampleOffset !== undefined\n )\n}\n\ntype IdentifiedSampleFragment = MFUFragment &\n Required<Pick<MFUFragment, 'movieFragmentSequenceNumber' | 'sampleNumber' | 'sampleOffset'>>\n\nfunction hasSampleIdentity(fragment: MFUFragment): fragment is IdentifiedSampleFragment {\n return (\n isU32(fragment.movieFragmentSequenceNumber) &&\n fragment.movieFragmentSequenceNumber > 0 &&\n isU32(fragment.sampleNumber) &&\n fragment.sampleNumber > 0 &&\n isU32(fragment.sampleOffset)\n )\n}\n\nfunction hasValidFragmentHeader(fragment: MFUFragment): boolean {\n const { fragmentationIndicator: fi, fragmentCounter: counter } = fragment\n if (!Number.isInteger(fi) || fi < 0 || fi > 3) return false\n if (!Number.isInteger(counter) || counter < 0 || counter > 0xff) return false\n return fi === 0 || fi === 3 ? counter === 0 : counter > 0\n}\n\nfunction fragmentCounterOrder(fragment: MFUFragment): number {\n return 0xff - fragment.fragmentCounter\n}\n\nfunction objectKey(fragment: MFUFragment): string {\n const scope = streamScope(fragment)\n if (fragment.itemId !== undefined) {\n return JSON.stringify([...scope, fragment.mpuSequenceNumber, 'item', fragment.itemId])\n }\n if (hasSampleIdentity(fragment)) {\n return JSON.stringify([\n ...scope,\n fragment.mpuSequenceNumber,\n 'sample',\n fragment.movieFragmentSequenceNumber,\n fragment.sampleNumber,\n // The DU's authored base offset (moqcast #37): sibling subsample DUs\n // of one sample get distinct keys instead of colliding. Only ever\n // called with the unmutated FI=1 (or FI=0) fragment, whose\n // sampleOffset is that authored base -- never the accumulating\n // per-continuation value written by #associateDataUnit.\n fragment.sampleOffset,\n ])\n }\n return JSON.stringify([...scope, fragment.mpuSequenceNumber, 'unidentified'])\n}\n\nfunction ordersByOffset(fragments: MFUFragment[]): boolean {\n return fragments.every(fragment => hasSampleIdentity(fragment))\n}\n\ntype ActiveDataUnit = {\n /** Wire address every continuation of this data unit carries. */\n address: string\n bufferKey: string\n nextFragmentCounter: number\n /** Monotonic admission ordinal of this data unit's slot. Never a clock. */\n admissionSeq: number\n catalogTrackName?: string\n} & (\n | {\n kind: 'sample'\n movieFragmentSequenceNumber: number\n sampleNumber: number\n nextOffset: number\n }\n | {\n kind: 'item'\n itemId: number\n }\n)\n\ninterface DataUnitAssociation {\n bufferKey: string\n first: boolean\n terminal: boolean\n}\n\ninterface PendingContinuationRun {\n fragments: Map<number, MFUFragment>\n startedAt: number\n /** Monotonic admission ordinal of this data unit's slot. Never a clock. */\n admissionSeq: number\n timeoutId?: ReturnType<typeof setTimeout>\n}\n\ninterface BufferTimeoutState {\n startedAt: number\n timeoutId?: ReturnType<typeof setTimeout>\n}\n\nfunction checkedSampleEnd(offset: number, byteLength: number): number | null {\n const end = offset + byteLength\n return Number.isSafeInteger(end) && end <= 0xffffffff ? end : null\n}\n\n/**\n * MFU Reassembler with bounded reordered-fragment association\n * Based on ISO/IEC 23008-1:2023 Section 9.2.3.3\n */\n\n/**\n * Completed-address window, as a multiple of maxBuffered. A late continuation\n * is refused as stale while its data unit's address is within this window and\n * parks as a headless data unit once the address has been forgotten. The\n * value is a cross-engine invariant shared with the Rust reassembler\n * (mmt-core COMPLETED_ADDRESS_WINDOW_MULTIPLE); change both together.\n */\nexport const COMPLETED_ADDRESS_WINDOW_MULTIPLE = 2\n\n/**\n * Cross-engine anchors for the clock-free eviction guard. The bound itself is\n * `maxBuffered`, a host parameter \u2014 these fix the one scenario both regression\n * corpora drive so the Rust and TypeScript guards assert the same boundary\n * rather than each deriving its own. Shared with the Rust reassembler\n * (`EVICTION_GUARD_MAX_BUFFERED` / `EVICTION_GUARD_DATA_UNITS` in\n * `mmt-core/src/reassembler/policy/mmt.rs`); change both together.\n *\n * `EVICTION_GUARD_DATA_UNITS` headless data units admitted against a bound of\n * `EVICTION_GUARD_MAX_BUFFERED` must evict exactly the difference.\n */\nexport const EVICTION_GUARD_MAX_BUFFERED = 4\nexport const EVICTION_GUARD_DATA_UNITS = 12\n\n/**\n * Does a parked continuation queue hold a `maxBuffered` slot of its own?\n *\n * One rule, four call sites. Two read the bound: the queue that is *counted*\n * toward it (`#unopenedParkedRunCount`) is exactly the queue that is *offered*\n * to eviction (`#evictLeastRecentlyAdmitted`). Two gate admission against it:\n * `#parkContinuation` and the FI=1 arm of `#associateDataUnit` each ask whether\n * this address is already charged before moving the bound. Stating the rule once\n * is what keeps those four answers from drifting apart \u2014 a queue that charges a\n * slot no eviction can reclaim wedges the receiver, because the bound then\n * refuses every new data unit forever with `active-data-unit-limit`.\n *\n * Two exclusions, for two different reasons:\n *\n * - **An open run owns the address.** The data unit is already charged through\n * that run, so its queue costs nothing further.\n * - **The queue is empty.** It holds nothing, so it charges nothing and offers\n * nothing.\n *\n * The emptiness half is belt-and-braces, not load-bearing. Both engines discard\n * an emptied queue eagerly \u2014 `#deletePendingRun` here on every drain exit,\n * `discard_empty_parked_run` in Rust \u2014 and `#parkContinuation` always inserts\n * into a queue it just created, so an empty queue is not observable today.\n * Correctness must not depend on that invariant holding in all three places per\n * engine: routing every site that asks the question through this one predicate\n * is what keeps the engines agreeing if a future edit ever misses one of them.\n *\n * Scope: this rule decides *slot accounting* \u2014 counted, offered, admitted. A\n * queue carries two further per-slot facts, its admission ordinal and its\n * retention anchor (`startedAt` plus the timer armed from it), and both must\n * move when an empty queue is charged again. `#parkContinuation` resets them\n * explicitly; Rust gets the same contract structurally, by deriving each from\n * the queue's live entries. Same contract, two mechanisms.\n *\n * The *reap schedule* asks the same question, and every site that arms a\n * parked queue reaches one function, `#armHeadlessPendingTimeout`, which takes\n * the test through this rule: `set timeoutMs` calls it for every queue when\n * the policy changes, `#parkContinuation` calls it after a created or\n * re-anchored queue holds its first fragment, and a run closing without\n * completing calls it for the queue it leaves behind. It never arms a timer\n * for an empty queue, because a timeout reports a data unit that failed to\n * complete in time and an empty queue holds no such data unit. Charging one\n * would record an MFU that never existed. No site installs a parked-queue\n * timer of its own, so neither the slot rule nor the `startedAt` anchoring can\n * drift one site at a time.\n *\n * That is the answer Rust reaches structurally: `next_timeout_at` derives\n * candidate deadlines from the queue's live entries, so an empty queue yields\n * none and no sweep is ever scheduled on its account, and `cleanup_timeouts`\n * retains by `parked_run_holds_a_slot`, so a sweep arriving for any *other*\n * queue leaves an empty one in place rather than charging\n * `record_policy_timeout` for it. Both engines therefore agree on when the\n * reap is scheduled, and the recorded statistic is unchanged for every queue\n * that really did hold a data unit: `#onPendingTimeout` charges\n * `stats.timedOutMFUs`, the shape Rust's `record_policy_timeout` produces, and\n * takes the same slot test before charging, so the statistic cannot be\n * recorded for an empty queue whatever order the arming sites run in.\n *\n * The trade this makes, in both engines: a queue that is emptied without being\n * discarded is invisible to the reap schedule, to the slot count, and to\n * eviction, so nothing reclaims it until `reset()` clears the policy state.\n * Disposal of an emptied queue therefore rests entirely on the eager discard\n * sites (`#deletePendingRun` here, `discard_empty_parked_run` in Rust), where\n * it is housekeeping rather than an expiry; a drain exit that skips that call\n * leaks the queue for the life of the reassembler rather than charging a\n * phantom timeout for it. Converged under BLO-32420. The arming half of this\n * rule is pinned in `mfu-reassembler-eviction.test.ts`: a headless queue that\n * holds fragments is armed by `set timeoutMs` and by its run closing, and\n * disarmed by `timeoutMs = null`. The emptiness half is Rust-only, because\n * `#pendingContinuations` is unreachable from this package's tests.\n *\n * The public counters are a separate matter and *are* governed: `pendingCount`\n * \u2014 and through it `getStatus().bufferedMFUs` \u2014 delegates to\n * `#unopenedParkedRunCount`, exactly as Rust's `buffered_count` is built on\n * `unopened_parked_run_count`. Filtering either on the open run alone makes the\n * engines report different numbers for the same state out of a host-observable\n * API.\n *\n * Twin of `parked_run_holds_a_slot` in\n * `mmt-core/src/reassembler/policy/mmt.rs`; change both together.\n */\nexport function parkedRunHoldsASlot(hasOpenRun: boolean, fragmentCount: number): boolean {\n return !hasOpenRun && fragmentCount > 0\n}\n\nexport class MFUReassembler {\n // Fragment buffers and active runs use the authored MFU DU identity.\n // Continuations may wait in one counter-indexed queue per data-unit address\n // until FI=1 supplies that identity. The address is transport association\n // only; the object key stays the intrinsic DU identity.\n #buffers: Map<string, MFUFragment[]> = new Map()\n #timeouts: Map<string, BufferTimeoutState> = new Map()\n #activeDataUnits: Map<string, ActiveDataUnit> = new Map()\n #pendingContinuations: Map<string, PendingContinuationRun> = new Map()\n #emitted: Set<string> = new Set() // same sample keys, so FEC-recovered duplicate heads are dropped\n #emittedOrder: string[] = []\n // Addresses of emitted data units. A continuation addressed to one of them\n // is a late copy of a fragment the run already consumed (FEC recovery\n // supplied it first), so it is rejected as stale rather than parked as a\n // headless data unit that would hold a slot until reset. Bounded at\n // COMPLETED_ADDRESS_WINDOW_MULTIPLE x maxBuffered, the same window the Rust\n // engine keeps, so both engines forget an address after the same number of\n // completions. (#emitted is a separate, per-engine window: this engine keeps\n // maxBuffered sample keys where Rust keeps 2x; the completed set does not\n // derive from it.)\n #completed: Set<string> = new Set()\n #completedOrder: string[] = []\n #timeoutMs: number | null\n #maxBuffered: number\n /**\n * Monotonic admission ordinal. Counts slot admissions, never reads a clock,\n * so eviction order survives on hosts that run `timeoutMs: null`.\n */\n #nextAdmissionSeq = 0\n\n /** Statistics for debugging */\n readonly stats: ReassemblerStats = {\n totalFragments: 0,\n reassembledMFUs: 0,\n rejectedFragments: 0,\n timedOutMFUs: 0,\n evictedMemoryLimit: 0,\n outOfOrderFragments: 0,\n }\n\n constructor(options: MFUReassemblerOptions) {\n if (options.timeoutMs !== null && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) {\n throw new Error('[MFUReassembler] timeoutMs must be > 0 (derive from frame duration or FEC config)')\n }\n const maxBuffered = options.maxBuffered ?? 100\n if (!Number.isInteger(maxBuffered) || maxBuffered <= 0) {\n throw new Error('[MFUReassembler] maxBuffered must be a positive integer')\n }\n this.#timeoutMs = options.timeoutMs\n this.#maxBuffered = maxBuffered\n }\n\n /** Update the expiry policy for all retained and future incomplete MFUs. */\n set timeoutMs(ms: number | null) {\n if (ms !== null && (!Number.isFinite(ms) || ms <= 0)) {\n throw new Error('[MFUReassembler] timeoutMs must be > 0 (derive from frame duration or FEC config)')\n }\n this.#timeoutMs = ms\n const now = Date.now()\n for (const [bufferKey, state] of this.#timeouts) {\n if (state.timeoutId !== undefined) clearTimeout(state.timeoutId)\n delete state.timeoutId\n if (ms !== null) {\n state.timeoutId = setTimeout(() => this.#onTimeout(bufferKey), Math.max(0, state.startedAt + ms - now))\n }\n }\n // Parked queues re-arm through the one arming function, which reads the\n // new policy from `#timeoutMs` (assigned above) and keeps each queue on its\n // own `startedAt` anchor. Clearing first releases any timer armed under the\n // old policy so the helper's already-armed guard cannot skip the re-arm.\n for (const address of this.#pendingContinuations.keys()) {\n this.#clearPendingTimeout(address)\n this.#armHeadlessPendingTimeout(address)\n }\n }\n get timeoutMs(): number | null {\n return this.#timeoutMs\n }\n\n /**\n * Add a fragment and return reassembled MFU if complete\n */\n addFragment(fragment: MFUFragment): ReassembledMFU | null {\n this.stats.totalFragments++\n\n if (!hasValidFragmentHeader(fragment)) {\n this.#reject('invalid-fragment-state', 'invalid MFU fragmentation indicator or counter')\n }\n if (fragment.itemId !== undefined && hasAnySampleIdentityField(fragment)) {\n this.#reject('conflicting-data-unit-identity', 'MFU carries timed and non-timed DU identities')\n }\n\n const { mpuSequenceNumber, fragmentationIndicator } = fragment\n\n // A complete FT=2 data unit still requires its intrinsic DU identity.\n // The transport parser strips the deterministic timed/non-timed DU header\n // before calling this layer; direct callers must provide the same identity.\n // Don't check or update #emitted: in MMTP over MoQ, multiple complete MFUs\n // share the same mpuSequenceNumber (all frames in a GOP have the same mpuSeq).\n // Blocking FI=0 would prevent keyframe fragment assembly at the same mpuSeq.\n if (fragmentationIndicator === 0) {\n const sampleIdentity = hasSampleIdentity(fragment)\n const hasSampleFields = hasAnySampleIdentityField(fragment)\n const hasItemIdentity = fragment.itemId !== undefined\n if (!sampleIdentity && !hasItemIdentity) {\n this.#reject('missing-data-unit-identity', 'complete MFU has no intrinsic DU identity')\n }\n if (hasSampleFields && !sampleIdentity) {\n this.#reject('missing-data-unit-identity', 'complete MFU has an incomplete sample identity')\n }\n if (hasItemIdentity && !isU32(fragment.itemId)) {\n this.#reject('missing-data-unit-identity', 'complete MFU has an invalid Item_ID')\n }\n this.stats.reassembledMFUs++\n return {\n mpuSequenceNumber,\n packetSequenceNumber: fragment.packetSequenceNumber,\n ...assembledPacketSequences([fragment]),\n data: fragment.data,\n timestamp: fragment.timestamp,\n fragmentCount: 1,\n rapFlag: fragment.rapFlag,\n priority: fragment.priority,\n sampleNumber: fragment.sampleNumber,\n movieFragmentSequenceNumber: fragment.movieFragmentSequenceNumber,\n sampleOffset: fragment.sampleOffset,\n source: fragment.source,\n deliveryProvenance: deliveryProvenanceOf([fragment]),\n catalogTrackName: fragment.catalogTrackName,\n }\n }\n\n const sequence = fragment.packetSequenceNumber\n if (!isU32(sequence)) {\n this.#reject('missing-packet-sequence', 'fragmented MFU has no MMTP packet sequence number')\n }\n const address = dataUnitAddress(fragment, sequence)\n\n if (fragmentationIndicator !== 1) {\n // Attribution is an exact per-data-unit lookup. The continuation joins\n // the one open run holding its address when its counter is that run's\n // next. It is stale when the run has already counted past the counter,\n // or when the data unit was already emitted: the position was accepted\n // once, so no run can ever take it again. Otherwise (no open run at this\n // address, or the run has not reached the counter yet) it parks under\n // its own address. Other runs' counters are never consulted.\n const open = this.#openRunAt(address)\n if (open === undefined) {\n if (this.#completed.has(address)) {\n this.#reject('fragment-counter-mismatch', 'MFU continuation names a data unit that already completed')\n }\n this.#parkContinuation(address, fragment)\n return null\n }\n const [runKey, active] = open\n if (active.nextFragmentCounter > fragment.fragmentCounter) {\n this.#parkContinuation(address, fragment)\n return null\n }\n if (active.nextFragmentCounter < fragment.fragmentCounter) {\n this.#reject('fragment-counter-mismatch', 'MFU continuation is behind the authored fragment-counter countdown')\n }\n const result = this.#addAssociatedFragment(fragment, runKey, address)\n if (result !== null) return result\n return this.#drainPendingContinuations(address, runKey)\n }\n\n const runKey = objectKey(fragment)\n if ((hasSampleIdentity(fragment) || isU32(fragment.itemId)) && this.#emitted.has(runKey)) {\n // The identity already emitted, so this first fragment is a duplicate\n // and is dropped. Queues under other addresses belong to other data\n // units and are untouched.\n return null\n }\n if (this.#activeDataUnits.has(runKey)) {\n this.#reject('overlapping-data-unit', 'fragmented MFU overlaps an active data unit')\n }\n // Two heads with one address are one data unit delivered twice or an\n // identity clash on the wire. The established run keeps the address.\n if (this.#openRunAt(address) !== undefined) {\n this.#reject('overlapping-data-unit', 'fragmented MFU opens on an address held by an open data unit')\n }\n const pending = this.#pendingContinuations.get(address)\n // A parked continuation of this data unit cannot carry a counter at or\n // above its own head's countdown.\n if (pending !== undefined && [...pending.fragments.keys()].some(counter => counter >= fragment.fragmentCounter)) {\n this.#reject(\n 'fragment-counter-mismatch',\n 'queued continuation counter is not below the authored first-fragment counter'\n )\n }\n\n // The run's deadline starts at the earliest arrival of its own fragments.\n const result = this.#addAssociatedFragment(fragment, runKey, address, pending?.startedAt)\n this.#clearPendingTimeout(address)\n if (result !== null) return result\n return this.#drainPendingContinuations(address, runKey)\n }\n\n #addAssociatedFragment(\n fragment: MFUFragment,\n runKey: string,\n address: string,\n startedAt?: number\n ): ReassembledMFU | null {\n const association = this.#associateDataUnit(fragment, runKey, address)\n const { bufferKey, first, terminal } = association\n\n let fragments = this.#buffers.get(bufferKey)\n if (first) {\n if (fragments !== undefined) {\n this.#reject('overlapping-data-unit', 'intrinsic data-unit buffer already exists')\n }\n fragments = []\n this.#buffers.set(bufferKey, fragments)\n } else if (fragments === undefined) {\n throw new Error('[MFUReassembler] active data unit has no fragment buffer')\n }\n\n // A lossy-path deadline is measured from the first received fragment,\n // including a continuation that arrived before FI=1. Reliable paths are\n // clock-free and never install this timer.\n if (first) this.#setTimeout(bufferKey, startedAt)\n fragments.push(fragment)\n\n if (!terminal) return null\n const result = this.#tryReassemble(bufferKey)\n this.#activeDataUnits.delete(runKey)\n if (result === null) {\n this.#clearTimeout(bufferKey)\n this.#buffers.delete(bufferKey)\n this.#reject('incomplete-terminal-fragment', 'validated terminal fragment did not complete its data unit')\n }\n this.#markCompleted(address)\n return result\n }\n\n #parkContinuation(address: string, fragment: MFUFragment): void {\n let pending = this.#pendingContinuations.get(address)\n const open = this.#openRunAt(address)\n // A data unit is charged one slot: through its open run, or through its\n // queue while no run is open. Parking under an address that is already\n // charged adds nothing to the bound, so admission is judged by what this\n // fragment would add, not by the total alone. The queue half routes through\n // `parkedRunHoldsASlot` rather than restating it, so this gate cannot drift\n // from the sites that count and evict by it: an empty queue charges nothing\n // here for the same reason it is offered nothing there. Gating on\n // `pending === undefined` instead would skip the bound entirely for a queue\n // that exists but holds nothing, overshooting `maxBuffered` by one while\n // Rust evicts \u2014 the cross-engine split BLO-31813 exists to close.\n const alreadyCharged =\n open !== undefined || parkedRunHoldsASlot(open !== undefined, pending?.fragments.size ?? 0)\n if (!alreadyCharged && this.#incompleteRunCount() >= this.#maxBuffered) {\n // `open` and `pending` are captured above, and eviction can delete from\n // `#pendingContinuations` \u2014 so both are read across a mutation. Neither\n // goes stale, and the predicate is what makes that true: eviction only\n // runs when `open === undefined`, and an empty queue is not a candidate,\n // so the record the `else if` below writes to cannot be the one released\n // here. Re-read both if that rule is ever loosened.\n if (!this.#evictLeastRecentlyAdmitted()) {\n this.#reject('active-data-unit-limit', 'active MFU data-unit limit reached')\n }\n }\n // Both branches below leave the timer to `#armHeadlessPendingTimeout`,\n // called once the fragment is in, so the queue is armed only while it\n // holds a data unit and through the same slot rule as every other site.\n let armAfterInsert = false\n if (pending === undefined) {\n // A queue opened under an already-charged address inherits that slot's\n // ordinal, so a run does not renew its admission by parking a fragment.\n // The arm below is a no-op under an open run: the run's own deadline\n // covers the data unit, and `#armHeadlessPendingTimeout` tests for it.\n pending = {\n fragments: new Map(),\n startedAt: Date.now(),\n admissionSeq: open !== undefined ? open[1].admissionSeq : this.#nextAdmissionSeq++,\n }\n this.#pendingContinuations.set(address, pending)\n armAfterInsert = true\n } else if (!alreadyCharged) {\n // A queue that exists but holds nothing charged no slot, so this fragment\n // is a fresh admission \u2014 and every per-slot fact the record still carries\n // describes an occupancy that ended when its last entry left. Rust derives\n // all of them from the queue's *live* entries, so re-parking moves them\n // together; TypeScript caches them as fields written once at creation, so\n // they have to be reset together here or the queue goes transparent to the\n // bound while staying stale on its deadline:\n //\n // - Ordinal. `slot_admission_seq` mins over the entries and an empty queue\n // has none, so Rust mints a fresh one. Decides victim ordering.\n // - Retention anchor. `cleanup_timeouts` retains on `min(received_at)`,\n // which a re-parked entry moves forward. Left stale, `#onPendingTimeout`\n // can discard the queue on the emptied queue's deadline while it holds a\n // fragment that was just charged a slot \u2014 and that may have evicted\n // another data unit to get it.\n // - Armed timer. `#armHeadlessPendingTimeout` anchors on `startedAt` and\n // arms only a queue that holds a fragment, so the re-arm runs once this\n // fragment is in (below), where the creation branch arms too, rather\n // than here, where the queue is still empty and the call would arm\n // nothing. Any timer left on the record is cleared first so the queue\n // cannot keep running on the old anchor.\n //\n // `Date.now()` here is the same retention anchor the queue-creation branch\n // above already reads, not a clock in the admission order: the ordinal\n // stays a monotonic sequence, so eviction remains clock-free per PR #93.\n //\n // Unreachable while every drain exit discards an emptied queue; pinned so\n // the engines cannot pick different victims \u2014 or different expiries \u2014 if\n // one ever misses a discard site.\n pending.admissionSeq = this.#nextAdmissionSeq++\n pending.startedAt = Date.now()\n this.#clearPendingTimeout(address)\n armAfterInsert = true\n }\n // The same data unit naming the same countdown position twice: the first\n // parked copy is kept.\n if (pending.fragments.has(fragment.fragmentCounter)) {\n this.#reject('fragment-counter-mismatch', 'duplicate queued continuation counter would replace retained media')\n }\n pending.fragments.set(fragment.fragmentCounter, fragment)\n // A created or re-anchored queue holds its first fragment only now, so this\n // is the first point at which `#armHeadlessPendingTimeout` arms it. With\n // `startedAt` set one tick earlier the deadline it computes is the full\n // `timeoutMs`, the same schedule a raw timer armed at creation would have\n // kept. The duplicate check above cannot reject on this path: both branches\n // that set the flag run exactly when the queue is empty.\n if (armAfterInsert) this.#armHeadlessPendingTimeout(address)\n }\n\n #drainPendingContinuations(address: string, runKey: string): ReassembledMFU | null {\n while (true) {\n const active = this.#activeDataUnits.get(runKey)\n const pending = this.#pendingContinuations.get(address)\n if (active === undefined) {\n // The run closed. Its queue holds only this data unit's fragments;\n // drop it once empty so it is not counted as a headless data unit.\n if (pending?.fragments.size === 0) this.#deletePendingRun(address)\n return null\n }\n const fragment = pending?.fragments.get(active.nextFragmentCounter)\n if (fragment === undefined) {\n if (pending?.fragments.size === 0) this.#deletePendingRun(address)\n return null\n }\n pending!.fragments.delete(active.nextFragmentCounter)\n if (pending!.fragments.size === 0) this.#deletePendingRun(address)\n const result = this.#addAssociatedFragment(fragment, runKey, address)\n if (result !== null) {\n return result\n }\n }\n }\n\n /** Parked data units that hold a `maxBuffered` slot of their own. Membership\n * is `parkedRunHoldsASlot`, the same rule #evictLeastRecentlyAdmitted offers\n * as candidates, so nothing can be counted here that eviction cannot reclaim.\n *\n * One site, two callers \u2014 `#incompleteRunCount` and `pendingCount` \u2014 mirroring\n * Rust's `unopened_parked_run_count`, which `incomplete_run_count` and\n * `buffered_count` both delegate to. Both callers used to inline this loop\n * verbatim, so converging the *rule* moved one copy and left the other as the\n * divergence (Ally's review of #400). The rule can no longer drift now that\n * every site routes through `parkedRunHoldsASlot`; keeping the loop itself at\n * one site is what stops the scaffolding drifting the next time this question\n * grows an argument. */\n #unopenedParkedRunCount(): number {\n let count = 0\n for (const [address, pending] of this.#pendingContinuations) {\n if (parkedRunHoldsASlot(this.#openRunAt(address) !== undefined, pending.fragments.size)) {\n count++\n }\n }\n return count\n }\n\n /** Every open run plus every headless parked data unit that holds a slot,\n * one slot each. This is the quantity maxBuffered bounds. The parked term is\n * `#unopenedParkedRunCount`, whose membership is `parkedRunHoldsASlot` \u2014 the\n * same rule #evictLeastRecentlyAdmitted offers as candidates, so nothing can\n * be counted here that eviction cannot reclaim. */\n #incompleteRunCount(): number {\n return this.#activeDataUnits.size + this.#unopenedParkedRunCount()\n }\n\n /**\n * Drop the incomplete data unit admitted longest ago so a newly arriving one\n * can take its slot. Selection is by monotonic admission ordinal, never by\n * wall clock, so this is a bounded-capacity replacement policy and not a\n * timeout: it behaves identically on hosts that never drive `cleanup_timeouts`\n * (`timeoutMs: null`). Only incomplete data units are candidates \u2014 a data\n * unit that completed has already left both maps.\n *\n * Returns false only if no slot is charged, which cannot happen at the bound\n * (`maxBuffered` is a positive integer); admission then falls back to the\n * fail-closed `active-data-unit-limit` reject, which stays a stable host code.\n * That holds because the candidate set here is built from the same\n * `parkedRunHoldsASlot` rule `#incompleteRunCount` counts by, so reaching the\n * bound implies at least one candidate. Filtering one and not the other would\n * make the claim false: a queue counted but not offered charges a slot no\n * eviction can reclaim, and the receiver refuses every later data unit\n * permanently.\n */\n #evictLeastRecentlyAdmitted(): boolean {\n let victimAddress: string | undefined\n let victimSeq = Number.POSITIVE_INFINITY\n for (const [, active] of this.#activeDataUnits) {\n if (active.admissionSeq < victimSeq) {\n victimSeq = active.admissionSeq\n victimAddress = active.address\n }\n }\n for (const [address, pending] of this.#pendingContinuations) {\n // Exactly the queues #incompleteRunCount charges a slot to: a queue under\n // an open run is that run's slot, already considered above, and an empty\n // queue holds nothing to evict.\n if (!parkedRunHoldsASlot(this.#openRunAt(address) !== undefined, pending.fragments.size)) {\n continue\n }\n if (pending.admissionSeq < victimSeq) {\n victimSeq = pending.admissionSeq\n victimAddress = address\n }\n }\n if (victimAddress === undefined) return false\n this.#releaseSlot(victimAddress)\n this.stats.evictedMemoryLimit++\n return true\n }\n\n /**\n * Release every form of state one address holds: its open run, that run's\n * buffered fragments and deadline, and its continuation queue. The address is\n * deliberately not marked completed \u2014 the data unit never emitted, so a later\n * fragment of it may open a fresh slot rather than being rejected as stale.\n */\n #releaseSlot(address: string): void {\n const open = this.#openRunAt(address)\n if (open !== undefined) {\n const [runKey, active] = open\n this.#activeDataUnits.delete(runKey)\n this.#clearTimeout(active.bufferKey)\n this.#buffers.delete(active.bufferKey)\n }\n this.#deletePendingRun(address)\n }\n\n #reject(code: MfuReassemblyErrorCode, message: string): never {\n this.stats.rejectedFragments++\n throw new MfuReassemblyError(code, message)\n }\n\n #associateDataUnit(fragment: MFUFragment, runKey: string, address: string): DataUnitAssociation {\n const fi = fragment.fragmentationIndicator\n if (fi === 1) {\n // Opening a run is free when this data unit already holds a parked\n // slot: the run inherits it, so the bound does not move.\n //\n // A second head on an open address is refused upstream, so no run is open\n // here. Read that live rather than assume it, and route the answer through\n // `parkedRunHoldsASlot`, so this gate is the fourth site of one rule\n // instead of a fourth restatement of it. `parked` is non-null wherever\n // this is true, because the rule requires a non-empty queue.\n const parked = this.#pendingContinuations.get(address)\n const inheritsParkedSlot = parkedRunHoldsASlot(\n this.#openRunAt(address) !== undefined,\n parked?.fragments.size ?? 0,\n )\n\n // Resolve identity and byte extent BEFORE taking a slot. Eviction\n // replaces a healthy data unit, so it must only ever run for a fragment\n // that is going to be admitted -- a reject reached after eviction would\n // destroy valid state and break the contract these rejects exist to\n // uphold (\"without advancing or replacing valid state\"). addFragment\n // pre-validates only the FI/counter pair and the itemId/sample-field\n // conflict, so every reject below is reachable at the bound.\n const sampleIdentity = hasSampleIdentity(fragment)\n const hasSampleFields = hasAnySampleIdentityField(fragment)\n const hasItemIdentity = fragment.itemId !== undefined\n if (sampleIdentity && hasItemIdentity) {\n this.#reject('conflicting-data-unit-identity', 'fragmented MFU carries timed and non-timed DU identities')\n }\n if (!sampleIdentity && !hasItemIdentity) {\n this.#reject('missing-data-unit-identity', 'first fragment has no intrinsic DU identity')\n }\n if (hasSampleFields && !sampleIdentity) {\n this.#reject('missing-data-unit-identity', 'first fragment has an incomplete sample identity')\n }\n if (hasItemIdentity && !isU32(fragment.itemId)) {\n this.#reject('missing-data-unit-identity', 'first fragment has an invalid Item_ID')\n }\n\n const bufferKey = objectKey(fragment)\n if (this.#buffers.has(bufferKey)) {\n this.#reject('overlapping-data-unit', 'intrinsic data-unit buffer already exists')\n }\n\n let sampleEnd: number | null = null\n if (sampleIdentity) {\n sampleEnd = checkedSampleEnd(fragment.sampleOffset, fragment.data.byteLength)\n if (sampleEnd === null) this.#reject('offset-overflow', 'sample byte offset overflow')\n }\n\n // The fragment is admissible from here on, so charging it a slot cannot\n // be undone by a later reject. A second head on an open address was\n // already refused upstream, so this address holds no run and can never\n // be its own eviction victim.\n if (!inheritsParkedSlot && this.#incompleteRunCount() >= this.#maxBuffered) {\n if (!this.#evictLeastRecentlyAdmitted()) {\n this.#reject('active-data-unit-limit', 'active MFU data-unit limit reached')\n }\n }\n // An inherited slot keeps the ordinal it was admitted with: the head\n // arriving late does not make the data unit younger than one parked\n // after it.\n const admissionSeq = inheritsParkedSlot ? parked!.admissionSeq : this.#nextAdmissionSeq++\n\n let active: ActiveDataUnit\n if (sampleIdentity) {\n active = {\n address,\n kind: 'sample',\n bufferKey,\n nextFragmentCounter: fragment.fragmentCounter - 1,\n admissionSeq,\n movieFragmentSequenceNumber: fragment.movieFragmentSequenceNumber,\n sampleNumber: fragment.sampleNumber,\n nextOffset: sampleEnd!,\n ...(fragment.catalogTrackName === undefined ? {} : { catalogTrackName: fragment.catalogTrackName }),\n }\n } else {\n active = {\n address,\n kind: 'item',\n bufferKey,\n nextFragmentCounter: fragment.fragmentCounter - 1,\n admissionSeq,\n itemId: fragment.itemId!,\n ...(fragment.catalogTrackName === undefined ? {} : { catalogTrackName: fragment.catalogTrackName }),\n }\n }\n this.#activeDataUnits.set(objectKey(fragment), active)\n return { bufferKey, first: true, terminal: false }\n }\n\n const active = this.#activeDataUnits.get(runKey)\n if (active === undefined) {\n throw new Error('[MFUReassembler] continuation was not parked before association')\n }\n if (fragment.fragmentCounter !== active.nextFragmentCounter) {\n this.#reject(\n 'fragment-counter-mismatch',\n 'MFU continuation does not follow the authored fragment-counter countdown'\n )\n }\n\n if (active.kind === 'sample') {\n if (fragment.itemId !== undefined) {\n this.#reject('data-unit-identity-mismatch', 'sample continuation carries an Item_ID')\n }\n if (\n hasAnySampleIdentityField(fragment) &&\n (!hasSampleIdentity(fragment) ||\n fragment.movieFragmentSequenceNumber !== active.movieFragmentSequenceNumber ||\n fragment.sampleNumber !== active.sampleNumber ||\n fragment.sampleOffset !== active.nextOffset)\n ) {\n this.#reject('data-unit-identity-mismatch', 'sample continuation identity does not match')\n }\n const nextOffset = checkedSampleEnd(active.nextOffset, fragment.data.byteLength)\n if (nextOffset === null) this.#reject('offset-overflow', 'sample byte offset overflow')\n fragment.movieFragmentSequenceNumber = active.movieFragmentSequenceNumber\n fragment.sampleNumber = active.sampleNumber\n fragment.sampleOffset = active.nextOffset\n if (fi === 2) {\n active.nextOffset = nextOffset\n active.nextFragmentCounter = fragment.fragmentCounter - 1\n }\n } else {\n if (hasAnySampleIdentityField(fragment) || (fragment.itemId !== undefined && fragment.itemId !== active.itemId)) {\n this.#reject('data-unit-identity-mismatch', 'Item_ID continuation identity does not match')\n }\n fragment.itemId = active.itemId\n if (fi === 2) active.nextFragmentCounter = fragment.fragmentCounter - 1\n }\n\n return {\n bufferKey: active.bufferKey,\n first: false,\n terminal: fi === 3,\n }\n }\n\n #tryReassemble(bufferKey: string): ReassembledMFU | null {\n const fragments = this.#buffers.get(bufferKey)\n if (!fragments || fragments.length === 0) {\n return null\n }\n\n const sorted = [...fragments].sort((a, b) => this.#orderValue(a) - this.#orderValue(b))\n\n if (sorted[0]!.fragmentationIndicator !== 1 || sorted[sorted.length - 1]!.fragmentationIndicator !== 3) {\n return null\n }\n if (sorted.slice(1, -1).some(fragment => fragment.fragmentationIndicator !== 2)) {\n return null\n }\n\n // ISO fragment_counter is the number of succeeding payloads. After\n // normalization (`255 - counter`), a complete non-wrapping chain is strictly\n // consecutive. This also caps a data unit at 256 payloads.\n for (let i = 1; i < sorted.length; i++) {\n if (this.#orderValue(sorted[i]!) !== this.#orderValue(sorted[i - 1]!) + 1) {\n return null\n }\n }\n\n // When every fragment carries a timed-MFU byte offset, independently prove\n // byte adjacency within this data unit. Counter order remains authoritative;\n // conflicting offsets fail closed instead of changing the fragment order.\n // sorted[0]'s offset is this DU's authored base (moqcast #37: zero for a\n // whole-sample DU, nonzero for one subsample DU of a partitioned sample) --\n // never asserted against a literal zero.\n if (ordersByOffset(sorted)) {\n for (let i = 1; i < sorted.length; i++) {\n const previous = sorted[i - 1]!\n if (sorted[i]!.sampleOffset !== previous.sampleOffset! + previous.data.byteLength) {\n return null\n }\n }\n }\n\n fragments.splice(0, fragments.length, ...sorted)\n return this.#assemble(bufferKey, fragments)\n }\n\n #assemble(bufferKey: string, fragments: MFUFragment[]): ReassembledMFU {\n // Reassemble: concatenate all fragment data\n const totalSize = fragments.reduce((sum, f) => sum + f.data.length, 0)\n const data = new Uint8Array(totalSize)\n\n let offset = 0\n for (const fragment of fragments) {\n data.set(fragment.data, offset)\n offset += fragment.data.length\n }\n\n // Cleanup + mark the intrinsic identity as emitted to prevent a recovered\n // duplicate from starting another fragmented run.\n this.#clearTimeout(bufferKey)\n this.#buffers.delete(bufferKey)\n this.#markEmitted(bufferKey)\n this.stats.reassembledMFUs++\n\n return {\n mpuSequenceNumber: fragments[0]!.mpuSequenceNumber,\n packetSequenceNumber: fragments[0]!.packetSequenceNumber,\n ...assembledPacketSequences(fragments),\n data,\n timestamp: fragments[0]!.timestamp,\n fragmentCount: fragments.length,\n rapFlag: fragments[0]!.rapFlag,\n priority: fragments.find(f => f.priority !== undefined)?.priority,\n sampleNumber: fragments[0]!.sampleNumber,\n movieFragmentSequenceNumber: fragments[0]!.movieFragmentSequenceNumber,\n sampleOffset: fragments[0]!.sampleOffset,\n source: fragments[0]!.source,\n deliveryProvenance: deliveryProvenanceOf(fragments),\n catalogTrackName: fragments[0]!.catalogTrackName,\n }\n }\n\n #setTimeout(bufferKey: string, startedAt = Date.now()): void {\n const state: BufferTimeoutState = { startedAt }\n if (this.#timeoutMs !== null) {\n state.timeoutId = setTimeout(\n () => this.#onTimeout(bufferKey),\n Math.max(0, startedAt + this.#timeoutMs - Date.now())\n )\n }\n this.#timeouts.set(bufferKey, state)\n }\n\n #clearPendingTimeout(address: string): void {\n const pending = this.#pendingContinuations.get(address)\n if (pending?.timeoutId !== undefined) clearTimeout(pending.timeoutId)\n if (pending !== undefined) {\n delete pending.timeoutId\n }\n }\n\n /**\n * A queue whose run closed without completing is headless again and expires\n * from its own first arrival, exactly as it would had the head never come.\n *\n * An empty queue is not armed: it holds no data unit that could fail to\n * complete, so there is nothing for a deadline to expire. Every arming site\n * reaches this function, which takes that test through `parkedRunHoldsASlot`\n * rather than filtering on the open run alone, which is how this engine\n * reaches Rust's answer: its\n * `next_timeout_at` derives candidate deadlines from the queue's live\n * entries, so an empty queue yields none (BLO-32420).\n */\n #armHeadlessPendingTimeout(address: string): void {\n const pending = this.#pendingContinuations.get(address)\n if (pending === undefined || pending.timeoutId !== undefined || this.#timeoutMs === null) return\n if (!parkedRunHoldsASlot(this.#openRunAt(address) !== undefined, pending.fragments.size)) return\n pending.timeoutId = setTimeout(\n () => this.#onPendingTimeout(address),\n Math.max(0, pending.startedAt + this.#timeoutMs - Date.now())\n )\n }\n\n #deletePendingRun(address: string): void {\n this.#clearPendingTimeout(address)\n this.#pendingContinuations.delete(address)\n }\n\n #onPendingTimeout(address: string): void {\n const pending = this.#pendingContinuations.get(address)\n if (pending === undefined) return\n // The charging site takes the same slot rule as the arming sites, evaluated\n // live at expiry the way Rust's `cleanup_timeouts` evaluates it at sweep\n // time: a queue that holds no slot is retained rather than charged\n // `record_policy_timeout`. That makes \"an empty queue is never charged a\n // timeout\" a property of this method, not of arming order. The fired timer\n // is released so a later re-park can arm the queue again.\n if (!parkedRunHoldsASlot(this.#openRunAt(address) !== undefined, pending.fragments.size)) {\n this.#clearPendingTimeout(address)\n return\n }\n this.stats.timedOutMFUs++\n console.warn(\n `[MfuReassembler] timeout: run=${address} fragments=${pending.fragments.size} ` +\n '(continuations awaiting first fragment, discarded)'\n )\n this.#pendingContinuations.delete(address)\n }\n\n #clearTimeout(bufferKey: string): void {\n const state = this.#timeouts.get(bufferKey)\n if (state !== undefined) {\n if (state.timeoutId !== undefined) clearTimeout(state.timeoutId)\n this.#timeouts.delete(bufferKey)\n }\n }\n\n /** Callback for deferred assembly results (set by caller for async frame delivery) */\n onDeferredAssembly?: (result: ReassembledMFU) => void\n\n #markEmitted(bufferKey: string): void {\n if (this.#emitted.has(bufferKey)) return\n this.#emitted.add(bufferKey)\n this.#emittedOrder.push(bufferKey)\n while (this.#emittedOrder.length > this.#maxBuffered) {\n const oldest = this.#emittedOrder.shift()\n if (oldest !== undefined) this.#emitted.delete(oldest)\n }\n }\n\n #markCompleted(address: string): void {\n if (this.#completed.has(address)) return\n this.#completed.add(address)\n this.#completedOrder.push(address)\n const cap = this.#maxBuffered * COMPLETED_ADDRESS_WINDOW_MULTIPLE\n while (this.#completedOrder.length > cap) {\n const oldest = this.#completedOrder.shift()\n if (oldest !== undefined) this.#completed.delete(oldest)\n }\n }\n\n #orderValue(fragment: MFUFragment): number {\n return fragmentCounterOrder(fragment)\n }\n\n /** Close the run behind an expired buffer. Early arrivals parked under its\n * address stay, now headless, and expire from their own arrival. */\n #removeActiveDataUnitForBuffer(bufferKey: string): void {\n for (const [objectKey, active] of this.#activeDataUnits) {\n if (active.bufferKey === bufferKey) {\n this.#activeDataUnits.delete(objectKey)\n this.#armHeadlessPendingTimeout(active.address)\n }\n }\n }\n\n /** The one open run holding this address, with its object key. Admission\n * rejects a second head on an open address, so at most one run can match. */\n #openRunAt(address: string): readonly [string, ActiveDataUnit] | undefined {\n for (const entry of this.#activeDataUnits) {\n if (entry[1].address === address) return entry\n }\n return undefined\n }\n\n #onTimeout(bufferKey: string): void {\n const fragments = this.#buffers.get(bufferKey)\n if (!fragments || fragments.length === 0) {\n this.#buffers.delete(bufferKey)\n this.#timeouts.delete(bufferKey)\n this.#removeActiveDataUnitForBuffer(bufferKey)\n this.stats.timedOutMFUs++\n console.warn(`[MfuReassembler] timeout: key=${bufferKey} fragments=0 (no data buffered)`)\n return\n }\n\n // Discard partial MFUs \u2014 incomplete AVCC data crashes WebCodecs with\n // \"Unable to determine size of bitstream buffer\". FEC recovery handles\n // loss; partial assembly only produces garbage frames.\n this.stats.timedOutMFUs++\n console.warn(`[MfuReassembler] timeout: key=${bufferKey} fragments=${fragments.length} (partial, discarded)`)\n\n this.#buffers.delete(bufferKey)\n this.#timeouts.delete(bufferKey)\n this.#removeActiveDataUnitForBuffer(bufferKey)\n }\n\n /**\n * Flush all buffered fragments\n */\n flush(): void {\n for (const key of this.#buffers.keys()) {\n this.#clearTimeout(key)\n }\n this.#buffers.clear()\n this.#timeouts.clear()\n this.#activeDataUnits.clear()\n for (const address of this.#pendingContinuations.keys()) {\n this.#clearPendingTimeout(address)\n }\n this.#pendingContinuations.clear()\n this.#emitted.clear()\n this.#emittedOrder.length = 0\n this.#completed.clear()\n this.#completedOrder.length = 0\n }\n\n /** Drop one catalog stream's incomplete and dedup state. */\n resetStream(catalogTrackName: string): void {\n const belongsToStream = (key: string): boolean => {\n try {\n const decoded = JSON.parse(key)\n return Array.isArray(decoded) && decoded[0] === 'catalog' && decoded[1] === catalogTrackName\n } catch {\n return false\n }\n }\n const discarded = new Set<string>()\n for (const [key, fragments] of this.#buffers) {\n if (fragments.some(fragment => fragment.catalogTrackName === catalogTrackName)) {\n discarded.add(key)\n }\n }\n for (const [objectKey, active] of this.#activeDataUnits) {\n if (active.catalogTrackName === catalogTrackName) {\n discarded.add(active.bufferKey)\n this.#activeDataUnits.delete(objectKey)\n }\n }\n for (const [address, pending] of this.#pendingContinuations) {\n if (\n [...pending.fragments.values()].some(fragment => fragment.catalogTrackName === catalogTrackName) ||\n belongsToStream(address)\n )\n this.#deletePendingRun(address)\n }\n for (const key of this.#emitted) {\n if (belongsToStream(key)) discarded.add(key)\n }\n for (const key of discarded) {\n this.#clearTimeout(key)\n this.#buffers.delete(key)\n this.#emitted.delete(key)\n }\n this.#emittedOrder = this.#emittedOrder.filter(key => !discarded.has(key))\n for (const address of this.#completed) {\n if (belongsToStream(address)) this.#completed.delete(address)\n }\n this.#completedOrder = this.#completedOrder.filter(address => this.#completed.has(address))\n }\n\n /** Reset all state \u2014 flush buffers and zero stats */\n reset(): void {\n this.flush()\n this.stats.totalFragments = 0\n this.stats.reassembledMFUs = 0\n this.stats.rejectedFragments = 0\n this.stats.timedOutMFUs = 0\n this.stats.evictedMemoryLimit = 0\n this.stats.outOfOrderFragments = 0\n }\n\n /**\n * Get number of pending MFUs: engine buffers plus every headless parked data\n * unit that holds a slot.\n *\n * This is not one of the four charging sites, but it shares their second term,\n * so it takes the same rule \u2014 by delegating to `#unopenedParkedRunCount`, the\n * one site that applies it. Rust's public `buffered_count()` is\n * `inner.buffered_count() + unopened_parked_run_count()`, and that count is\n * `parked_run_holds_a_slot` \u2014 so filtering here on the open run alone would\n * report a phantom slot for an empty published queue in TypeScript and not in\n * Rust, out of a *host-observable* counter (`getStatus().bufferedMFUs`). Same\n * state, two numbers, decided by which engine the host loaded: the divergence\n * class this file exists to close, so the public counters have to agree too.\n *\n * Distinct from `#incompleteRunCount`, which sums `#activeDataUnits` rather\n * than `#buffers`. Those are different quantities by design and are not being\n * merged; only the parked term they share is required to agree, which is what\n * routing both through one method makes structural rather than conventional.\n */\n get pendingCount(): number {\n return this.#buffers.size + this.#unopenedParkedRunCount()\n }\n\n /**\n * Get current buffer status\n */\n getStatus(): {\n bufferedMFUs: number\n pendingFragments: number\n pendingSamples: PendingMfuDebug[]\n stats: ReassemblerStats\n } {\n let pendingFragments = 0\n for (const fragments of this.#buffers.values()) {\n pendingFragments += fragments.length\n }\n for (const pending of this.#pendingContinuations.values()) {\n pendingFragments += pending.fragments.size\n }\n\n return {\n bufferedMFUs: this.pendingCount,\n pendingFragments,\n pendingSamples: this.#pendingDebugSamples(),\n stats: { ...this.stats },\n }\n }\n\n #pendingDebugSamples(): PendingMfuDebug[] {\n const out: PendingMfuDebug[] = []\n for (const [key, fragments] of this.#buffers) {\n if (fragments.length === 0) continue\n const sorted = [...fragments].sort((a, b) => this.#orderValue(a) - this.#orderValue(b))\n const fiCounts = [0, 0, 0, 0]\n let minCounter = Infinity\n let maxCounter = -Infinity\n let minOffset = Infinity\n let maxOffset = -Infinity\n for (const fragment of sorted) {\n fiCounts[fragment.fragmentationIndicator] = (fiCounts[fragment.fragmentationIndicator] ?? 0) + 1\n minCounter = Math.min(minCounter, fragment.fragmentCounter)\n maxCounter = Math.max(maxCounter, fragment.fragmentCounter)\n if (typeof fragment.sampleOffset === 'number') {\n minOffset = Math.min(minOffset, fragment.sampleOffset)\n maxOffset = Math.max(maxOffset, fragment.sampleOffset)\n }\n }\n const point = (fragment: MFUFragment) => ({\n fi: fragment.fragmentationIndicator,\n counter: fragment.fragmentCounter,\n order: this.#orderValue(fragment),\n ...(typeof fragment.packetSequenceNumber === 'number' ? { seq: fragment.packetSequenceNumber } : {}),\n ...(typeof fragment.sampleOffset === 'number' ? { offset: fragment.sampleOffset } : {}),\n })\n const summary: PendingMfuDebug = {\n key,\n fragments: sorted.length,\n fi0: fiCounts[0] ?? 0,\n fi1: fiCounts[1] ?? 0,\n fi2: fiCounts[2] ?? 0,\n fi3: fiCounts[3] ?? 0,\n firstFi: sorted[0]!.fragmentationIndicator,\n lastFi: sorted[sorted.length - 1]!.fragmentationIndicator,\n firstOrder: this.#orderValue(sorted[0]!),\n lastOrder: this.#orderValue(sorted[sorted.length - 1]!),\n minCounter,\n maxCounter,\n movieFragmentSequenceNumber: sorted.find(f => f.movieFragmentSequenceNumber !== undefined)\n ?.movieFragmentSequenceNumber,\n sampleNumber: sorted.find(f => f.sampleNumber !== undefined)?.sampleNumber,\n mpuSequenceNumber: sorted[0]!.mpuSequenceNumber,\n sortedHead: sorted.slice(0, 6).map(point),\n sortedTail: sorted.slice(-6).map(point),\n }\n if (minOffset !== Infinity) summary.minOffset = minOffset\n if (maxOffset !== -Infinity) summary.maxOffset = maxOffset\n out.push(summary)\n if (out.length >= 12) break\n }\n return out\n }\n}\n\n/**\n * Create MFU fragment from MMTP packet for reassembly\n */\nexport function createFragment(mmtpPacket: Uint8Array): MFUFragment {\n const extraction = extractMFUPayload(mmtpPacket)\n if (!extraction) throw new Error('Unable to extract MFU payload from MMTP packet')\n const { header, mpu, data, mfuDuHeader, itemId } = extraction\n\n return {\n fragmentationIndicator: mpu.fragmentationIndicator,\n mpuSequenceNumber: mpu.mpuSequenceNumber,\n fragmentCounter: mpu.fragmentCounter,\n data,\n timestamp: header.timestamp,\n rapFlag: header.rapFlag,\n sampleNumber: mfuDuHeader?.sampleNumber,\n movieFragmentSequenceNumber: mfuDuHeader?.movieFragmentSequenceNumber,\n sampleOffset: mfuDuHeader?.offset,\n priority: mfuDuHeader?.priority,\n itemId: itemId ?? undefined,\n packetSequenceNumber: header.sequenceNumber,\n packetId: header.packetId,\n }\n}\n\n/**\n * Process MMTP packet with fragment reassembly\n */\nexport function processMMTPWithReassembly(mmtpPacket: Uint8Array, reassembler: MFUReassembler): ReassembledMFU | null {\n // Skip FEC repair packets\n if (isFecRepairPacket(mmtpPacket)) {\n return null\n }\n\n const fragment = createFragment(mmtpPacket)\n return reassembler.addFragment(fragment)\n}\n\n// =============================================================================\n// Inline Tests (vitest)\n// =============================================================================\n\nif (import.meta.vitest) {\n const { describe, test, expect, vi } = import.meta.vitest\n\n const expectRejection = (call: () => unknown, code: MfuReassemblyErrorCode): void => {\n let rejection: unknown\n try {\n call()\n } catch (error) {\n rejection = error\n }\n expect(rejection).toBeInstanceOf(MfuReassemblyError)\n expect((rejection as MfuReassemblyError).code).toBe(code)\n }\n\n const testPacketSequence = (mpu: number, counter: number, run = 0): number =>\n (mpu * 1000 + run - counter + 0x1_0000_0000) % 0x1_0000_0000\n\n describe('MFUReassembler', () => {\n test('should return complete MFU for FI=0', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const result = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n itemId: 1,\n data: new Uint8Array([1, 2, 3, 4]),\n timestamp: 1000n,\n\n rapFlag: true,\n })\n expect(result).not.toBeNull()\n expect(result?.data).toEqual(new Uint8Array([1, 2, 3, 4]))\n expect(result?.fragmentCount).toBe(1)\n expect(result?.rapFlag).toBe(true)\n })\n\n test('rejects a complete MFU without a valid sample identity', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const complete = (identity: Partial<MFUFragment>): MFUFragment => ({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: true,\n ...identity,\n })\n expectRejection(() => reassembler.addFragment(complete({})), 'missing-data-unit-identity')\n expectRejection(\n () =>\n reassembler.addFragment(\n complete({\n movieFragmentSequenceNumber: 2,\n sampleNumber: 1,\n })\n ),\n 'missing-data-unit-identity'\n )\n })\n\n test('accepts a complete subsample MFU with a nonzero authored offset', () => {\n // moqcast #37: a subsample DU's offset is its authored base byte\n // position within the sample -- nonzero identifies one subsample DU\n // among siblings partitioning an oversized sample.\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const result = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n movieFragmentSequenceNumber: 2,\n sampleNumber: 1,\n sampleOffset: 4,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: true,\n })\n expect(result).not.toBeNull()\n expect(result?.sampleOffset).toBe(4)\n })\n\n test('accepts a fragmented subsample DU whose first fragment has a nonzero authored offset', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 2,\n fragmentCounter: 1,\n movieFragmentSequenceNumber: 2,\n sampleNumber: 1,\n sampleOffset: 4,\n packetSequenceNumber: testPacketSequence(2, 1),\n data: new Uint8Array([0xa1]),\n timestamp: 1000n,\n rapFlag: true,\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n expect(reassembler.getStatus().pendingFragments).toBe(1)\n\n const out = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n data: new Uint8Array([0xa2]),\n timestamp: 1001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(2, 0),\n })\n expect(out?.data).toEqual(new Uint8Array([0xa1, 0xa2]))\n expect(out?.sampleOffset).toBe(4)\n })\n\n // Rust twin: `a_mid_data_unit_sequence_gap_strands_the_data_unit` in\n // `mmt-core/src/reassembler/policy/mmt.rs`.\n //\n // One data unit's fragments occupy sequences 100, 102, 103 with an\n // unrelated complete unit at 101 -- an interleave *inside* the fragmented\n // unit rather than after it. ISO/IEC 23008-1 permits that, so this is the\n // boundary of the contiguous-source producer profile, not a bug within it.\n //\n // This test exists because no fixture in either corpus could previously\n // express a mid-data-unit sequence gap at all: the helpers derive\n // continuation sequences arithmetically from the head, so every fixture\n // was contiguous by construction and the profile's edge was untestable\n // (BLO-33550). It pins current behaviour rather than the guarantee we\n // would prefer -- change the association path and update it deliberately.\n test('a mid-data-unit packet sequence gap strands the data unit', () => {\n const reassembler = new MFUReassembler({ timeoutMs: null, maxBuffered: 10 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 7,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n sampleOffset: 0,\n packetSequenceNumber: 100,\n data: new Uint8Array([0xa1]),\n timestamp: 1100n,\n rapFlag: true,\n })\n ).toBeNull()\n\n // An unrelated complete unit of the same MPU consumes sequence 101,\n // *between* the fragmented unit's head and its next fragment.\n const interleaved = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 7,\n fragmentCounter: 0,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 18,\n sampleOffset: 0,\n packetSequenceNumber: 101,\n data: new Uint8Array([0xff]),\n timestamp: 1101n,\n rapFlag: true,\n })\n expect(interleaved?.data).toEqual(new Uint8Array([0xff]))\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 7,\n fragmentCounter: 1,\n packetSequenceNumber: 102,\n data: new Uint8Array([0xa2]),\n timestamp: 1102n,\n rapFlag: false,\n })\n ).toBeNull()\n\n const output = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 7,\n fragmentCounter: 0,\n packetSequenceNumber: 103,\n data: new Uint8Array([0xa3]),\n timestamp: 1103n,\n rapFlag: false,\n })\n\n // What we would want, and do not get: `output` carrying a1 a2 a3. The\n // head addresses 100+2=102; both continuations address 102+1 = 103+0 =\n // 103, so they are two data units and neither can complete.\n expect(output).toBeNull()\n\n // Fail-closed, not corrupt: only the interleaver emitted, and no\n // fragment was donated across data units.\n expect(reassembler.stats.reassembledMFUs).toBe(1)\n expect(reassembler.stats.rejectedFragments).toBe(0)\n\n // The loss is invisible at this scale -- since BLO-31513 the bound is\n // enforced by replacement, so at `maxBuffered` this same stranding is\n // charged only to `evictedMemoryLimit` and presents as intermittent\n // missing media rather than an error.\n expect(reassembler.stats.evictedMemoryLimit).toBe(0)\n\n // Two slots for one data unit -- the open run at 102 plus the headless\n // parked queue at 103 -- so sustained profile-violating interleave\n // consumes `maxBuffered` at twice the expected rate and evicts healthy\n // data units alongside the stranded one. Matches the Rust twin's\n // `buffered_count() == 2` / `pending_fragment_count() == 3`.\n expect(reassembler.getStatus().bufferedMFUs).toBe(2)\n expect(reassembler.getStatus().pendingFragments).toBe(3)\n })\n\n test('sibling subsample DUs of one sample reassemble independently by offset', () => {\n // moqcast #37: each subsample DU of a partitioned sample runs its own\n // FI chain and gets its own buffer, keyed in part by its authored base\n // offset -- distinct from a sibling DU sharing (movie, sample).\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n mpuSequenceNumber: 3,\n movieFragmentSequenceNumber: 5,\n sampleNumber: 9,\n fragmentationIndicator: 1,\n fragmentCounter: 1,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 2000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(3, 1, 0),\n })\n ).toBeNull()\n // The sibling's head sits at its own packet sequence, so its address\n // (packetSequenceNumber + fragmentCounter) differs from the first's.\n // Both stay open at once and every headerless continuation names exactly\n // one of them by that address. A second head on an already-open address\n // is refused instead ('rejects an overlapping non-timed item without\n // replacing the active run').\n expect(\n reassembler.addFragment({\n mpuSequenceNumber: 3,\n movieFragmentSequenceNumber: 5,\n sampleNumber: 9,\n fragmentationIndicator: 1,\n fragmentCounter: 2,\n sampleOffset: 3,\n data: new Uint8Array([0xb1]),\n timestamp: 2001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(3, 1, 1),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(2)\n\n const first = reassembler.addFragment({\n mpuSequenceNumber: 3,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n data: new Uint8Array([0xa2]),\n timestamp: 2000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(3, 0, 0),\n })\n expect(first?.data).toEqual(new Uint8Array([0xa1, 0xa2]))\n expect(first?.sampleOffset).toBe(0)\n\n expect(\n reassembler.addFragment({\n mpuSequenceNumber: 3,\n fragmentationIndicator: 2,\n fragmentCounter: 1,\n data: new Uint8Array([0xb2]),\n timestamp: 2001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(3, 0, 1),\n })\n ).toBeNull()\n const second = reassembler.addFragment({\n mpuSequenceNumber: 3,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n data: new Uint8Array([0xb3]),\n timestamp: 2001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(3, 0, 2),\n })\n expect(second?.data).toEqual(new Uint8Array([0xb1, 0xb2, 0xb3]))\n expect(second?.sampleOffset).toBe(3)\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('isolates colliding fragment coordinates by catalog track identity', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const fragment = (\n catalogTrackName: string,\n fragmentationIndicator: number,\n fragmentCounter: number,\n byte: number\n ): MFUFragment => ({\n itemId: 1,\n fragmentationIndicator,\n fragmentCounter,\n mpuSequenceNumber: 1,\n data: new Uint8Array([byte]),\n timestamp: 1000n,\n rapFlag: fragmentationIndicator === 1,\n catalogTrackName,\n packetSequenceNumber: testPacketSequence(1, fragmentCounter),\n })\n\n expect(reassembler.addFragment(fragment('video/720p', 1, 1, 0x71))).toBeNull()\n expect(reassembler.addFragment(fragment('video/540p', 1, 1, 0x51))).toBeNull()\n const high = reassembler.addFragment(fragment('video/720p', 3, 0, 0x72))\n const low = reassembler.addFragment(fragment('video/540p', 3, 0, 0x52))\n\n expect(high?.data).toEqual(new Uint8Array([0x71, 0x72]))\n expect(high?.catalogTrackName).toBe('video/720p')\n expect(low?.data).toEqual(new Uint8Array([0x51, 0x52]))\n expect(low?.catalogTrackName).toBe('video/540p')\n })\n\n test('rejects an overlapping non-timed item without replacing the active run', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const fragment = (\n itemId: number,\n fragmentationIndicator: number,\n fragmentCounter: number,\n byte: number\n ): MFUFragment => ({\n itemId,\n fragmentationIndicator,\n fragmentCounter,\n mpuSequenceNumber: 1,\n data: new Uint8Array([byte]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, fragmentCounter),\n })\n\n expect(reassembler.addFragment(fragment(10, 1, 1, 0x10))).toBeNull()\n expectRejection(() => reassembler.addFragment(fragment(20, 1, 1, 0x20)), 'overlapping-data-unit')\n expect(reassembler.pendingCount).toBe(1)\n expect(reassembler.addFragment(fragment(10, 3, 0, 0x11))?.data).toEqual(new Uint8Array([0x10, 0x11]))\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('associates continuations by authored state when packet timestamps change', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const fragment = (\n itemId: number | undefined,\n fragmentationIndicator: number,\n fragmentCounter: number,\n byte: number,\n timestamp: bigint\n ): MFUFragment => ({\n ...(itemId === undefined ? {} : { itemId }),\n fragmentationIndicator,\n fragmentCounter,\n mpuSequenceNumber: 1,\n data: new Uint8Array([byte]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, fragmentCounter),\n })\n\n expect(reassembler.addFragment(fragment(10, 1, 2, 0x10, 1000n))).toBeNull()\n expect(reassembler.addFragment(fragment(undefined, 2, 1, 0x11, 1001n))).toBeNull()\n const out = reassembler.addFragment(fragment(undefined, 3, 0, 0x12, 1002n))\n expect(out?.data).toEqual(new Uint8Array([0x10, 0x11, 0x12]))\n expect(out?.timestamp).toBe(1000n)\n })\n\n test('should buffer and reassemble 2 fragments', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // First fragment (FI=1)\n const result1 = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([1, 2]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n expect(result1).toBeNull()\n\n // Last fragment (FI=3)\n const result2 = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([3, 4]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(result2).not.toBeNull()\n expect(result2?.data).toEqual(new Uint8Array([1, 2, 3, 4]))\n expect(result2?.fragmentCount).toBe(2)\n expect(result2?.rapFlag).toBe(true) // Should use first fragment's rapFlag\n })\n\n test('buffers a terminal continuation until its authored first fragment arrives', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // Last fragment first (FI=3)\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([3, 4]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n\n // Then first fragment (FI=1)\n const completed = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([1, 2]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n\n expect(completed?.data).toEqual(new Uint8Array([1, 2, 3, 4]))\n expect(completed?.rapFlag).toBe(true)\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('clock-free mode keeps an admitted MFU until completion', async () => {\n const reassembler = new MFUReassembler({ timeoutMs: null })\n\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([1, 2]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n\n await new Promise(resolve => setTimeout(resolve, 5))\n\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([3, 4]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n\n expect(result).not.toBeNull()\n expect(result?.data).toEqual(new Uint8Array([1, 2, 3, 4]))\n expect(reassembler.stats.timedOutMFUs).toBe(0)\n })\n\n test('should track pending count', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n expect(reassembler.pendingCount).toBe(0)\n\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n expect(reassembler.pendingCount).toBe(1)\n\n reassembler.flush()\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('should reassemble 3 fragments (FI=1, FI=2, FI=3)', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // First fragment\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 10,\n fragmentCounter: 2,\n itemId: 1,\n data: new Uint8Array([0xaa, 0xbb]),\n timestamp: 5000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(10, 2),\n })\n ).toBeNull()\n\n // Middle fragment\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 10,\n fragmentCounter: 1,\n data: new Uint8Array([0xcc, 0xdd]),\n timestamp: 5000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(10, 1),\n })\n ).toBeNull()\n\n // Last fragment \u2014 triggers reassembly\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 10,\n fragmentCounter: 0,\n data: new Uint8Array([0xee, 0xff]),\n timestamp: 5000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(10, 0),\n })\n\n expect(result).not.toBeNull()\n expect(result!.data).toEqual(new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]))\n expect(result!.fragmentCount).toBe(3)\n expect(result!.rapFlag).toBe(true) // from first fragment\n expect(result!.mpuSequenceNumber).toBe(10)\n })\n\n test('should handle interleaved MPU sequences', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // MPU 1: first fragment\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0x01]),\n timestamp: 100n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n // MPU 2: complete (FI=0)\n const r2 = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n itemId: 2,\n data: new Uint8Array([0x02]),\n timestamp: 200n,\n rapFlag: false,\n })\n expect(r2).not.toBeNull()\n expect(r2!.mpuSequenceNumber).toBe(2)\n\n // MPU 1: last fragment\n const r1 = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0x03]),\n timestamp: 100n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(r1).not.toBeNull()\n expect(r1!.data).toEqual(new Uint8Array([0x01, 0x03]))\n })\n\n test('rejects overlapping fragmented MFUs sharing one stream and MPU', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const mpuSeq = 5\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 1,\n itemId: 10,\n data: new Uint8Array([0xa1]),\n timestamp: 100n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(mpuSeq, 1),\n })\n ).toBeNull()\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 1,\n itemId: 20,\n data: new Uint8Array([0xb1]),\n timestamp: 200n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(mpuSeq, 1),\n }),\n 'overlapping-data-unit'\n )\n expect(reassembler.pendingCount).toBe(1)\n\n const a = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 0,\n data: new Uint8Array([0xa2]),\n timestamp: 101n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(mpuSeq, 0),\n })\n expect(a).not.toBeNull()\n expect(a!.data).toEqual(new Uint8Array([0xa1, 0xa2]))\n expect(a!.rapFlag).toBe(true) // from A's first fragment\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 1,\n itemId: 20,\n data: new Uint8Array([0xb1]),\n timestamp: 200n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(mpuSeq, 1, 10),\n })\n ).toBeNull()\n const b = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 0,\n data: new Uint8Array([0xb2]),\n timestamp: 201n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(mpuSeq, 0, 10),\n })\n expect(b).not.toBeNull()\n expect(b!.data).toEqual(new Uint8Array([0xb1, 0xb2]))\n })\n\n test('reassembles FFmpeg timed MFU fragments by sample number and byte offset', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const common = {\n mpuSequenceNumber: 0,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n }\n\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 2,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(0, 2),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 2,\n fragmentCounter: 1,\n sampleOffset: 1,\n data: new Uint8Array([0xb2]),\n timestamp: 1001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(0, 1),\n })\n ).toBeNull()\n const out = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n sampleOffset: 2,\n data: new Uint8Array([0xc3]),\n timestamp: 1002n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(0, 0),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3]))\n expect(out!.timestamp).toBe(1000n)\n expect(out!.sampleNumber).toBe(17)\n expect(out!.movieFragmentSequenceNumber).toBe(9)\n })\n\n test('buffers a counter gap without advancing the active data unit', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const common = {\n mpuSequenceNumber: 0,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n timestamp: 1000n,\n rapFlag: true,\n }\n\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 2,\n sampleOffset: 0,\n data: new Uint8Array([0xa1, 0xa2]),\n packetSequenceNumber: testPacketSequence(0, 2),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n sampleOffset: 4,\n data: new Uint8Array([0xc1, 0xc2]),\n packetSequenceNumber: testPacketSequence(0, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n\n const out = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 2,\n fragmentCounter: 1,\n sampleOffset: 2,\n data: new Uint8Array([0xb1, 0xb2]),\n packetSequenceNumber: testPacketSequence(0, 1),\n })\n expect(out?.data).toEqual(new Uint8Array([0xa1, 0xa2, 0xb1, 0xb2, 0xc1, 0xc2]))\n })\n\n test('reassembles first-only DU-header fragments across changing packet timestamps', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 44,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 12345n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(44, 2),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 44,\n fragmentCounter: 1,\n data: new Uint8Array([0xb2]),\n timestamp: 12346n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 1),\n })\n ).toBeNull()\n const out = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 44,\n fragmentCounter: 0,\n data: new Uint8Array([0xc3]),\n timestamp: 12347n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 0),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3]))\n expect(out!.timestamp).toBe(12345n)\n expect(out!.sampleNumber).toBe(17)\n expect(out!.movieFragmentSequenceNumber).toBe(9)\n })\n\n test('reassembles first-only DU-header fragments with an ISO countdown', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 12345n\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 44,\n fragmentCounter: 3,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(44, 3),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 44,\n fragmentCounter: 2,\n data: new Uint8Array([0xb2]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 2),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 44,\n fragmentCounter: 1,\n data: new Uint8Array([0xc3]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 1),\n })\n ).toBeNull()\n const out = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 44,\n fragmentCounter: 0,\n data: new Uint8Array([0xd4]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 0),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3, 0xd4]))\n expect(out!.sampleNumber).toBe(17)\n expect(out!.movieFragmentSequenceNumber).toBe(9)\n })\n\n test('rejects fragmented MFUs without an authored DU identity', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 22222n\n\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 55,\n fragmentCounter: 2,\n data: new Uint8Array([0xa1]),\n timestamp,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(55, 2),\n }),\n 'missing-data-unit-identity'\n )\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('rejects fragmented MFUs without an authored packet sequence', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 55,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xa1]),\n timestamp: 22222n,\n rapFlag: true,\n }),\n 'missing-packet-sequence'\n )\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('rejects fragmented MFUs whose authored packet sequence is not u32', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n for (const packetSequenceNumber of [-1, 0x1_0000_0000, 1.5, Number.NaN]) {\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 55,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xa1]),\n timestamp: 22222n,\n rapFlag: true,\n packetSequenceNumber,\n }),\n 'missing-packet-sequence'\n )\n }\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('a packet sequence gap inside a fragmented DU splits it and never completes', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 66666n\n\n // The head is addressed 1000 + 3 = 1003. One unrelated same-pid packet\n // consumed psn 1001, so every later fragment is addressed 1004: a\n // second data unit on the wire, parked headless and never merged with\n // the head by scope, counter, timestamp, or arrival order.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 80,\n fragmentCounter: 3,\n movieFragmentSequenceNumber: 241,\n sampleNumber: 1,\n sampleOffset: 0,\n packetSequenceNumber: 1000,\n data: new Uint8Array([0xa0]),\n timestamp,\n rapFlag: true,\n })\n ).toBeNull()\n for (const [counter, psn] of [\n [2, 1002],\n [1, 1003],\n ] as const) {\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 80,\n fragmentCounter: counter,\n packetSequenceNumber: psn,\n data: new Uint8Array([0xa0 + counter]),\n timestamp,\n rapFlag: false,\n })\n ).toBeNull()\n }\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 80,\n fragmentCounter: 0,\n packetSequenceNumber: 1004,\n data: new Uint8Array([0xb1]),\n timestamp,\n rapFlag: false,\n })\n ).toBeNull()\n\n // The open head plus one headless parked data unit, one slot each.\n expect(reassembler.pendingCount).toBe(2)\n expect(reassembler.getStatus().pendingFragments).toBe(4)\n expect(reassembler.stats.reassembledMFUs).toBe(0)\n expect(reassembler.stats.rejectedFragments).toBe(0)\n })\n\n test('keeps adjacent MFUs with equal countdowns in one MPU separate', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const common = { mpuSequenceNumber: 80, packetId: 7, timestamp: 66666n }\n\n // Two adjacent samples of one MPU fragmented into the same countdown are\n // both open at once. Each continuation carries its unit's terminal\n // sequence (100 + 1 and 102 + 1), so each run completes with its own\n // terminal fragment.\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 1,\n packetSequenceNumber: 100,\n movieFragmentSequenceNumber: 241,\n sampleNumber: 1,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n rapFlag: true,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 1,\n packetSequenceNumber: 102,\n movieFragmentSequenceNumber: 241,\n sampleNumber: 2,\n sampleOffset: 0,\n data: new Uint8Array([0xb1]),\n rapFlag: false,\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(2)\n\n const earlier = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n packetSequenceNumber: 101,\n data: new Uint8Array([0xa2]),\n rapFlag: false,\n })\n expect(earlier?.data).toEqual(new Uint8Array([0xa1, 0xa2]))\n expect(earlier?.packetSequenceNumbers).toEqual([100, 101])\n\n const later = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n packetSequenceNumber: 103,\n data: new Uint8Array([0xb2]),\n rapFlag: false,\n })\n expect(later?.data).toEqual(new Uint8Array([0xb1, 0xb2]))\n expect(later?.packetSequenceNumbers).toEqual([102, 103])\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('wraps the fragmented run coordinate at u32 packet sequence', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 81,\n fragmentCounter: 1,\n packetSequenceNumber: 0xffffffff,\n itemId: 1,\n data: new Uint8Array([0xa1]),\n timestamp: 66666n,\n rapFlag: true,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 81,\n fragmentCounter: 0,\n packetSequenceNumber: 0,\n data: new Uint8Array([0xa2]),\n timestamp: 66667n,\n rapFlag: false,\n })?.data\n ).toEqual(new Uint8Array([0xa1, 0xa2]))\n })\n\n test('dedupes recovered copies by ISO fragment-counter position', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 44444n\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 77,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 12,\n sampleNumber: 1,\n sampleOffset: 0,\n packetSequenceNumber: 2000,\n data: new Uint8Array([0xa1]),\n timestamp,\n rapFlag: true,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 77,\n fragmentCounter: 1,\n packetSequenceNumber: 2001,\n data: new Uint8Array([0xb2]),\n timestamp,\n rapFlag: false,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 77,\n fragmentCounter: 0,\n packetSequenceNumber: 2002,\n data: new Uint8Array([0xc3]),\n timestamp,\n rapFlag: false,\n })\n ).not.toBeNull()\n\n reassembler.flush()\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 78,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 13,\n sampleNumber: 1,\n sampleOffset: 0,\n packetSequenceNumber: 3000,\n data: new Uint8Array([0xd4]),\n timestamp,\n rapFlag: true,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 78,\n fragmentCounter: 1,\n packetSequenceNumber: 3001,\n data: new Uint8Array([0xe5]),\n timestamp,\n rapFlag: false,\n })\n ).toBeNull()\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 78,\n fragmentCounter: 1,\n packetSequenceNumber: 3001,\n data: new Uint8Array([0xe5]),\n timestamp,\n rapFlag: false,\n }),\n 'fragment-counter-mismatch'\n )\n const out = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 78,\n fragmentCounter: 0,\n packetSequenceNumber: 3002,\n data: new Uint8Array([0xf6]),\n timestamp,\n rapFlag: false,\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xd4, 0xe5, 0xf6]))\n })\n\n test('reassembles middle and terminal continuations that arrive before first', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 54321n\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 45,\n fragmentCounter: 1,\n data: new Uint8Array([0xb2]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(45, 1),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 45,\n fragmentCounter: 0,\n data: new Uint8Array([0xc3]),\n timestamp: timestamp + 1n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(45, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n const out = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 45,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 10,\n sampleNumber: 18,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(45, 2),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3]))\n expect(out!.sampleNumber).toBe(18)\n expect(out!.movieFragmentSequenceNumber).toBe(10)\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('rejects a duplicate continuation before first without replacing retained media', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 54322n\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 46,\n fragmentCounter: 0,\n data: new Uint8Array([0xc3]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(46, 0),\n })\n ).toBeNull()\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 46,\n fragmentCounter: 0,\n data: new Uint8Array([0xff]),\n timestamp: timestamp + 1n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(46, 0),\n }),\n 'fragment-counter-mismatch'\n )\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 46,\n fragmentCounter: 1,\n data: new Uint8Array([0xb2]),\n timestamp: timestamp + 2n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(46, 1),\n })\n ).toBeNull()\n\n const out = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 46,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 10,\n sampleNumber: 19,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: timestamp + 3n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(46, 2),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3]))\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('rejects invalid ISO fragmentation-indicator and counter combinations', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const invalidHeaders = [\n { fragmentationIndicator: 0, fragmentCounter: 1 },\n { fragmentationIndicator: 3, fragmentCounter: 1 },\n { fragmentationIndicator: 1, fragmentCounter: 0 },\n { fragmentationIndicator: 2, fragmentCounter: 0 },\n { fragmentationIndicator: -1, fragmentCounter: 1 },\n { fragmentationIndicator: 4, fragmentCounter: 1 },\n { fragmentationIndicator: 1.5, fragmentCounter: 1 },\n { fragmentationIndicator: 1, fragmentCounter: -1 },\n { fragmentationIndicator: 1, fragmentCounter: 256 },\n { fragmentationIndicator: 1, fragmentCounter: 1.5 },\n { fragmentationIndicator: 1, fragmentCounter: Number.NaN },\n ]\n\n for (const header of invalidHeaders) {\n expectRejection(\n () =>\n reassembler.addFragment({\n ...header,\n mpuSequenceNumber: 48,\n data: new Uint8Array([0xa1]),\n timestamp: 54324n,\n rapFlag: false,\n }),\n 'invalid-fragment-state'\n )\n }\n\n expect(reassembler.pendingCount).toBe(0)\n expect(reassembler.stats.reassembledMFUs).toBe(0)\n })\n\n test('reassembles the maximum ISO chain of 256 payloads', () => {\n const reassembler = new MFUReassembler({ timeoutMs: null })\n const common = {\n mpuSequenceNumber: 47,\n timestamp: 54323n,\n rapFlag: false,\n }\n\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 255,\n itemId: 1,\n data: new Uint8Array([0]),\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(47, 255),\n })\n ).toBeNull()\n for (let counter = 254; counter > 0; counter--) {\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 2,\n fragmentCounter: counter,\n data: new Uint8Array([255 - counter]),\n packetSequenceNumber: testPacketSequence(47, counter),\n })\n ).toBeNull()\n }\n const out = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n data: new Uint8Array([255]),\n packetSequenceNumber: testPacketSequence(47, 0),\n })\n\n expect(out?.fragmentCount).toBe(256)\n expect(out?.data).toEqual(Uint8Array.from({ length: 256 }, (_, index) => index))\n })\n\n test('should not reassemble without last fragment (FI=3)', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 2,\n itemId: 1,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 2),\n })\n const result = reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n data: new Uint8Array([2]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n expect(result).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n })\n\n test('bounds continuations that arrive before their first fragments', () => {\n const reassembler = new MFUReassembler({\n timeoutMs: null,\n maxBuffered: 1,\n })\n // Data unit 1 parks headless at address 1000 and takes the only slot.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n\n // Data unit 2 parks headless at address 2000. The bound holds by\n // replacement: data unit 1, admitted first, yields its slot.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n data: new Uint8Array([2]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(2, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n expect(reassembler.getStatus().pendingFragments).toBe(1)\n expect(reassembler.stats.evictedMemoryLimit).toBe(1)\n\n // Data unit 2's head inherits the slot its queue already holds, so it\n // evicts nothing and completes from the parked continuation.\n const completed = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 2,\n fragmentCounter: 1,\n itemId: 2,\n data: new Uint8Array([4]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(2, 1),\n })\n expect(completed?.data).toEqual(new Uint8Array([4, 2]))\n expect(reassembler.stats.evictedMemoryLimit).toBe(1)\n expect(reassembler.pendingCount).toBe(0)\n\n // Data unit 1's continuation was released with its slot, so its head\n // opens a fresh run and waits rather than completing from evicted state.\n const result = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([3]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n expect(result).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n })\n\n test('expires a continuation that never receives its first fragment', () => {\n vi.useFakeTimers()\n try {\n const reassembler = new MFUReassembler({\n timeoutMs: 50,\n maxBuffered: 1,\n })\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n vi.advanceTimersByTime(50)\n expect(reassembler.pendingCount).toBe(0)\n expect(reassembler.stats.timedOutMFUs).toBe(1)\n } finally {\n vi.useRealTimers()\n }\n })\n\n test('retimes retained continuations when the timeout policy changes', () => {\n vi.useFakeTimers()\n try {\n const enabled = new MFUReassembler({ timeoutMs: null, maxBuffered: 1 })\n expect(\n enabled.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n enabled.timeoutMs = 10\n vi.advanceTimersByTime(10)\n expect(enabled.pendingCount).toBe(0)\n\n const disabled = new MFUReassembler({ timeoutMs: 10, maxBuffered: 1 })\n expect(\n disabled.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n data: new Uint8Array([2]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(2, 0),\n })\n ).toBeNull()\n disabled.timeoutMs = null\n vi.advanceTimersByTime(10)\n expect(disabled.pendingCount).toBe(1)\n disabled.flush()\n } finally {\n vi.useRealTimers()\n }\n })\n\n test('bounds active data units by evicting the oldest at maxBuffered', () => {\n const reassembler = new MFUReassembler({\n timeoutMs: 1000,\n maxBuffered: 2,\n })\n\n // Two runs remain active; the third first fragment displaces the run\n // admitted longest ago instead of being refused.\n for (let i = 0; i < 2; i++) {\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: i,\n fragmentCounter: 1,\n itemId: i,\n data: new Uint8Array([i]),\n timestamp: BigInt(i * 100),\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(i, 1),\n })\n }\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 2,\n fragmentCounter: 1,\n itemId: 2,\n data: new Uint8Array([2]),\n timestamp: 200n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(2, 1),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(2)\n // Eviction is a replacement policy, not a deadline: it must not be\n // reported as a timeout, and it must be separately countable.\n expect(reassembler.stats.timedOutMFUs).toBe(0)\n expect(reassembler.stats.evictedMemoryLimit).toBe(1)\n\n // The evicted run's continuation no longer has a run to join; it parks\n // as a fresh headless data unit, which displaces the next-oldest run.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 0,\n fragmentCounter: 0,\n data: new Uint8Array([9]),\n timestamp: 300n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(0, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(2)\n expect(reassembler.stats.evictedMemoryLimit).toBe(2)\n expect(reassembler.stats.timedOutMFUs).toBe(0)\n })\n\n test('should report stats correctly', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // One complete MFU\n reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n itemId: 1,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n })\n\n const status = reassembler.getStatus()\n expect(status.bufferedMFUs).toBe(0)\n expect(status.stats.totalFragments).toBe(1)\n expect(status.stats.reassembledMFUs).toBe(1)\n })\n\n test('rejects an overlapping first without replacing the original fragment', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n source: 'multicast',\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n // Duplicate of fragmentCounter=1\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 2,\n data: new Uint8Array([0xbb]),\n timestamp: 1000n,\n rapFlag: true,\n source: 'moq',\n packetSequenceNumber: testPacketSequence(1, 1),\n }),\n 'overlapping-data-unit'\n )\n\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(result).not.toBeNull()\n expect(result!.data).toEqual(new Uint8Array([0xaa, 0xcc]))\n expect(result!.fragmentCount).toBe(2)\n expect(result!.source).toBe('multicast')\n })\n })\n\n test('should retain an assembly with a gap in fragment counters', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // First fragment declares three succeeding payloads.\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 3,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 3),\n })\n // Counter 1 arrives while counter 2 is missing.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n ).toBeNull()\n // Last fragment has the required zero counter.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0xdd]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n expect(reassembler.getStatus().pendingFragments).toBe(3)\n })\n\n test('accepts a reordered countdown and rejects a stale duplicate', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // Counters 3, 1, 0 arrive (gap at 2).\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 3,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 3),\n })\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 2,\n data: new Uint8Array([0xbb]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 2),\n })\n ).toBeNull()\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 1001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n }),\n 'fragment-counter-mismatch'\n )\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0xdd]),\n timestamp: 1002n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(result).not.toBeNull()\n expect(result!.data).toEqual(new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd]))\n expect(result!.fragmentCount).toBe(4)\n })\n\n test('rejects an overlapping first without discarding queued continuations', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const fragment = (fragmentationIndicator: number, fragmentCounter: number, byte: number): MFUFragment => ({\n fragmentationIndicator,\n mpuSequenceNumber: 2,\n fragmentCounter,\n data: new Uint8Array([byte]),\n timestamp: 1000n,\n rapFlag: fragmentationIndicator === 1,\n packetSequenceNumber: testPacketSequence(2, fragmentCounter),\n ...(fragmentationIndicator === 1 ? { itemId: 9 } : {}),\n })\n\n expect(reassembler.addFragment(fragment(1, 3, 0xaa))).toBeNull()\n expect(reassembler.addFragment(fragment(2, 1, 0xcc))).toBeNull()\n expectRejection(() => reassembler.addFragment(fragment(1, 1, 0xee)), 'overlapping-data-unit')\n expect(reassembler.addFragment(fragment(2, 2, 0xbb))).toBeNull()\n expect(reassembler.addFragment(fragment(3, 0, 0xdd))?.data).toEqual(new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd]))\n })\n\n test('rejects a duplicate emitted intrinsic identity', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 42,\n fragmentCounter: 0,\n data: new Uint8Array([0xab]),\n timestamp: 1001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(42, 0),\n })\n ).not.toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 42,\n fragmentCounter: 0,\n data: new Uint8Array([0xbc]),\n timestamp: 1002n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(42, 0, 10),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xbb]),\n timestamp: 2000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1, 10),\n })\n ).toBeNull()\n // The queued FI=3 above is addressed to a data unit of MPU 42 whose own\n // FI=1 has not arrived. This duplicate head names that same address, but\n // its identity already emitted, so it is dropped before it can open a run\n // and touches nothing: the queue is retained and reclaimed by its own\n // arrival timeout.\n expect(reassembler.pendingCount).toBe(1)\n\n const result = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 43,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 2000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(43, 1),\n })\n expect(result).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 43,\n fragmentCounter: 0,\n data: new Uint8Array([0xdd]),\n timestamp: 2001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(43, 0),\n })?.data\n ).toEqual(new Uint8Array([0xcc, 0xdd]))\n })\n\n test('does not create active state for an emitted identity duplicate', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 50,\n fragmentCounter: 1,\n movieFragmentSequenceNumber: 10,\n sampleNumber: 18,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(50, 1),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 50,\n fragmentCounter: 0,\n data: new Uint8Array([0xb2]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(50, 0),\n })?.data\n ).toEqual(new Uint8Array([0xa1, 0xb2]))\n expect(reassembler.pendingCount).toBe(0)\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 50,\n fragmentCounter: 1,\n movieFragmentSequenceNumber: 10,\n sampleNumber: 18,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 2000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(50, 1, 10),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('flush should clear emitted set and allow re-assembly', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // Use fragmented MFU (FI=1 start + FI=3 end) \u2014 only fragmented MFUs\n // go through the #emitted dedup set. FI=0 (complete) bypasses it.\n const first = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1),\n })\n expect(first).toBeNull() // first fragment, not yet complete\n const assembled = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 42,\n fragmentCounter: 0,\n data: new Uint8Array([0xbb]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 0),\n })\n expect(assembled).not.toBeNull() // complete \u2014 mpuSeq 42 now in emitted set\n\n // Rejected before flush \u2014 same mpuSeq already emitted\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1, 10),\n })\n ).toBeNull()\n\n // After flush, same mpuSeq should work again\n reassembler.flush()\n const r2 = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xdd]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1),\n })\n expect(r2).toBeNull() // first fragment of new assembly\n const r3 = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 42,\n fragmentCounter: 0,\n data: new Uint8Array([0xee]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 0),\n })\n expect(r3).not.toBeNull()\n expect(r3!.data).toEqual(new Uint8Array([0xdd, 0xee]))\n })\n\n describe('createFragment', () => {\n test('should create fragment from complete timed MMTP packet', () => {\n // Build: MMTP(12) + MPU(8, T=1 FI=0 payloadLength=25) + MFU_DU(14) + data(5)\n const packet = new Uint8Array(12 + 8 + 14 + 5)\n // MMTP header: byte0 = rap=1, byte1 = MPU type\n packet[0] = 0x01 // RAP flag set\n packet[1] = 0x00 // packet_type = MPU\n // packetId = 1\n const view = new DataView(packet.buffer)\n view.setUint16(2, 1, false)\n view.setUint32(4, 42, false) // timestamp\n view.setUint32(8, 100, false) // sequence number\n\n // MPU header at offset 12\n view.setUint16(12, 25, false) // 6 remaining MPU bytes + 14 DU + 5 media\n packet[14] = 0x28 // FT=2, T=1, FI=0, A=0\n packet[15] = 0 // fragment_counter\n view.setUint32(16, 999, false) // mpuSequenceNumber\n\n // MFU DU header (14 bytes) at offset 20.\n view.setUint32(20, 7, false) // movie_fragment_sequence_number\n view.setUint32(24, 1, false) // sample_number\n // Video data at offset 34\n packet[34] = 0xde\n packet[35] = 0xad\n packet[36] = 0xbe\n packet[37] = 0xef\n packet[38] = 0x01\n\n const frag = createFragment(packet)\n expect(frag.mpuSequenceNumber).toBe(999)\n expect(frag.fragmentationIndicator).toBe(0)\n expect(frag.rapFlag).toBe(true)\n expect(frag.movieFragmentSequenceNumber).toBe(7)\n expect(frag.sampleNumber).toBe(1)\n expect(frag.sampleOffset).toBe(0)\n expect(frag.data.length).toBe(5) // 5 bytes of video data\n })\n\n test('reads Item_ID from a non-timed MFU DU header', () => {\n const packet = new Uint8Array(12 + 8 + 4 + 2)\n const view = new DataView(packet.buffer)\n view.setUint16(2, 1, false)\n view.setUint32(4, 42, false)\n view.setUint32(8, 100, false)\n view.setUint16(12, 12, false) // 6 remaining MPU bytes + 4 DU + 2 media\n packet[14] = 0x20 // FT=2, T=0, FI=0\n view.setUint32(16, 999, false)\n view.setUint32(20, 0x01020304, false)\n packet.set([0xde, 0xad], 24)\n\n const fragment = createFragment(packet)\n expect(fragment.itemId).toBe(0x01020304)\n expect(fragment.data).toEqual(new Uint8Array([0xde, 0xad]))\n })\n })\n\n describe('processMMTPWithReassembly', () => {\n test('should skip FEC repair packets', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const packet = new Uint8Array(12 + 8 + 14 + 4)\n packet[0] = 0x10 // fecType=2 (repair)\n const result = processMMTPWithReassembly(packet, reassembler)\n expect(result).toBeNull()\n })\n\n test('should return reassembled MFU for complete packet', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const packet = new Uint8Array(12 + 8 + 14 + 4)\n packet[0] = 0x01 // RAP\n packet[1] = 0x00 // MPU\n const view = new DataView(packet.buffer)\n view.setUint16(2, 1, false) // packetId\n view.setUint32(4, 100, false) // timestamp\n view.setUint32(8, 1, false) // seq\n // MPU: payloadLength=24 (6 + 14 + 4), FT=2 T=1 FI=0 A=0\n view.setUint16(12, 24, false)\n packet[14] = 0x28\n view.setUint32(16, 42, false) // mpuSeq\n view.setUint32(20, 7, false) // movie_fragment_sequence_number\n view.setUint32(24, 1, false) // sample_number\n\n packet[34] = 0x00\n packet[35] = 0x00\n packet[36] = 0x00\n packet[37] = 0x01\n\n const result = processMMTPWithReassembly(packet, reassembler)\n expect(result).not.toBeNull()\n expect(result!.mpuSequenceNumber).toBe(42)\n expect(result!.rapFlag).toBe(true)\n })\n })\n\n describe('strict delivery provenance', () => {\n test('folds to multicast only when every fragment is explicitly multicast', () => {\n expect(deliveryProvenanceOf([{ source: 'multicast' }, { source: 'multicast' }])).toBe('multicast')\n })\n\n test('a single moq fragment makes the whole frame mixed', () => {\n expect(deliveryProvenanceOf([{ source: 'multicast' }, { source: 'moq' }])).toBe('mixed')\n // ...and it does not matter where in the run it lands.\n expect(deliveryProvenanceOf([{ source: 'moq' }, { source: 'multicast' }])).toBe('mixed')\n })\n\n test('an unattributed fragment degrades multicast to unknown, never to proven', () => {\n expect(deliveryProvenanceOf([{ source: 'multicast' }, {}])).toBe('unknown')\n expect(deliveryProvenanceOf([{}])).toBe('unknown')\n })\n\n test('mixed absorbs unknown \u2014 a moq byte is worse news than a missing label', () => {\n expect(deliveryProvenanceOf([{ source: 'moq' }, {}])).toBe('mixed')\n expect(deliveryProvenanceOf([{}, { source: 'moq' }])).toBe('mixed')\n })\n\n test('an empty fragment set proves nothing', () => {\n expect(deliveryProvenanceOf([])).toBe('unknown')\n })\n\n test('the fold never upgrades a verdict', () => {\n expect(foldDeliveryProvenance('mixed', 'multicast')).toBe('mixed')\n expect(foldDeliveryProvenance('unknown', 'multicast')).toBe('unknown')\n expect(foldDeliveryProvenance('unknown', 'moq')).toBe('mixed')\n })\n\n test('a multicast head with a non-multicast tail is NOT multicast-exclusive', () => {\n // This is the leak this field exists to close. `source` legitimately\n // reports 'multicast' \u2014 head-fragment-wins is the routing contract \u2014\n // but the frame contains bytes that never crossed a multicast socket,\n // so it must never be counted as physical-delivery evidence.\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n source: 'multicast',\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: false,\n source: 'moq',\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(result).not.toBeNull()\n expect(result!.fragmentCount).toBe(2)\n expect(result!.source).toBe('multicast')\n expect(result!.deliveryProvenance).toBe('mixed')\n })\n\n test('an all-multicast fragmented frame is multicast-exclusive', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 2,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 2000n,\n rapFlag: true,\n source: 'multicast',\n packetSequenceNumber: testPacketSequence(2, 1),\n })\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n data: new Uint8Array([0xcc]),\n timestamp: 2000n,\n rapFlag: false,\n source: 'multicast',\n packetSequenceNumber: testPacketSequence(2, 0),\n })\n expect(result).not.toBeNull()\n expect(result!.deliveryProvenance).toBe('multicast')\n })\n\n test('an unlabelled complete frame proves nothing', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const result = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 3,\n fragmentCounter: 0,\n itemId: 1,\n data: new Uint8Array([1]),\n timestamp: 3000n,\n rapFlag: true,\n })\n expect(result).not.toBeNull()\n expect(result!.deliveryProvenance).toBe('unknown')\n })\n })\n}\n", "/**\n * H.264/AVC and H.265/HEVC codec utilities for MMTP container format conversion.\n *\n * Shared between moq-lib (hang-mmt-fec) and moqtail players.\n * Handles AVCC\u2194Annex-B conversion, SPS/PPS extraction, fallback avcC generation,\n * HEVC keyframe (IRAP) detection, HEVC codec strings and hvcC records, and MMTP\n * timestamp conversion. The video codec family is always selected from the\n * catalog codec string ({@link videoSampleEntryFromCodecString}), never sniffed.\n *\n * @packageDocumentation\n */\n\n// \u2500\u2500\u2500 Public Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Result of extracting a video codec configuration record from an fMP4 init segment. */\nexport interface AvcCInfo {\n codec: string\n description: Uint8Array\n}\n\n/** Result of extracting AAC AudioSpecificConfig from an fMP4 init segment. */\nexport interface AacAscInfo {\n /** Codec string like \"mp4a.40.2\" (AAC-LC). Derived from AOT in ASC[0] >> 3. */\n codec: string\n /** AudioSpecificConfig bytes \u2014 what AudioDecoder.configure({description}) needs for raw AAC. */\n description: Uint8Array\n}\n\n/** SPS and PPS NAL units extracted from an avcC record. */\nexport interface SpsPps {\n sps: Uint8Array\n pps: Uint8Array\n}\n\n/** Options for fallback avcC generation. */\nexport interface FallbackAvcCOptions {\n /** Codec string like \"avc1.640033\". Profile/constraint/level parsed from hex triplet. */\n codec?: string\n /** Coded width in pixels. Used in SPS pic_width_in_mbs. */\n width?: number\n /** Coded height in pixels. Used in SPS pic_height_in_map_units. */\n height?: number\n}\n\n// \u2500\u2500\u2500 Public Functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Extract the codec configuration record (avcC/hvcC/av1C/vpcC) from\n * an fMP4 init segment (moov box).\n * MMTP FT=0 packets contain a complete fMP4: moov > trak > mdia > minf > stbl > stsd > sample entry > codec config.\n *\n * Walks every trak and returns the first supported video sample entry. This\n * handles both per-track CMSF init segments and multiplexed ftyp+moov blobs\n * where the audio trak may appear before video.\n */\nexport function extractAvcCFromInitSegment(data: Uint8Array): AvcCInfo | undefined {\n if (!hasIsoBmffMagic(data)) return undefined\n\n for (const stsd of findTrackStsdBoxes(data)) {\n const info = extractVideoConfigFromStsd(data, stsd)\n if (info) return info\n }\n\n return undefined\n}\n\nfunction extractVideoConfigFromStsd(data: Uint8Array, stsd: { o: number; sz: number }): AvcCInfo | undefined {\n let s = stsd.o + 16\n const e = stsd.o + stsd.sz\n\n // H.264: avc1 VisualSampleEntry \u2192 avcC.\n const avc1 = findBox(data, s, e, 'avc1')\n if (avc1 && avc1.sz >= 86) {\n const avcC = findBox(data, avc1.o + 86, avc1.o + avc1.sz, 'avcC')\n if (avcC && avcC.sz >= 12) {\n const d = data.subarray(avcC.o + 8, avcC.o + avcC.sz)\n const codec = `avc1.${hex2(d[1])}${hex2(d[2])}${hex2(d[3])}`\n return { codec, description: d }\n }\n }\n // HEVC: hvc1/hev1 VisualSampleEntry \u2192 hvcC. Same +86 child-box offset as\n // avc1 (identical VisualSampleEntry layout). Without this branch, HEVC\n // ftyp+moov inits yield no descriptor, so the WebCodecs path configures the\n // VideoDecoder with no `description` and the first length-prefixed hvc1\n // keyframe decode() throws DataError (\"must fill out the description field\"),\n // which the player's MoQ pump catch swallows \u2192 video track silently 0-fps\n // (BLO-8054).\n // The codec string's first element is the sample entry actually present\n // (ISO/IEC 14496-15 Annex E.3), so an hev1 entry yields \"hev1.*\".\n for (const sampleEntry of ['hvc1', 'hev1'] as const) {\n const hvc = findBox(data, s, e, sampleEntry)\n if (!hvc || hvc.sz < 86) continue\n const hvcC = findBox(data, hvc.o + 86, hvc.o + hvc.sz, 'hvcC')\n // HEVCDecoderConfigurationRecord is \u226523 bytes (+8 box header).\n if (hvcC && hvcC.sz >= 8 + 23) {\n const d = data.subarray(hvcC.o + 8, hvcC.o + hvcC.sz)\n return { codec: hevcCodecStringFromHvcC(d, sampleEntry), description: d }\n }\n }\n // AV1: av01 VisualSampleEntry -> av1C. Real FFmpeg MMTP inits can carry an\n // empty av1C box (size=8) while still providing the authoritative sample\n // entry/codec signal. Return an empty description for that shape so callers\n // can open their FT=0 gate without passing a zero-byte WebCodecs description.\n const av01 = findBox(data, s, e, 'av01')\n if (av01 && av01.sz >= 86) {\n const av1C = findBox(data, av01.o + 86, av01.o + av01.sz, 'av1C')\n if (av1C && av1C.sz >= 8) {\n const d = data.subarray(av1C.o + 8, av1C.o + av1C.sz)\n return { codec: codecStringFromAv1C(d), description: d }\n }\n }\n // VP9: vp09 VisualSampleEntry -> vpcC. Some encoders omit the VP codec record\n // payload; use the sample entry as readiness evidence and fall back to a\n // conservative vp09 codec string in that case.\n const vp09 = findBox(data, s, e, 'vp09')\n if (vp09 && vp09.sz >= 86) {\n const vpcC = findBox(data, vp09.o + 86, vp09.o + vp09.sz, 'vpcC')\n if (vpcC && vpcC.sz >= 8) {\n const d = data.subarray(vpcC.o + 8, vpcC.o + vpcC.sz)\n return { codec: codecStringFromVpcC(d), description: d }\n }\n }\n return undefined\n}\n\n/**\n * Generate a minimal avcC (AVCDecoderConfigurationRecord) from codec parameters.\n * Parses \"avc1.XXYYZZ\" \u2192 profile_idc=0xXX, constraint_flags=0xYY, level_idc=0xZZ.\n * Width/height are encoded in the SPS; if not provided, they're inferred from the\n * H.264 level's max frame size (e.g. level 4.2 \u2192 2048x1088).\n * Used when late MoQ subscribers miss the FT=0 init segment (moov/avcC).\n */\nexport function generateFallbackAvcC(options?: FallbackAvcCOptions | string): Uint8Array {\n const opts: FallbackAvcCOptions = typeof options === 'string' ? { codec: options } : (options ?? {})\n\n let profile = 0x64\n let constraint = 0x00\n let level = 0x2a\n\n if (opts.codec) {\n const match = opts.codec.match(/^avc[13]\\.([0-9a-fA-F]{6})$/)\n if (match) {\n profile = Number.parseInt(match[1].substring(0, 2), 16)\n constraint = Number.parseInt(match[1].substring(2, 4), 16)\n level = Number.parseInt(match[1].substring(4, 6), 16)\n }\n }\n\n const { width, height } = resolveResolution(opts.width, opts.height, level)\n const sps = buildMinimalSps(profile, constraint, level, width, height)\n const pps = buildMinimalPps(profile)\n\n const avcC = new Uint8Array(5 + 1 + 2 + sps.length + 1 + 2 + pps.length)\n let w = 0\n avcC[w++] = 0x01\n avcC[w++] = profile\n avcC[w++] = constraint\n avcC[w++] = level\n avcC[w++] = 0xff\n avcC[w++] = 0xe1\n avcC[w++] = (sps.length >> 8) & 0xff\n avcC[w++] = sps.length & 0xff\n avcC.set(sps, w)\n w += sps.length\n avcC[w++] = 0x01\n avcC[w++] = (pps.length >> 8) & 0xff\n avcC[w++] = pps.length & 0xff\n avcC.set(pps, w)\n return avcC\n}\n\n/**\n * Extract raw SPS and PPS NAL unit bytes from an avcC (AVCDecoderConfigurationRecord).\n * Returns the first SPS and first PPS found.\n */\nexport function extractSpsPpsFromAvcC(avcC: Uint8Array): SpsPps | undefined {\n if (avcC.length < 8) return undefined\n const numSPS = avcC[5] & 0x1f\n let offset = 6\n let sps: Uint8Array | undefined\n for (let i = 0; i < numSPS && offset + 2 <= avcC.length; i++) {\n const spsLen = (avcC[offset] << 8) | avcC[offset + 1]\n offset += 2\n if (offset + spsLen <= avcC.length) {\n if (!sps) sps = avcC.subarray(offset, offset + spsLen)\n offset += spsLen\n }\n }\n let pps: Uint8Array | undefined\n if (offset < avcC.length) {\n const numPPS = avcC[offset] & 0x1f\n offset++\n for (let i = 0; i < numPPS && offset + 2 <= avcC.length; i++) {\n const ppsLen = (avcC[offset] << 8) | avcC[offset + 1]\n offset += 2\n if (offset + ppsLen <= avcC.length) {\n if (!pps) pps = avcC.subarray(offset, offset + ppsLen)\n offset += ppsLen\n }\n }\n }\n return sps && pps ? { sps, pps } : undefined\n}\n\n/**\n * Convert AVCC (4-byte NAL length prefixed) to Annex-B (start code prefixed).\n * Prepends SPS/PPS NALs before keyframes.\n */\nexport function avccToAnnexB(avccData: Uint8Array, isKeyframe: boolean, spsPps: SpsPps): Uint8Array {\n let offset = 0\n let totalNalSize = 0\n let nalCount = 0\n while (offset + 4 <= avccData.length) {\n const nalLen =\n (avccData[offset] << 24) |\n (avccData[offset + 1] << 16) |\n (avccData[offset + 2] << 8) |\n avccData[offset + 3]\n offset += 4\n if (nalLen <= 0 || offset + nalLen > avccData.length) break\n totalNalSize += nalLen\n nalCount++\n offset += nalLen\n }\n if (nalCount === 0) return avccData\n\n let outSize = nalCount * 4 + totalNalSize\n if (isKeyframe) {\n outSize += 4 + spsPps.sps.length + 4 + spsPps.pps.length\n }\n const out = new Uint8Array(outSize)\n let w = 0\n\n if (isKeyframe) {\n out[w++] = 0; out[w++] = 0; out[w++] = 0; out[w++] = 1\n out.set(spsPps.sps, w); w += spsPps.sps.length\n out[w++] = 0; out[w++] = 0; out[w++] = 0; out[w++] = 1\n out.set(spsPps.pps, w); w += spsPps.pps.length\n }\n\n offset = 0\n while (offset + 4 <= avccData.length) {\n const nalLen =\n (avccData[offset] << 24) |\n (avccData[offset + 1] << 16) |\n (avccData[offset + 2] << 8) |\n avccData[offset + 3]\n offset += 4\n if (nalLen <= 0 || offset + nalLen > avccData.length) break\n out[w++] = 0; out[w++] = 0; out[w++] = 0; out[w++] = 1\n out.set(avccData.subarray(offset, offset + nalLen), w)\n w += nalLen\n offset += nalLen\n }\n return out\n}\n\n/**\n * Build an avcC record by parsing Annex-B data to extract SPS (NAL type 7) and PPS (NAL type 8).\n * Returns the avcC record, or null if SPS/PPS not found.\n */\nexport function extractCodecDescriptionFromAnnexB(data: Uint8Array): Uint8Array | null {\n const nalUnits: Array<{ type: number; data: Uint8Array }> = []\n let i = 0\n while (i < data.length - 4) {\n if (data[i] === 0 && data[i + 1] === 0) {\n let nalStart = 0\n if (data[i + 2] === 1) {\n nalStart = i + 3\n } else if (data[i + 2] === 0 && data[i + 3] === 1) {\n nalStart = i + 4\n }\n if (nalStart > 0 && nalStart < data.length) {\n let nalEnd = data.length\n for (let j = nalStart; j < data.length - 3; j++) {\n if (data[j] === 0 && data[j + 1] === 0 &&\n (data[j + 2] === 1 || (data[j + 2] === 0 && data[j + 3] === 1))) {\n nalEnd = j\n break\n }\n }\n const nalType = data[nalStart] & 0x1f\n nalUnits.push({ type: nalType, data: data.slice(nalStart, nalEnd) })\n i = nalEnd\n continue\n }\n }\n i++\n }\n\n const sps = nalUnits.find(n => n.type === 7)\n const pps = nalUnits.find(n => n.type === 8)\n if (!sps || !pps) return null\n\n const avcCLength = 6 + 2 + sps.data.length + 1 + 2 + pps.data.length\n const avcC = new Uint8Array(avcCLength)\n let offset = 0\n avcC[offset++] = 1\n avcC[offset++] = sps.data[1]\n avcC[offset++] = sps.data[2]\n avcC[offset++] = sps.data[3]\n avcC[offset++] = 0xff\n avcC[offset++] = 0xe1\n avcC[offset++] = (sps.data.length >> 8) & 0xff\n avcC[offset++] = sps.data.length & 0xff\n avcC.set(sps.data, offset); offset += sps.data.length\n avcC[offset++] = 1\n avcC[offset++] = (pps.data.length >> 8) & 0xff\n avcC[offset++] = pps.data.length & 0xff\n avcC.set(pps.data, offset)\n return avcC\n}\n\n/**\n * Derive codec string from avcC record (e.g. \"avc1.640033\").\n */\nexport function codecStringFromAvcC(avcC: Uint8Array): string {\n if (avcC.length < 4) return 'avc1.640033'\n return `avc1.${hex2(avcC[1])}${hex2(avcC[2])}${hex2(avcC[3])}`\n}\n\n/** VPS/SPS/PPS NAL units extracted from an HEVC keyframe (raw NAL bytes, no start code/length prefix). */\nexport interface HevcParameterSets {\n vps?: Uint8Array\n sps?: Uint8Array\n pps?: Uint8Array\n}\n\n/** True for HEVC/H.265 codec strings (hev1.* / hvc1.*). */\nexport function isHevcCodec(codec: string): boolean {\n return codec.startsWith('hev1') || codec.startsWith('hvc1')\n}\n\n/** Video sample entries this package decodes (ISO/IEC 14496-15 5.4.2, 8.4.1). */\nexport type VideoSampleEntry = 'avc1' | 'avc3' | 'hev1' | 'hvc1'\n\n/** Video codec family of a sample entry. */\nexport type VideoCodecFamily = 'avc' | 'hevc'\n\n/**\n * Select the video sample entry named by a catalog codec string's first\n * element (RFC 6381 \u00A73.3). The match is exact and case-sensitive; any other\n * string, including an empty one, throws. There is no default family and the\n * bitstream is never sniffed.\n */\nexport function videoSampleEntryFromCodecString(codec: string): VideoSampleEntry {\n const fourCc = codec.split('.')[0]\n if (fourCc === 'avc1' || fourCc === 'avc3' || fourCc === 'hev1' || fourCc === 'hvc1') return fourCc\n throw new Error(`Unsupported video codec family in codec string ${JSON.stringify(codec)}`)\n}\n\n/** Codec family of a sample entry. */\nexport function videoCodecFamily(sampleEntry: VideoSampleEntry): VideoCodecFamily {\n return sampleEntry === 'avc1' || sampleEntry === 'avc3' ? 'avc' : 'hevc'\n}\n\n/**\n * HEVC `nal_unit_type` codes (ITU-T H.265 Table 7-1). The HEVC NAL unit header\n * is two bytes (H.265 \u00A77.3.1.2): forbidden_zero_bit u(1) | nal_unit_type u(6) |\n * nuh_layer_id u(6) | nuh_temporal_id_plus1 u(3).\n */\nexport const HEVC_NAL_UNIT_TYPE = {\n TRAIL_R: 1,\n /** First IRAP type. */\n BLA_W_LP: 16,\n IDR_W_RADL: 19,\n IDR_N_LP: 20,\n CRA_NUT: 21,\n /** Last IRAP type (reserved IRAP VCL). */\n RSV_IRAP_VCL23: 23,\n /** Last VCL type; 32..=63 are non-VCL. */\n LAST_VCL: 31,\n VPS_NUT: 32,\n SPS_NUT: 33,\n PPS_NUT: 34,\n AUD_NUT: 35,\n PREFIX_SEI_NUT: 39,\n SUFFIX_SEI_NUT: 40,\n} as const\n\n/** Bytes in the HEVC NAL unit header (H.265 \u00A77.3.1.2). */\nexport const HEVC_NAL_HEADER_LENGTH = 2\n\n/**\n * Highest `bitDepth{Luma,Chroma}Minus8` an `hvcC` can carry: both are u(3)\n * (ISO/IEC 14496-15 \u00A78.3.3.1.2). H.265 allows the SPS fields up to 8.\n */\nconst HVCC_MAX_BIT_DEPTH_MINUS8 = 7\n\n/** HEVC `nal_unit_type` from the first NAL header byte: (byte >> 1) & 0x3f. */\nexport function hevcNalUnitType(nal: Uint8Array): number {\n return (nal[0]! >> 1) & 0x3f\n}\n\n/**\n * True for an IRAP picture's `nal_unit_type`: BLA_W_LP..=RSV_IRAP_VCL23\n * (16..=23, H.265 Table 7-1 / \u00A77.4.2.2), including the reserved IRAP types 22\n * and 23. An IRAP picture is the HEVC keyframe.\n */\nexport function isHevcIrapNalUnitType(nalUnitType: number): boolean {\n return nalUnitType >= HEVC_NAL_UNIT_TYPE.BLA_W_LP && nalUnitType <= HEVC_NAL_UNIT_TYPE.RSV_IRAP_VCL23\n}\n\n/** Internal alias kept for the existing helpers below. */\nfunction hevcNalType(nal: Uint8Array): number {\n return hevcNalUnitType(nal)\n}\n\n/**\n * Remove H.26x emulation-prevention bytes from a NAL unit so fixed-position\n * fields (e.g. HEVC profile_tier_level) can be read by byte offset.\n *\n * H.265 \u00A77.3.1.1 / \u00A77.4.2: every 0x03 that follows two zero bytes inside a NAL\n * unit is an emulation_prevention_three_byte and is discarded, including one\n * that ends the NAL unit (the cabac_zero_words case).\n */\nexport function removeEmulationPrevention(rbsp: Uint8Array): Uint8Array {\n const out: number[] = []\n let zeros = 0\n for (let i = 0; i < rbsp.length; i++) {\n const b = rbsp[i]!\n if (zeros >= 2 && b === 0x03) {\n zeros = 0\n continue\n }\n out.push(b)\n zeros = b === 0 ? zeros + 1 : 0\n }\n return Uint8Array.from(out)\n}\n\n/**\n * Fields of an HEVC SPS (H.265 \u00A77.3.2.2.1) needed for the codec string and the\n * hvcC record. `profileCompatibilityFlags` holds the 32 flags in bitstream\n * order (flag 0 is the most significant bit); `constraintIndicatorFlags` holds\n * the 48 bits from general_progressive_source_flag onward.\n */\nexport interface HevcSpsInfo {\n profileSpace: number\n tierFlag: boolean\n profileIdc: number\n profileCompatibilityFlags: number\n constraintIndicatorFlags: Uint8Array\n levelIdc: number\n maxSubLayersMinus1: number\n temporalIdNestingFlag: boolean\n seqParameterSetId: number\n chromaFormatIdc: number\n picWidthInLumaSamples: number\n picHeightInLumaSamples: number\n bitDepthLumaMinus8: number\n bitDepthChromaMinus8: number\n}\n\n/** MSB-first RBSP bit reader (H.265 \u00A77.2 read_bits, \u00A79.2 ue(v)). */\nclass RbspReader {\n #bit = 0\n constructor(private readonly data: Uint8Array) {}\n\n bits(count: number): number {\n if (this.#bit + count > this.data.length * 8) throw new Error('HEVC SPS: RBSP truncated')\n let value = 0\n for (let i = 0; i < count; i++, this.#bit++) {\n value = value * 2 + ((this.data[this.#bit >> 3]! >> (7 - (this.#bit & 7))) & 1)\n }\n return value\n }\n\n skip(count: number): void {\n if (this.#bit + count > this.data.length * 8) throw new Error('HEVC SPS: RBSP truncated')\n this.#bit += count\n }\n\n ue(): number {\n let leadingZeros = 0\n while (this.bits(1) === 0) {\n if (++leadingZeros > 31) throw new Error('HEVC SPS: ue(v) exceeds 32 bits')\n }\n return leadingZeros === 0 ? 0 : 2 ** leadingZeros - 1 + this.bits(leadingZeros)\n }\n}\n\n/**\n * Parse the leading fields of an HEVC SPS NAL unit (2-byte header included, no\n * start code). Emulation prevention is removed first. Throws on a non-SPS or\n * non-base-layer NAL unit, truncation, or a value outside its H.265 \u00A77.4.3.2.1\n * range: a malformed SPS must not yield a guessed record.\n */\nexport function parseHevcSps(sps: Uint8Array): HevcSpsInfo {\n if (sps.length < HEVC_NAL_HEADER_LENGTH || (sps[0]! & 0x80) !== 0 || (sps[1]! & 0x07) === 0) {\n throw new Error('HEVC SPS: invalid NAL unit header')\n }\n if (hevcNalUnitType(sps) !== HEVC_NAL_UNIT_TYPE.SPS_NUT) throw new Error('HEVC SPS: NAL unit is not an SPS')\n if (((sps[0]! & 0x01) << 5 | sps[1]! >> 3) !== 0) {\n throw new Error('HEVC SPS: not in the base layer (nuh_layer_id != 0)')\n }\n const r = new RbspReader(removeEmulationPrevention(sps).subarray(HEVC_NAL_HEADER_LENGTH))\n r.bits(4) // sps_video_parameter_set_id\n const maxSubLayersMinus1 = r.bits(3)\n if (maxSubLayersMinus1 > 6) throw new Error('HEVC SPS: sps_max_sub_layers_minus1 is greater than 6')\n const temporalIdNestingFlag = r.bits(1) === 1\n // profile_tier_level(1, sps_max_sub_layers_minus1), H.265 \u00A77.3.3.\n const profileSpace = r.bits(2)\n const tierFlag = r.bits(1) === 1\n const profileIdc = r.bits(5)\n const profileCompatibilityFlags = r.bits(32) >>> 0\n const constraintIndicatorFlags = new Uint8Array(6)\n for (let i = 0; i < 6; i++) constraintIndicatorFlags[i] = r.bits(8)\n const levelIdc = r.bits(8)\n const subLayerProfilePresent: boolean[] = []\n const subLayerLevelPresent: boolean[] = []\n for (let i = 0; i < maxSubLayersMinus1; i++) {\n subLayerProfilePresent.push(r.bits(1) === 1)\n subLayerLevelPresent.push(r.bits(1) === 1)\n }\n if (maxSubLayersMinus1 > 0) {\n for (let i = maxSubLayersMinus1; i < 8; i++) r.skip(2) // reserved_zero_2bits\n }\n for (let i = 0; i < maxSubLayersMinus1; i++) {\n if (subLayerProfilePresent[i]) r.skip(88) // sub-layer profile fields\n if (subLayerLevelPresent[i]) r.skip(8) // sub_layer_level_idc\n }\n const seqParameterSetId = r.ue()\n if (seqParameterSetId > 15) throw new Error('HEVC SPS: sps_seq_parameter_set_id is greater than 15')\n const chromaFormatIdc = r.ue()\n if (chromaFormatIdc > 3) throw new Error('HEVC SPS: chroma_format_idc is greater than 3')\n if (chromaFormatIdc === 3) r.skip(1) // separate_colour_plane_flag\n const picWidthInLumaSamples = r.ue()\n const picHeightInLumaSamples = r.ue()\n if (picWidthInLumaSamples === 0 || picHeightInLumaSamples === 0) {\n throw new Error('HEVC SPS: picture dimension is zero')\n }\n if (r.bits(1) === 1) for (let i = 0; i < 4; i++) r.ue() // conformance window offsets\n const bitDepthLumaMinus8 = r.ue()\n const bitDepthChromaMinus8 = r.ue()\n if (bitDepthLumaMinus8 > 8 || bitDepthChromaMinus8 > 8) throw new Error('HEVC SPS: bit depth is greater than 16')\n return {\n profileSpace,\n tierFlag,\n profileIdc,\n profileCompatibilityFlags,\n constraintIndicatorFlags,\n levelIdc,\n maxSubLayersMinus1,\n temporalIdNestingFlag,\n seqParameterSetId,\n chromaFormatIdc,\n picWidthInLumaSamples,\n picHeightInLumaSamples,\n bitDepthLumaMinus8,\n bitDepthChromaMinus8,\n }\n}\n\n/**\n * RFC 6381 codec string of an HEVC stream from its SPS, per ISO/IEC 14496-15\n * Annex E.3 (e.g. \"hev1.1.6.L93.B0\"); the first element is `sampleEntry`.\n */\nexport function hevcCodecStringFromSps(sps: Uint8Array, sampleEntry: 'hev1' | 'hvc1'): string {\n const info = parseHevcSps(sps)\n return formatHevcCodecString(\n sampleEntry,\n info.profileSpace,\n info.tierFlag,\n info.profileIdc,\n info.profileCompatibilityFlags,\n info.constraintIndicatorFlags,\n info.levelIdc,\n )\n}\n\n/**\n * ISO/IEC 14496-15 Annex E.3 codec string: sample entry; profile space\n * (none/A/B/C) + decimal profile_idc; the 32 compatibility flags in reverse bit\n * order as hex without leading zeros; L/H tier + decimal level_idc; each\n * constraint byte as hex, trailing zero bytes omitted. Hex digits are upper\n * case, as in the Annex E.3 examples.\n */\nfunction formatHevcCodecString(\n sampleEntry: 'hev1' | 'hvc1',\n profileSpace: number,\n tierFlag: boolean,\n profileIdc: number,\n profileCompatibilityFlags: number,\n constraintIndicatorFlags: Uint8Array,\n levelIdc: number,\n): string {\n let reversed = 0\n let v = profileCompatibilityFlags >>> 0\n for (let i = 0; i < 32; i++) {\n reversed = ((reversed << 1) | (v & 1)) >>> 0\n v >>>= 1\n }\n let lastNonZero = -1\n for (let i = 0; i < constraintIndicatorFlags.length; i++) if (constraintIndicatorFlags[i] !== 0) lastNonZero = i\n const space = profileSpace === 0 ? '' : String.fromCharCode(64 + profileSpace) // 1\u2192'A', 2\u2192'B', 3\u2192'C'\n let codec = `${sampleEntry}.${space}${profileIdc}.${reversed.toString(16).toUpperCase()}.${tierFlag ? 'H' : 'L'}${levelIdc}`\n for (let i = 0; i <= lastNonZero; i++) codec += `.${constraintIndicatorFlags[i]!.toString(16).toUpperCase()}`\n return codec\n}\n\n/**\n * The NAL units of an HEVC sample, split by its framing: a complete 4-byte\n * length-prefixed sample is read as such, otherwise Annex B. Null when the\n * sample is neither \u2014 there is no third framing to read it as.\n */\nfunction splitHevcSample(data: Uint8Array): { nals: Uint8Array[]; lengthPrefixed: boolean } | null {\n if (isCompleteAvccSample(data)) return { nals: splitAvccNalUnits(data), lengthPrefixed: true }\n if (isAnnexB(data)) return { nals: splitAnnexBNalUnits(data), lengthPrefixed: false }\n return null\n}\n\n/** IRAP classification of split NAL units; undefined when none has a header. */\nfunction hevcNalUnitsContainIrap(nals: Uint8Array[]): boolean | undefined {\n let sawNal = false\n for (const nal of nals) {\n if (nal.length < HEVC_NAL_HEADER_LENGTH) continue\n sawNal = true\n if (isHevcIrapNalUnitType(hevcNalUnitType(nal))) return true\n }\n return sawNal ? false : undefined\n}\n\n/**\n * Classify an HEVC access unit as a keyframe (contains an IRAP slice,\n * {@link isHevcIrapNalUnitType}). Complete 4-byte length-prefixed samples are\n * read as such; otherwise Annex B. Returns undefined when no NAL unit is found\n * \u2014 an unreadable sample, which is not the same as a delta frame.\n */\nexport function classifyHevcKeyframe(data: Uint8Array): boolean | undefined {\n const split = splitHevcSample(data)\n return split === null ? undefined : hevcNalUnitsContainIrap(split.nals)\n}\n\n/**\n * True when an HEVC access unit contains an IRAP slice. Collapses \"unreadable\"\n * into false; a caller that must tell the two apart (anything about to write\n * the sample into an mdat) uses {@link prepareHevcCmafSample} instead.\n */\nexport function detectHevcKeyframe(data: Uint8Array): boolean {\n return classifyHevcKeyframe(data) === true\n}\n\n/** An HEVC sample ready for an fMP4 mdat whose hvcC has lengthSizeMinusOne = 3. */\nexport interface HevcCmafSample {\n /** The access unit contains an IRAP slice ({@link isHevcIrapNalUnitType}). */\n keyframe: boolean\n /** 4-byte length-prefixed NAL units (the input itself when already so). */\n data: Uint8Array\n}\n\n/**\n * Classify and normalize an HEVC sample for an fMP4 mdat from one split of its\n * NAL units. Returns null \u2014 the sample must not be written \u2014 when its framing\n * is neither a complete 4-byte length-prefixed sample nor Annex B, when it\n * holds no NAL unit, or when nothing is left after `hvc1` drops the parameter\n * sets: bytes the hvcC's lengthSizeMinusOne = 3 does not describe would be\n * misparsed by the decoder, and an unreadable sample is not a delta frame.\n * Normalization as in {@link normalizeHevcSampleToLengthPrefixed}.\n */\nexport function prepareHevcCmafSample(data: Uint8Array, sampleEntry: 'hev1' | 'hvc1'): HevcCmafSample | null {\n const split = splitHevcSample(data)\n if (split === null) return null\n const keyframe = hevcNalUnitsContainIrap(split.nals)\n if (keyframe === undefined) return null\n const normalized = hevcNalUnitsToLengthPrefixed(data, split.nals, split.lengthPrefixed, sampleEntry)\n return normalized === null ? null : { keyframe, data: normalized }\n}\n\n/**\n * Normalize an HEVC sample to 4-byte length-prefixed NAL units for an fMP4\n * mdat whose hvcC declares lengthSizeMinusOne = 3. Complete length-prefixed\n * samples pass through; Annex B is converted. For `hvc1` the VPS/SPS/PPS are\n * dropped (ISO/IEC 14496-15 8.4.1.1: they live only in the hvcC); for `hev1`\n * they are kept in-band. Throws when the sample cannot be read\n * ({@link prepareHevcCmafSample} returns null) rather than pass it through.\n */\nexport function normalizeHevcSampleToLengthPrefixed(data: Uint8Array, sampleEntry: 'hev1' | 'hvc1'): Uint8Array {\n const prepared = prepareHevcCmafSample(data, sampleEntry)\n if (prepared === null) {\n throw new Error('HEVC sample is neither 4-byte length-prefixed nor Annex B, or holds no NAL unit to write')\n }\n return prepared.data\n}\n\nfunction hevcNalUnitsToLengthPrefixed(\n data: Uint8Array,\n nals: Uint8Array[],\n lengthPrefixed: boolean,\n sampleEntry: 'hev1' | 'hvc1',\n): Uint8Array | null {\n const kept = sampleEntry === 'hvc1'\n ? nals.filter((nal) => {\n const t = hevcNalUnitType(nal)\n return t !== HEVC_NAL_UNIT_TYPE.VPS_NUT && t !== HEVC_NAL_UNIT_TYPE.SPS_NUT && t !== HEVC_NAL_UNIT_TYPE.PPS_NUT\n })\n : nals\n if (kept.length === 0) return null\n if (lengthPrefixed && kept.length === nals.length) return data\n const out = new Uint8Array(kept.reduce((sum, nal) => sum + 4 + nal.length, 0))\n let w = 0\n for (const nal of kept) {\n w = writeNalLength(out, w, nal.length)\n out.set(nal, w)\n w += nal.length\n }\n return out\n}\n\n/**\n * Split an AVCC/hvcC bitstream (length-prefixed NAL units) into raw NAL byte slices.\n * `lengthSize` is the NAL length prefix width (4 for FFmpeg LOC / fMP4 sample data).\n */\nexport function splitAvccNalUnits(data: Uint8Array, lengthSize = 4): Uint8Array[] {\n const nals: Uint8Array[] = []\n let i = 0\n while (i + lengthSize <= data.length) {\n let len = 0\n for (let k = 0; k < lengthSize; k++) len = (len << 8) | data[i + k]\n i += lengthSize\n if (len <= 0 || i + len > data.length) break\n nals.push(data.subarray(i, i + len))\n i += len\n }\n return nals\n}\n\n/**\n * True when `data` is exactly one or more length-prefixed NAL units.\n *\n * This is intentionally stricter than \"starts with a plausible length\": a\n * H.264 AVCC access unit may begin with bytes `00 00 00 01` when the first\n * NAL is a one-byte AUD. That byte pattern is also an Annex-B start code, so\n * callers must prove the whole buffer is complete AVCC before falling back to\n * Annex-B detection/conversion.\n */\nexport function isCompleteAvccSample(data: Uint8Array, lengthSize = 4): boolean {\n let i = 0\n let count = 0\n while (i + lengthSize <= data.length) {\n let len = 0\n for (let k = 0; k < lengthSize; k++) len = (len << 8) | data[i + k]\n i += lengthSize\n if (len <= 0 || i + len > data.length) return false\n i += len\n count++\n }\n return count > 0 && i === data.length\n}\n\nexport type H264SampleFormat = 'avcc' | 'annexb' | 'raw-nal' | 'unknown'\nexport type H264NalUnitFormat = H264SampleFormat | 'partial-avcc'\n\nexport interface H264AvccSample {\n data: Uint8Array\n format: H264SampleFormat\n}\n\nexport interface H264NalUnitSample {\n nals: Uint8Array[]\n format: H264NalUnitFormat\n}\n\n/**\n * Normalize a H.264 access unit/sample to length-prefixed AVCC for fMP4 mdat\n * payloads. Already-AVCC samples are returned as-is. Annex-B and single raw\n * NAL samples are converted one-way to AVCC at the MSE/fMP4 boundary.\n */\nexport function normalizeH264SampleToAvcc(data: Uint8Array): H264AvccSample {\n if (isCompleteAvccSample(data)) return { data, format: 'avcc' }\n if (isAnnexB(data)) return { data: annexBToAvcc(data), format: 'annexb' }\n if (isLikelyRawH264NalUnit(data)) return { data: rawH264NalToAvcc(data), format: 'raw-nal' }\n return { data, format: 'unknown' }\n}\n\n/**\n * Split a H.264 access unit/sample into NAL units without changing its framing.\n * Use this for routing, keyframe gates, and diagnostics. Conversion to AVCC is\n * reserved for `normalizeH264SampleToAvcc` at the fMP4/MSE boundary.\n */\nexport function splitH264SampleNalUnits(\n data: Uint8Array,\n options: H264KeyframeDetectionOptions = {},\n): H264NalUnitSample {\n if (isCompleteAvccSample(data)) return { nals: splitAvccNalUnits(data), format: 'avcc' }\n if (isAnnexB(data)) return { nals: splitAnnexBNalUnits(data), format: 'annexb' }\n if (options.allowPartialAvcc === true) {\n const partial = splitPartialAvccNalUnits(data)\n if (partial.length > 0) return { nals: partial, format: 'partial-avcc' }\n }\n if (isLikelyRawH264NalUnit(data)) return { nals: [data], format: 'raw-nal' }\n const nals = splitAvccNalUnits(data)\n return { nals, format: nals.length > 0 ? 'avcc' : 'unknown' }\n}\n\n/**\n * Extract HEVC VPS (type 32) / SPS (33) / PPS (34) NAL units from a keyframe access\n * unit. Accepts either Annex-B (start-code delimited) or AVCC/hvcC (length-prefixed)\n * framing \u2014 FFmpeg LOC delivers the latter.\n */\nexport function extractHevcParameterSets(keyframe: Uint8Array): HevcParameterSets {\n const nals = isAnnexB(keyframe) ? splitAnnexBNalUnits(keyframe) : splitAvccNalUnits(keyframe)\n const out: HevcParameterSets = {}\n for (const nal of nals) {\n if (nal.length === 0) continue\n const t = hevcNalType(nal)\n if (t === 32 && !out.vps) out.vps = nal\n else if (t === 33 && !out.sps) out.sps = nal\n else if (t === 34 && !out.pps) out.pps = nal\n }\n return out\n}\n\n/**\n * Build an hvcC (HEVCDecoderConfigurationRecord, ISO/IEC 14496-15 \u00A78.3.3.1) from\n * VPS/SPS/PPS NAL units. Suitable as the `description` for a WebCodecs HEVC\n * VideoDecoderConfig and as the fMP4 `hvcC` box payload.\n *\n * Field sources (\u00A78.3.3.1.2): general profile/tier/level, chromaFormat,\n * bitDepth{Luma,Chroma}Minus8, numTemporalLayers (= sps_max_sub_layers_minus1\n * + 1) and temporalIdNested come from the parsed SPS. min_spatial_segmentation\n * _idc = 0, parallelismType = 0, avgFrameRate = 0 and constantFrameRate = 0\n * are the record's \"no restriction / unknown / unspecified\" encodings (the VUI\n * and PPS fields that could tighten them are not parsed). lengthSizeMinusOne =\n * 3 (4-byte NAL lengths). array_completeness = 0: the record is derived from\n * in-band parameter sets, which is the `hev1` arrangement (\u00A78.4.1.1).\n *\n * Returns null when the SPS or PPS is absent; throws when the SPS is malformed\n * or its bit depth (16) is not representable in the record.\n *\n * WebCodecs REQUIRES a description for length-prefixed HEVC \u2014 unlike H.264, Chrome will\n * not self-configure HEVC from the bitstream (BLO-7764).\n */\nexport function generateHvcCFromParameterSets(sets: HevcParameterSets): Uint8Array | null {\n const { vps, sps, pps } = sets\n if (!sps || !pps) return null\n const info = parseHevcSps(sps)\n // bitDepth{Luma,Chroma}Minus8 are u(3) in the record (\u00A78.3.3.1.2), so 0..7.\n // H.265 also allows 8 (16-bit), which `0xf8 | 8` would write as 0 \u2014 a\n // record declaring 8-bit \u2014 so a 16-bit SPS is refused, not mis-described.\n if (info.bitDepthLumaMinus8 > HVCC_MAX_BIT_DEPTH_MINUS8 || info.bitDepthChromaMinus8 > HVCC_MAX_BIT_DEPTH_MINUS8) {\n throw new Error('HEVC SPS: bit depth of 16 is not representable in an hvcC record')\n }\n const arrays = [\n { type: 32, nal: vps },\n { type: 33, nal: sps },\n { type: 34, nal: pps },\n ].filter((a): a is { type: number; nal: Uint8Array } => a.nal !== undefined)\n\n let size = 23\n for (const a of arrays) size += 5 + a.nal.length\n const h = new Uint8Array(size)\n let o = 0\n h[o++] = 1 // configurationVersion\n h[o++] = (info.profileSpace << 6) | ((info.tierFlag ? 1 : 0) << 5) | info.profileIdc\n h[o++] = (info.profileCompatibilityFlags >>> 24) & 0xff\n h[o++] = (info.profileCompatibilityFlags >>> 16) & 0xff\n h[o++] = (info.profileCompatibilityFlags >>> 8) & 0xff\n h[o++] = info.profileCompatibilityFlags & 0xff\n h.set(info.constraintIndicatorFlags, o); o += 6 // general_constraint_indicator_flags\n h[o++] = info.levelIdc // general_level_idc\n h[o++] = 0xf0; h[o++] = 0x00 // reserved(1111) + min_spatial_segmentation_idc(0)\n h[o++] = 0xfc // reserved(111111) + parallelismType(0)\n h[o++] = 0xfc | info.chromaFormatIdc // reserved(111111) + chromaFormat\n h[o++] = 0xf8 | info.bitDepthLumaMinus8 // reserved(11111) + bitDepthLumaMinus8\n h[o++] = 0xf8 | info.bitDepthChromaMinus8 // reserved(11111) + bitDepthChromaMinus8\n h[o++] = 0x00; h[o++] = 0x00 // avgFrameRate(0 = unspecified)\n // constantFrameRate(0) | numTemporalLayers | temporalIdNested | lengthSizeMinusOne(3)\n h[o++] = ((info.maxSubLayersMinus1 + 1) << 3) | ((info.temporalIdNestingFlag ? 1 : 0) << 2) | 3\n h[o++] = arrays.length // numOfArrays\n for (const a of arrays) {\n h[o++] = a.type & 0x3f // array_completeness(0) + NAL_unit_type\n h[o++] = 0; h[o++] = 1 // numNalus = 1\n h[o++] = (a.nal.length >> 8) & 0xff\n h[o++] = a.nal.length & 0xff\n h.set(a.nal, o); o += a.nal.length\n }\n return h.subarray(0, o)\n}\n\n/**\n * Build an hvcC `description` for a WebCodecs HEVC VideoDecoderConfig directly from a\n * keyframe access unit (Annex-B or length-prefixed). Returns null if VPS/SPS/PPS aren't\n * present in-band. HEVC analogue of {@link extractCodecDescriptionFromAnnexB}.\n */\nexport function extractHevcCodecDescription(keyframe: Uint8Array): Uint8Array | null {\n return generateHvcCFromParameterSets(extractHevcParameterSets(keyframe))\n}\n\n/**\n * Strip non-VCL NAL units (VPS/SPS/PPS/SEI/AUD, type >= 32) from an HEVC access unit,\n * returning only the VCL NAL units (type <= 31) as a 4-byte length-prefixed buffer.\n *\n * Required when decoding `hvc1` with an explicit hvcC `description`: WebCodecs rejects a\n * sample whose data still carries in-band parameter sets (\"Unable to determine size of\n * bitstream buffer\"). Accepts Annex-B or length-prefixed input. Returns the input\n * unchanged if it already contains only VCL NALs (cheap no-op for delta frames).\n */\nexport function hevcVclOnly(data: Uint8Array): Uint8Array {\n const nals = isAnnexB(data) ? splitAnnexBNalUnits(data) : splitAvccNalUnits(data)\n const vcl = nals.filter((n) => n.length > 0 && hevcNalType(n) <= 31)\n if (vcl.length === nals.length && !isAnnexB(data)) return data\n let size = 0\n for (const n of vcl) size += 4 + n.length\n const out = new Uint8Array(size)\n let o = 0\n for (const n of vcl) {\n out[o++] = (n.length >> 24) & 0xff\n out[o++] = (n.length >> 16) & 0xff\n out[o++] = (n.length >> 8) & 0xff\n out[o++] = n.length & 0xff\n out.set(n, o); o += n.length\n }\n return out\n}\n\n/**\n * Check if data starts with an Annex-B start code (00 00 01 or 00 00 00 01).\n */\nexport function isAnnexB(data: Uint8Array): boolean {\n if (data.length >= 4 && data[0] === 0 && data[1] === 0 &&\n (data[2] === 1 || (data[2] === 0 && data[3] === 1))) {\n return true\n }\n return false\n}\n\n/**\n * Find the byte offset of the first Annex-B start code in data.\n * Returns -1 if no start code found.\n */\nexport function findAnnexBStartCode(data: Uint8Array): number {\n for (let i = 0; i < data.length - 3; i++) {\n if (data[i] === 0 && data[i + 1] === 0) {\n if (data[i + 2] === 1) return i\n if (data[i + 2] === 0 && data[i + 3] === 1) return i\n }\n }\n return -1\n}\n\n/**\n * Split Annex-B bitstream into NAL units.\n * Returns array of NAL unit byte slices (without start codes).\n */\nexport function splitAnnexBNalUnits(data: Uint8Array): Uint8Array[] {\n const nalUnits: Uint8Array[] = []\n let i = 0\n while (i < data.length - 3) {\n if (data[i] === 0 && data[i + 1] === 0) {\n let scLen = 0\n if (data[i + 2] === 1) scLen = 3\n else if (data[i + 2] === 0 && i + 3 < data.length && data[i + 3] === 1) scLen = 4\n if (scLen > 0) {\n const nalStart = i + scLen\n let nalEnd = data.length\n for (let j = nalStart + 1; j < data.length - 2; j++) {\n if (data[j] === 0 && data[j + 1] === 0 &&\n (data[j + 2] === 1 || (data[j + 2] === 0 && j + 3 < data.length && data[j + 3] === 1))) {\n nalEnd = j\n break\n }\n }\n if (nalEnd > nalStart) nalUnits.push(data.subarray(nalStart, nalEnd))\n i = nalEnd\n continue\n }\n }\n i++\n }\n return nalUnits\n}\n\n/**\n * Convert Annex-B (start-code delimited) to AVCC (4-byte length prefixed).\n * Optionally prepends SPS/PPS NALs for keyframes.\n */\nexport function annexBToAvcc(data: Uint8Array, spsPps?: SpsPps): Uint8Array {\n const nalUnits = splitAnnexBNalUnits(data)\n if (nalUnits.length === 0) return data\n\n let extraSize = 0\n if (spsPps) {\n extraSize = 4 + spsPps.sps.length + 4 + spsPps.pps.length\n }\n const frameSize = nalUnits.reduce((sum, nal) => sum + 4 + nal.length, 0)\n const output = new Uint8Array(extraSize + frameSize)\n let w = 0\n\n if (spsPps) {\n w = writeNalLength(output, w, spsPps.sps.length)\n output.set(spsPps.sps, w); w += spsPps.sps.length\n w = writeNalLength(output, w, spsPps.pps.length)\n output.set(spsPps.pps, w); w += spsPps.pps.length\n }\n\n for (const nal of nalUnits) {\n w = writeNalLength(output, w, nal.length)\n output.set(nal, w); w += nal.length\n }\n return output\n}\n\nexport interface H264KeyframeDetectionOptions {\n /**\n * Allow classifying a length-prefixed NAL when the declared NAL length runs\n * beyond this buffer. MMTP routing can see only the first packet fragment of\n * a sample, where the NAL header is present but the full NAL is not.\n */\n allowPartialAvcc?: boolean\n}\n\n/**\n * Classify H.264 keyframe status by scanning NAL units. Returns undefined when\n * the payload is not H.264-classifiable. Handles Annex-B and AVCC formats.\n */\nexport function classifyH264Keyframe(\n data: Uint8Array,\n options: H264KeyframeDetectionOptions = {},\n): boolean | undefined {\n const nals = h264NalUnitsForKeyframeDetection(data, options)\n if (nals.length === 0) return undefined\n let sawIntraSlice = false\n let sawNonIntraVcl = false\n let sawNal = false\n for (const nal of nals) {\n sawNal = true\n const nalType = nal[0] !== undefined ? nal[0] & 0x1f : -1\n if (nalType === 5 || nalType === 7) return true\n if (nalType === 1) {\n if (isH264IntraSlice(nal)) sawIntraSlice = true\n else sawNonIntraVcl = true\n }\n }\n if (sawIntraSlice && !sawNonIntraVcl) return true\n return sawNal ? false : undefined\n}\n\n/**\n * Detect H.264 keyframe by scanning for IDR/SPS or intra non-IDR slices.\n * Handles both Annex-B and AVCC formats.\n */\nexport function detectH264Keyframe(\n data: Uint8Array,\n options: H264KeyframeDetectionOptions = {},\n): boolean {\n return classifyH264Keyframe(data, options) === true\n}\n\n/**\n * Convert MMTP timestamp (NTP short format) to monotonic microseconds.\n * Per ISO 23008-1 \u00A79.2.2 / RFC 5905: upper 16 bits = seconds (lower 16\n * of NTP seconds), lower 16 bits = fractional seconds.\n * Returns a monotonically increasing \u00B5s value within an ~18-hour window.\n */\nexport function mmtpTimestampToMicros(rawTs: bigint | number): number {\n // MMTP carries an NTP short timestamp as an unsigned 32-bit integer. Do not\n // use `Number(rawTs) >>> 0` here: bitwise coercion silently turns malformed\n // values such as -1, 2**32, fractions, and strings into plausible timestamps\n // and can poison downstream latency observations.\n const ts = typeof rawTs === 'bigint'\n ? rawTs >= 0n && rawTs <= 0xffff_ffffn\n ? Number(rawTs)\n : undefined\n : Number.isSafeInteger(rawTs) && rawTs >= 0 && rawTs <= 0xffff_ffff\n ? rawTs\n : undefined\n if (ts === undefined) {\n throw new RangeError(`MMTP timestamp must be an unsigned 32-bit integer, got ${String(rawTs)}`)\n }\n if (ts === 0) return 0\n const seconds = ts >>> 16\n const frac = ts & 0xFFFF\n return seconds * 1_000_000 + Math.floor(frac * 1_000_000 / 65536)\n}\n\n/** Validate the CMSF/MMTP catalog initData contract: complete top-level ftyp+moov. */\nexport function isFmp4InitSegment(data: Uint8Array): boolean {\n let offset = 0\n let sawFtyp = false\n let sawMoov = false\n\n while (offset + 8 <= data.byteLength) {\n const view = new DataView(data.buffer, data.byteOffset + offset, data.byteLength - offset)\n let size = view.getUint32(0, false)\n const type = String.fromCharCode(data[offset + 4]!, data[offset + 5]!, data[offset + 6]!, data[offset + 7]!)\n let headerSize = 8\n\n if (size === 1) {\n if (offset + 16 > data.byteLength) return false\n const largeSize = view.getBigUint64(8, false)\n if (largeSize > BigInt(Number.MAX_SAFE_INTEGER)) return false\n size = Number(largeSize)\n headerSize = 16\n } else if (size === 0) {\n size = data.byteLength - offset\n }\n\n if (size < headerSize || offset + size > data.byteLength) return false\n // `mmpu` is the ISO MMT MPU metadata box. It is valid inside an MMTP\n // FT=0 payload but is not an ISO-BMFF initialization box that an MSE\n // SourceBuffer can append as a top-level box.\n if (type === 'mmpu') return false\n if (type === 'ftyp') sawFtyp = true\n if (type === 'moov') {\n if (!sawFtyp) return false\n sawMoov = true\n }\n offset += size\n }\n\n return offset === data.byteLength && sawFtyp && sawMoov\n}\n\n/**\n * Return the fMP4 initialization segment carried by MMTP MPU metadata.\n *\n * ISO MMTP FT=0 metadata may contain an `mmpu` box between the top-level\n * `ftyp` and `moov` boxes (`ftyp + mmpu + moov`). `mmpu` is an MMT metadata\n * box, not an ISO-BMFF initialization box accepted by an MSE SourceBuffer;\n * appending it produces Chromium's `Invalid top-level ISO BMFF box type mmpu`\n * error, most visibly when a rendition switch delivers a fresh FT=0 packet.\n * The returned bytes retain every other top-level box, in wire order, and are\n * only returned when the resulting bytes are a complete `ftyp` + `moov` init.\n * Inputs that are already a valid fMP4 init are returned unchanged.\n */\nexport function normalizeMmtpInitSegment(data: Uint8Array): Uint8Array | undefined {\n if (isFmp4InitSegment(data)) return data\n\n const parts: Uint8Array[] = []\n let offset = 0\n let removedMpu = false\n\n while (offset + 8 <= data.byteLength) {\n const view = new DataView(data.buffer, data.byteOffset + offset, data.byteLength - offset)\n let size = view.getUint32(0, false)\n let headerSize = 8\n if (size === 1) {\n if (offset + 16 > data.byteLength) return undefined\n const largeSize = view.getBigUint64(8, false)\n if (largeSize > BigInt(Number.MAX_SAFE_INTEGER)) return undefined\n size = Number(largeSize)\n headerSize = 16\n } else if (size === 0) {\n size = data.byteLength - offset\n }\n if (size < headerSize || offset + size > data.byteLength) return undefined\n\n const type = String.fromCharCode(\n data[offset + 4]!,\n data[offset + 5]!,\n data[offset + 6]!,\n data[offset + 7]!,\n )\n if (type === 'mmpu') removedMpu = true\n else parts.push(data.subarray(offset, offset + size))\n offset += size\n }\n\n if (!removedMpu || offset !== data.byteLength) return undefined\n const normalized = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0))\n let writeOffset = 0\n for (const part of parts) {\n normalized.set(part, writeOffset)\n writeOffset += part.byteLength\n }\n return isFmp4InitSegment(normalized) ? normalized : undefined\n}\n\n// \u2500\u2500\u2500 Internal Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction hasIsoBmffMagic(data: Uint8Array): boolean {\n for (let i = 0; i + 8 <= data.length && i < 32; i += 4) {\n const tag = String.fromCharCode(data[i + 4], data[i + 5], data[i + 6], data[i + 7])\n if (tag === 'ftyp' || tag === 'moov') return true\n }\n return false\n}\n\nfunction findTrackStsdBoxes(data: Uint8Array): Array<{ o: number; sz: number }> {\n const moov = findBox(data, 0, data.length, 'moov')\n if (!moov) return []\n\n const out: Array<{ o: number; sz: number }> = []\n for (const trak of findBoxes(data, moov.o + 8, moov.o + moov.sz, 'trak')) {\n const stsd = findNestedBox(data, trak.o + 8, trak.o + trak.sz, ['mdia', 'minf', 'stbl', 'stsd'])\n if (stsd) out.push(stsd)\n }\n return out\n}\n\nfunction findNestedBox(\n buf: Uint8Array,\n start: number,\n end: number,\n path: string[],\n): { o: number; sz: number } | undefined {\n let s = start\n let e = end\n let current: { o: number; sz: number } | undefined\n for (const type of path) {\n current = findBox(buf, s, e, type)\n if (!current) return undefined\n s = current.o + 8\n e = current.o + current.sz\n }\n return current\n}\n\nfunction findBox(buf: Uint8Array, start: number, end: number, type: string): { o: number; sz: number } | undefined {\n return findBoxes(buf, start, end, type)[0]\n}\n\nfunction findBoxes(buf: Uint8Array, start: number, end: number, type: string): Array<{ o: number; sz: number }> {\n const boxes: Array<{ o: number; sz: number }> = []\n let o = start\n while (o + 8 <= end) {\n let sz = (buf[o] << 24) | (buf[o + 1] << 16) | (buf[o + 2] << 8) | buf[o + 3]\n const t = String.fromCharCode(buf[o + 4], buf[o + 5], buf[o + 6], buf[o + 7])\n if (sz === 0) sz = end - o\n if (sz < 8 || o + sz > end) break\n if (t === type) boxes.push({ o, sz })\n o += sz\n }\n return boxes\n}\n\nfunction writeNalLength(buf: Uint8Array, offset: number, len: number): number {\n buf[offset] = (len >> 24) & 0xff\n buf[offset + 1] = (len >> 16) & 0xff\n buf[offset + 2] = (len >> 8) & 0xff\n buf[offset + 3] = len & 0xff\n return offset + 4\n}\n\nfunction h264NalUnitsForKeyframeDetection(\n data: Uint8Array,\n options: H264KeyframeDetectionOptions,\n): Uint8Array[] {\n return splitH264SampleNalUnits(data, options).nals\n}\n\nfunction splitPartialAvccNalUnits(data: Uint8Array, lengthSize = 4): Uint8Array[] {\n const nals: Uint8Array[] = []\n let i = 0\n while (i + lengthSize < data.length) {\n let len = 0\n for (let k = 0; k < lengthSize; k++) len = (len << 8) | data[i + k]\n i += lengthSize\n if (len <= 0 || i >= data.length) break\n const end = Math.min(i + len, data.length)\n nals.push(data.subarray(i, end))\n if (end < i + len) break\n i = end\n }\n return nals\n}\n\nfunction isLikelyRawH264NalUnit(data: Uint8Array): boolean {\n if (data.length === 0) return false\n const header = data[0]!\n if ((header & 0x80) !== 0) return false\n const nalType = header & 0x1f\n return nalType > 0 && nalType < 24\n}\n\nfunction rawH264NalToAvcc(data: Uint8Array): Uint8Array {\n const out = new Uint8Array(4 + data.length)\n new DataView(out.buffer, out.byteOffset, out.byteLength).setUint32(0, data.length)\n out.set(data, 4)\n return out\n}\n\nfunction isH264IntraSlice(nal: Uint8Array): boolean {\n const sliceType = readH264SliceType(nal)\n return sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9\n}\n\nfunction readH264SliceType(nal: Uint8Array): number | null {\n if (nal.length < 2 || (nal[0]! & 0x1f) !== 1) return null\n const rbsp = removeEmulationPrevention(nal.subarray(1))\n const state = { bitOffset: 0 }\n if (readUnsignedExpGolomb(rbsp, state) === null) return null\n return readUnsignedExpGolomb(rbsp, state)\n}\n\nfunction readUnsignedExpGolomb(data: Uint8Array, state: { bitOffset: number }): number | null {\n let leadingZeroBits = 0\n while (true) {\n const bit = readBit(data, state)\n if (bit === null) return null\n if (bit === 1) break\n leadingZeroBits++\n if (leadingZeroBits > 30) return null\n }\n\n let suffix = 0\n for (let i = 0; i < leadingZeroBits; i++) {\n const bit = readBit(data, state)\n if (bit === null) return null\n suffix = (suffix << 1) | bit\n }\n return (1 << leadingZeroBits) - 1 + suffix\n}\n\nfunction readBit(data: Uint8Array, state: { bitOffset: number }): 0 | 1 | null {\n if (state.bitOffset >= data.length * 8) return null\n const byte = data[state.bitOffset >> 3]!\n const bit = (byte >> (7 - (state.bitOffset & 7))) & 1\n state.bitOffset++\n return bit as 0 | 1\n}\n\nfunction hex2(n: number): string {\n return n.toString(16).padStart(2, '0')\n}\n\nfunction pad2(n: number): string {\n return String(n).padStart(2, '0')\n}\n\nfunction codecStringFromAv1C(av1C: Uint8Array): string {\n if (av1C.length < 4) return 'av01.0.01M.08'\n const profile = (av1C[1]! >>> 5) & 0x07\n const level = av1C[1]! & 0x1f\n const tier = (av1C[2]! & 0x80) !== 0 ? 'H' : 'M'\n let bitDepth = 8\n if ((av1C[2]! & 0x40) !== 0) {\n bitDepth = (av1C[2]! & 0x20) !== 0 ? 12 : 10\n }\n return `av01.${profile}.${pad2(level)}${tier}.${pad2(bitDepth)}`\n}\n\nfunction codecStringFromVpcC(vpcC: Uint8Array): string {\n if (vpcC.length < 3) return 'vp09.00.10.08'\n // ISO/IEC 14496-15 defines vpcC as a FullBox. Some tooling hands callers\n // the VPCodecConfigurationRecord after the 4-byte FullBox header; tolerate\n // both shapes because WebCodecs callers only need a stable RFC 6381 fallback.\n const offset = vpcC.length >= 7 && vpcC[0] <= 1 ? 4 : 0\n if (offset + 2 >= vpcC.length) return 'vp09.00.10.08'\n const profile = vpcC[offset]!\n const level = vpcC[offset + 1]!\n const bitDepth = (vpcC[offset + 2]! >>> 4) || 8\n return `vp09.${pad2(profile)}.${pad2(level)}.${pad2(bitDepth)}`\n}\n\n/**\n * Derive an RFC 6381 HEVC codec string (e.g. \"hvc1.1.6.L93.90\") from the\n * payload of an hvcC box (the HEVCDecoderConfigurationRecord, ISO 14496-15 \u00A78.3.3.1).\n * Only the general_* fields in the fixed 23-byte header are needed:\n * byte 1: general_profile_space(2) | general_tier_flag(1) | general_profile_idc(5)\n * bytes 2-5: general_profile_compatibility_flags (32 bits)\n * bytes 6-11: general_constraint_indicator_flags (48 bits)\n * byte 12: general_level_idc\n */\nfunction hevcCodecStringFromHvcC(c: Uint8Array, sampleEntry: 'hev1' | 'hvc1'): string {\n return formatHevcCodecString(\n sampleEntry,\n (c[1]! >> 6) & 0x3,\n ((c[1]! >> 5) & 0x1) === 1,\n c[1]! & 0x1f,\n ((c[2]! << 24) | (c[3]! << 16) | (c[4]! << 8) | c[5]!) >>> 0,\n c.subarray(6, 12),\n c[12]!,\n )\n}\n\n/** Exp-Golomb bitstream writer for SPS/PPS generation. */\nclass BitWriter {\n buf: number[] = []\n byte = 0\n bit = 7\n\n u(val: number, bits: number) {\n for (let i = bits - 1; i >= 0; i--) {\n this.byte |= ((val >> i) & 1) << this.bit\n if (--this.bit < 0) {\n this.buf.push(this.byte)\n this.byte = 0\n this.bit = 7\n }\n }\n }\n\n ue(val: number) {\n const v = val + 1\n const bits = Math.floor(Math.log2(v)) + 1\n this.u(0, bits - 1)\n this.u(v, bits)\n }\n\n se(val: number) {\n this.ue(val <= 0 ? -val * 2 : val * 2 - 1)\n }\n\n finish(): Uint8Array {\n if (this.bit < 7) {\n this.byte |= 1 << this.bit\n this.buf.push(this.byte)\n }\n return new Uint8Array(this.buf)\n }\n}\n\nfunction resolveResolution(\n explicitW: number | undefined,\n explicitH: number | undefined,\n level: number,\n): { width: number; height: number } {\n if (explicitW && explicitH) return { width: explicitW, height: explicitH }\n\n const levelResolutions: Record<number, [number, number]> = {\n 10: [176, 144], 11: [352, 288], 12: [352, 288], 13: [352, 288],\n 20: [352, 288], 21: [720, 576], 22: [720, 576],\n 30: [1280, 720], 31: [1280, 720], 32: [1280, 1024],\n 40: [1920, 1088], 41: [1920, 1088], 42: [2048, 1088],\n 50: [4096, 2160], 51: [4096, 2160], 52: [4096, 2160],\n }\n\n const [w, h] = levelResolutions[level] ?? [1920, 1088]\n return { width: explicitW ?? w, height: explicitH ?? h }\n}\n\nfunction buildMinimalSps(\n profile: number, constraint: number, level: number,\n width: number, height: number,\n): Uint8Array {\n const widthInMbs = Math.ceil(width / 16)\n const heightInMapUnits = Math.ceil(height / 16)\n\n const w = new BitWriter()\n w.u(0, 1); w.u(3, 2); w.u(7, 5) // NAL header: SPS\n w.u(profile, 8); w.u(constraint, 8); w.u(level, 8)\n w.ue(0) // seq_parameter_set_id\n\n if (profile === 100 || profile === 110 || profile === 122 || profile === 244 || profile === 44) {\n w.ue(1); w.ue(0); w.ue(0) // chroma + bit depth\n w.u(0, 1); w.u(0, 1) // transform bypass + scaling matrix\n }\n\n w.ue(0) // log2_max_frame_num_minus4\n w.ue(2) // pic_order_cnt_type=2\n w.ue(1) // max_num_ref_frames\n w.u(0, 1) // gaps_in_frame_num\n w.ue(widthInMbs - 1)\n w.ue(heightInMapUnits - 1)\n w.u(1, 1) // frame_mbs_only\n w.u(1, 1) // direct_8x8_inference\n\n const cropRight = widthInMbs * 16 - width\n const cropBottom = heightInMapUnits * 16 - height\n if (cropRight > 0 || cropBottom > 0) {\n w.u(1, 1)\n w.ue(0); w.ue(cropRight / 2); w.ue(0); w.ue(cropBottom / 2)\n } else {\n w.u(0, 1)\n }\n\n w.u(0, 1) // vui_parameters_present\n return w.finish()\n}\n\nfunction buildMinimalPps(profile: number): Uint8Array {\n const isHighProfile = profile === 100 || profile === 110 || profile === 122 || profile === 244 || profile === 44\n const w = new BitWriter()\n w.u(0, 1); w.u(3, 2); w.u(8, 5) // NAL header: PPS\n\n w.ue(0); w.ue(0) // pps_id, sps_id\n w.u(isHighProfile ? 1 : 0, 1) // entropy_coding_mode\n w.u(0, 1) // bottom_field_pic_order\n w.ue(0); w.ue(0); w.ue(0) // slice groups, ref_idx defaults\n w.u(0, 1); w.u(0, 2) // weighted_pred\n w.se(0); w.se(0); w.se(0) // qp offsets\n w.u(1, 1); w.u(0, 1); w.u(0, 1) // deblock, constrained_intra, redundant_pic_cnt\n\n if (isHighProfile) {\n w.u(1, 1); w.u(0, 1); w.se(0) // transform_8x8, scaling_matrix, chroma_qp_offset\n }\n\n return w.finish()\n}\n\n// \u2500\u2500\u2500 AAC / ADTS Utilities \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** ISO 14496-3 sampling frequency index lookup. */\nconst ADTS_SAMPLE_RATES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350] as const\n\n/**\n * Prepend a 7-byte ADTS header to a raw AAC access unit.\n *\n * WebCodecs AudioDecoder configured WITHOUT an AudioSpecificConfig `description`\n * expects ADTS-framed AAC. MMTP carries raw AAC (no ADTS sync), so this wrapper\n * must be applied before calling `AudioDecoder.decode()`.\n */\nexport function wrapAdtsFrame(raw: Uint8Array, sampleRate: number, channels: number): Uint8Array {\n const sri = ADTS_SAMPLE_RATES.indexOf(sampleRate as typeof ADTS_SAMPLE_RATES[number])\n const idx = sri >= 0 ? sri : 3 // default 48 kHz\n const len = 7 + raw.length\n const out = new Uint8Array(len)\n out[0] = 0xFF\n out[1] = 0xF1 // MPEG-4, Layer 0, no CRC\n out[2] = (1 << 6) | (idx << 2) | (channels >> 2) // profile=AAC-LC(1), freq, ch high\n out[3] = ((channels & 3) << 6) | ((len >> 11) & 3)\n out[4] = (len >> 3) & 0xFF\n out[5] = ((len & 7) << 5) | 0x1F\n out[6] = 0xFC\n out.set(raw, 7)\n return out\n}\n\n/**\n * Extract AAC AudioSpecificConfig (ASC) from an fMP4 init segment.\n *\n * Walks moov > trak > mdia > minf > stbl > stsd > mp4a > esds, then descends\n * the MPEG-4 descriptor chain (ES_Descriptor 0x03 > DecoderConfigDescriptor\n * 0x04 > DecoderSpecificInfo 0x05) to return the ASC payload.\n *\n * Sibling of extractAvcCFromInitSegment, used by the same BLO-4830 / CMSF \u00A73.1\n * path: when the catalog initData is the full ftyp+moov, WebCodecs' AudioDecoder\n * needs the raw ASC (~2 bytes for AAC-LC) as `description`, NOT the whole\n * segment. Without this extraction the configure() succeeds but every decode()\n * silently produces zero output (no error fires), exactly the symptom that\n * shows up as \"Audio continuity FAILED: energy unstable CV=1.00\" in the\n * 7-point E2E test (CV=1.00 is the meanEnergy=0 sentinel from audio-fft.mjs).\n *\n * Returns undefined when:\n * - data has no ftyp/moov magic in the first 32 bytes\n * - no mp4a sample entry exists in any trak's stbl\n * - the esds descriptor chain doesn't yield a DecoderSpecificInfo\n * - the ASC payload is shorter than 2 bytes (AOT + sampling-freq nibble)\n *\n * Walks every trak and returns the first AAC sample entry. This mirrors\n * extractAvcCFromInitSegment so multiplexed ftyp+moov init blobs work\n * regardless of track order.\n */\nexport function extractAacAscFromInitSegment(data: Uint8Array): AacAscInfo | undefined {\n if (!hasIsoBmffMagic(data)) return undefined\n\n for (const stsd of findTrackStsdBoxes(data)) {\n const info = extractAacAscFromStsd(data, stsd)\n if (info) return info\n }\n\n return undefined\n}\n\nfunction extractAacAscFromStsd(data: Uint8Array, stsd: { o: number; sz: number }): AacAscInfo | undefined {\n // stsd: 8 box header + 4 version+flags + 4 entry_count = 16 bytes before entries.\n const s = stsd.o + 16\n const e = stsd.o + stsd.sz\n // mp4a AudioSampleEntry minimum length: 8 box header + 28 fixed fields = 36 bytes\n // before any child boxes (esds lives here).\n const mp4a = findBox(data, s, e, 'mp4a')\n if (!mp4a || mp4a.sz < 36) return undefined\n const esds = findBox(data, mp4a.o + 36, mp4a.o + mp4a.sz, 'esds')\n // esds = 8 box header + 4 full-box version+flags = 12 bytes minimum before descriptors.\n if (!esds || esds.sz < 12) return undefined\n const esdsPayload = data.subarray(esds.o + 8, esds.o + esds.sz)\n const asc = extractAudioSpecificConfig(esdsPayload)\n if (!asc || asc.length < 2) return undefined\n // AudioObjectType = top 5 bits of ASC[0]. AAC-LC = 2, HE-AAC = 5, etc.\n const aot = asc[0] >> 3\n return { codec: `mp4a.40.${aot}`, description: asc }\n}\n\n/**\n * Walk an esds descriptor chain to find DecoderSpecificInfo (tag 0x05).\n * Step over fixed-size headers of container descriptors (0x03, 0x04) so the\n * scan continues into their children rather than skipping their full payload.\n *\n * Assumes the ES_Descriptor flags byte is zero (no streamDependence, URL, OCR\n * ES_ID fields). FFmpeg's mov/mp4 muxer emits exactly that for all standard\n * AAC tracks; the assumption is the same one extractAvcCFromInitSegment makes\n * about the avc1 sample entry's reserved fields.\n *\n * Input is the esds box payload AFTER the 8-byte box header; the first 4 bytes\n * (full-box version+flags) are skipped here.\n */\nfunction extractAudioSpecificConfig(esds: Uint8Array): Uint8Array | undefined {\n let offset = 4 // skip esds version+flags\n while (offset < esds.length) {\n const tag = esds[offset++]\n let size = 0\n for (let i = 0; i < 4 && offset < esds.length; i++) {\n const b = esds[offset++]\n size = (size << 7) | (b & 0x7f)\n if ((b & 0x80) === 0) break\n }\n if (tag === 0x05) {\n if (offset + size > esds.length) return undefined\n return esds.subarray(offset, offset + size)\n }\n if (tag === 0x03) {\n offset += 3 // ES_ID (2) + flags (1)\n } else if (tag === 0x04) {\n offset += 13 // OTI (1) + streamType (1) + bufferSizeDB (3) + maxBitrate (4) + avgBitrate (4)\n } else {\n offset += size\n }\n }\n return undefined\n}\n\n// =============================================================================\n// Inline Tests (vitest)\n// =============================================================================\n\nif (import.meta.vitest) {\n const { describe, test, expect } = import.meta.vitest\n\n describe('codec-utils', () => {\n describe('isAnnexB', () => {\n test('4-byte start code', () => {\n expect(isAnnexB(new Uint8Array([0, 0, 0, 1, 0x65]))).toBe(true)\n })\n test('3-byte start code', () => {\n expect(isAnnexB(new Uint8Array([0, 0, 1, 0x65]))).toBe(true)\n })\n test('rejects AVCC', () => {\n expect(isAnnexB(new Uint8Array([0, 0, 0, 10, 0x65]))).toBe(false)\n })\n })\n\n describe('findAnnexBStartCode', () => {\n test('at offset 0', () => {\n expect(findAnnexBStartCode(new Uint8Array([0, 0, 0, 1, 0x65]))).toBe(0)\n })\n test('at offset 5', () => {\n expect(findAnnexBStartCode(new Uint8Array([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0, 0, 1, 0x65]))).toBe(5)\n })\n test('returns -1 when none', () => {\n expect(findAnnexBStartCode(new Uint8Array([0xAA, 0xBB, 0xCC]))).toBe(-1)\n })\n })\n\n describe('splitAnnexBNalUnits', () => {\n test('splits two NALs', () => {\n const data = new Uint8Array([0, 0, 0, 1, 0x65, 0xAA, 0xBB, 0, 0, 0, 1, 0x41, 0xCC])\n const nals = splitAnnexBNalUnits(data)\n expect(nals.length).toBe(2)\n expect(nals[0]).toEqual(new Uint8Array([0x65, 0xAA, 0xBB]))\n expect(nals[1]).toEqual(new Uint8Array([0x41, 0xCC]))\n })\n test('handles 3-byte start codes', () => {\n const nals = splitAnnexBNalUnits(new Uint8Array([0, 0, 1, 0x67, 0x42, 0, 0, 1, 0x68, 0x01]))\n expect(nals.length).toBe(2)\n })\n })\n\n describe('annexBToAvcc', () => {\n test('converts single NAL', () => {\n const annexB = new Uint8Array([0, 0, 0, 1, 0x41, 0xAA, 0xBB])\n const avcc = annexBToAvcc(annexB)\n expect(avcc[3]).toBe(3) // NAL length\n expect(avcc[4]).toBe(0x41)\n })\n test('prepends SPS/PPS', () => {\n const annexB = new Uint8Array([0, 0, 0, 1, 0x65, 0x01])\n const avcc = annexBToAvcc(annexB, { sps: new Uint8Array([0x67, 0x42]), pps: new Uint8Array([0x68, 0x01]) })\n expect(avcc[3]).toBe(2) // SPS length\n expect(avcc[4]).toBe(0x67)\n })\n })\n\n describe('detectH264Keyframe', () => {\n test('IDR in Annex-B', () => {\n expect(detectH264Keyframe(new Uint8Array([0, 0, 0, 1, 0x65]))).toBe(true)\n })\n test('SPS in Annex-B', () => {\n expect(detectH264Keyframe(new Uint8Array([0, 0, 0, 1, 0x67]))).toBe(true)\n })\n test('IDR in AVCC', () => {\n expect(detectH264Keyframe(new Uint8Array([0, 0, 0, 5, 0x65, 1, 2, 3, 4]))).toBe(true)\n })\n test('IDR after AVCC AUD', () => {\n expect(detectH264Keyframe(new Uint8Array([\n 0, 0, 0, 1, 0x09,\n 0, 0, 0, 2, 0x65, 0xaa,\n ]))).toBe(true)\n })\n test('non-IDR AVC I-slice in AVCC', () => {\n expect(detectH264Keyframe(new Uint8Array([\n 0, 0, 0, 2, 0x41, 0x88,\n ]))).toBe(true)\n })\n test('raw H.264 IDR NAL', () => {\n expect(detectH264Keyframe(new Uint8Array([0x65, 0x88, 0x84]))).toBe(true)\n })\n test('raw H.264 non-IDR I-slice NAL', () => {\n expect(detectH264Keyframe(new Uint8Array([0x41, 0x88]))).toBe(true)\n })\n test('classifies partial length-prefixed IDR only when requested', () => {\n const partial = new Uint8Array([0, 0, 0x20, 0, 0x65])\n expect(classifyH264Keyframe(partial)).toBeUndefined()\n expect(classifyH264Keyframe(partial, { allowPartialAvcc: true })).toBe(true)\n })\n test('rejects P-frame', () => {\n expect(detectH264Keyframe(new Uint8Array([0, 0, 0, 1, 0x41]))).toBe(false)\n })\n test('rejects AVCC sample with only non-IDR slices', () => {\n expect(detectH264Keyframe(new Uint8Array([\n 0, 0, 0, 2, 0x41, 0xc0,\n 0, 0, 0, 2, 0x41, 0xc0,\n ]))).toBe(false)\n })\n })\n\n describe('avccToAnnexB', () => {\n test('converts with SPS/PPS on keyframe', () => {\n const avcc = new Uint8Array([0, 0, 0, 3, 0x65, 0xAA, 0xBB])\n const spsPps: SpsPps = { sps: new Uint8Array([0x67, 0x42]), pps: new Uint8Array([0x68, 0x01]) }\n const annexB = avccToAnnexB(avcc, true, spsPps)\n expect(annexB[3]).toBe(1) // start code\n expect(annexB[4]).toBe(0x67) // SPS\n })\n })\n\n describe('codecStringFromAvcC', () => {\n test('derives avc1.64002a', () => {\n expect(codecStringFromAvcC(new Uint8Array([1, 100, 0, 42]))).toBe('avc1.64002a')\n })\n })\n\n describe('extractAacAscFromInitSegment', () => {\n // Build a minimal valid ftyp+moov containing one mp4a sample entry whose\n // esds carries the supplied AudioSpecificConfig. The walker only needs\n // the box hierarchy moov > trak > mdia > minf > stbl > stsd > mp4a > esds;\n // mvhd/tkhd/mdhd/hdlr/smhd/dinf/stts/stsc/stsz/stco are spec-required for\n // a truly valid init but the extractor never reads them, so we omit them\n // to keep the fixture tight (~150 bytes vs ~500). FFmpeg's real output\n // includes them; the production path passes through `findBox` exactly\n // the same way.\n function buildMinimalAacInit(asc: Uint8Array): Uint8Array {\n const box = (type: string, payload: Uint8Array): Uint8Array => {\n const sz = 8 + payload.length\n const out = new Uint8Array(sz)\n out[0] = (sz >>> 24) & 0xff\n out[1] = (sz >>> 16) & 0xff\n out[2] = (sz >>> 8) & 0xff\n out[3] = sz & 0xff\n out[4] = type.charCodeAt(0); out[5] = type.charCodeAt(1)\n out[6] = type.charCodeAt(2); out[7] = type.charCodeAt(3)\n out.set(payload, 8)\n return out\n }\n const concat = (...arrs: Uint8Array[]): Uint8Array => {\n const total = arrs.reduce((n, a) => n + a.length, 0)\n const out = new Uint8Array(total)\n let off = 0\n for (const a of arrs) { out.set(a, off); off += a.length }\n return out\n }\n // ftyp: major_brand=iso5, minor=0x200, compat=iso5,iso6\n const ftyp = box('ftyp', new Uint8Array([\n 0x69, 0x73, 0x6f, 0x35, // 'iso5'\n 0x00, 0x00, 0x02, 0x00, // minor 0x200\n 0x69, 0x73, 0x6f, 0x35, // 'iso5'\n 0x69, 0x73, 0x6f, 0x36, // 'iso6'\n ]))\n\n // ESDescriptor (0x03) > DecoderConfigDescriptor (0x04) > DecoderSpecificInfo (0x05) > ASC.\n // Use 1-byte BER sizes (all payloads < 128).\n const dsi = new Uint8Array(2 + asc.length)\n dsi[0] = 0x05; dsi[1] = asc.length\n dsi.set(asc, 2)\n // DCD = 13 fixed bytes + DSI: OTI=0x40 (AAC), streamType=0x05<<2|0x01 (audio, upStream=0)\n const dcd = new Uint8Array(2 + 13 + dsi.length)\n dcd[0] = 0x04; dcd[1] = 13 + dsi.length\n dcd[2] = 0x40 // objectTypeIndication: Audio ISO/IEC 14496-3\n dcd[3] = (0x05 << 2) | 1 // streamType=AudioStream(5), upStream=0, reserved=1\n // bufferSizeDB(3) + maxBitrate(4) + avgBitrate(4) all zero is fine for the walker.\n dcd.set(dsi, 15)\n // ESDescriptor: ES_ID=0, flags=0, then DCD\n const esd = new Uint8Array(2 + 3 + dcd.length)\n esd[0] = 0x03; esd[1] = 3 + dcd.length\n // ES_ID(2) + flags(1) all zero\n esd.set(dcd, 5)\n // esds box = full-box (4-byte version+flags) + ES_Descriptor chain\n const esdsPayload = new Uint8Array(4 + esd.length)\n esdsPayload.set(esd, 4)\n const esds = box('esds', esdsPayload)\n\n // mp4a sample entry: 6 reserved + 2 dref_idx + 8 reserved + 2 channels + 2 samplesize +\n // 2 pre_defined + 2 reserved + 4 samplerate(48000<<16) = 28 bytes,\n // then esds child.\n const mp4aFixed = new Uint8Array(28)\n mp4aFixed[7] = 0x01 // data_reference_index = 1\n mp4aFixed[16] = 0x00; mp4aFixed[17] = 0x01 // channelcount = 1\n mp4aFixed[18] = 0x00; mp4aFixed[19] = 0x10 // samplesize = 16\n // samplerate at offset 24-27 (top 16 bits = sampleRate). 48000 = 0xBB80.\n mp4aFixed[24] = 0xBB; mp4aFixed[25] = 0x80\n const mp4a = box('mp4a', concat(mp4aFixed, esds))\n\n // stsd: 4 ver+flags + 4 entry_count(=1) + entries\n const stsdPayload = new Uint8Array(8 + mp4a.length)\n stsdPayload[7] = 0x01 // entry_count = 1\n stsdPayload.set(mp4a, 8)\n const stsd = box('stsd', stsdPayload)\n\n const stbl = box('stbl', stsd)\n const minf = box('minf', stbl)\n const mdia = box('mdia', minf)\n const trak = box('trak', mdia)\n const moov = box('moov', trak)\n\n return concat(ftyp, moov)\n }\n\n test('extracts AAC-LC mono 48k ASC and derives codec', () => {\n // AOT=2 (00010), samplingFrequencyIndex=3 (0011, 48000Hz), channelConfig=1 (0001),\n // GA: frameLengthFlag=0, dependsOnCoreCoder=0, extensionFlag=0 \u2192 0x11 0x88.\n const asc = new Uint8Array([0x11, 0x88])\n const init = buildMinimalAacInit(asc)\n const result = extractAacAscFromInitSegment(init)\n expect(result).toBeDefined()\n expect(result!.codec).toBe('mp4a.40.2')\n expect(Array.from(result!.description)).toEqual([0x11, 0x88])\n })\n\n test('derives AOT 5 (HE-AAC) from ASC[0] >> 3', () => {\n // AOT=5 (00101), rest don't matter for codec derivation.\n const asc = new Uint8Array([0x29, 0x88]) // 00101 001 1000 1000\n const init = buildMinimalAacInit(asc)\n expect(extractAacAscFromInitSegment(init)?.codec).toBe('mp4a.40.5')\n })\n\n test('rejects data without ftyp/moov magic', () => {\n expect(extractAacAscFromInitSegment(new Uint8Array(64))).toBeUndefined()\n })\n\n test('rejects ftyp+moov with no audio (no mp4a sample entry)', () => {\n // ftyp + moov with empty trak chain \u2014 no stsd, let alone mp4a.\n const ftyp = new Uint8Array([\n 0, 0, 0, 24, 0x66, 0x74, 0x79, 0x70,\n 0x69, 0x73, 0x6f, 0x35, 0, 0, 2, 0,\n 0x69, 0x73, 0x6f, 0x35, 0x69, 0x73, 0x6f, 0x36,\n ])\n const moov = new Uint8Array([0, 0, 0, 8, 0x6d, 0x6f, 0x6f, 0x76]) // empty moov\n const cat = new Uint8Array(ftyp.length + moov.length)\n cat.set(ftyp); cat.set(moov, ftyp.length)\n expect(extractAacAscFromInitSegment(cat)).toBeUndefined()\n })\n })\n\n describe('hevc', () => {\n // Minimal HEVC NAL units: header byte0 = (type << 1). VPS=32(0x40), SPS=33(0x42), PPS=34(0x44).\n // Real x265 repeat-headers=1 parameter sets (the cast encoder profile); the\n // SPS carries emulation-prevention bytes inside profile_tier_level. See\n // test-vectors/hevc-x265-repeat-headers-v1.json for the full access units.\n const vps = new Uint8Array([\n 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00,\n 0x03, 0x00, 0x00, 0x03, 0x00, 0x3c, 0x92, 0x80, 0x90,\n ])\n const sps = new Uint8Array([\n 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00,\n 0x03, 0x00, 0x3c, 0xa0, 0x0a, 0x08, 0x0f, 0x16, 0x59, 0x2a, 0x49, 0x32, 0xbc, 0x05, 0xa0,\n 0x20, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x03, 0x03, 0xc1,\n ])\n const pps = new Uint8Array([0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40])\n const idr = new Uint8Array([0x26, 0x01, 0xaf, 0x06]) // type 19 (IDR_W_RADL)\n const lp = (...nals: Uint8Array[]) => {\n const parts: number[] = []\n for (const n of nals) { parts.push((n.length >> 24) & 0xff, (n.length >> 16) & 0xff, (n.length >> 8) & 0xff, n.length & 0xff, ...n) }\n return Uint8Array.from(parts)\n }\n\n test('isHevcCodec', () => {\n expect(isHevcCodec('hev1.1.6.L93.B0')).toBe(true)\n expect(isHevcCodec('hvc1.1.6.L93.B0')).toBe(true)\n expect(isHevcCodec('avc1.640033')).toBe(false)\n })\n\n test('removeEmulationPrevention strips 0x03', () => {\n expect(Array.from(removeEmulationPrevention(new Uint8Array([0, 0, 0x03, 1])))).toEqual([0, 0, 1])\n expect(Array.from(removeEmulationPrevention(new Uint8Array([0xaa, 0xbb])))).toEqual([0xaa, 0xbb])\n })\n\n test('splitAvccNalUnits splits length-prefixed NALs', () => {\n const nals = splitAvccNalUnits(lp(vps, sps))\n expect(nals.length).toBe(2)\n expect(nals[0]).toEqual(vps)\n expect(nals[1]).toEqual(sps)\n })\n\n test('isCompleteAvccSample accepts complete length-prefixed NAL samples', () => {\n expect(isCompleteAvccSample(lp(new Uint8Array([0x09]), idr))).toBe(true)\n })\n\n test('isCompleteAvccSample rejects Annex-B start-code samples', () => {\n expect(isCompleteAvccSample(new Uint8Array([0, 0, 0, 1, 0x65, 0xaa]))).toBe(false)\n })\n\n test('isCompleteAvccSample rejects truncated length-prefixed samples', () => {\n expect(isCompleteAvccSample(new Uint8Array([0, 0, 0, 4, 0x65, 0xaa]))).toBe(false)\n })\n\n test('normalizeH264SampleToAvcc leaves complete AVCC samples unchanged', () => {\n const avcc = lp(new Uint8Array([0x09]), idr)\n const normalized = normalizeH264SampleToAvcc(avcc)\n expect(normalized.format).toBe('avcc')\n expect(normalized.data).toBe(avcc)\n })\n\n test('normalizeH264SampleToAvcc converts a single raw H.264 NAL to AVCC', () => {\n const raw = new Uint8Array([0x65, 0x88, 0x84])\n const normalized = normalizeH264SampleToAvcc(raw)\n expect(normalized.format).toBe('raw-nal')\n expect(normalized.data).toEqual(new Uint8Array([0, 0, 0, 3, 0x65, 0x88, 0x84]))\n })\n\n test('splitH264SampleNalUnits exposes Annex-B NALs without AVCC conversion', () => {\n const annexB = new Uint8Array([0, 0, 0, 1, 0x65, 0x88, 0x84])\n const sample = splitH264SampleNalUnits(annexB)\n expect(sample.format).toBe('annexb')\n expect(sample.nals).toHaveLength(1)\n expect(sample.nals[0]).toEqual(new Uint8Array([0x65, 0x88, 0x84]))\n })\n\n test('splitH264SampleNalUnits exposes single raw H.264 NALs without wrapping', () => {\n const raw = new Uint8Array([0x65, 0x88, 0x84])\n const sample = splitH264SampleNalUnits(raw)\n expect(sample.format).toBe('raw-nal')\n expect(sample.nals).toEqual([raw])\n })\n\n test('splitH264SampleNalUnits can expose partial AVCC NAL headers', () => {\n const partial = new Uint8Array([0, 0, 0, 5, 0x65, 0x88])\n const sample = splitH264SampleNalUnits(partial, { allowPartialAvcc: true })\n expect(sample.format).toBe('partial-avcc')\n expect(sample.nals).toHaveLength(1)\n expect(sample.nals[0]).toEqual(new Uint8Array([0x65, 0x88]))\n })\n\n test('extractHevcParameterSets finds VPS/SPS/PPS (length-prefixed)', () => {\n const sets = extractHevcParameterSets(lp(vps, sps, pps, idr))\n expect(sets.vps).toEqual(vps)\n expect(sets.sps).toEqual(sps)\n expect(sets.pps).toEqual(pps)\n })\n\n test('extractHevcParameterSets finds VPS/SPS/PPS (Annex-B)', () => {\n const sc = new Uint8Array([0, 0, 0, 1])\n const annexb = Uint8Array.from([...sc, ...vps, ...sc, ...sps, ...sc, ...pps, ...sc, ...idr])\n const sets = extractHevcParameterSets(annexb)\n expect(sets.vps).toEqual(vps)\n expect(sets.sps).toEqual(sps)\n expect(sets.pps).toEqual(pps)\n })\n\n test('generateHvcCFromParameterSets builds a valid hvcC', () => {\n const hvcc = generateHvcCFromParameterSets({ vps, sps, pps })!\n expect(hvcc).not.toBeNull()\n expect(hvcc[0]).toBe(1) // configurationVersion\n expect(hvcc[1]).toBe(sps[3]) // general profile_tier_level byte 0 copied from SPS[3]\n expect(hvcc[22]).toBe(3) // numOfArrays (VPS+SPS+PPS)\n // first array entry: NAL_unit_type 32 (VPS), numNalus 1\n expect(hvcc[23] & 0x3f).toBe(32)\n expect((hvcc[24] << 8) | hvcc[25]).toBe(1)\n })\n\n test('generateHvcCFromParameterSets returns null without SPS/PPS', () => {\n expect(generateHvcCFromParameterSets({ vps })).toBeNull()\n })\n\n test('extractHevcCodecDescription end-to-end from keyframe', () => {\n const desc = extractHevcCodecDescription(lp(vps, sps, pps, idr))\n expect(desc).not.toBeNull()\n expect(desc![0]).toBe(1)\n })\n\n test('hevcVclOnly strips parameter-set NALs, keeps VCL', () => {\n const stripped = hevcVclOnly(lp(vps, sps, pps, idr))\n const nals = splitAvccNalUnits(stripped)\n expect(nals.length).toBe(1) // only the IDR VCL NAL remains\n expect(nals[0]).toEqual(idr)\n })\n\n test('hevcVclOnly is a no-op for VCL-only length-prefixed input', () => {\n const vclOnly = lp(idr)\n expect(hevcVclOnly(vclOnly)).toBe(vclOnly)\n })\n })\n })\n}\n", "/**\n * Exact timing authored by an ISO-BMFF media fragment.\n *\n * `timestamp`/`duration` fields on the browser-facing APIs are converted to\n * microseconds for legacy callers. This value remains in the media track's\n * native timescale so consumers that own a CMAF/WebCodecs timeline do not\n * have to reconstruct integer ticks from rounded wall-clock values.\n */\nexport interface AuthoredTiming {\n /** Exact decode time in the media track's timescale. */\n decodeTime: bigint\n /** Exact presentation time in the media track's timescale, including CTS. */\n presentationTime: bigint\n /**\n * Exact sample duration in the media track's timescale. This remains a\n * number because ISO-BMFF stores sample_duration as an unsigned 32-bit\n * field, whose full range is exactly representable by JavaScript numbers.\n */\n durationTicks: number\n /** `mdhd` timescale for all values above. */\n mediaTimescale: number\n}\n\n/** Convert authored media ticks to an integer target timescale without first\n * narrowing the authored tick value to a wall-clock float. */\nexport function scaleAuthoredTicks(\n ticks: bigint,\n mediaTimescale: number,\n targetTimescale: number,\n): number | null {\n if (!Number.isSafeInteger(mediaTimescale) || mediaTimescale <= 0 ||\n !Number.isSafeInteger(targetTimescale) || targetTimescale <= 0) return null\n\n const negative = ticks < 0n\n const numerator = (negative ? -ticks : ticks) * BigInt(targetTimescale)\n const denominator = BigInt(mediaTimescale)\n const whole = numerator / denominator\n const remainder = numerator % denominator\n if (whole > BigInt(Number.MAX_SAFE_INTEGER)) return null\n const value = Number(whole) + Number(remainder) / mediaTimescale\n return Number.isFinite(value) ? (negative ? -value : value) : null\n}\n\n/** Convert exact authored presentation time and duration to an assembler's\n * public timestamp scale. */\nexport function scaleAuthoredTiming(\n timing: AuthoredTiming,\n targetTimescale: number,\n): { timestamp: number; duration: number } | null {\n if (!Number.isSafeInteger(timing.durationTicks) || timing.durationTicks <= 0) return null\n const timestamp = scaleAuthoredTicks(\n timing.presentationTime,\n timing.mediaTimescale,\n targetTimescale,\n )\n const duration = scaleAuthoredTicks(\n BigInt(timing.durationTicks),\n timing.mediaTimescale,\n targetTimescale,\n )\n if (timestamp === null || duration === null || duration <= 0) return null\n return { timestamp, duration }\n}\n", "/**\n * MMTP packet processing and frame routing layer.\n *\n * Delegates MMTP header parsing, MFU extraction, reassembly, and keyframe\n * detection to MmtpContainerParser while owning FEC orchestration, sync\n * gating, CMAF assembly, and callback routing.\n *\n * Extracted from MulticastManager \u2014 this module owns MMTP-layer concerns:\n * FEC symbol feeding, sync gating, MSE segment assembly, and frame routing.\n * It does NOT own network I/O, watchdog timers, or multicast join/leave.\n *\n * @packageDocumentation\n */\n\nimport {\n MMTP_SIGNALING_PACKET_ID,\n parseMMTPHeader,\n parseMoqRepairFrameWithHeader,\n serial32Add,\n serial32Distance,\n serial32Precedes,\n type FecBlockProfile,\n type MMTPHeader,\n} from \"@blockcast/mmt-base\";\nimport type { IsoHeldSource } from \"./fec-manager.js\";\nimport type { IsoPlacedSource, IsoRepairPlacement } from \"./iso-block-locator.js\";\nimport { isMfuReassemblyError } from \"./mfu-reassembler.js\";\nimport type {\n MfuReassemblerBackend,\n MmtpSourcePath,\n MmtpDeliveryProvenance,\n} from \"./mfu-reassembler.js\";\nimport type { MfuReassemblyPolicy } from \"./mfu-reassembly-policy.js\";\nimport { mmtpTimestampToMicros } from \"./codec-utils.js\";\nimport { scaleAuthoredTiming, type AuthoredTiming } from \"./authored-timing.js\";\nimport {\n processFecBlock,\n processFecSymbol,\n FecBlockMapper,\n type ProcessFecBlockResult,\n} from \"@blockcast/mmt-fec\";\nimport {\n CmafBlockAssembler,\n type BlockAssemblerConfig,\n type CmafBlockSample,\n} from \"./cmaf-block-assembler.js\";\nimport {\n MmtpContainerParser,\n isMfuKeyframeMetadata,\n type FecReleaseGate,\n type FrameMeta as ParserFrameMeta,\n type ParsedMmtpPacket,\n} from \"./mmtp-container-parser.js\";\nimport { type TrackRole } from \"./carp-catalog.js\";\nimport {\n decideGap,\n type BlockClass,\n type ClassifierMode,\n type ClassifierReason,\n type FrameGapDecision,\n type GapFrameEvidence,\n type SourceSymbolEvidence,\n} from \"./block-policy.js\";\nimport {\n duplicateRetentionMs,\n StreamDuplicateLedgers,\n type DuplicateHorizon,\n} from \"./duplicate-ledger.js\";\nimport type {\n ManifestAuthMode,\n ManifestObjectIdentity,\n ManifestTrackContext,\n ManifestVerifier,\n} from \"@blockcast/mmt-manifest-verify\";\n\n// \u2500\u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Keyframe loss timeout slack factor (Phase 13, tracks WITHOUT FEC only).\n *\n * FEC tracks never arm the keyframe-lost timer: vanished FEC blocks are\n * materialised from SBN evidence and resolve through their catalog-derived\n * repair and fail deadlines, and the stall/conceal decision rides on the\n * first frame released after the gap (`RoutedFrameMeta.gapBefore`).\n *\n * Derivation: The keyframe loss timer must fire after one full keyframe group\n * has been missed. Group duration must come from explicit catalog timing; FEC\n * sourceSymbols is a block width, not a GOP length. Multicast jitter means the\n * last packet of a group may arrive up to ~0.5 group durations late (network\n * reordering + FEC recovery latency). The total timeout is therefore:\n *\n * timeout = group_duration * (1.0 + 0.5_jitter) = group_duration * 1.5\n *\n * The 0.5 jitter margin covers: (a) FEC interleave recovery window (up to D\n * frames), (b) network reordering within one interleave depth, and (c) OS\n * socket buffer draining latency. This is tight enough to detect loss within\n * one missing group while avoiding false positives from normal jitter.\n */\nexport const KEYFRAME_LOST_TIMEOUT_SLACK = 1.5;\n\n/**\n * Largest delay a JavaScript timer honours: HTML and Node store it as a signed\n * 32-bit millisecond count, and a larger value overflows to fire almost at\n * once. A keyframe-lost timeout above it, fixed or read, would therefore\n * declare every keyframe lost immediately, so it is refused like any other\n * unusable value.\n */\nconst MAX_TIMER_DELAY_MS = 0x7fffffff;\n\nfunction isRouterDebugEnabled(): boolean {\n const globalDebug = (globalThis as { __MMTP_ROUTER_DEBUG?: unknown }).__MMTP_ROUTER_DEBUG;\n if (globalDebug === true || globalDebug === \"true\" || globalDebug === \"1\") return true;\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process\n ?.env;\n return env?.[\"MMTP_ROUTER_DEBUG\"] === \"1\" || env?.[\"MMTP_ROUTER_DEBUG\"] === \"true\";\n}\nfunction debugRouter(message: string): void {\n if (isRouterDebugEnabled()) console.debug(message);\n}\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type RoutedFrameMeta = {\n /** Authored ISO-BMFF presentation timestamp, in microseconds. */\n timestamp: number;\n /**\n * Raw MMTP short-NTP header timestamp. This is transport timing metadata\n * only; it is deliberately separate from the authored media timestamp.\n */\n mmtpTimestamp?: bigint;\n keyframe: boolean;\n /** Exact ISO-BMFF timing in the media track's native timescale. */\n authoredTiming?: AuthoredTiming;\n /** Transport that supplied the accepted FI=1 head (or complete FI=0 frame). */\n source?: MmtpSourcePath;\n /**\n * Whole-frame delivery verdict folded across every contributing fragment.\n *\n * Distinct from `source` on purpose. `source` answers a routing question \u2014\n * which transport's copy is this \u2014 and deliberately lets the head fragment\n * decide. This answers an evidence question: did every byte of this frame\n * physically arrive over multicast? A frame can be routed as 'multicast'\n * and still be 'mixed'.\n */\n deliveryProvenance?: MmtpDeliveryProvenance;\n packetId?: number;\n packetSequenceNumber?: number;\n /** Every authoritative MMTP source-packet sequence that formed this frame. */\n packetSequenceNumbers?: number[];\n /** Subset of packetSequenceNumbers reconstructed by FEC. */\n repairedPacketSequenceNumbers?: number[];\n /** Native MMTP MPU group coordinate. */\n mpuSequenceNumber?: number;\n /**\n * Timed-MFU sample identity `(movie_fragment_sequence_number, sample_number)`\n * from the MFU data-unit header. Absent when the MFU header is untimed.\n */\n movieFragmentSequenceNumber?: number;\n sampleNumber?: number;\n /** Immutable catalog identity supplied by the owning ingress subscription. */\n catalogTrackName?: string;\n recovered?: boolean;\n /**\n * True when this frame opens playback after the router suppressed earlier\n * video deltas while waiting for a sync keyframe. Downstream decoders can\n * use this to treat the following dependency chain as provisional.\n */\n sourceDiscontinuity?: boolean;\n /**\n * Stall/conceal decision for the first frame released after a gap: its\n * FEC blocks resolved with lost media, or authored decode time shows a\n * missing sample. Sinks act on it in order, on this frame. It is the same\n * object the manager emits as its `gap` event.\n */\n gapBefore?: FrameGapDecision;\n};\n\nexport interface MmtpIngressContext {\n tracks: readonly Readonly<{ name: string; packetId: number }>[];\n}\n\n/** One physical stream: an MMTP packet ID and the catalog track it carries. */\nexport interface MmtpStreamIdentity {\n readonly packetId: number;\n readonly catalogTrackName: string;\n}\n\n/**\n * Where a staged stream switch committed, relative to the outgoing stream.\n *\n * - `outgoing-reached-rap`: the outgoing stream was about to deliver a frame\n * at or past the incoming RAP's presentation time. The splice is exact:\n * the outgoing stream delivered every frame before the RAP that reached\n * the router, and none at or after it. An MSE assembler is the one\n * exception, since its flush is unbounded (see `stageStreamSwitch`).\n * - `outgoing-ahead`: when the incoming RAP arrived, the outgoing stream had\n * already delivered a frame at or past it. The switch commits on that RAP\n * at once; incoming frames up to `outgoingHighWater` cover media time the\n * outgoing stream already delivered.\n * - `outgoing-overdue`: the incoming stream reached its next RAP before the\n * outgoing stream reached the one held for it, so the outgoing stream is at\n * least a GOP behind (stalled, or losing media) and waiting longer only\n * widens the gap. The switch commits on the held RAP: the held GOP is\n * delivered, then the newer RAP. It overlaps nothing, since every outgoing\n * frame delivered was compared against the held RAP and is before it.\n */\nexport type StreamSwitchCommitReason =\n | \"outgoing-reached-rap\"\n | \"outgoing-ahead\"\n | \"outgoing-overdue\";\n\n/** Presentation position of one frame, in the router's frame vocabulary. */\nexport interface StreamSwitchPosition {\n /** Authored presentation timestamp, in microseconds. */\n readonly timestamp: number;\n /** Exact authored timing, when the frame carried it. */\n readonly authoredTiming?: Readonly<AuthoredTiming>;\n}\n\n/** A staged stream switch, at the moment it commits. */\nexport interface StreamSwitchCommit {\n readonly from: MmtpStreamIdentity;\n readonly to: MmtpStreamIdentity;\n /** The incoming RAP the switch commits on: the first `to` frame delivered. */\n readonly rap: StreamSwitchPosition & {\n readonly mpuSequenceNumber?: number;\n };\n /**\n * The furthest-presented outgoing frame delivered before the commit,\n * counting the gap fills the commit released, or undefined when the\n * outgoing stream delivered none.\n */\n readonly outgoingHighWater: StreamSwitchPosition | undefined;\n readonly reason: StreamSwitchCommitReason;\n /**\n * Incoming frames never delivered: the deltas before the incoming stream's\n * first RAP (the GOP it was admitted into mid-way), and a held GOP a reset\n * of the incoming stream invalidated.\n */\n readonly droppedIncomingFrames: number;\n}\n\n/** What MmtpRouter.stageStreamSwitch needs. */\nexport interface StreamSwitchOptions {\n /** The stream being replaced: a registered video packet ID. */\n readonly from: MmtpStreamIdentity;\n /** The replacing stream: a registered video packet ID other than `from`'s. */\n readonly to: MmtpStreamIdentity;\n /**\n * Called synchronously at the commit, after `from`'s packet ID is retired\n * and before `to`'s withheld init and its RAP are delivered. Throwing fails\n * the commit closed: `to` is retired as well and nothing of it is\n * delivered, since `from` can no longer be restored.\n */\n readonly onCommit: (commit: StreamSwitchCommit) => void;\n}\n\n/** A switch MmtpRouter.stageStreamSwitch staged. */\nexport interface StagedStreamSwitch {\n /**\n * Abandon the switch before it commits: `to` is no longer withheld and its\n * withheld init is dropped. Returns false when the switch already committed\n * or was cancelled.\n */\n cancel(): boolean;\n}\n\n/** One incoming frame withheld until the switch commits. */\ninterface HeldSwitchFrame {\n readonly data: Uint8Array;\n readonly meta: ParserFrameMeta;\n /** The FEC coordinates the frame arrived under, for its MSE assembler. */\n readonly fecInfo: { sbn: number; esi: number; oversized?: boolean } | undefined;\n}\n\n/** Router-side state of a staged stream switch. */\ninterface StreamSwitchState {\n readonly from: MmtpStreamIdentity;\n readonly to: MmtpStreamIdentity;\n readonly onCommit: (commit: StreamSwitchCommit) => void;\n /** Null until the incoming stream's first RAP; then that RAP and every incoming frame after it. */\n held: HeldSwitchFrame[] | null;\n /** The incoming stream's newest init, withheld until the commit. */\n heldInit: Uint8Array | undefined;\n droppedIncomingFrames: number;\n}\n\nfunction assertStreamIdentity(role: string, stream: MmtpStreamIdentity | undefined): void {\n if (\n stream === undefined ||\n !Number.isInteger(stream.packetId) ||\n stream.packetId <= MMTP_SIGNALING_PACKET_ID ||\n typeof stream.catalogTrackName !== \"string\" ||\n stream.catalogTrackName.length === 0\n ) {\n throw new Error(\n `[MmtpRouter] stream switch ${role} needs a track packet ID and a non-empty catalog track name`,\n );\n }\n}\n\nfunction isStream(\n meta: { packetId: number; catalogTrackName?: string },\n stream: MmtpStreamIdentity,\n): boolean {\n return meta.packetId === stream.packetId && meta.catalogTrackName === stream.catalogTrackName;\n}\n\n/**\n * Presentation order of two frames of different renditions. Exact when both\n * carry authored timing (renditions may use different timescales); otherwise\n * by the microsecond timestamps the router emits.\n */\nfunction comparePresentation(a: StreamSwitchPosition, b: StreamSwitchPosition): number {\n const at = a.authoredTiming;\n const bt = b.authoredTiming;\n if (at !== undefined && bt !== undefined) {\n const left = at.presentationTime * BigInt(bt.mediaTimescale);\n const right = bt.presentationTime * BigInt(at.mediaTimescale);\n return left < right ? -1 : left > right ? 1 : 0;\n }\n return a.timestamp - b.timestamp;\n}\n\nfunction switchPosition(meta: {\n timestamp: number;\n authoredTiming?: AuthoredTiming;\n}): StreamSwitchPosition {\n return Object.freeze({\n timestamp: meta.timestamp,\n ...(meta.authoredTiming === undefined\n ? {}\n : { authoredTiming: Object.freeze({ ...meta.authoredTiming }) }),\n });\n}\n\n/** What one ingress track list says about one packet ID. */\ninterface IngressPacketEntry {\n /** The one non-empty catalog name the list gives the packet ID, if exactly one. */\n readonly name?: string;\n /** More than one non-empty catalog name claims the packet ID. */\n readonly ambiguous: boolean;\n}\n\nfunction indexIngressTracks(\n tracks: MmtpIngressContext[\"tracks\"],\n): ReadonlyMap<number, IngressPacketEntry> {\n const names = new Map<number, Set<string>>();\n for (const track of tracks) {\n let packetNames = names.get(track.packetId);\n if (!packetNames) {\n packetNames = new Set();\n names.set(track.packetId, packetNames);\n }\n if (track.name.length > 0) packetNames.add(track.name);\n }\n const index = new Map<number, IngressPacketEntry>();\n for (const [packetId, packetNames] of names) {\n index.set(\n packetId,\n packetNames.size === 1\n ? { name: packetNames.values().next().value, ambiguous: false }\n : { ambiguous: packetNames.size > 1 },\n );\n }\n return index;\n}\n\n/**\n * Index of each frozen ingress track list (BLO-36599). MulticastManager.join\n * freezes the list it hands the transport, which passes that same list with\n * every packet, so the packetId -> name mapping is derived once per join\n * rather than filtered up to four times per packet. A list that is not frozen\n * may still change, so it is indexed afresh on every packet as before.\n */\nconst frozenIngressIndexes = new WeakMap<object, ReadonlyMap<number, IngressPacketEntry>>();\n\nfunction ingressEntry(\n ingress: MmtpIngressContext,\n packetId: number,\n): IngressPacketEntry | undefined {\n const tracks = ingress.tracks;\n if (!Object.isFrozen(tracks)) return indexIngressTracks(tracks).get(packetId);\n let index = frozenIngressIndexes.get(tracks);\n if (!index) {\n index = indexIngressTracks(tracks);\n frozenIngressIndexes.set(tracks, index);\n }\n return index.get(packetId);\n}\n\nfunction catalogTrackNameForPacket(\n ingress: MmtpIngressContext | undefined,\n packetId: number,\n): string | undefined {\n if (!ingress) return undefined;\n return ingressEntry(ingress, packetId)?.name;\n}\n\nfunction hasAmbiguousCatalogTrackName(\n ingress: MmtpIngressContext | undefined,\n packetId: number,\n): boolean {\n if (!ingress) return false;\n return ingressEntry(ingress, packetId)?.ambiguous === true;\n}\n\nfunction ingressHasPacketId(\n ingress: MmtpIngressContext | undefined,\n packetId: number,\n): boolean {\n // packet_id 0 is the MMTP signaling flow, never a track's, whatever the ingress.\n if (packetId === MMTP_SIGNALING_PACKET_ID) return false;\n // Direct/legacy callers have no catalog-backed ingress policy to enforce.\n // MulticastManager supplies a populated context whenever packetIds are selected.\n return ingress === undefined || ingressEntry(ingress, packetId) !== undefined;\n}\n\n/** Callback for decoded video/audio frames. */\nexport type FrameCallback = (data: Uint8Array, meta: RoutedFrameMeta) => void;\n\n/** Metadata accompanying an accepted raw MMTP source packet. */\nexport type RoutedPacketMeta = {\n packetId: number;\n packetSequenceNumber: number;\n /** True when FEC reconstructed this source packet. */\n recovered: boolean;\n /** True when the packet has no trailing Source FEC Payload ID. */\n skipFecPayloadId: boolean;\n /** Immutable catalog identity supplied by the owning ingress subscription. */\n catalogTrackName?: string;\n /**\n * Strict provenance for the fragments this packet produces. Set for\n * FEC-recovered packets, which carry no `source` of their own but inherit\n * their FEC block's symbol provenance.\n */\n deliveryProvenance?: MmtpDeliveryProvenance;\n};\n\n/** Callback for accepted raw MMTP source packets. */\nexport type TrackPacketCallback = (packet: Uint8Array, meta: RoutedPacketMeta) => void;\n\ntype PendingRecoveredVideoFrame = {\n data: Uint8Array;\n meta: RoutedFrameMeta;\n};\n\ntype PendingRecoveredAudioFrame = {\n data: Uint8Array;\n meta: RoutedFrameMeta;\n};\n\ntype DirectAudioCursor = {\n timestamp: number;\n sequenceNumber: number;\n};\n\n/**\n * One physical stream's stable key and duplicate ledgers, keyed by wire\n * numbers rather than strings.\n */\ninterface StreamLedgers {\n /** JSON tuple naming the stream; see #mediaStreamKey. */\n readonly key: string;\n /**\n * The stream's three duplicate ledgers, each retained for the stream's FEC\n * horizon (see duplicate-ledger.ts):\n *\n * - `sourceSeqs`: packet_sequence_numbers already received on the direct\n * source path. Recovered FEC blocks contain all K source symbols, so skip\n * direct-path duplicates even when the source frame is still buffered by\n * parser reordering.\n * - `routedSeqs`: packet_sequence_numbers already handed to the track packet\n * callback.\n * - `parsedSymbols`: Source FEC Payload IDs (SS_ID) already parsed for\n * playback. An overlap copy and its multicast twin carry the same symbol,\n * and the copy never feeds FEC, so FEC's first-arrival check cannot dedup\n * the pair.\n */\n readonly duplicates: StreamDuplicateLedgers;\n}\n\nconst NO_FEC_MANAGER_HORIZON: DuplicateHorizon = Object.freeze({\n kind: \"none\",\n reason: \"no-fec-manager\",\n});\nconst NO_FEC_GEOMETRY_HORIZON: DuplicateHorizon = Object.freeze({\n kind: \"none\",\n reason: \"no-fec-geometry\",\n});\nconst NO_BLOCK_SPAN_HORIZON: DuplicateHorizon = Object.freeze({\n kind: \"none\",\n reason: \"no-block-span\",\n});\n\nconst DIRECT_RECOVERED_VIDEO_QUEUE_LIMIT = 512;\nconst DIRECT_RECOVERED_AUDIO_QUEUE_LIMIT = 512;\n\ntype ParserDiagnostics = {\n packets: number;\n frames: number;\n initSegments: number;\n noFragment: number;\n fragments: number;\n reassembled: number;\n pendingBuffers: number;\n pendingFragments: number;\n pendingSamples?: unknown[];\n lastPacketLength: number;\n lastSequenceNumber: number;\n lastFecSsId?: number;\n lastFrameTimestamp?: number;\n directAudioSourceEmitted: number;\n directAudioSourceRegressions: number;\n directAudioRecoveredQueued: number;\n directAudioRecoveredDrained: number;\n directAudioRecoveredStaleDropped: number;\n};\n\n/**\n * FECManager interface \u2014 the contract MmtpRouter expects for FEC\n * source/repair symbol processing and block lifecycle events.\n *\n * Concrete implementation: `FECManager` class in `./fec-manager.ts`.\n * MmtpRouter only interacts with FEC through this interface.\n */\nexport interface FECManagerLike {\n /** Feed a source symbol to the FEC decoder for a given track.\n * Returns recovered block bytes if recovery completes, else null.\n *\n * `symbolData` is the complete MMTP packet without its Source FEC Payload\n * ID, unpadded and possibly a view into the received packet. The decoder\n * builds the ssbg_mode1 symbol (`u16 len || packet || 00h`, zero-filled to\n * the packet's own symbol size T) from it; nothing pads it on the way.\n *\n * Optional `alta` carries the detached ALTA authenticator extracted from\n * the MMTP header-extension trailer (08-07 Path 2) \u2014 present on signed\n * source packets, absent on relay-reassembled / unsigned streams. When\n * provided, the fec-worker runs verify_detached instead of counting the\n * symbol as `altaPending`. */\n feedSourceSymbol(\n packetId: number,\n ssId: number,\n symbolData: Uint8Array,\n alta?: { auth: Uint8Array; payloadEnd: number },\n catalogTrackName?: string,\n ): Uint8Array | null;\n\n /** Feed a repair symbol to the FEC decoder for a given track.\n * Returns recovered block bytes if recovery completes, else null. */\n feedRepairSymbol(\n packetId: number,\n ssStart: number,\n ssbLength: number,\n rsId: number,\n repairData: Uint8Array,\n catalogTrackName?: string,\n ): Uint8Array | null;\n\n /** Delivery-gap sample for one multicast packet (FECManager.noteMulticastArrival). */\n noteMulticastArrival?(packetId: number, packetSequenceNumber: number): void;\n\n /** Record a transport symbol for first-arrival-wins dedup and path counters. */\n feedSymbolFromSource(\n packetId: number,\n sbn: number,\n esi: number,\n data: Uint8Array,\n source: \"multicast\" | \"moq\",\n catalogTrackName?: string,\n ): boolean;\n\n /** Strict provenance for a block: 'multicast' only when no symbol arrived\n * over MoQ. Recovered bytes inherit it, since a recovered symbol is a\n * function of every symbol that entered the block. */\n blockDeliveryProvenance?(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): MmtpDeliveryProvenance;\n\n /** Track a first-arrival network source symbol for block completion and\n * classification. Refreshes the inactivity deadline and marks clean when\n * K arrive. */\n trackBlock(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n esi: number,\n evidence: SourceSymbolEvidence,\n ): void;\n\n /** True once the packetId has a per-track FEC configuration. */\n isTrackConfigured?(packetId: number): boolean;\n\n /** Lifecycle resolution of one block for in-order frame release. */\n blockResolution?(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): \"pending\" | \"complete\" | \"failed\" | \"untracked\";\n\n /** Catalog/reset epoch of a packet's streams. */\n epochFor?(packetId: number): number;\n\n /**\n * Catalog `fec.interleaveDepthMs` (\u00A75.1 full block span) of a packetId; 0\n * when the stream has none.\n */\n interleaveDepthMsForPacket?(packetId: number): number;\n\n /**\n * How long a terminal block stays authoritative before it retires to\n * untracked. The release gate holds a frame at most this long, so its loss\n * scan never reads a retired, delivered block as lost. Required whenever\n * blockResolution is.\n */\n terminalRetentionMsForPacket?(packetId: number): number;\n\n /** Configured codec of a packetId. */\n codecFor?(packetId: number): string | undefined;\n\n /** Current classifier mode of a stream. */\n classifierModeFor?(\n packetId: number,\n catalogTrackName?: string,\n ): { mode: ClassifierMode; reason: ClassifierReason | null };\n\n /** Whether an MPU boundary still bounds the stream's RAPs. */\n mpuBoundsRapsFor?(packetId: number, catalogTrackName?: string): boolean;\n\n /** Record a RAP released in order (RAP-spacing invariant). */\n noteReleasedRap?(\n packetId: number,\n catalogTrackName: string | undefined,\n mpu: number,\n presentationTime: bigint,\n mediaTimescale: number,\n ): void;\n\n /** Refresh the block inactivity deadline after accepted repair progress. */\n noteBlockActivity?(sbn: number, packetId: number, catalogTrackName?: string): void;\n\n /** Cancel block timeout (block completed via clean or FEC recovery). */\n cancelBlockTimeout(sbn: number, packetId?: number, catalogTrackName?: string): void;\n\n /** Check if a block is already completed (clean or recovered). */\n isBlockCompleted(sbn: number, packetId?: number, catalogTrackName?: string): boolean;\n\n /** Mark a block as completed externally (after routing recovered data). */\n markBlockCompleted(sbn: number, packetId?: number, catalogTrackName?: string): void;\n\n /** Increment blocksRecovered stat (called after routing recovery). */\n countRecovery(packetId?: number, sbn?: number, catalogTrackName?: string): void;\n\n /**\n * The configured catalog `fec.blockProfile` of a packetId, or undefined\n * while it is unconfigured. Required: a Source or Repair FEC Payload ID is\n * read under exactly one profile, and the router never guesses it.\n */\n blockProfileForPacket(packetId: number): FecBlockProfile | undefined;\n /** iso-ssbg1-v1: whether a protected packet fits one ssbg_mode1 symbol. */\n isoSourceFits?(packetId: number, packetLength: number): boolean;\n /** iso-ssbg1-v1: locate a source in a block a repair already stated. */\n locateIsoSource?(\n packetId: number,\n catalogTrackName: string | undefined,\n ssId: number,\n ): { sbn: number; esi: number } | null;\n /** iso-ssbg1-v1: hold a first-arrival source until a repair states its block. */\n holdIsoSource?(\n packetId: number,\n catalogTrackName: string | undefined,\n ssId: number,\n source: IsoHeldSource,\n ): boolean;\n /** iso-ssbg1-v1: state a repair's block and hand back the held sources it places. */\n placeIsoRepair?(\n packetId: number,\n catalogTrackName: string | undefined,\n ssStart: number,\n ssbLength: number,\n ): IsoRepairPlacement<IsoHeldSource>;\n /** iso-ssbg1-v1: resolution of the SS_IDs strictly between two released frames. */\n isoSpanResolution?(\n packetId: number,\n catalogTrackName: string | undefined,\n fromSsId: number,\n toSsId: number,\n ): readonly number[] | null;\n\n /** FEC source symbol count K per block. */\n readonly k: number;\n kForPacket?(packetId: number): number;\n\n /**\n * The longest a block stays open after its last symbol (inactivity fail\n * bound plus D-28 repair grace). With interleaveDepthMsForPacket it bounds\n * how long the router keeps a stream's duplicate ledgers.\n */\n blockLifetimeMsForPacket?(packetId: number): number;\n\n /** FEC symbol size T in bytes (0 = unknown / no FEC). */\n readonly symbolSize: number;\n symbolSizeForPacket?(packetId: number): number;\n\n /** Update framerate \u2014 used to refine FEC block timeout. */\n setFramerate(fps: number): void;\n\n /** Per-block state snapshot for FecBlockGrid visualization. */\n getBlockStates(): Array<{\n sbn: number;\n packetId?: number;\n state: \"collecting\" | \"complete\" | \"failed\";\n sourceCount: number;\n k: number;\n progress: number;\n }>;\n\n /** FEC statistics snapshot. */\n readonly fecStats: {\n sourcePackets: number;\n repairPackets: number;\n blocksClean: number;\n blocksRecovered: number;\n blocksFailed: number;\n };\n\n // \u2500\u2500 Event hooks (set by MmtpRouter in constructor) \u2500\u2500\n\n /** Called when a block receives its first source symbol. */\n onBlockStarted:\n | ((sbn: number, packetId: number, catalogTrackName?: string) => void)\n | null;\n\n /** Called when a block completes cleanly (all K sources arrived). */\n onBlockComplete:\n | ((sbn: number, packetId: number, catalogTrackName?: string) => void)\n | null;\n\n /** Called when a block times out (unrecoverable), with its class. */\n onBlockFailed:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => void)\n | null;\n\n /** Called when a block reaches any terminal state. */\n onBlockResolved?:\n | ((sbn: number, packetId: number, catalogTrackName: string | undefined) => void)\n | null;\n\n /** Called when a stream's FEC source restarted (SBN discontinuity). */\n onSourceDiscontinuity?:\n | ((packetId: number, catalogTrackName: string | undefined) => void)\n | null;\n\n /** Dispose timers and state. */\n dispose(): void;\n}\n\n// \u2500\u2500\u2500 MmtpRouter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ManifestIdentityResolutionInput {\n packetId: number;\n sbn: number;\n esi: number;\n /**\n * The packet's SS_ID and its MPU's mpu_sequence_number. Under\n * iso-ssbg1-v1 blocks vary in length, so a packet's media group comes from\n * its own MPU header, never from SBN arithmetic (BLO-35597 D2a).\n */\n ssId?: number;\n mpuSequenceNumber?: number;\n catalogTrackName?: string;\n recovered: boolean;\n sourceSymbolFormat: \"full-mmtp-packet\" | \"fec-padded-recovery\";\n}\n\nexport type ManifestIdentityResolver = (\n input: ManifestIdentityResolutionInput,\n) => ManifestObjectIdentity | null;\n\n/**\n * Classification evidence carried by one first-arrival network source\n * packet: the MMTP header R bit and the MPU / data-unit identity it states\n * (movie fragment, sample, DU offset). `idx` is the 0-based sample position\n * (`sample_number` is 1-based). The packet_sequence_number rides along for\n * FECManager's forward-jump bound.\n */\nfunction sourceSymbolEvidence(\n packet: Uint8Array,\n header: MMTPHeader,\n rapFlag: boolean,\n packetSequenceNumber: number,\n): SourceSymbolEvidence {\n const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);\n return {\n rap: rapFlag,\n packetSequenceNumber,\n ...(identity === null ? {} : { mpu: identity.mpuSequenceNumber }),\n ...(identity?.sampleNumber !== undefined && identity.sampleNumber >= 1\n ? { idx: identity.sampleNumber - 1 }\n : {}),\n ...(identity?.movieFragmentSequenceNumber !== undefined\n ? { movieFragment: identity.movieFragmentSequenceNumber }\n : {}),\n ...(identity?.offset !== undefined ? { offset: identity.offset } : {}),\n };\n}\n\nexport class MmtpRouter {\n readonly #parser: MmtpContainerParser;\n // Source AND FEC-recovered MFU fragments share ONE reassembler \u2014 the parser's\n // (see #parser.reassembler). A keyframe split between source-received and\n // FEC-recovered fragments must reassemble in a single buffer (BLO-8151).\n readonly #fecManager: FECManagerLike | null;\n #defaultFecMapper: FecBlockMapper | null;\n #fecMappersByPacketId = new Map<number, FecBlockMapper>();\n readonly #manifestVerifier: ManifestVerifier | null;\n readonly #ownsManifestVerifier: boolean;\n readonly #resolveManifestIdentity:\n | ((\n packetId: number,\n sbn: number,\n esi: number,\n catalogTrackName?: string,\n ) => ManifestObjectIdentity | null)\n | null;\n readonly #resolveManifestIdentityForRoute: ManifestIdentityResolver | null;\n #manifestRecoveryQueues = new Map<\n string,\n Array<{\n blockId: number;\n result: ProcessFecBlockResult;\n ownerPacketId: number;\n catalogTrackName?: string;\n }>\n >();\n\n // Temp state set by #processMMTP before calling parser.feed(),\n // read by the parser's onFrame callback for CMAF assembly + dedup.\n #currentFecInfo:\n | { sbn: number; esi: number; oversized?: boolean }\n | undefined;\n #currentSeqNum = 0;\n\n // Track type registry: which packetIds are video vs audio (from catalog)\n #videoPacketIds = new Set<number>();\n #audioPacketIds = new Set<number>();\n #catalogTrackNamesByPacketId = new Map<number, Set<string>>();\n\n // Per-track codec string for keyframe detection (e.g. 'avc1.42001e', 'av01.0.04M.08')\n #trackCodecs = new Map<number, string>();\n\n // Per-track frame callbacks keyed by packetId \u2014 used for ABR / multi-track routing\n #trackCallbacks = new Map<\n number,\n FrameCallback\n >();\n #trackPacketCallbacks = new Map<number, TrackPacketCallback>();\n // Cached resolved callbacks: per-track + legacy composite per packetId.\n // Invalidated when any callback is registered (onTrackFrame, onVideoFrame, onAudioFrame).\n #resolvedCallbacks = new Map<number, FrameCallback>();\n\n // Per-ingress-stream sync flag: suppress direct frame emission until the first complete\n // FEC block confirms we've joined mid-stream cleanly. Prevents corrupt partial\n // frames from reaching the decoder on multicast mid-stream join. Catalog\n // identity keeps renditions that reuse a packet ID from opening each other's gate.\n #trackSynced = new Set<string>();\n #droppedPreSyncVideo = new Set<string>();\n\n // One entry per physical stream (packetId, catalog track name): its stable\n // JSON key and its per-packet duplicate ledgers. The ledgers used to be\n // three router-wide Sets of \"<streamKey>:<coordinates>\" strings, so every\n // packet built and hashed up to three strings, and resetTrack() scanned\n // every retained key by prefix (BLO-36599). They now hold the wire numbers\n // themselves, and a reset clears one stream's Sets.\n #streams = new Map<number, Map<string | undefined, StreamLedgers>>();\n\n // Live WebCodecs callbacks cannot accept late recovered inter-frame video.\n // MSE recovery routes through CmafBlockAssembler and can reorder by block; the\n // direct callback path has to preserve decode order before frames leave libmmt.\n #latestVideoSeqByStream = new Map<string, number>();\n #lastDirectSourceVideoTsByStream = new Map<string, number>();\n #lastDirectEmittedVideoTsByStream = new Map<string, number>();\n #pendingDirectRecoveredVideoByStream = new Map<string, Map<string, PendingRecoveredVideoFrame>>();\n // AudioDecoder input must remain monotonic too. A repair that completes after\n // the live source cursor passed its AAC access unit is useful for FEC\n // accounting, but injecting it into the decoder can stall PCM output.\n #lastDirectSourceAudioCursorByStream = new Map<string, DirectAudioCursor>();\n #lastDirectEmittedAudioCursorByStream = new Map<string, DirectAudioCursor>();\n #pendingDirectRecoveredAudioByStream = new Map<string, Map<string, PendingRecoveredAudioFrame>>();\n\n // The furthest-presented video frame each stream delivered, which a staged\n // switch reads as its outgoing high water. Recorded where a frame reaches a\n // decoder callback or an MSE assembler, not on arrival: a recovered frame\n // can be queued ahead of the source cursor and discarded at the commit. A\n // max, because an MSE assembler takes a late FEC gap fill below frames\n // already delivered; an under-stated high water reports an overlapping\n // splice as exact.\n #deliveredVideoHighWaterByStream = new Map<string, StreamSwitchPosition>();\n\n // Per-track CMAF block assemblers for MSE mode. When active, frames are buffered per-SBN\n // and emitted as multi-sample CMAF segments on block completion instead of per-frame.\n #mseAssemblers = new Map<string, CmafBlockAssembler>();\n\n // Callbacks (legacy \u2014 used when per-track callbacks are not registered)\n #onVideoFrame: FrameCallback | null = null;\n #onAudioFrame: FrameCallback | null = null;\n\n // Init segment (ftyp+moov) from MMTP MPU metadata \u2014 needed for MSE SourceBuffer\n #onInitSegment: ((data: Uint8Array) => void) | null = null;\n\n // Per-track init segment callbacks keyed by packetId\n #trackInitCallbacks = new Map<number, (data: Uint8Array) => void>();\n // Track whether init has been delivered per physical stream \u2014 prevent duplicates\n #trackInitDelivered = new Set<string>();\n\n // FEC block lifecycle. A failed block is NOT a decoder instruction: the\n // stall/conceal decision rides on the first frame released after the gap.\n #onBlockStarted: ((sbn: number, packetId: number) => void) | null = null;\n /** Diagnostic subscriber for unrecoverable FEC blocks, with their class. */\n #onBlockFailed:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => void)\n | null = null;\n /** Typed observer of every gap decision the release gate makes. */\n readonly #onFrameGap: ((decision: FrameGapDecision) => void) | null;\n // Pokes requested while a packet is being processed are deferred to its\n // end: a synchronous recovery completes its block BEFORE routing the\n // recovered frames, and releasing a held gap in between would strand them.\n #fecPokeDepth = 0;\n #pendingFecPokes = new Map<string, { packetId: number; catalogTrackName?: string }>();\n // \u2500\u2500 Phase 14: per-role packetId dispatch (D-26, D-27, D-28) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Maps packetId \u2192 TrackRole. Default: 'single' when absent (legacy path). */\n #packetIdRoles: Map<number, TrackRole> = new Map();\n /** Explicit catalog ownership for dedicated repair-track packet IDs. */\n #repairOwnersByPacketId = new Map<number, Set<number>>();\n /** Callback fired when a delta-track FEC block completes or fails. */\n #onDeltaBlockCompleteCb:\n | ((packetId: number, failed: boolean) => void)\n | null = null;\n\n // Debug counters\n #pidCounts: Record<number, number> = {};\n #repairPidCounts: Record<number, number> = {};\n #moqRepairPidCounts: Record<number, number> = {};\n #moqRepairOwnerCounts: Record<number, number> = {};\n #parserDiagnostics = new Map<string, ParserDiagnostics>();\n\n // \u2500\u2500 Keyframe loss detection state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n /** Catalog-derived keyframe loss timeout (ms), or a reader evaluated at each arm. null = not configured (warns on video track registration; Phase-13 keyframe repair stays off). */\n #keyframeLostTimeoutMs: number | (() => number) | null;\n /** Why the last reader evaluation left the timer unarmed; null while arming. Dedups the warning. */\n #keyframeLostUnarmedReason: string | null = null;\n /** Active keyframe loss timer handle. Single timer per video track \u2014 cleared on each rapFlag and on dispose(). */\n #keyframeLostTimer: ReturnType<typeof setTimeout> | null = null;\n /** Monotonic counter incremented on each emitted video keyframe. Tracks current GOP position. */\n #lastKeyframeGroupId = 0;\n /** Callback fired when no complete keyframe is emitted within keyframeLostTimeoutMs. Argument: next expected groupId. */\n #onKeyframeLost: ((expectedGroupId: number) => void) | null = null;\n\n /**\n * Owner-held state to reset when a multicast stream's publisher restarts\n * (BLO-36599); see #processMMTP. The router has already reset its own state\n * for the stream when this runs.\n */\n #onPublisherRestart: ((packetId: number, catalogTrackName: string) => void) | null;\n #publisherRestarts = 0;\n /** Inits delivered with no onTrackInit handler for their packetId (MOQT-01). */\n #unmappedInitDeliveries = 0;\n // FEC Payload IDs left unread because their (owner) packetId had no\n // configured fec.blockProfile (see #noteUnreadableFecPayloadId).\n #fecPayloadIdsWithoutProfile = 0;\n\n /** The staged in-place stream switch, if any (stageStreamSwitch). */\n #staged: StreamSwitchState | null = null;\n\n #disposed = false;\n\n constructor(config?: {\n fecManager?: FECManagerLike;\n fecMapper?: FecBlockMapper;\n /** MFU reassembly timeout (ms) for FEC recovery path. Derive from FEC interleaveDepthMs. */\n mfuTimeoutMs?: number;\n reassemblyPolicy?: MfuReassemblyPolicy;\n reassembler?: MfuReassemblerBackend;\n maxBufferedMfus?: number;\n /** Catalog-derived keyframe loss timeout (ms). Derive from explicit\n * keyframe/group duration; do not use FEC sourceSymbols as GOP length.\n * Pass a reader when its inputs are measured later; see\n * setKeyframeLostTimeoutMs. */\n keyframeLostTimeoutMs?: number | (() => number);\n manifestVerifier?: ManifestVerifier;\n /** Existing behavior is router ownership; shared browser plans opt into caller ownership. */\n manifestVerifierOwnership?: \"caller\" | \"router\";\n resolveManifestIdentity?: (\n packetId: number,\n sbn: number,\n esi: number,\n catalogTrackName?: string,\n ) => ManifestObjectIdentity | null;\n resolveManifestIdentityForRoute?: ManifestIdentityResolver;\n /** Internal terminal-failure sink owned by the composing transport. */\n onBlockFailed?: (\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => void;\n /** Internal gap-decision sink owned by the composing transport. */\n onFrameGap?: (decision: FrameGapDecision) => void;\n /**\n * Called when a multicast stream's publisher restarted its wire counters,\n * after the router reset its own state for the stream, so the composing\n * transport can reset what it holds (FEC blocks, repair I/O) before the\n * packet that revealed the restart is processed.\n */\n onPublisherRestart?: (packetId: number, catalogTrackName: string) => void;\n }) {\n this.#onPublisherRestart = config?.onPublisherRestart ?? null;\n this.#keyframeLostTimeoutMs = config?.keyframeLostTimeoutMs ?? null;\n this.#fecManager = config?.fecManager ?? null;\n this.#onFrameGap = config?.onFrameGap ?? null;\n this.#defaultFecMapper = config?.fecMapper ?? null;\n this.#manifestVerifier = config?.manifestVerifier ?? null;\n this.#ownsManifestVerifier = config?.manifestVerifierOwnership !== \"caller\";\n this.#resolveManifestIdentity = config?.resolveManifestIdentity ?? null;\n this.#resolveManifestIdentityForRoute = config?.resolveManifestIdentityForRoute ?? null;\n\n // Create MMTP parser \u2014 handles MFU extraction, reassembly, keyframe detection.\n // Its MFUReassembler is the single reassembler for BOTH the source path\n // (parser.feed) and the FEC-recovery path (#routeRecoveredBlock), so a frame\n // split across both completes (BLO-8151). mfuTimeoutMs threads the\n // catalog-derived timeout through.\n this.#parser = new MmtpContainerParser({\n mfuTimeoutMs: config?.mfuTimeoutMs,\n reassemblyPolicy: config?.reassemblyPolicy,\n reassembler: config?.reassembler,\n maxBufferedMfus: config?.maxBufferedMfus,\n ...(this.#fecManager ? { fecReleaseGate: this.#createFecReleaseGate() } : {}),\n });\n\n // Wire parser init callback \u2192 router's init delivery (dedup).\n this.#parser.onInit((data, packetId, catalogTrackName) => {\n this.#diagFor(packetId, catalogTrackName).initSegments++;\n this.#deliverInit(packetId, data, catalogTrackName);\n });\n\n // Wire parser frame callback \u2192 router's callback routing + CMAF assembly.\n this.#parser.onFrame((data, meta) => this.#routeParsedFrame(data, meta));\n\n // Wire up FECManager block lifecycle events.\n // NOTE: Recovery routing is handled from feedSourceSymbol/feedRepairSymbol\n // return values in #processMMTP \u2014 no onBlockRecovered event needed.\n this.#wireFecLifecycle(config?.onBlockFailed);\n }\n\n /**\n * Deliver one parser frame: MSE assembly, sync gating, and callback\n * routing. Reads #currentFecInfo, the FEC coordinates of the packet the\n * frame completed on.\n */\n #deliverParsedFrame(data: Uint8Array, meta: ParserFrameMeta): void {\n const packetId = meta.packetId;\n const diag = this.#diagFor(packetId, meta.catalogTrackName);\n diag.frames++;\n diag.lastFrameTimestamp = meta.timestamp;\n const isVideo = this.#classifyPacketId(packetId) === true;\n const frameMeta: RoutedFrameMeta = {\n timestamp: meta.timestamp,\n ...(meta.mmtpTimestamp === undefined\n ? {}\n : { mmtpTimestamp: meta.mmtpTimestamp }),\n keyframe: isVideo ? meta.keyframe : true,\n packetId,\n packetSequenceNumber: meta.packetSequenceNumber ?? this.#currentSeqNum,\n ...(meta.authoredTiming === undefined\n ? {}\n : { authoredTiming: meta.authoredTiming }),\n ...(meta.packetSequenceNumbers === undefined\n ? {}\n : { packetSequenceNumbers: meta.packetSequenceNumbers }),\n ...(meta.repairedPacketSequenceNumbers === undefined\n ? {}\n : { repairedPacketSequenceNumbers: meta.repairedPacketSequenceNumbers }),\n ...(meta.mpuSequenceNumber === undefined\n ? {}\n : { mpuSequenceNumber: meta.mpuSequenceNumber }),\n ...(meta.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: meta.movieFragmentSequenceNumber }),\n ...(meta.sampleNumber === undefined\n ? {}\n : { sampleNumber: meta.sampleNumber }),\n ...(meta.catalogTrackName === undefined\n ? {}\n : { catalogTrackName: meta.catalogTrackName }),\n };\n if (meta.source) frameMeta.source = meta.source;\n // Forward whenever present, not when truthy: 'unknown' is a verdict the\n // parser reached, not a value to drop.\n if (meta.deliveryProvenance !== undefined)\n frameMeta.deliveryProvenance = meta.deliveryProvenance;\n if (meta.recovered) frameMeta.recovered = true;\n if (meta.gapBefore) frameMeta.gapBefore = meta.gapBefore;\n const sourceDiscontinuity = this.#opensFecSync(\n packetId,\n frameMeta.keyframe,\n frameMeta.catalogTrackName,\n );\n\n // Route to MSE assembler if active for this track.\n const assembler = this.#mseAssemblerFor(packetId, frameMeta.catalogTrackName);\n if (assembler && frameMeta.recovered) {\n // An FEC-recovered frame released in order by the FEC release gate.\n assembler.addRecoveredBlock(meta.fecSbn ?? -1, [{\n data,\n timestamp: frameMeta.timestamp,\n keyframe: frameMeta.keyframe,\n ...(frameMeta.mmtpTimestamp === undefined ? {} : { mmtpTimestamp: frameMeta.mmtpTimestamp }),\n ...(frameMeta.authoredTiming === undefined ? {} : { authoredTiming: frameMeta.authoredTiming }),\n ...(frameMeta.packetSequenceNumber === undefined\n ? {}\n : { packetSequenceNumber: frameMeta.packetSequenceNumber }),\n ...(frameMeta.packetSequenceNumbers === undefined\n ? {}\n : { packetSequenceNumbers: frameMeta.packetSequenceNumbers }),\n ...(frameMeta.repairedPacketSequenceNumbers === undefined\n ? {}\n : { repairedPacketSequenceNumbers: frameMeta.repairedPacketSequenceNumbers }),\n ...(frameMeta.mpuSequenceNumber === undefined ? {} : { mpuSequenceNumber: frameMeta.mpuSequenceNumber }),\n ...(frameMeta.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: frameMeta.movieFragmentSequenceNumber }),\n ...(frameMeta.sampleNumber === undefined ? {} : { sampleNumber: frameMeta.sampleNumber }),\n ...(frameMeta.catalogTrackName === undefined ? {} : { catalogTrackName: frameMeta.catalogTrackName }),\n ...(frameMeta.deliveryProvenance === undefined\n ? {}\n : { deliveryProvenance: frameMeta.deliveryProvenance }),\n ...(frameMeta.gapBefore === undefined ? {} : { gapBefore: frameMeta.gapBefore }),\n }]);\n } else if (assembler) {\n const fecInfo = this.#currentFecInfo;\n if (fecInfo && !fecInfo.oversized) {\n assembler.addFrame(\n fecInfo.sbn,\n fecInfo.esi,\n data,\n frameMeta.timestamp,\n frameMeta.keyframe,\n frameMeta.catalogTrackName,\n frameMeta.mpuSequenceNumber,\n frameMeta.packetSequenceNumber,\n frameMeta.packetSequenceNumbers,\n frameMeta.repairedPacketSequenceNumbers,\n frameMeta.authoredTiming,\n frameMeta.deliveryProvenance,\n frameMeta.mmtpTimestamp,\n frameMeta.movieFragmentSequenceNumber,\n frameMeta.sampleNumber,\n frameMeta.gapBefore,\n );\n } else {\n assembler.addBypassFrame(\n data,\n frameMeta.timestamp,\n frameMeta.keyframe,\n frameMeta.catalogTrackName,\n frameMeta.mpuSequenceNumber,\n frameMeta.packetSequenceNumber,\n frameMeta.packetSequenceNumbers,\n frameMeta.repairedPacketSequenceNumbers,\n frameMeta.authoredTiming,\n frameMeta.deliveryProvenance,\n frameMeta.mmtpTimestamp,\n frameMeta.movieFragmentSequenceNumber,\n frameMeta.sampleNumber,\n frameMeta.gapBefore,\n );\n }\n }\n\n if (assembler && isVideo) {\n this.#noteDeliveredVideo(this.#mediaStreamKey(packetId, frameMeta.catalogTrackName), frameMeta);\n }\n\n if (!this.#shouldEmitFrame(\n packetId,\n frameMeta.keyframe,\n frameMeta.catalogTrackName,\n )) return;\n if (sourceDiscontinuity) frameMeta.sourceDiscontinuity = true;\n\n this.#noteEmittedKeyframe(packetId, frameMeta.keyframe);\n\n // The emitted-sequence high-water mark (#noteEmittedVideoSeq) is recorded\n // at the actual emit inside #emitDirectVideo, not here \u2014 a recovered frame\n // can be queued ahead of the source cursor, so it must track real emits.\n\n // Route to per-track or legacy callback. Even when an MSE block-assembler\n // is registered, this callback still fires \u2014 downstream consumers\n // (`mc.onVideoFrame` \u2192 takeover detection, multicast frame counter,\n // `confirmVideo`/`confirmAudio`) need every reassembled frame. The\n // SourceBuffer-write side of this callback path is suppressed inside\n // `MseBackend.feedVideo`/`feedAudio` when the assembler owns the track,\n // which is configured via `MseBackend.setAssemblerOwns('video' | 'audio')`.\n // Prevents the dual-CMAF-timeline collision (BLO-3035 layer 3).\n const cb = this.#getFrameCallback(packetId);\n if (isVideo && !assembler) {\n if (frameMeta.recovered) {\n this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb);\n } else {\n this.#routeDirectSourceVideo(packetId, data, frameMeta, cb);\n }\n } else if (!isVideo && !assembler) {\n if (frameMeta.recovered) {\n this.#routeDirectRecoveredAudio(packetId, data, frameMeta, cb);\n } else {\n this.#routeDirectSourceAudio(packetId, data, frameMeta, cb);\n }\n } else {\n cb?.(data, frameMeta);\n }\n }\n\n #wireFecLifecycle(\n onBlockFailed:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => void)\n | undefined,\n ): void {\n if (this.#fecManager) {\n this.#fecManager.onBlockStarted = (\n sbn: number,\n packetId: number,\n _catalogTrackName?: string,\n ) => {\n this.#onBlockStarted?.(sbn, packetId);\n };\n\n this.#fecManager.onBlockResolved = (\n _sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n ) => {\n this.#requestFecPoke(packetId, catalogTrackName);\n };\n this.#fecManager.onSourceDiscontinuity = (\n packetId: number,\n catalogTrackName: string | undefined,\n ) => {\n // Mark now, drain through the same deferral as every other gate\n // wake: this fires from inside FECManager.trackBlock, and a\n // synchronous drain would re-enter FECManager (noteReleasedRap) and\n // the downstream sink mid-update.\n this.#parser.markFecDiscontinuity(packetId, catalogTrackName);\n this.#requestFecPoke(packetId, catalogTrackName);\n };\n\n this.#fecManager.onBlockComplete = (\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ) => {\n this.#mseAssemblerFor(packetId, catalogTrackName)?.completeBlock(sbn);\n // Phase 14 D-26: fan-out to delta callback when delta-role packetId completes.\n if (\n this.#isCatalogTrackUnambiguous(packetId, catalogTrackName) &&\n this.#packetIdRoles.get(packetId) === \"delta\"\n ) {\n this.#onDeltaBlockCompleteCb?.(packetId, false); // failed=false\n }\n };\n\n this.#fecManager.onBlockFailed = (\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => {\n this.#mseAssemblerFor(packetId, catalogTrackName)?.failBlock(sbn);\n if (\n this.#isCatalogTrackUnambiguous(packetId, catalogTrackName) &&\n this.#packetIdRoles.get(packetId) === \"delta\"\n ) {\n this.#onDeltaBlockCompleteCb?.(packetId, true); // failed=true\n }\n onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);\n this.#onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);\n };\n }\n }\n\n // \u2500\u2500 Accessors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n get fecManager(): FECManagerLike | null {\n return this.#fecManager;\n }\n\n /**\n * How long this router keeps a packet ID's duplicate ledgers, derived from\n * the FEC manager's geometry for it (see duplicate-ledger.ts), or why no\n * horizon is derivable (the stream then keeps count-bounded ledgers, or none\n * without an FEC manager). A stream reads this when it moves to a new FEC\n * block, so a catalog change applies from the next block.\n */\n duplicateHorizonFor(packetId: number): DuplicateHorizon {\n const fec = this.#fecManager;\n if (!fec) return NO_FEC_MANAGER_HORIZON;\n if (!fec.interleaveDepthMsForPacket || !fec.blockLifetimeMsForPacket) {\n return NO_FEC_GEOMETRY_HORIZON;\n }\n const blockSpanMs = fec.interleaveDepthMsForPacket(packetId);\n // FECManager reports 0 for a stream the catalog gave no block span.\n if (blockSpanMs === 0) return NO_BLOCK_SPAN_HORIZON;\n return {\n kind: \"fec-horizon\",\n retentionMs: duplicateRetentionMs({\n blockSpanMs,\n blockLifetimeMs: fec.blockLifetimeMsForPacket(packetId),\n }),\n };\n }\n\n /**\n * Init segments delivered for a packetId with no `onTrackInit` handler\n * since construction, whether the deprecated aggregate `onInitSegment`\n * callback then received them or nothing did. A supported deployment reads\n * 0 here; a non-zero value is the evidence that blocks removing the\n * aggregate callback (MOQT-01, BLO-21181).\n */\n get unmappedInitDeliveryCount(): number {\n return this.#unmappedInitDeliveries;\n }\n\n /** Publisher restarts detected on the multicast leg since construction. */\n get publisherRestartCount(): number {\n return this.#publisherRestarts;\n }\n\n /**\n * Packets whose Source or Repair FEC Payload ID was left unread because\n * their (owner) packetId had no configured `fec.blockProfile`: FEC not\n * configured yet for a track whose packets already flow, or a track whose\n * catalog FEC was unusable. Such a source packet's media is routed as on a\n * track without FEC; such a repair packet is dropped. Neither is read as a\n * block coordinate under a guessed profile, and neither touches FEC state.\n */\n get fecPayloadIdsWithoutProfile(): number {\n return this.#fecPayloadIdsWithoutProfile;\n }\n\n get reassembler(): MfuReassemblerBackend {\n return this.#parser.reassembler;\n }\n\n getDiagnostics(): Record<number, ParserDiagnostics> {\n const out: Record<number, ParserDiagnostics> = {};\n // The reassembler is shared by every stream; its occupancy is one reading\n // taken now, not a per-packet snapshot (BLO-36599).\n const status = this.#parserDiagnostics.size > 0\n ? this.#parser.reassembler.getStatus()\n : null;\n for (const [key, diag] of this.#parserDiagnostics) {\n if (status) {\n diag.pendingBuffers = status.bufferedMFUs;\n diag.pendingFragments = status.pendingFragments;\n diag.pendingSamples = status.pendingSamples;\n }\n const packetId = this.#packetIdFromStreamKey(key);\n const existing = out[packetId];\n if (!existing) {\n out[packetId] = { ...diag };\n continue;\n }\n const latest =\n diag.lastSequenceNumber >= existing.lastSequenceNumber ? diag : existing;\n out[packetId] = {\n packets: existing.packets + diag.packets,\n frames: existing.frames + diag.frames,\n initSegments: existing.initSegments + diag.initSegments,\n noFragment: existing.noFragment + diag.noFragment,\n fragments: existing.fragments + diag.fragments,\n reassembled: existing.reassembled + diag.reassembled,\n pendingBuffers: existing.pendingBuffers + diag.pendingBuffers,\n pendingFragments: existing.pendingFragments + diag.pendingFragments,\n directAudioSourceEmitted:\n existing.directAudioSourceEmitted + diag.directAudioSourceEmitted,\n directAudioSourceRegressions:\n existing.directAudioSourceRegressions + diag.directAudioSourceRegressions,\n directAudioRecoveredQueued:\n existing.directAudioRecoveredQueued + diag.directAudioRecoveredQueued,\n directAudioRecoveredDrained:\n existing.directAudioRecoveredDrained + diag.directAudioRecoveredDrained,\n directAudioRecoveredStaleDropped:\n existing.directAudioRecoveredStaleDropped + diag.directAudioRecoveredStaleDropped,\n pendingSamples: [\n ...((existing.pendingSamples as unknown[]) ?? []),\n ...((diag.pendingSamples as unknown[]) ?? []),\n ],\n lastPacketLength: latest.lastPacketLength,\n lastSequenceNumber: latest.lastSequenceNumber,\n ...(latest.lastFecSsId === undefined ? {} : { lastFecSsId: latest.lastFecSsId }),\n ...(latest.lastFrameTimestamp === undefined\n ? {}\n : { lastFrameTimestamp: latest.lastFrameTimestamp }),\n };\n }\n return out;\n }\n\n // \u2500\u2500 Track configuration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Register callback for keyframe loss detection.\n * Fires when no complete keyframe is emitted within keyframeLostTimeoutMs.\n * Argument: next expected groupId (lastKeyframeGroupId + 1). */\n onKeyframeLost(cb: (expectedGroupId: number) => void): void {\n this.#onKeyframeLost = cb;\n }\n\n /**\n * Set the keyframe loss timeout after construction.\n * Must be called BEFORE setPacketIdType('video').\n * A fixed timeout derives from explicit keyframe/group duration:\n * Math.ceil(groupDurationMs * KEYFRAME_LOST_TIMEOUT_SLACK).\n * A timeout whose inputs are measured after configuration (e.g. keyframe\n * cadence plus measured playout lateness) is passed as a reader, evaluated\n * each time the timer is armed. Both forms pass the same check at each arm:\n * a throw, or a value that is not finite, positive and within the timer\n * maximum, leaves that keyframe unarmed (fail closed per keyframe, not per\n * session). A fixed NaN, e.g. derived from a catalog without a group\n * duration, therefore never reaches setTimeout.\n */\n setKeyframeLostTimeoutMs(ms: number | (() => number)): void {\n this.#keyframeLostTimeoutMs = ms;\n this.#keyframeLostUnarmedReason = null;\n }\n\n /** Update MFU reassembly timeout. Source and FEC-recovery paths share the\n * parser's single reassembler (BLO-8151), so one update covers both. */\n setMfuTimeoutMs(ms: number): void {\n if (ms <= 0) throw new Error(\"[MmtpRouter] mfuTimeoutMs must be > 0\");\n this.#parser.setMfuTimeoutMs(ms);\n }\n\n /** Update parser frame reordering for unordered multicast delivery.\n * Callers derive this from the catalog FEC block span (\u00A75.1 \u2014 the\n * interleave value IS the span): positive round-half-up of span / frameMs. */\n setReorderWindow(windowSize: number): void {\n this.#parser.setReorderWindow(windowSize);\n }\n\n /** Register MMTP packetId \u2192 track type mapping.\n * Call BEFORE packets arrive to ensure correct routing from the first packet.\n * If keyframeLostTimeoutMs is not provided, Phase 13 keyframe repair is\n * disabled (video still routes/decodes normally). framerate is OPTIONAL per\n * draft-ietf-moq-catalogformat-01 \u00A73.2.23, so we can't require the timeout. */\n setPacketIdType(packetId: number, trackType: \"video\" | \"audio\"): void {\n if (trackType === \"video\" && this.#keyframeLostTimeoutMs === null) {\n console.warn(\n \"[MmtpRouter] keyframeLostTimeoutMs not configured \u2014 Phase 13 keyframe \" +\n \"repair disabled for this track. Set it via setKeyframeLostTimeoutMs() \" +\n \"or provide in constructor config to enable: \" +\n \"Math.ceil(groupDurationMs * KEYFRAME_LOST_TIMEOUT_SLACK).\",\n );\n }\n if (trackType === \"video\") this.#videoPacketIds.add(packetId);\n else this.#audioPacketIds.add(packetId);\n // Sync track info to parser for codec-aware keyframe detection.\n const codec = this.#trackCodecs.get(packetId) ?? \"\";\n this.#parser.setTrack(packetId, { type: trackType, codec });\n }\n\n /** Media kind registered for a packetId, if any. */\n trackKindFor(packetId: number): \"video\" | \"audio\" | undefined {\n const classification = this.#classifyPacketId(packetId);\n return classification === null ? undefined : classification ? \"video\" : \"audio\";\n }\n\n /** Register codec string for a packetId \u2014 enables codec-aware keyframe detection.\n * Without this, only H.264 IDR (NAL type 5) is detected as keyframe fallback.\n * AV1 (OBU_SEQUENCE_HEADER) and VP9 (frame_type bit) require the codec string. */\n setTrackCodec(packetId: number, codec: string): void {\n this.#trackCodecs.set(packetId, codec);\n // Sync to parser. Determine track type from existing registration.\n const trackType = this.#videoPacketIds.has(packetId)\n ? (\"video\" as const)\n : this.#audioPacketIds.has(packetId)\n ? (\"audio\" as const)\n : null;\n if (trackType) {\n this.#parser.setTrack(packetId, { type: trackType, codec });\n }\n }\n\n // \u2500\u2500 Frame callbacks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** @deprecated Use onTrackFrame(packetId, cb) for per-track routing */\n onVideoFrame(cb: FrameCallback): void {\n this.#onVideoFrame = cb;\n this.#resolvedCallbacks.clear();\n }\n\n /** @deprecated Use onTrackFrame(packetId, cb) for per-track routing */\n onAudioFrame(cb: FrameCallback): void {\n this.#onAudioFrame = cb;\n this.#resolvedCallbacks.clear();\n }\n\n /** Register a frame callback for a specific MMTP packetId (track).\n * When a legacy callback (onVideoFrame/onAudioFrame) also exists for this\n * packetId's type, both fire: per-track first, then legacy. This ensures\n * element.ts wiring (confirmAudio, feedMulticast) continues working when\n * TrackPipeline registers per-track sources. */\n onTrackFrame(\n packetId: number,\n cb: FrameCallback,\n ): void {\n this.#trackCallbacks.set(packetId, cb);\n this.#resolvedCallbacks.clear();\n }\n\n /** Register a raw MMTP source-packet callback for one track.\n *\n * Direct packets retain their original bytes, including a Source FEC Payload\n * ID when present. FEC-recovered packets are the recovered source-symbol\n * bytes and set `skipFecPayloadId` because that trailer is not part of the\n * protected symbol. Repair packets are never delivered here.\n */\n onTrackPacket(packetId: number, cb: TrackPacketCallback): void {\n this.#trackPacketCallbacks.set(packetId, cb);\n }\n\n /**\n * Register an init segment callback (ftyp+moov from MMTP MPU metadata).\n * Receives an init only for a packetId with no `onTrackInit` handler.\n *\n * @deprecated Use onTrackInit(packetId, cb). Still delivered as a fallback;\n * removed in the 1.0.0 major, no earlier than one minor release after this\n * deprecation reaches the `latest` dist-tag (BLO-21181). Count fallback\n * deliveries with `unmappedInitDeliveryCount`.\n */\n onInitSegment(cb: (data: Uint8Array) => void): void {\n this.#onInitSegment = cb;\n }\n\n /** Register an init segment callback for a specific MMTP packetId.\n * Called when FT=0 init arrives via direct source or FEC recovery. */\n onTrackInit(packetId: number, cb: (data: Uint8Array) => void): void {\n this.#trackInitCallbacks.set(packetId, cb);\n }\n\n // \u2500\u2500 MSE integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Create a CMAF block assembler for MSE mode on a specific track.\n * When active, source frames are buffered per FEC block and emitted as\n * multi-sample CMAF segments instead of per-frame callbacks. */\n enableMseAssembler(\n packetId: number,\n config: Omit<BlockAssemblerConfig, \"k\">,\n catalogTrackName?: string,\n ): CmafBlockAssembler {\n const k = this.#kForPacket(packetId);\n const assemblerKey = this.#mseAssemblerKey(packetId, catalogTrackName);\n const previous = this.#mseAssemblers.get(assemblerKey);\n if (previous) {\n previous.flush();\n previous.dispose();\n }\n const assembler = new CmafBlockAssembler({ ...config, k });\n this.#mseAssemblers.set(assemblerKey, assembler);\n console.log(\n `[MmtpRouter] MSE assembler enabled for packetId=${packetId}${catalogTrackName === undefined ? \"\" : ` track=${catalogTrackName}`} (K=${k}, ${config.trackType})`,\n );\n return assembler;\n }\n\n /** Register a segment callback for a track's MSE assembler.\n * Called with complete moof+mdat CMAF segments on block completion. */\n onMseSegment(packetId: number, cb: (segment: Uint8Array) => void, catalogTrackName?: string): void {\n const assembler = this.#mseAssemblers.get(this.#mseAssemblerKey(packetId, catalogTrackName));\n if (assembler) assembler.onSegment = cb;\n }\n\n /** Register a raw-sample callback for a track's MSE assembler.\n * Use this when the caller already owns the fMP4/MSE timeline and only wants\n * FEC recovery, keyframe gating, and dedup from the assembler. */\n onMseSample(packetId: number, cb: (sample: CmafBlockSample) => void, catalogTrackName?: string): void {\n const assembler = this.#mseAssemblers.get(this.#mseAssemblerKey(packetId, catalogTrackName));\n if (assembler) assembler.onSample = cb;\n }\n\n /** Register an init segment callback for a track's MSE assembler. */\n onMseInitSegment(packetId: number, cb: (init: Uint8Array) => void, catalogTrackName?: string): void {\n const assembler = this.#mseAssemblers.get(this.#mseAssemblerKey(packetId, catalogTrackName));\n if (assembler) assembler.onInitSegment = cb;\n }\n\n /** Build and emit init segment for a track's MSE assembler from catalog config. */\n emitMseInit(packetId: number, catalogTrackName?: string): void {\n this.#mseAssemblers.get(this.#mseAssemblerKey(packetId, catalogTrackName))?.emitInit();\n }\n\n /** Mark a video track as synchronized by an external path.\n * This opens the MMTP/FEC pre-sync gates without waiting for a multicast\n * keyframe, for MSE timelines already seeded by MoQ with the same track. */\n markExternalVideoSync(packetId?: number, catalogTrackName?: string): void {\n const packetIds =\n packetId === undefined\n ? [...this.#videoPacketIds]\n : this.#videoPacketIds.has(packetId)\n ? [packetId]\n : [];\n for (const id of packetIds) {\n const streamKey = this.#mediaStreamKey(id, catalogTrackName);\n this.#trackSynced.add(streamKey);\n this.#droppedPreSyncVideo.delete(streamKey);\n this.#mseAssemblerFor(id, catalogTrackName)?.markVideoSynced();\n }\n }\n\n // \u2500\u2500 FEC event handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Register callback for FEC blocks when the first source symbol arrives. */\n onFecBlockStarted(cb: (sbn: number, packetId: number) => void): void {\n this.#onBlockStarted = cb;\n }\n\n /** Register a diagnostic callback for FEC blocks that fail. A failure is\n * not a decoder instruction: act on `RoutedFrameMeta.gapBefore`. */\n onFecBlockFailed(\n cb: (\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => void,\n ): void {\n this.#onBlockFailed = cb;\n }\n\n // \u2500\u2500 Phase 14: per-role packetId dispatch (D-26, D-27, D-28) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Register a TrackRole for a specific packetId.\n *\n * D-26: Enables per-role block-complete fanout \u2014 delta-role packetIds fire\n * onDeltaBlockComplete when their FEC block completes or fails.\n * Composite callback preserved: legacy onVideoFrame/onAudioFrame still fire\n * (MEMORY.md \"prevents callback stealing\").\n *\n * Valid roles: 'base' | 'delta' | 'repair' | 'single'.\n * Throws on unknown role (error on bad init, CLAUDE.md).\n */\n setPacketIdRole(packetId: number, role: TrackRole): void {\n const VALID_ROLES: readonly TrackRole[] = [\n \"base\",\n \"delta\",\n \"repair\",\n \"single\",\n ];\n if (!VALID_ROLES.includes(role)) {\n throw new Error(\n `[MmtpRouter] setPacketIdRole: unknown role '${role}'. Valid: ${VALID_ROLES.join(\", \")}`,\n );\n }\n this.#packetIdRoles.set(packetId, role);\n }\n\n /** Replace catalog-derived repair ownership before a catalog reconfiguration. */\n clearRepairPacketIdOwners(): void {\n this.#repairOwnersByPacketId.clear();\n }\n\n /** Bind one dedicated repair-track PID to its authoritative media PID. */\n setRepairPacketIdOwner(repairPacketId: number, ownerPacketId: number): void {\n const owners = this.#repairOwnersByPacketId.get(repairPacketId) ?? new Set<number>();\n owners.add(ownerPacketId);\n this.#repairOwnersByPacketId.set(repairPacketId, owners);\n }\n\n /**\n * Register callback fired when a delta-track FEC block completes or fails.\n *\n * @param cb - (packetId, failed) \u2014 packetId identifies the delta track,\n * failed=false means clean/recovered, failed=true means unrecoverable.\n *\n * Called from both onBlockComplete (failed=false) and onBlockFailed (failed=true)\n * for every packetId registered with role='delta' via setPacketIdRole().\n */\n onDeltaBlockComplete(cb: (packetId: number, failed: boolean) => void): void {\n this.#onDeltaBlockCompleteCb = cb;\n }\n\n // \u2500\u2500 Packet processing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Update framerate \u2014 delegates to FECManager for timeout refinement. */\n setFramerate(fps: number): void {\n this.#fecManager?.setFramerate(fps);\n }\n\n /** Set FEC block mapper (created when FEC config becomes known via configureFec). */\n setFecMapper(mapper: FecBlockMapper, packetId?: number): void {\n if (packetId == null) {\n this.#defaultFecMapper = mapper;\n return;\n }\n this.#fecMappersByPacketId.set(packetId, mapper);\n }\n\n /**\n * Release the router side of a packetId whose FEC is ending: drop its block\n * mapper and end its FEC release gating. Frames the gate holds are released\n * in order, each gap stalled (no block will resolve it), not stranded. Call\n * before FECManager.releasePacket, while the gate can still read its blocks.\n */\n releaseFecPacket(packetId: number): void {\n this.#fecMappersByPacketId.delete(packetId);\n // Released frames reach FECManager (noteReleasedRap): defer the pokes it\n // requests, as for every other drain, and drop this packet's.\n this.#withDeferredFecPokes(() => {\n this.#parser.endFecRelease(packetId);\n for (const [key, poke] of this.#pendingFecPokes) {\n if (poke.packetId === packetId) this.#pendingFecPokes.delete(key);\n }\n });\n }\n\n registerManifestTrack(context: ManifestTrackContext): void {\n if (!this.#manifestVerifier) throw new Error(\"manifest verifier is not configured\");\n this.#manifestVerifier.registerTrack(context);\n }\n\n feedManifestFrame(trackName: string, frame: Uint8Array): void {\n this.#manifestVerifier?.ingestManifestFrame(trackName, frame);\n }\n\n setManifestAuthMode(mode: ManifestAuthMode): void {\n this.#manifestVerifier?.setMode(mode);\n if (mode !== \"enforce\") {\n for (const queue of this.#manifestRecoveryQueues.values()) {\n for (const item of queue) {\n this.#routeProcessedFecSymbols(\n item.blockId,\n item.result,\n item.ownerPacketId,\n item.catalogTrackName,\n );\n }\n }\n this.#manifestRecoveryQueues.clear();\n }\n }\n\n /**\n * Main entry point: feed a raw MMTP packet for processing.\n *\n * Source packets are played immediately AND fed to FECManager for recovery.\n * Repair packets are fed to FECManager only (no direct playback).\n */\n /**\n * Feed one MMTP source packet.\n *\n * `source` is REQUIRED. It used to default to \"multicast\", which meant any\n * caller that forgot it silently attributed its bytes to a multicast socket\n * they may never have crossed \u2014 and that attribution is what the physical-\n * delivery evidence path is built on. Absence is not multicast; say which.\n */\n feedPacket(\n packet: Uint8Array,\n source: \"multicast\" | \"moq\",\n manifestIdentity?: ManifestObjectIdentity,\n ingress?: MmtpIngressContext,\n ): void {\n if (this.#disposed) return;\n this.#withDeferredFecPokes(() => {\n this.#processMMTP(packet, source, manifestIdentity, false, ingress);\n });\n }\n\n /**\n * Feed a reliable MoQ duplicate of a source packet that the multicast leg\n * also carries, during make-before-break overlap.\n *\n * The copy is parsed for playback (attributed to \"moq\") so a pending\n * multicast decoder can warm. For an FEC-tagged packet the router keeps a\n * per-symbol (SBN/ESI) parse ledger, so whichever of it and its multicast\n * twin arrives first is parsed and the second is not. The MFU reassembler\n * cannot be that dedup: complete (FI=0) MFUs bypass its #emitted set.\n *\n * The ledger exists only where a symbol coordinate does: a packet with no\n * FEC payload ID, or a router with no FEC manager, has none, so it and its\n * twin are BOTH parsed. On an audio track that delivers the sample twice\n * (#emitDirectSourceAudio counts the regression and still emits). The\n * intended deployment is safe because the multicast leg is FEC-protected,\n * so its media packets carry the ID; the untagged timing packets\n * (init/moof) take this path too, where a re-parse is benign. Do not feed\n * untagged media through here.\n *\n * The copy never enters FEC\n * accounting: no `feedSymbolFromSource`, `trackBlock` or\n * `feedSourceSymbol`. Block completion and the repair deadline therefore\n * follow the multicast leg alone, and a multicast erasure still starves its\n * block and requests repair. Feeding the copy through `feedPacket` instead\n * would let it close the block, hiding exactly the loss repair exists for.\n *\n * MoQ unicast fallback is not an overlap copy: it is the only source, so it\n * uses `feedPacket(packet, \"moq\")` and counts toward completion.\n */\n feedOverlapCopy(packet: Uint8Array, ingress?: MmtpIngressContext): void {\n if (this.#disposed) return;\n this.#withDeferredFecPokes(() => {\n this.#processMMTP(packet, \"moq\", undefined, false, ingress, true);\n });\n }\n\n /**\n * Feed a repair packet from MoQ (reliable QUIC) \u2014 parses MMTP repair\n * header and delegates to FECManager for FEC recovery.\n */\n feedRepairPacket(\n packet: Uint8Array,\n ownerPacketIdHint?: number,\n catalogTrackName?: string,\n ): void {\n if (this.#disposed) return;\n this.#withDeferredFecPokes(() => {\n this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName);\n });\n }\n\n #feedRepairPacket(\n packet: Uint8Array,\n ownerPacketIdHint: number | undefined,\n catalogTrackName: string | undefined,\n ): void {\n // One header parse per packet, as on the multicast repair branch: mmt-fec's\n // parseRepairPacket() parsed it once more before this parse (BLO-36599).\n let header: MMTPHeader;\n try {\n header = parseMMTPHeader(packet);\n } catch {\n return;\n }\n const parsed = parseMoqRepairFrameWithHeader(packet, header);\n if (!parsed) return;\n\n // A dedicated repair track can carry a packet ID that differs from the\n // media packet ID. The subscribing track supplies the authoritative owner.\n const repairPacketId = header.packetId;\n\n const hintedOwner =\n ownerPacketIdHint != null && this.#classifyPacketId(ownerPacketIdHint) !== null\n ? ownerPacketIdHint\n : null;\n // A MoQ repair subscription owns a media track explicitly. Dedicated\n // repair PIDs must carry that hint; only same-PID repair is self-describing.\n const ownerPacketId = hintedOwner ??\n (this.#classifyPacketId(repairPacketId) !== null ? repairPacketId : null);\n if (ownerPacketId === null) {\n debugRouter(\n `[MmtpRouter] Dropping MoQ repair packet pid=${repairPacketId}: owner hint is required`,\n );\n }\n\n if (ownerPacketId != null && this.#fecManager) {\n this.#moqRepairPidCounts[repairPacketId] =\n (this.#moqRepairPidCounts[repairPacketId] || 0) + 1;\n this.#moqRepairOwnerCounts[ownerPacketId] =\n (this.#moqRepairOwnerCounts[ownerPacketId] || 0) + 1;\n const pidCount = this.#moqRepairPidCounts[repairPacketId]!;\n const ownerCount = this.#moqRepairOwnerCounts[ownerPacketId]!;\n if (pidCount <= 2 || pidCount % 500 === 0) {\n console.log(\n `[MOQ-REPAIR] pid=${repairPacketId} repairOwner=${ownerPacketId} pidCount=${pidCount} ownerCount=${ownerCount} ssbLength=${parsed.ssbLength} len=${packet.length}`,\n );\n }\n\n if (this.#configuredBlockProfile(ownerPacketId) === undefined) {\n this.#noteUnreadableFecPayloadId(ownerPacketId, \"repair\");\n return;\n }\n this.#handleIsoRepair(ownerPacketId, catalogTrackName, parsed, \"moq\");\n }\n }\n\n // \u2500\u2500 Packet FEC geometry and block profile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #kForPacket(packetId?: number): number {\n // Once the packet ID is known, only its explicitly registered mapper may\n // stand in for the FEC manager's per-packet K \u2014 never the default\n // single-track mapper, whose K belongs to another track.\n if (packetId != null)\n return (\n this.#fecManager?.kForPacket?.(packetId) ??\n this.#fecMappersByPacketId.get(packetId)?.k ??\n this.#fecManager?.k ??\n 4\n );\n return this.#defaultFecMapper?.k ?? this.#fecManager?.k ?? 4;\n }\n\n #symbolSizeForPacket(packetId?: number): number {\n if (packetId != null) return this.#fecManager?.symbolSizeForPacket?.(packetId) ?? this.#fecManager?.symbolSize ?? 0;\n return this.#fecManager?.symbolSize ?? 0;\n }\n\n /**\n * The configured block profile of a packetId, or undefined while it is\n * unconfigured. With an FEC manager its per-packet configuration is the\n * only source. A router without one still recovers symbols through its\n * registered mapper, built with the catalog's explicit profile: the\n * packet's own mapper, else the default one. A NullBlockMapper (a stream\n * without an FEC instance) states none.\n */\n #configuredBlockProfile(packetId: number): FecBlockProfile | undefined {\n if (this.#fecManager) return this.#fecManager.blockProfileForPacket(packetId);\n const mapper = this.#fecMappersByPacketId.get(packetId) ?? this.#defaultFecMapper;\n return mapper?.blockProfile ?? undefined;\n }\n\n /**\n * The block profile an FEC coordinate of `packetId` is read under:\n * `iso-ssbg1-v1`, the only one (`k-padded-v1` is retired and refused).\n * Throws while none is configured: such a packet has no FEC geometry, and\n * nothing is inferred from the wire or defaulted.\n * Packet ingress leaves such a packet's FEC Payload ID unread first\n * (#noteUnreadableFecPayloadId); a throw here is a wiring fault on a path\n * that cannot hold an unconfigured packet.\n */\n #blockProfileFor(packetId: number): FecBlockProfile {\n const profile = this.#configuredBlockProfile(packetId);\n if (profile === undefined) {\n throw new Error(\n `[MmtpRouter] packetId=${packetId} carries an FEC payload ID but has no configured ` +\n \"fec.blockProfile; configure the track's FEC from its catalog before routing its packets\",\n );\n }\n return profile;\n }\n\n /**\n * The block coordinates (SS_Start, ESI) of an FEC source: range membership\n * in a block a repair already stated (ISO/IEC 23008-1 C.5.3). Null while no\n * repair has stated its block, while its packetId has no configured block\n * profile, and without an FEC manager: SS_ID alone locates no block, so\n * nothing is derived from it.\n */\n #locateFecSource(\n packetId: number,\n catalogTrackName: string | undefined,\n ssId: number,\n ): { sbn: number; esi: number } | null {\n if (this.#configuredBlockProfile(packetId) === undefined) return null;\n return this.#fecManager?.locateIsoSource?.(packetId, catalogTrackName, ssId) ?? null;\n }\n\n /**\n * Explicit refusal to read an FEC Payload ID: its (owner) packetId has no\n * configured block profile. Ordering is the host's to guarantee (configure\n * a track's FEC before its packets are routed). Until then a source packet\n * plays as on a track without FEC and a repair packet is dropped; neither\n * is read under a guessed profile or seeds FEC state. Counted and warned.\n */\n #noteUnreadableFecPayloadId(packetId: number, kind: \"source\" | \"repair\"): void {\n this.#fecPayloadIdsWithoutProfile++;\n const count = this.#fecPayloadIdsWithoutProfile;\n if (count <= 5 || count % 500 === 0) {\n console.warn(\n `[MmtpRouter] packetId=${packetId} has no configured fec.blockProfile: ` +\n (kind === \"source\"\n ? \"its FEC payload ID is not read and its media is routed without FEC\"\n : \"dropping its repair packet\") +\n ` (${count} so far)`,\n );\n }\n }\n\n /**\n * The release-gate anchor of a recovered symbol: its own SS_ID, SS_Start +\n * ESI modulo 2^32 (C.5.2), where a frame is gated by the SS_IDs between it\n * and the last released frame.\n */\n #fecAnchorForRecovered(blockStart: number, index: number): number {\n return serial32Add(blockStart, index);\n }\n\n /**\n * One iso-ssbg1-v1 repair symbol (ISO/IEC 23008-1 C.5.3). It states the\n * block `[SS_Start, SS_Start + SSB_length)`; repair ESI = SSB_length +\n * RS_ID (K' = SSB_length). The held sources it placed (and those of a\n * repair-less span in front of it) are fed as first-arrival sources, then\n * the repair, the order they had on the wire, so a loss-free block completes\n * clean on its repair. A repair contradicting a known block is dropped.\n */\n #handleIsoRepair(\n ownerPacketId: number,\n catalogTrackName: string | undefined,\n repair: { ssStart: number; ssbLength: number; rsId: number; symbolData: Uint8Array },\n source: \"multicast\" | \"moq\",\n ): void {\n const fec = this.#fecManager;\n if (!fec?.placeIsoRepair) {\n throw new Error(\n `[MmtpRouter] packetId=${ownerPacketId} is iso-ssbg1-v1 but its FEC manager cannot place ISO blocks`,\n );\n }\n const placement = fec.placeIsoRepair(ownerPacketId, catalogTrackName, repair.ssStart, repair.ssbLength);\n if (placement.status !== \"placed\") return;\n const blockStart = placement.block.start;\n if (placement.span !== undefined) {\n this.#replayIsoSources(ownerPacketId, catalogTrackName, placement.span.block.start, placement.span.placed);\n }\n this.#replayIsoSources(ownerPacketId, catalogTrackName, blockStart, placement.placed);\n if (this.#classifyPacketId(ownerPacketId) === null) return;\n if (!this.#feedFecTransportSymbol(\n ownerPacketId,\n blockStart,\n repair.ssbLength + repair.rsId,\n repair.symbolData,\n source,\n catalogTrackName,\n )) return;\n fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);\n const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);\n const recovered = fec.feedRepairSymbol(\n ownerPacketId,\n repair.ssStart,\n repair.ssbLength,\n repair.rsId,\n repair.symbolData,\n catalogTrackName,\n );\n if (recovered && !alreadyCompleted) {\n fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);\n fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);\n fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);\n this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName);\n }\n }\n\n /**\n * Feed iso-ssbg1-v1 sources a repair just placed, in SS_ID order, exactly as\n * a first-arrival source is fed once its block is known: transport\n * accounting, block tracking with its own classification evidence, then the\n * decoder. Their media was routed when they arrived.\n */\n #replayIsoSources(\n ownerPacketId: number,\n catalogTrackName: string | undefined,\n blockStart: number,\n placed: readonly IsoPlacedSource<IsoHeldSource>[],\n ): void {\n const fec = this.#fecManager;\n if (!fec) return;\n for (const { ssId, esi, payload } of placed) {\n if (this.#classifyPacketId(ownerPacketId) === null) return;\n if (!this.#feedFecTransportSymbol(\n ownerPacketId,\n blockStart,\n esi,\n payload.symbolData,\n payload.source,\n catalogTrackName,\n )) continue;\n if (fec.isTrackConfigured?.(ownerPacketId) !== false) {\n fec.trackBlock(blockStart, ownerPacketId, catalogTrackName, esi, payload.evidence);\n this.#requestFecPoke(ownerPacketId, catalogTrackName);\n }\n if (payload.oversized) continue;\n const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);\n const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, undefined, catalogTrackName);\n if (!recovered || alreadyCompleted) continue;\n fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);\n fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);\n fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);\n this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName);\n }\n }\n\n #resolveRepairOwnerPacketId(\n packetId: number,\n ingress?: MmtpIngressContext,\n ): number | null {\n if (this.#classifyPacketId(packetId) !== null) return packetId;\n\n if (this.#packetIdRoles.get(packetId) !== \"repair\") {\n debugRouter(\n `[MmtpRouter] Dropping repair packet pid=${packetId}: physical PID is unregistered`,\n );\n return null;\n }\n\n const registeredOwners = this.#repairOwnersByPacketId.get(packetId) ?? new Set<number>();\n const owners = ingress === undefined\n ? [...registeredOwners]\n : [...registeredOwners].filter((ownerPacketId) =>\n ingressHasPacketId(ingress, ownerPacketId));\n if (owners.length === 1) return owners[0]!;\n\n debugRouter(\n `[MmtpRouter] Dropping repair packet pid=${packetId}: catalog owner is ${owners.length === 0 ? \"absent\" : \"ambiguous\"}`,\n );\n return null;\n }\n\n /** Route a recovered FEC block through the parser + dedup pipeline.\n * Sync WASM decoders return the block's K' = SSB_length fixed-width\n * ssbg_mode1 source symbols concatenated. */\n routeRecoveredBlock(\n blockId: number,\n data: Uint8Array,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName);\n }\n\n /** Route a single recovered source symbol through the parser + dedup pipeline.\n * Worker-backed decoders emit one padded source symbol per frame event. */\n routeRecoveredSymbol(\n blockId: number,\n data: Uint8Array,\n packetId: number,\n esi = 0,\n catalogTrackName?: string,\n ): void {\n const blockProfile = this.#blockProfileFor(packetId);\n const result = processFecSymbol(data, { ownerPacketId: packetId, index: esi, blockProfile });\n this.#routeVerifiedFecSymbols(blockId, result, packetId, catalogTrackName);\n }\n\n // \u2500\u2500 Keyframe timer cancellation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Cancel the active keyframe loss timer.\n * Call on failbackToMoq to prevent a stale timer from firing\n * onKeyframeLost after multicast is no longer the active source. */\n cancelKeyframeLostTimer(): void {\n if (this.#keyframeLostTimer) {\n clearTimeout(this.#keyframeLostTimer);\n this.#keyframeLostTimer = null;\n }\n }\n\n // \u2500\u2500 Dispose \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Emit parser frames buffered for a finite input before teardown. */\n flush(): void {\n if (this.#disposed) return;\n this.#parser.flush();\n }\n\n /** Drop one rendition's parser/router/CMAF epoch without touching peers. */\n resetTrack(packetId: number, catalogTrackName: string): void {\n if (this.#disposed) return;\n const staged = this.#staged;\n if (staged !== null) {\n const stream = { packetId, catalogTrackName };\n // A new epoch of either side invalidates what the switch held about it.\n if (isStream(stream, staged.to)) {\n staged.droppedIncomingFrames += staged.held?.length ?? 0;\n staged.held = null;\n staged.heldInit = undefined;\n }\n }\n this.#parser.resetTrack(packetId, catalogTrackName);\n const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);\n this.#trackSynced.delete(streamKey);\n this.#droppedPreSyncVideo.delete(streamKey);\n this.#latestVideoSeqByStream.delete(streamKey);\n this.#lastDirectSourceVideoTsByStream.delete(streamKey);\n this.#lastDirectEmittedVideoTsByStream.delete(streamKey);\n this.#pendingDirectRecoveredVideoByStream.delete(streamKey);\n this.#lastDirectSourceAudioCursorByStream.delete(streamKey);\n this.#lastDirectEmittedAudioCursorByStream.delete(streamKey);\n this.#pendingDirectRecoveredAudioByStream.delete(streamKey);\n this.#manifestRecoveryQueues.delete(streamKey);\n this.#deliveredVideoHighWaterByStream.delete(streamKey);\n this.#stream(packetId, catalogTrackName).duplicates.clear();\n const assemblersToReset = new Set<CmafBlockAssembler>();\n const exactAssembler = this.#mseAssemblers.get(streamKey);\n if (exactAssembler) assemblersToReset.add(exactAssembler);\n const selectedAssembler = this.#mseAssemblerFor(packetId, catalogTrackName);\n if (selectedAssembler) assemblersToReset.add(selectedAssembler);\n for (const assembler of assemblersToReset) assembler.reset();\n this.#trackInitDelivered.delete(streamKey);\n this.#parserDiagnostics.delete(streamKey);\n }\n\n /**\n * Retire one packet ID, drop-only: remove every registration of it (media\n * kind, codec, frame/packet/init callbacks, FEC mapper, role, repair\n * ownership) and every stream state kept for it (sync gates, emission\n * cursors, duplicate ledgers, queued recovered frames, MSE assemblers,\n * parser buffers and authored timing, MFU reassembly per catalog stream).\n * Nothing buffered is delivered, as on live dispose. A dedicated repair\n * packet ID that this packet ID alone owned loses its repair role with it.\n *\n * Afterwards the packet ID is unknown to the router: its packets and\n * frames are dropped until it is registered again. FEC state lives in the\n * FEC manager and is released there (FECManager.releasePacket).\n */\n retirePacketId(packetId: number): void {\n if (this.#disposed) return;\n if (!Number.isInteger(packetId) || packetId < 0) {\n throw new Error(`[MmtpRouter] retirePacketId: invalid packetId ${packetId}`);\n }\n const staged = this.#staged;\n if (staged !== null && (staged.from.packetId === packetId || staged.to.packetId === packetId)) {\n throw new Error(\n `[MmtpRouter] retirePacketId(${packetId}): the packet ID is in a staged stream switch; ` +\n \"cancel the switch first\",\n );\n }\n const names = new Set<string>(this.#catalogTrackNamesByPacketId.get(packetId) ?? []);\n const streams = this.#streams.get(packetId);\n for (const [name, stream] of streams ?? []) {\n if (name !== undefined) names.add(name);\n const key = stream.key;\n this.#trackSynced.delete(key);\n this.#droppedPreSyncVideo.delete(key);\n this.#latestVideoSeqByStream.delete(key);\n this.#lastDirectSourceVideoTsByStream.delete(key);\n this.#lastDirectEmittedVideoTsByStream.delete(key);\n this.#pendingDirectRecoveredVideoByStream.delete(key);\n this.#lastDirectSourceAudioCursorByStream.delete(key);\n this.#lastDirectEmittedAudioCursorByStream.delete(key);\n this.#pendingDirectRecoveredAudioByStream.delete(key);\n this.#manifestRecoveryQueues.delete(key);\n this.#trackInitDelivered.delete(key);\n this.#parserDiagnostics.delete(key);\n this.#pendingFecPokes.delete(key);\n this.#deliveredVideoHighWaterByStream.delete(key);\n const assembler = this.#mseAssemblers.get(key);\n if (assembler) {\n assembler.dispose();\n this.#mseAssemblers.delete(key);\n }\n }\n this.#streams.delete(packetId);\n\n this.#videoPacketIds.delete(packetId);\n this.#audioPacketIds.delete(packetId);\n this.#trackCodecs.delete(packetId);\n this.#trackCallbacks.delete(packetId);\n this.#trackPacketCallbacks.delete(packetId);\n this.#resolvedCallbacks.delete(packetId);\n this.#trackInitCallbacks.delete(packetId);\n this.#fecMappersByPacketId.delete(packetId);\n this.#packetIdRoles.delete(packetId);\n this.#catalogTrackNamesByPacketId.delete(packetId);\n this.#repairOwnersByPacketId.delete(packetId);\n delete this.#pidCounts[packetId];\n delete this.#repairPidCounts[packetId];\n delete this.#moqRepairPidCounts[packetId];\n delete this.#moqRepairOwnerCounts[packetId];\n for (const [repairPacketId, owners] of [...this.#repairOwnersByPacketId]) {\n if (!owners.delete(packetId) || owners.size > 0) continue;\n this.#repairOwnersByPacketId.delete(repairPacketId);\n if (this.#packetIdRoles.get(repairPacketId) === \"repair\") {\n this.#packetIdRoles.delete(repairPacketId);\n }\n delete this.#repairPidCounts[repairPacketId];\n delete this.#moqRepairPidCounts[repairPacketId];\n }\n\n this.#parser.removeTrack(packetId, names);\n }\n\n /**\n * Stage an in-place switch from one video stream to another, committed at\n * a RAP of the incoming stream without leaving the multicast group.\n *\n * Until the commit the outgoing stream (`from`) is delivered as usual and\n * the incoming stream (`to`) is not: its packets are parsed and feed FEC,\n * so its first RAP arrives FEC-protected, but none of its frames, and not\n * its init, reach any callback or MSE assembler. Incoming deltas before its\n * first RAP are dropped. When that RAP arrives:\n *\n * - if the outgoing stream already delivered a frame at or past the RAP's\n * presentation time, the switch commits on it (`outgoing-ahead`);\n * - otherwise the RAP and the incoming frames after it are held, and the\n * switch commits when the outgoing stream is about to deliver a frame at\n * or past the RAP (`outgoing-reached-rap`): that outgoing frame, and every\n * later one, is never delivered, so the splice is exact. If the incoming\n * stream reaches its next RAP first, the outgoing stream is a GOP or more\n * behind: the switch commits on the held RAP and delivers the held GOP\n * and then the newer RAP (`outgoing-overdue`), the smallest gap left once\n * the outgoing stream stops. The hold is bounded by the incoming stream's\n * own RAP cadence, so no timer is involved.\n *\n * The commit is one synchronous transition inside the frame path: the\n * outgoing stream's already-admitted frames are released (its queued FEC\n * gap fills only up to the RAP, since recovery can queue a frame ahead of\n * the source cursor; its MSE assembler's ordering queue whole, as\n * CmafBlockAssembler.flush has no bound, so on the MSE path a recovered\n * outgoing frame at or past the RAP can still be emitted), its packet ID\n * is retired\n * (retirePacketId), `onCommit` runs, and then the incoming stream's newest\n * init and its RAP are delivered, followed by any held frames. No outgoing\n * frame is delivered after the commit and no incoming frame before it. If\n * `onCommit` throws, `to` is retired too and none of it is delivered.\n *\n * Both packet IDs must be registered video streams; the commit reads RAPs.\n * Admission (the transport allow-list and ingress tracks) is the caller's:\n * MulticastManager.switchRendition drives both.\n */\n stageStreamSwitch(options: StreamSwitchOptions): StagedStreamSwitch {\n if (this.#disposed) throw new Error(\"[MmtpRouter] stageStreamSwitch on a disposed router\");\n if (this.#staged !== null) {\n throw new Error(\n \"[MmtpRouter] a stream switch is already staged; cancel it before staging another\",\n );\n }\n const { from, to, onCommit } = options;\n assertStreamIdentity(\"from\", from);\n assertStreamIdentity(\"to\", to);\n if (typeof onCommit !== \"function\") {\n throw new Error(\"[MmtpRouter] stageStreamSwitch requires an onCommit callback\");\n }\n if (from.packetId === to.packetId) {\n throw new Error(\n `[MmtpRouter] stream switch ${from.catalogTrackName} -> ${to.catalogTrackName} keeps ` +\n `packet ID ${from.packetId}: an in-place switch remaps packet IDs, so each stream needs its own`,\n );\n }\n for (const stream of [from, to]) {\n if (!this.#videoPacketIds.has(stream.packetId)) {\n throw new Error(\n `[MmtpRouter] stream switch: packet ID ${stream.packetId} (${stream.catalogTrackName}) ` +\n \"is not a registered video stream (setPacketIdType): a switch commits on a video RAP\",\n );\n }\n }\n const state: StreamSwitchState = {\n from: Object.freeze({ packetId: from.packetId, catalogTrackName: from.catalogTrackName }),\n to: Object.freeze({ packetId: to.packetId, catalogTrackName: to.catalogTrackName }),\n onCommit,\n held: null,\n heldInit: undefined,\n droppedIncomingFrames: 0,\n };\n this.#staged = state;\n return {\n cancel: () => {\n if (this.#staged !== state) return false;\n this.#staged = null;\n return true;\n },\n };\n }\n\n /**\n * Apply the staged switch to one parser frame of either side. Returns true\n * when the frame was consumed (held, dropped, or past the splice).\n */\n #interceptSwitchFrame(data: Uint8Array, meta: ParserFrameMeta): boolean {\n const state = this.#staged!;\n if (isStream(meta, state.to)) {\n if (state.held === null) {\n if (!meta.keyframe) {\n state.droppedIncomingFrames++;\n return true;\n }\n const high = this.#deliveredVideoHighWaterByStream.get(\n this.#mediaStreamKey(state.from.packetId, state.from.catalogTrackName),\n );\n if (high !== undefined && comparePresentation(high, meta) >= 0) {\n if (this.#commitStreamSwitch(state, meta, \"outgoing-ahead\")) this.#routeParsedFrame(data, meta);\n return true;\n }\n state.held = [this.#holdSwitchFrame(data, meta)];\n return true;\n }\n if (meta.keyframe) {\n // The outgoing stream never reached the held RAP, so everything it\n // delivered is before it: the held GOP is complete, overlaps nothing,\n // and dropping it would only widen the gap by a whole incoming GOP.\n const held = state.held;\n if (this.#commitStreamSwitch(state, held[0]!.meta, \"outgoing-overdue\")) {\n for (const frame of held) this.#replayHeldSwitchFrame(frame);\n this.#routeParsedFrame(data, meta);\n }\n return true;\n }\n state.held.push(this.#holdSwitchFrame(data, meta));\n return true;\n }\n if (!isStream(meta, state.from)) return false;\n const rap = state.held?.[0];\n if (rap !== undefined && comparePresentation(meta, rap.meta) >= 0) {\n const held = state.held!;\n if (this.#commitStreamSwitch(state, rap.meta, \"outgoing-reached-rap\")) {\n for (const frame of held) this.#replayHeldSwitchFrame(frame);\n }\n return true;\n }\n return false;\n }\n\n #holdSwitchFrame(data: Uint8Array, meta: ParserFrameMeta): HeldSwitchFrame {\n const fecInfo = this.#currentFecInfo;\n return {\n data,\n meta: { ...meta },\n fecInfo: fecInfo === undefined ? undefined : { ...fecInfo },\n };\n }\n\n /**\n * Replay a held incoming frame as if it completed now, under its own FEC\n * coordinates, through the same gate as a parser frame, so a switch staged\n * from inside the commit's onCommit sees it.\n */\n #replayHeldSwitchFrame(frame: HeldSwitchFrame): void {\n if (this.#disposed) return;\n const current = this.#currentFecInfo;\n this.#currentFecInfo = frame.fecInfo;\n try {\n this.#routeParsedFrame(frame.data, frame.meta);\n } finally {\n this.#currentFecInfo = current;\n }\n }\n\n /**\n * The one path from a completed frame into delivery: parser frames, held\n * frames a commit replays, and the frame that triggered a commit all pass\n * a staged switch's interception here.\n */\n #routeParsedFrame(data: Uint8Array, meta: ParserFrameMeta): void {\n // A frame of a packet ID retired while it was in flight has no route\n // left: an in-place switch commits from inside the parser's emission,\n // and the retired stream's remaining frames can still drain from the\n // same call. Nothing registers a packet ID mid-frame, so this drops\n // only frames that lost their registration.\n if (this.#classifyPacketId(meta.packetId) === null) return;\n if (this.#staged !== null && this.#interceptSwitchFrame(data, meta)) return;\n this.#deliverParsedFrame(data, meta);\n }\n\n #commitStreamSwitch(\n state: StreamSwitchState,\n rap: ParserFrameMeta,\n reason: StreamSwitchCommitReason,\n ): boolean {\n this.#staged = null;\n // Release the outgoing stream's queued FEC gap fills that precede the\n // splice before the packet ID is retired, which discards everything else.\n // The queue can hold recovered frames ahead of the source cursor, so it is\n // bounded at the RAP by the same comparison that decides the commit: a\n // queued outgoing frame at or past the RAP is never delivered.\n const outgoingKey = this.#mediaStreamKey(state.from.packetId, state.from.catalogTrackName);\n this.#drainDirectRecoveredVideo(\n outgoingKey,\n state.from.packetId,\n frame => comparePresentation(frame, rap) < 0,\n this.#getFrameCallback(state.from.packetId),\n );\n this.#mseAssemblerFor(state.from.packetId, state.from.catalogTrackName)?.flush();\n const outgoingHighWater = this.#deliveredVideoHighWaterByStream.get(outgoingKey);\n this.retirePacketId(state.from.packetId);\n const commit: StreamSwitchCommit = Object.freeze({\n from: state.from,\n to: state.to,\n rap: Object.freeze({\n ...switchPosition(rap),\n ...(rap.mpuSequenceNumber === undefined ? {} : { mpuSequenceNumber: rap.mpuSequenceNumber }),\n }),\n outgoingHighWater,\n reason,\n droppedIncomingFrames: state.droppedIncomingFrames,\n });\n try {\n state.onCommit(commit);\n } catch (error) {\n // The caller could not take the incoming stream (typically a decoder\n // reconfiguration that threw). `from` is already retired, so there is\n // nothing to fall back to, and delivering `to` would feed a decoder\n // still configured for `from`. Fail closed: retire `to` too, so neither\n // its init nor its held GOP nor anything after reaches a callback.\n console.warn(\"[MmtpRouter] stream switch commit callback failed; retiring the incoming stream\", error);\n if (this.#disposed) return false;\n // onCommit may have staged a new switch; one on `to` cannot outlive it\n // (retirePacketId refuses a staged packet ID).\n const restaged = this.#staged as StreamSwitchState | null;\n if (restaged !== null && (restaged.from.packetId === state.to.packetId || restaged.to.packetId === state.to.packetId)) {\n this.#staged = null;\n }\n this.retirePacketId(state.to.packetId);\n return false;\n }\n if (this.#disposed) return false;\n const init = state.heldInit;\n if (init !== undefined) this.#deliverInit(state.to.packetId, init, state.to.catalogTrackName);\n return true;\n }\n\n /** Live teardown is drop-only. Finite inputs must opt into a final flush. */\n dispose(options: { flush?: boolean } = {}): void {\n if (options.flush) this.flush();\n this.#disposed = true;\n this.#staged = null;\n this.cancelKeyframeLostTimer();\n this.#parser.dispose();\n this.#streams.clear();\n this.#trackPacketCallbacks.clear();\n this.#latestVideoSeqByStream.clear();\n this.#lastDirectSourceVideoTsByStream.clear();\n this.#lastDirectEmittedVideoTsByStream.clear();\n this.#pendingDirectRecoveredVideoByStream.clear();\n this.#lastDirectSourceAudioCursorByStream.clear();\n this.#lastDirectEmittedAudioCursorByStream.clear();\n this.#pendingDirectRecoveredAudioByStream.clear();\n this.#trackSynced.clear();\n this.#droppedPreSyncVideo.clear();\n this.#trackInitDelivered.clear();\n this.#deliveredVideoHighWaterByStream.clear();\n this.#repairPidCounts = {};\n this.#moqRepairPidCounts = {};\n this.#moqRepairOwnerCounts = {};\n this.#parserDiagnostics.clear();\n this.#catalogTrackNamesByPacketId.clear();\n this.#repairOwnersByPacketId.clear();\n if (this.#ownsManifestVerifier) this.#manifestVerifier?.dispose();\n this.#manifestRecoveryQueues.clear();\n for (const assembler of this.#mseAssemblers.values()) assembler.dispose();\n this.#mseAssemblers.clear();\n }\n\n // \u2500\u2500 Private: keyframe loss detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Arm (or re-arm) the keyframe loss timer.\n * Clears any existing timer and sets a new one for keyframeLostTimeoutMs.\n * On timeout, fires onKeyframeLost with the next expected groupId.\n * A reader without a valid value leaves the timer cleared and unarmed. */\n #armKeyframeLostTimer(): void {\n if (this.#keyframeLostTimer) {\n clearTimeout(this.#keyframeLostTimer);\n this.#keyframeLostTimer = null;\n }\n const timeoutMs = this.#readKeyframeLostTimeoutMs();\n if (timeoutMs === null) return;\n const expectedNext = this.#lastKeyframeGroupId + 1;\n this.#keyframeLostTimer = setTimeout(() => {\n this.#keyframeLostTimer = null;\n this.#onKeyframeLost?.(expectedNext);\n }, timeoutMs);\n }\n\n /** The configured number, or the reader's current value; null when the\n * reader throws or either form yields a value no timer can honour. Warns\n * on entering that state or when its cause changes (a different exception,\n * or a different class of bad value), not once per keyframe. */\n #readKeyframeLostTimeoutMs(): number | null {\n const timeout = this.#keyframeLostTimeoutMs!;\n let reason: string;\n let detail: unknown;\n try {\n const ms = typeof timeout === \"function\" ? timeout() : timeout;\n if (Number.isFinite(ms) && ms > 0 && ms <= MAX_TIMER_DELAY_MS) {\n this.#keyframeLostUnarmedReason = null;\n return ms;\n }\n reason =\n typeof ms !== \"number\" ? \"yielded a non-number\"\n : Number.isNaN(ms) ? \"yielded NaN\"\n : !Number.isFinite(ms) ? \"yielded an infinite value\"\n : ms <= 0 ? \"yielded a non-positive value\"\n : `exceeds the ${MAX_TIMER_DELAY_MS} ms timer maximum`;\n detail = ms;\n } catch (error) {\n // Keyed on the cause, so a real reader bug (say a TypeError) is still\n // reported after the expected \"unmeasured\" throw.\n reason = `threw ${error instanceof Error ? `${error.name}: ${error.message}` : String(error)}`;\n detail = error;\n }\n if (reason !== this.#keyframeLostUnarmedReason) {\n this.#keyframeLostUnarmedReason = reason;\n console.warn(\n `[MmtpRouter] keyframeLostTimeoutMs ${reason} \u2014 keyframe loss timer ` +\n \"not armed until it yields a finite positive value:\",\n detail,\n );\n }\n return null;\n }\n\n #noteEmittedKeyframe(packetId: number, keyframe: boolean): void {\n if (!keyframe) return;\n if (!this.#videoPacketIds.has(packetId)) return;\n if (this.#keyframeLostTimeoutMs === null) return;\n // FEC tracks resolve lost keyframes through block deadlines and the\n // release gate's stall decision, never through this timer.\n if (this.#isFecTrack(packetId)) return;\n this.#lastKeyframeGroupId++;\n this.#armKeyframeLostTimer();\n }\n\n // \u2500\u2500 Private: FEC release gate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** A packetId is an FEC track once the FEC manager configured it. */\n #isFecTrack(packetId: number): boolean {\n return this.#fecManager?.isTrackConfigured?.(packetId) === true;\n }\n\n #withDeferredFecPokes(work: () => void): void {\n this.#fecPokeDepth++;\n try {\n work();\n } finally {\n this.#fecPokeDepth--;\n if (this.#fecPokeDepth === 0 && this.#pendingFecPokes.size > 0 && !this.#disposed) {\n const pokes = [...this.#pendingFecPokes.values()];\n this.#pendingFecPokes.clear();\n for (const poke of pokes) {\n this.#parser.pokeFecRelease(poke.packetId, poke.catalogTrackName);\n }\n }\n }\n }\n\n #requestFecPoke(packetId: number, catalogTrackName: string | undefined): void {\n if (this.#disposed) return;\n if (this.#fecPokeDepth > 0) {\n this.#pendingFecPokes.set(this.#mediaStreamKey(packetId, catalogTrackName), {\n packetId,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n });\n return;\n }\n this.#parser.pokeFecRelease(packetId, catalogTrackName);\n }\n\n /**\n * The release policy the parser applies to FEC tracks: a frame after a gap\n * is held until every block that could hold the missing frames resolved,\n * then released with the stall/conceal decision when media was lost.\n */\n #createFecReleaseGate(): FecReleaseGate {\n if (this.#fecManager?.blockResolution && !this.#fecManager.terminalRetentionMsForPacket) {\n throw new Error(\n \"[MmtpRouter] an FEC manager with blockResolution must provide terminalRetentionMsForPacket: \" +\n \"it bounds how long the release gate holds a frame\",\n );\n }\n return {\n gates: (packetId) => this.#isFecTrack(packetId),\n resolvedLoss: (packetId, catalogTrackName, fromSbn, toSbn, releasedSbn) => {\n const fec = this.#fecManager;\n if (!fec?.blockResolution) return [];\n // A frame is anchored by its packet's SS_ID (iso-ssbg1-v1, C.5.2): wait\n // until every block that could hold the SS_IDs between the last\n // released frame and this one is stated and resolved.\n const head = fromSbn === releasedSbn ? toSbn : fromSbn;\n return fec.isoSpanResolution?.(packetId, catalogTrackName, releasedSbn, head) ?? null;\n },\n // The retention window itself: a frame is never still held when a block\n // it waits on retires. Only consulted while resolvedLoss holds a gap,\n // which needs blockResolution, so the check above guarantees the method.\n holdLimitMs: (packetId) => this.#fecManager?.terminalRetentionMsForPacket?.(packetId) ?? Infinity,\n decide: (before, after, lostBlocks, discontinuity) => {\n const decision = this.#decideGap(before, after, lostBlocks, discontinuity);\n this.#onFrameGap?.(decision);\n return decision;\n },\n onReleased: (data, meta) => {\n if (meta.trackType !== \"video\" || meta.mpuSequenceNumber === undefined) return;\n const timing = meta.authoredTiming;\n if (!timing || !this.#isRapForGap(data, meta)) return;\n this.#fecManager?.noteReleasedRap?.(\n meta.packetId,\n meta.catalogTrackName,\n meta.mpuSequenceNumber,\n timing.presentationTime,\n timing.mediaTimescale,\n );\n },\n };\n }\n\n /**\n * Codec-aware RAP check for gap decisions: the bitstream, with the MMTP R\n * bit only as the fallback for source frames. Recovered frames are\n * bitstream-only, and the MFU priority hint never counts.\n */\n #isRapForGap(data: Uint8Array, meta: ParserFrameMeta): boolean {\n const codec = this.#trackCodecs.get(meta.packetId) ??\n this.#fecManager?.codecFor?.(meta.packetId) ?? \"\";\n const rapFlag = meta.recovered ? false : meta.rapFlag === true;\n return MmtpContainerParser.isKeyframe(data, rapFlag, codec);\n }\n\n #decideGap(\n before: { data: Uint8Array; meta: ParserFrameMeta },\n after: { data: Uint8Array; meta: ParserFrameMeta },\n lostBlocks: readonly number[],\n discontinuity: boolean,\n ): FrameGapDecision {\n const { packetId, catalogTrackName } = after.meta;\n const trackKind = this.#classifyPacketId(packetId) === false ? \"audio\" : \"video\";\n // Sample positions are MPU positions only while the classifier verified\n // the geometry (one movie fragment per MPU, RAP at index 0).\n const positionsTrusted =\n this.#fecManager?.classifierModeFor?.(packetId, catalogTrackName).mode === \"classifying\";\n // \"Same MPU on both sides\" proves a delta-only gap only while an MPU\n // boundary bounds the stream's RAPs. Once a RAP was seen inside an MPU\n // (or an MPU outran its GOP), a lost RAP can hide inside one MPU, so the\n // MPU is withheld and a non-RAP after the gap stalls as rap-unknown. A\n // manager that cannot answer is treated as unable to prove it.\n const mpuTrusted =\n this.#fecManager?.mpuBoundsRapsFor?.(packetId, catalogTrackName) === true;\n const evidence = (frame: { data: Uint8Array; meta: ParserFrameMeta }): GapFrameEvidence => {\n const timing = frame.meta.authoredTiming;\n const sampleNumber = frame.meta.sampleNumber;\n return {\n rap: trackKind === \"video\" && this.#isRapForGap(frame.data, frame.meta),\n ...(!mpuTrusted || frame.meta.mpuSequenceNumber === undefined\n ? {}\n : { mpu: frame.meta.mpuSequenceNumber }),\n ...(positionsTrusted && sampleNumber !== undefined && sampleNumber >= 1\n ? { idx: sampleNumber - 1 }\n : {}),\n ...(timing === undefined\n ? {}\n : {\n pts: timing.presentationTime,\n durationTicks: timing.durationTicks,\n timescale: timing.mediaTimescale,\n }),\n };\n };\n return decideGap({\n stream: { packetId, catalogTrackName: catalogTrackName ?? null },\n trackKind,\n before: evidence(before),\n after: evidence(after),\n discontinuity,\n blocks: lostBlocks,\n maxMissingUnits: this.#maxMissingUnits(before.meta, after.meta),\n epoch: this.#fecManager?.epochFor?.(packetId) ?? 0,\n });\n }\n\n /**\n * The most media units a gap between two released frames can hold: the\n * FEC blocks between `before`'s anchor and `after`'s, times D, the media\n * units one block touches (ceil(interleaveDepthMs / unit duration), \u00A75.1).\n * Null when either anchor, the unit duration or the catalog interleave is\n * unknown.\n */\n #maxMissingUnits(before: ParserFrameMeta, after: ParserFrameMeta): number | null {\n const from = before.fecSbn;\n const to = after.fecSbn;\n const timing = before.authoredTiming;\n const interleaveMs = this.#fecManager?.interleaveDepthMsForPacket?.(after.packetId);\n if (from === undefined || to === undefined || !timing || interleaveMs === undefined) return null;\n if (!(timing.durationTicks > 0) || !(timing.mediaTimescale > 0) || !(interleaveMs > 0)) return null;\n // D = ceil(interleaveMs * timescale / (durationTicks * 1000)), in integers.\n const numerator = BigInt(Math.ceil(interleaveMs)) * BigInt(timing.mediaTimescale);\n const denominator = BigInt(timing.durationTicks) * 1000n;\n const unitsPerBlock = (numerator + denominator - 1n) / denominator;\n // Anchors are SS_IDs (C.5.2): every block holds at least one, so the\n // serial SS_ID distance bounds the blocks between them.\n const blocks = BigInt(\n (serial32Precedes(from, to) ? serial32Distance(from, to) : serial32Distance(to, from)) + 1,\n );\n const bound = unitsPerBlock * blocks;\n return bound > BigInt(Number.MAX_SAFE_INTEGER) ? null : Number(bound);\n }\n\n // \u2500\u2500 Private: MMTP processing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Classify a packetId from the catalog. Returns true=video, false=audio, null=unknown (drop). */\n #classifyPacketId(packetId: number): boolean | null {\n if (this.#videoPacketIds.has(packetId)) return true;\n if (this.#audioPacketIds.has(packetId)) return false;\n return null;\n }\n\n /** Resolve callback for a packetId (cached per-packetId, invalidated on registration).\n * When a per-track callback is registered AND a legacy callback exists,\n * fires BOTH: per-track first, then legacy. This ensures element.ts wiring\n * (confirmAudio, feedMulticast) continues to fire even when TrackPipeline\n * registers per-track sources.\n * Returns null for unknown packetIds \u2014 callers must drop the packet. */\n #getFrameCallback(packetId: number): FrameCallback | null {\n const cached = this.#resolvedCallbacks.get(packetId);\n if (cached) return cached;\n\n const perTrack = this.#trackCallbacks.get(packetId);\n const classification = this.#classifyPacketId(packetId);\n const legacy =\n classification === true\n ? this.#onVideoFrame\n : classification === false\n ? this.#onAudioFrame\n : null;\n\n let resolved: FrameCallback | null;\n if (perTrack && legacy) {\n // Composite: fire both so per-track routing AND legacy confirm/feedMulticast work.\n resolved = (data, meta) => {\n perTrack(data, meta);\n legacy(data, meta);\n };\n } else {\n resolved = perTrack ?? legacy ?? null;\n }\n\n if (resolved) this.#resolvedCallbacks.set(packetId, resolved);\n return resolved;\n }\n\n /** Deliver init segment for a track (deduped per physical stream). */\n #deliverInit(packetId: number, data: Uint8Array, catalogTrackName?: string): void {\n const staged = this.#staged;\n if (staged !== null && isStream({ packetId, catalogTrackName }, staged.to)) {\n // The incoming stream's init describes media delivered after the\n // commit. Handing it over before would reconfigure a decoder that is\n // still fed the outgoing stream, so it travels with the RAP instead.\n if (data.byteLength > 8) staged.heldInit = data;\n return;\n }\n const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);\n if (this.#trackInitDelivered.has(streamKey)) return;\n if (data.byteLength <= 8) return;\n this.#trackInitDelivered.add(streamKey);\n const cb = this.#trackInitCallbacks.get(packetId);\n if (cb) {\n cb(data);\n } else {\n this.#unmappedInitDeliveries++;\n this.#onInitSegment?.(data);\n }\n console.log(\n `[MmtpRouter] Init delivered for packetId=${packetId}${catalogTrackName === undefined ? \"\" : ` track=${catalogTrackName}`} (${data.byteLength}B)`,\n );\n }\n\n /**\n * Core MMTP packet processing \u2014 source packets \u2192 play + FEC, repair \u2192 FEC only.\n * Both source and repair feed FECManager so RaptorQ has enough symbols (source + repair >= K).\n *\n * Two-phase parsing via MmtpContainerParser:\n * 1. parseHeader() \u2192 extract packetId + fecPayloadId for FEC feeding\n * 2. feed() \u2192 MFU extraction, reassembly, keyframe detection, emit via callbacks\n */\n #processMMTP(\n packet: Uint8Array,\n source: \"multicast\" | \"moq\",\n manifestIdentity?: ManifestObjectIdentity,\n manifestAdmitted = false,\n ingress?: MmtpIngressContext,\n overlapCopy = false,\n releasedFromQuarantine = false,\n ): void {\n // One header parse per packet: the parser reuses it in feedParsed()\n // below instead of parsing the same header again (BLO-36599).\n const parsed = this.#parser.parsePacket(packet);\n if (!parsed) return;\n const info = parsed.info;\n // Every multicast packet is a delivery-gap sample, including one dropped\n // below: the gap is a property of the link the FEC deadlines run on, not\n // of which packets this router routes. The re-entry after a manifest\n // admission is the same arrival, not a new one.\n if (source === \"multicast\" && !manifestAdmitted) {\n this.#fecManager?.noteMulticastArrival?.(info.packetId, info.sequenceNumber);\n }\n if (source === \"multicast\" && !ingressHasPacketId(ingress, info.packetId)) {\n debugRouter(\n `[MmtpRouter] Dropping multicast packet pid=${info.packetId}: absent from ingress tracks`,\n );\n return;\n }\n const packetRole = this.#packetIdRoles.get(info.packetId);\n if (\n source === \"multicast\" &&\n this.#classifyPacketId(info.packetId) === null &&\n (!info.isRepair || packetRole !== \"repair\")\n ) {\n debugRouter(\n `[MmtpRouter] Dropping multicast packet pid=${info.packetId}: physical PID is unregistered`,\n );\n return;\n }\n if (!info.isRepair && hasAmbiguousCatalogTrackName(ingress, info.packetId)) {\n return;\n }\n // A source FEC Payload ID is read under the packet's block profile. Until\n // the track's FEC is configured it has none: nothing (manifest identity,\n // ledgers, FEC state) reads it as a block coordinate, and the media is\n // routed as on a track without FEC.\n const fecUnreadable =\n !info.isRepair &&\n info.fecPayloadId != null &&\n this.#fecManager !== null &&\n this.#classifyPacketId(info.packetId) !== null &&\n this.#configuredBlockProfile(info.packetId) === undefined;\n if (fecUnreadable && !manifestAdmitted) this.#noteUnreadableFecPayloadId(info.packetId, \"source\");\n\n if (!info.isRepair && this.#manifestVerifier && !manifestAdmitted) {\n let identity = manifestIdentity;\n if (!identity && info.fecPayloadId && !fecUnreadable && (this.#resolveManifestIdentityForRoute || this.#resolveManifestIdentity)) {\n // A source resolves with its block coordinates (SS_Start, ESI), the\n // pair a recovered symbol of the same block resolves with. SS_ID alone\n // locates no block (C.5.3): until a repair states this source's block\n // it has none, its identity stays absent, and the verifier's\n // missing-identity policy below applies.\n const catalogTrackName = catalogTrackNameForPacket(ingress, info.packetId);\n const coordinates = this.#locateFecSource(info.packetId, catalogTrackName, info.fecPayloadId.ssId);\n identity = coordinates === null ? undefined : (this.#resolveManifestIdentityForRoute\n ? this.#resolveManifestIdentityForRoute({\n packetId: info.packetId,\n sbn: coordinates.sbn,\n esi: coordinates.esi,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n recovered: false,\n sourceSymbolFormat: \"full-mmtp-packet\",\n })\n : this.#resolveManifestIdentity!(\n info.packetId,\n coordinates.sbn,\n coordinates.esi,\n catalogTrackName,\n )) ?? undefined;\n }\n if (identity) {\n identity = {\n ...identity,\n packetId: info.packetId,\n ...(info.fecPayloadId ? { ssId: info.fecPayloadId.ssId } : {}),\n };\n const authenticated = info.fecPayloadId ? packet.subarray(0, packet.length - 4) : packet;\n // A `stall` admission quarantines the packet and releases it later,\n // after the stream has moved on; a synchronous release happens inside\n // admitObject, before this flag is set.\n let quarantined = false;\n const admission = this.#manifestVerifier.admitObject(identity, authenticated, () => {\n if (!this.#disposed) {\n this.#processMMTP(packet, source, identity, true, ingress, overlapCopy, quarantined);\n }\n });\n quarantined = admission.action === \"stall\";\n return;\n }\n if (\n this.#manifestVerifier.config.mode === \"enforce\" &&\n this.#manifestVerifier.config.onMissingManifest === \"stall\"\n ) {\n console.warn(`[MmtpRouter] manifest identity absent; stalling packetId=${info.packetId}`);\n return;\n }\n }\n\n if (info.isRepair) {\n // Repair packet \u2014 per ISO 23008-1 \u00A7C.5.3, carries (SS_Start, SSB_length, RS_ID).\n // Read from the header parsed above rather than parsing it again\n // (BLO-36599).\n const repair = parseMoqRepairFrameWithHeader(packet, parsed.header);\n if (!repair) return;\n\n const ownerPacketId = this.#resolveRepairOwnerPacketId(info.packetId, ingress);\n if (ownerPacketId != null && this.#fecManager) {\n if (hasAmbiguousCatalogTrackName(ingress, ownerPacketId)) return;\n this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;\n const count = this.#repairPidCounts[info.packetId]!;\n if (count <= 2 || count % 500 === 0) {\n console.log(\n `[MC-PKT] pid=${info.packetId} repairOwner=${ownerPacketId} type=${info.packetType} count=${count} ssbLength=${repair.ssbLength} len=${packet.length}`,\n );\n }\n\n if (this.#configuredBlockProfile(ownerPacketId) === undefined) {\n this.#noteUnreadableFecPayloadId(ownerPacketId, \"repair\");\n return;\n }\n // NOTE: Do NOT call trackBlock for repair packets. Only source symbols\n // should count toward block completion \u2014 repair inflates blocksClean stats\n // and masks blocks that actually needed FEC recovery.\n this.#handleIsoRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair, source);\n }\n } else {\n // Drop source packets with unknown packetId \u2014 not in catalog.\n if (this.#classifyPacketId(info.packetId) === null) return;\n\n // Debug: count packets per packetId\n this.#pidCounts[info.packetId] = (this.#pidCounts[info.packetId] || 0) + 1;\n const count = this.#pidCounts[info.packetId]!;\n if (count <= 2 || count % 500 === 0) {\n console.log(\n `[MC-PKT] pid=${info.packetId} type=${info.packetType} count=${count} len=${packet.length}`,\n );\n }\n\n // Source packet \u2014 parse for playback first, then feed FEC only if the\n // source block did not complete cleanly. Feeding FEC before parser/feed\n // lets a source-triggered recovery race ahead of the source timeline.\n let fecInfo:\n | { sbn: number; esi: number; oversized?: boolean }\n | undefined;\n let sourceFec:\n | {\n ssId: number;\n sbn: number;\n symbolData: Uint8Array;\n }\n | null = null;\n let sourceSymbolAccepted = false;\n let sourceSymbolTrackable = false;\n let parseSourceSymbol = false;\n const catalogTrackName = catalogTrackNameForPacket(ingress, info.packetId);\n if (catalogTrackName !== undefined) {\n const names = this.#catalogTrackNamesByPacketId.get(info.packetId) ?? new Set();\n names.add(catalogTrackName);\n this.#catalogTrackNamesByPacketId.set(info.packetId, names);\n }\n\n const duplicates = this.#stream(info.packetId, catalogTrackName).duplicates;\n // The FEC Payload ID FEC may read: none while the packetId has no\n // configured block profile (fecUnreadable above).\n const fecPayloadId = fecUnreadable ? null : info.fecPayloadId;\n // An FEC source is located by range membership in a block a repair\n // already stated; until then it is held, unplaced (BLO-35597 iso:3). A\n // readable FEC Payload ID implies a configured block profile\n // (fecUnreadable above), and iso-ssbg1-v1 is the only one.\n const fecSource = this.#fecManager !== null && fecPayloadId != null;\n const coords = fecSource && fecPayloadId\n ? this.#locateFecSource(info.packetId, catalogTrackName, fecPayloadId.ssId) ?? undefined\n : undefined;\n // Note the packet's FEC block (none without an FEC coordinate) before\n // any ledger lookup, the restart check below included.\n duplicates.advance(coords?.sbn);\n // A multicast packet that predates everything the stream has kept for a\n // whole FEC horizon is a publisher that restarted its\n // packet_sequence_number / SS_ID counters (BLO-36599,\n // StreamDuplicateLedgers.predatesHorizon). Its first packets would\n // otherwise read as duplicates or as frames behind the reorder cursor.\n // Start a fresh epoch for the stream before this packet is looked up.\n // The reset needs the catalog identity, so a stream without one is not\n // checked; an overlap copy is MoQ bytes and says nothing about the\n // multicast publisher, and neither does a packet the manifest verifier\n // quarantined: it predates the horizon because it waited, not because the\n // publisher restarted.\n if (\n source === \"multicast\" &&\n !overlapCopy &&\n !releasedFromQuarantine &&\n catalogTrackName !== undefined &&\n duplicates.predatesHorizon(info.sequenceNumber, fecPayloadId?.ssId)\n ) {\n this.#publisherRestarts++;\n console.warn(\n `[MmtpRouter] publisher restart on pid=${info.packetId} track=${catalogTrackName}: ` +\n `packet_sequence_number=${info.sequenceNumber} predates the stream's FEC horizon; ` +\n \"starting a new epoch\",\n );\n // Frames the release gate holds belong to the old epoch: release them\n // as a discontinuity stall, as FECManager's source-discontinuity path\n // does, before the reset discards the stream.\n this.#parser.markFecDiscontinuity(info.packetId, catalogTrackName);\n this.#parser.pokeFecRelease(info.packetId, catalogTrackName);\n this.resetTrack(info.packetId, catalogTrackName);\n this.#onPublisherRestart?.(info.packetId, catalogTrackName);\n // The reset cleared the ledgers: note this packet's block in the new epoch.\n duplicates.advance(coords?.sbn);\n }\n if (this.#fecManager && fecPayloadId && coords) {\n const fecId = fecPayloadId;\n // The mmtp-packet source symbol protects the complete MMTP packet\n // before SS_ID is appended, self-delimiting and zero-padded to T.\n const rawSymbol = packet.subarray(0, packet.length - 4);\n // ssbg_mode1 symbols carry a two-octet length (C.3.3): admission\n // refuses length + 2 > T.\n const oversized = !(this.#fecManager.isoSourceFits?.(info.packetId, rawSymbol.length) ?? false);\n\n // NOTE: trackBlock() is deferred to AFTER #feedViaParser (below).\n // Reason: trackBlock synchronously fires onBlockComplete when the\n // K-th source symbol arrives, which synchronously calls\n // CmafBlockAssembler.completeBlock \u2192 #flushBlock \u2192 emit. If\n // trackBlock fires BEFORE parser.feed, the K-th packet's\n // parser.onFrame \u2192 assembler.addFrame happens AFTER flushBlock\n // already drained the block \u2014 the K-th frame is stranded in a\n // newly-allocated empty block that never gets a completeBlock\n // event again, because that SBN is already in fecManager's\n // #completedBlocks set (BLO-3035 layer 4).\n //\n // Complete-packet symbols larger than T cannot be decoded but still\n // represent a source-symbol arrival, so trackBlock counts them below.\n\n let overlapBlockCompleted = false;\n if (overlapCopy) {\n // Playback only. The multicast twin still feeds FEC when it\n // arrives. The per-symbol parse ledger below parses whichever copy\n // arrives first and drops the second; the MFU reassembler cannot,\n // because complete (FI=0) MFUs bypass its #emitted set. A completed\n // block stays authoritative, as it does for FEC input.\n overlapBlockCompleted = catalogTrackName === undefined\n ? this.#fecManager.isBlockCompleted(coords.sbn, info.packetId)\n : this.#fecManager.isBlockCompleted(\n coords.sbn,\n info.packetId,\n catalogTrackName,\n );\n } else if (oversized) {\n // Oversized packet \u2014 not FEC-encoded by FFmpeg, skip FEC feed.\n // Still emitted via parser.feed() below (direct path).\n sourceSymbolAccepted = this.#feedFecTransportSymbol(\n info.packetId,\n coords.sbn,\n coords.esi,\n rawSymbol,\n source,\n catalogTrackName,\n );\n sourceSymbolTrackable = sourceSymbolAccepted;\n } else {\n // The exact protected packet travels unpadded: the decoder builds\n // the ssbg_mode1 symbol itself, and FECManager owns any copy it\n // hands the FEC Worker. Padding here cost a second T-byte\n // allocation and copy per short packet (BLO-36599).\n const symbolData = rawSymbol;\n\n sourceSymbolAccepted = this.#feedFecTransportSymbol(\n info.packetId,\n coords.sbn,\n coords.esi,\n symbolData,\n source,\n catalogTrackName,\n );\n sourceSymbolTrackable = sourceSymbolAccepted;\n if (sourceSymbolAccepted) {\n sourceFec = { ssId: fecId.ssId, sbn: coords.sbn, symbolData };\n }\n }\n\n // First copy wins per symbol, in either arrival order.\n parseSourceSymbol =\n (overlapCopy ? !overlapBlockCompleted : sourceSymbolAccepted) &&\n !duplicates.parsedSymbols.has(fecId.ssId);\n\n // Compute FEC block info for the MSE assembler.\n fecInfo = { sbn: coords.sbn, esi: coords.esi, oversized };\n } else if (fecSource && this.#fecManager && fecPayloadId) {\n // An FEC source whose block no repair has stated yet: route its\n // media now and hold its FEC symbol until a repair places it.\n const ssId = fecPayloadId.ssId;\n if (overlapCopy) {\n parseSourceSymbol = !duplicates.parsedSymbols.has(ssId);\n } else {\n const rawSymbol = packet.subarray(0, packet.length - 4);\n const held = this.#fecManager.holdIsoSource?.(info.packetId, catalogTrackName, ssId, {\n symbolData: rawSymbol,\n source,\n evidence: sourceSymbolEvidence(packet, parsed.header, info.rapFlag, info.sequenceNumber),\n oversized: !(this.#fecManager.isoSourceFits?.(info.packetId, rawSymbol.length) ?? false),\n }) ?? false;\n parseSourceSymbol = held && !duplicates.parsedSymbols.has(ssId);\n }\n } else if (fecUnreadable && info.fecPayloadId) {\n // FEC is unavailable for this packetId: its media is routed as on a\n // track without FEC, first copy per SS_ID. The parse ledger reads\n // SS_ID only as a symbol identity, never as a block coordinate.\n parseSourceSymbol = !duplicates.parsedSymbols.has(info.fecPayloadId.ssId);\n }\n\n // Parse only the first copy of an FEC source symbol. A late direct copy\n // of a worker-recovered fragment has the same SBN/ESI and fragment\n // counter; feeding it again can make the MFU buffer non-contiguous even\n // when FI=1..3 were already complete. Non-FEC packets still always flow.\n if (!this.#fecManager || !info.fecPayloadId || parseSourceSymbol) {\n if (this.#fecManager && info.fecPayloadId) {\n duplicates.parsedSymbols.add(info.fecPayloadId.ssId);\n }\n this.#noteSourcePacketSeq(\n info.packetId,\n info.sequenceNumber,\n catalogTrackName,\n );\n this.#routeTrackPacket(packet, info.packetId, info.sequenceNumber, {\n recovered: false,\n skipFecPayloadId: false,\n catalogTrackName,\n });\n this.#feedViaParser(packet, info, fecInfo, {\n source,\n catalogTrackName,\n // An FEC source frame is anchored by its own SS_ID (C.5.2).\n ...(fecSource && fecPayloadId ? { fecSbn: fecPayloadId.ssId } : {}),\n }, parsed);\n }\n // The frame this packet completed can commit an in-place switch that\n // retires this very packet ID. Its FEC state is gone then, and feeding\n // the rest of this packet would only rebuild it for a stream that ended.\n if (this.#classifyPacketId(info.packetId) === null) return;\n\n // Deferred trackBlock (BLO-3035 layer 4 race fix). Fire AFTER\n // #feedViaParser \u2192 parser.feed \u2192 addFrame, so the K-th source\n // packet's frame is buffered in CmafBlockAssembler.#blocks BEFORE\n // trackBlock synchronously triggers onBlockComplete \u2192 completeBlock\n // \u2192 #flushBlock. Without this ordering, the K-th frame was lost on\n // every block \u2014 only K-1 frames per block were ever emitted to MSE.\n // Only first-arriving symbols count toward completion or playback.\n // A source symbol is accepted only once placed, so it has coords.\n if (\n this.#fecManager &&\n fecPayloadId &&\n coords &&\n sourceSymbolAccepted &&\n sourceSymbolTrackable\n ) {\n const { sbn, esi } = coords;\n if (\n this.#classifyPacketId(info.packetId) !== null &&\n this.#fecManager.isTrackConfigured?.(info.packetId) !== false\n ) {\n // Only this first-arrival network source symbol is classification\n // evidence: the R bit and the MPU / DU identity it carries.\n // Recovered and repair symbols never reach this call.\n this.#fecManager.trackBlock(\n sbn,\n info.packetId,\n catalogTrackName,\n esi,\n sourceSymbolEvidence(packet, parsed.header, info.rapFlag, info.sequenceNumber),\n );\n this.#requestFecPoke(info.packetId, catalogTrackName);\n }\n }\n\n // Only now feed source symbols into FEC. The decoder must still receive\n // the K-th source symbol after trackBlock() marks a clean block complete:\n // Worker-backed decoders use that symbol for their own block lifecycle\n // and stats. Suppress any synchronous recovered-block return when the\n // manager already completed the block, because every source frame was\n // parsed and emitted above.\n if (this.#fecManager && sourceFec) {\n const alreadyCompleted = catalogTrackName === undefined\n ? this.#fecManager.isBlockCompleted(sourceFec.sbn, info.packetId)\n : this.#fecManager.isBlockCompleted(\n sourceFec.sbn,\n info.packetId,\n catalogTrackName,\n );\n const recovered = catalogTrackName === undefined\n ? this.#fecManager.feedSourceSymbol(\n info.packetId,\n sourceFec.ssId,\n sourceFec.symbolData,\n undefined,\n )\n : this.#fecManager.feedSourceSymbol(\n info.packetId,\n sourceFec.ssId,\n sourceFec.symbolData,\n undefined,\n catalogTrackName,\n );\n if (recovered) {\n if (!alreadyCompleted && this.#classifyPacketId(info.packetId) !== null) {\n if (catalogTrackName === undefined) {\n this.#fecManager.cancelBlockTimeout(sourceFec.sbn, info.packetId);\n } else {\n this.#fecManager.cancelBlockTimeout(\n sourceFec.sbn,\n info.packetId,\n catalogTrackName,\n );\n }\n }\n // Only route recovered blocks that needed actual repair.\n if (!alreadyCompleted) {\n this.#fecManager.countRecovery(info.packetId, sourceFec.sbn, catalogTrackName);\n if (catalogTrackName === undefined) {\n this.#fecManager.markBlockCompleted(sourceFec.sbn, info.packetId);\n } else {\n this.#fecManager.markBlockCompleted(\n sourceFec.sbn,\n info.packetId,\n catalogTrackName,\n );\n }\n this.#routeRecoveredBlock(\n sourceFec.sbn,\n recovered,\n info.packetId,\n catalogTrackName,\n );\n }\n }\n }\n }\n }\n\n /**\n * Set temp context and delegate to parser.feed() for MFU extraction,\n * reassembly, keyframe detection, and frame emission via callbacks.\n */\n #feedViaParser(\n packet: Uint8Array,\n info: {\n packetId: number;\n sequenceNumber: number;\n fecPayloadId?: { ssId: number } | null;\n },\n fecInfo?: { sbn: number; esi: number; oversized?: boolean },\n parserOptions?: {\n skipFecPayloadId?: boolean;\n recovered?: boolean;\n source?: MmtpSourcePath;\n deliveryProvenance?: MmtpDeliveryProvenance;\n catalogTrackName?: string;\n fecSbn?: number;\n },\n parsed?: ParsedMmtpPacket,\n ): void {\n const diag = this.#diagFor(info.packetId, parserOptions?.catalogTrackName);\n // Plain parser counters, not reassembler.getStatus(): on the wasm backend\n // each status read is a stats() crossing, and this runs for every packet\n // (BLO-36599). Buffer occupancy is read once, in getDiagnostics().\n const fragmentsBefore = this.#parser.reassemblerFragmentsFed;\n const reassembledBefore = this.#parser.reassemblerDataUnitsReturned;\n diag.packets++;\n diag.lastPacketLength = packet.byteLength;\n diag.lastSequenceNumber = info.sequenceNumber;\n if (info.fecPayloadId) diag.lastFecSsId = info.fecPayloadId.ssId;\n this.#currentFecInfo = fecInfo;\n this.#currentSeqNum = info.sequenceNumber;\n try {\n const feedOptions = {\n skipFecPayloadId: parserOptions?.skipFecPayloadId,\n recovered: parserOptions?.recovered,\n source: parserOptions?.source,\n deliveryProvenance: parserOptions?.deliveryProvenance,\n catalogTrackName: parserOptions?.catalogTrackName,\n ...(parserOptions?.fecSbn === undefined ? {} : { fecSbn: parserOptions.fecSbn }),\n };\n if (parsed) this.#parser.feedParsed(packet, parsed, feedOptions);\n else this.#parser.feed(packet, feedOptions);\n } finally {\n this.#currentFecInfo = undefined;\n }\n const fragmentDelta = this.#parser.reassemblerFragmentsFed - fragmentsBefore;\n const reassembledDelta =\n this.#parser.reassemblerDataUnitsReturned - reassembledBefore;\n if (fragmentDelta > 0) diag.fragments += fragmentDelta;\n else diag.noFragment++;\n if (reassembledDelta > 0) diag.reassembled += reassembledDelta;\n }\n\n #feedFecTransportSymbol(\n packetId: number,\n sbn: number,\n esi: number,\n data: Uint8Array,\n source: MmtpSourcePath,\n catalogTrackName?: string,\n ): boolean {\n const fecManager = this.#fecManager;\n if (!fecManager) return false;\n return catalogTrackName === undefined\n ? fecManager.feedSymbolFromSource(packetId, sbn, esi, data, source)\n : fecManager.feedSymbolFromSource(\n packetId,\n sbn,\n esi,\n data,\n source,\n catalogTrackName,\n );\n }\n\n #diagFor(packetId: number, catalogTrackName?: string): ParserDiagnostics {\n const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);\n let diag = this.#parserDiagnostics.get(streamKey);\n if (!diag) {\n diag = {\n packets: 0,\n frames: 0,\n initSegments: 0,\n noFragment: 0,\n fragments: 0,\n reassembled: 0,\n pendingBuffers: 0,\n pendingFragments: 0,\n lastPacketLength: 0,\n lastSequenceNumber: 0,\n directAudioSourceEmitted: 0,\n directAudioSourceRegressions: 0,\n directAudioRecoveredQueued: 0,\n directAudioRecoveredDrained: 0,\n directAudioRecoveredStaleDropped: 0,\n };\n this.#parserDiagnostics.set(streamKey, diag);\n }\n return diag;\n }\n\n #shouldEmitFrame(\n packetId: number,\n keyframe: boolean,\n catalogTrackName?: string,\n ): boolean {\n if (!this.#fecManager) return true;\n if (!this.#videoPacketIds.has(packetId)) return true;\n const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);\n if (this.#trackSynced.has(streamKey)) return true;\n if (!keyframe) {\n this.#droppedPreSyncVideo.add(streamKey);\n return false;\n }\n this.#trackSynced.add(streamKey);\n this.#droppedPreSyncVideo.delete(streamKey);\n return true;\n }\n\n #opensFecSync(\n packetId: number,\n keyframe: boolean,\n catalogTrackName?: string,\n ): boolean {\n const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);\n return !!this.#fecManager &&\n this.#videoPacketIds.has(packetId) &&\n !this.#trackSynced.has(streamKey) &&\n this.#droppedPreSyncVideo.has(streamKey) &&\n keyframe;\n }\n\n #noteEmittedVideoSeq(streamKey: string, packetSequenceNumber: number | undefined): void {\n if (packetSequenceNumber === undefined || !Number.isFinite(packetSequenceNumber)) return;\n const latest = this.#latestVideoSeqByStream.get(streamKey);\n if (latest === undefined || packetSequenceNumber > latest) {\n this.#latestVideoSeqByStream.set(streamKey, packetSequenceNumber);\n }\n }\n\n #noteSourcePacketSeq(\n packetId: number,\n packetSequenceNumber: number | undefined,\n catalogTrackName?: string,\n ): void {\n if (packetSequenceNumber === undefined || !Number.isFinite(packetSequenceNumber)) return;\n this.#stream(packetId, catalogTrackName).duplicates.sourceSeqs.add(packetSequenceNumber);\n }\n\n #sourceSeqReceived(\n packetId: number,\n packetSequenceNumber: number,\n catalogTrackName?: string,\n ): boolean {\n return this.#stream(packetId, catalogTrackName).duplicates.sourceSeqs.has(packetSequenceNumber);\n }\n\n #routeTrackPacket(\n packet: Uint8Array,\n packetId: number,\n packetSequenceNumber: number,\n meta: Pick<\n RoutedPacketMeta,\n \"recovered\" | \"skipFecPayloadId\" | \"catalogTrackName\" | \"deliveryProvenance\"\n >,\n ): void {\n if (!Number.isFinite(packetSequenceNumber)) return;\n const callback = this.#trackPacketCallbacks.get(packetId);\n if (!callback) return;\n const routedSeqs = this.#stream(packetId, meta.catalogTrackName).duplicates.routedSeqs;\n if (routedSeqs.has(packetSequenceNumber)) return;\n routedSeqs.add(packetSequenceNumber);\n callback(packet, {\n packetId,\n packetSequenceNumber,\n recovered: meta.recovered,\n skipFecPayloadId: meta.skipFecPayloadId,\n ...(meta.catalogTrackName === undefined\n ? {}\n : { catalogTrackName: meta.catalogTrackName }),\n ...(meta.deliveryProvenance === undefined\n ? {}\n : { deliveryProvenance: meta.deliveryProvenance }),\n });\n }\n\n #isStaleRecoveredVideo(\n packetId: number,\n packetSequenceNumber: number,\n catalogTrackName?: string,\n ): boolean {\n const latest = this.#latestVideoSeqByStream.get(\n this.#mediaStreamKey(packetId, catalogTrackName),\n );\n return latest !== undefined && packetSequenceNumber <= latest;\n }\n\n /**\n * JSON tuple naming one physical stream. Built several times per packet, so\n * each (packetId, catalog name) pair is serialized once and reused; the set\n * of pairs is bounded by the catalog (BLO-36599).\n */\n #mediaStreamKey(packetId: number, catalogTrackName?: string): string {\n return this.#stream(packetId, catalogTrackName).key;\n }\n\n /** The (packetId, catalog name) stream's key and duplicate ledgers. */\n #stream(packetId: number, catalogTrackName?: string): StreamLedgers {\n let byName = this.#streams.get(packetId);\n if (!byName) {\n byName = new Map();\n this.#streams.set(packetId, byName);\n }\n let stream = byName.get(catalogTrackName);\n if (stream === undefined) {\n stream = {\n key: JSON.stringify([packetId, catalogTrackName ?? null]),\n duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId)),\n };\n byName.set(catalogTrackName, stream);\n }\n return stream;\n }\n\n #packetIdFromStreamKey(key: string): number {\n try {\n const decoded = JSON.parse(key);\n if (Array.isArray(decoded) && typeof decoded[0] === \"number\") return decoded[0];\n } catch {\n // Internal keys are JSON tuples; malformed legacy keys should not happen.\n }\n return 0;\n }\n\n #mseAssemblerKey(packetId: number, catalogTrackName?: string): string {\n return this.#mediaStreamKey(packetId, catalogTrackName);\n }\n\n #mseAssemblerFor(\n packetId: number,\n catalogTrackName?: string,\n ): CmafBlockAssembler | undefined {\n const exact = this.#mseAssemblers.get(\n this.#mseAssemblerKey(packetId, catalogTrackName),\n );\n if (exact || catalogTrackName === undefined) return exact;\n if (!this.#isCatalogTrackUnambiguous(packetId, catalogTrackName)) return undefined;\n return this.#mseAssemblers.get(this.#mseAssemblerKey(packetId));\n }\n\n #isCatalogTrackUnambiguous(\n packetId: number,\n catalogTrackName?: string,\n ): boolean {\n if (catalogTrackName === undefined) return true;\n const names = this.#catalogTrackNamesByPacketId.get(packetId);\n return names?.size === 1 && names.has(catalogTrackName);\n }\n\n #resolveRecoveredTiming(\n packetId: number,\n mpuSequenceNumber: number,\n movieFragmentSequenceNumber: number | undefined,\n sampleNumber: number | undefined,\n mmtpTimestamp: bigint,\n catalogTrackName?: string,\n ): { authoredTiming?: AuthoredTiming; timestamp: number; mmtpTimestamp: bigint } | null {\n // Non-timed MFUs do not carry a movie-fragment/sample identity. They are\n // outside the ISO-BMFF presentation timeline, so retain the raw MMTP\n // timestamp path used by the untimed router contract.\n if (movieFragmentSequenceNumber === undefined || sampleNumber === undefined) {\n return {\n timestamp: mmtpTimestampToMicros(mmtpTimestamp),\n mmtpTimestamp,\n };\n }\n\n // Claim the resolver entry before scaling it. The claim is the single\n // consume operation for a recovered sample; doing lookup \u2192 scale \u2192 claim\n // would leave an invalid entry live forever when scaling rejects it.\n const authoredTiming = this.#parser.consumeAuthoredTimingFor(\n packetId,\n mpuSequenceNumber,\n movieFragmentSequenceNumber,\n sampleNumber,\n catalogTrackName,\n );\n // Recovery is accepted only when the timed stream's FT=1 metadata names\n // this exact sample. MMTP send timestamps describe transport arrival, not\n // the authored presentation timeline, so they are never a fallback.\n if (authoredTiming === null) return null;\n const scaled = scaleAuthoredTiming(authoredTiming, 1_000_000);\n if (scaled === null) return null;\n return { authoredTiming, timestamp: scaled.timestamp, mmtpTimestamp };\n }\n\n #pendingRecoveredKey(meta: RoutedFrameMeta): string {\n return `${meta.timestamp}:${meta.packetSequenceNumber ?? \"\"}`;\n }\n\n #pendingRecoveredQueue(streamKey: string): Map<string, PendingRecoveredVideoFrame> {\n let queue = this.#pendingDirectRecoveredVideoByStream.get(streamKey);\n if (!queue) {\n queue = new Map();\n this.#pendingDirectRecoveredVideoByStream.set(streamKey, queue);\n }\n return queue;\n }\n\n #comparePendingRecoveredVideo(\n a: PendingRecoveredVideoFrame,\n b: PendingRecoveredVideoFrame,\n ): number {\n if (a.meta.timestamp !== b.meta.timestamp) return a.meta.timestamp - b.meta.timestamp;\n const aSeq = a.meta.packetSequenceNumber ?? Number.MAX_SAFE_INTEGER;\n const bSeq = b.meta.packetSequenceNumber ?? Number.MAX_SAFE_INTEGER;\n return aSeq - bSeq;\n }\n\n #noteDeliveredVideo(streamKey: string, meta: StreamSwitchPosition): void {\n const high = this.#deliveredVideoHighWaterByStream.get(streamKey);\n if (high === undefined || comparePresentation(meta, high) > 0) {\n this.#deliveredVideoHighWaterByStream.set(streamKey, switchPosition(meta));\n }\n }\n\n #emitDirectVideo(\n packetId: number,\n data: Uint8Array,\n meta: RoutedFrameMeta,\n cb: FrameCallback | null,\n ): boolean {\n const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);\n const lastEmitted = this.#lastDirectEmittedVideoTsByStream.get(streamKey);\n if (lastEmitted !== undefined && meta.timestamp <= lastEmitted) return false;\n this.#lastDirectEmittedVideoTsByStream.set(streamKey, meta.timestamp);\n this.#noteDeliveredVideo(streamKey, meta);\n // Record the emitted sequence number HERE \u2014 at the real emit \u2014 not when a\n // recovered frame is queued. #routeDirectRecoveredVideo may enqueue a frame\n // that is ahead of the source cursor; noting its seq before it drains would\n // make #isStaleRecoveredVideo drop an earlier gap frame that arrives later\n // (it would look <= the queued-but-unemitted seq). The high-water mark must\n // reflect what actually reached the decoder callback.\n this.#noteEmittedVideoSeq(streamKey, meta.packetSequenceNumber);\n cb?.(data, meta);\n return true;\n }\n\n #drainDirectRecoveredVideo(\n streamKey: string,\n packetId: number,\n releases: (meta: RoutedFrameMeta) => boolean,\n cb: FrameCallback | null,\n ): void {\n const queue = this.#pendingDirectRecoveredVideoByStream.get(streamKey);\n if (!queue || queue.size === 0) return;\n\n const ready = [...queue.values()]\n .filter(frame => releases(frame.meta))\n .sort((a, b) => this.#comparePendingRecoveredVideo(a, b));\n\n for (const frame of ready) {\n queue.delete(this.#pendingRecoveredKey(frame.meta));\n this.#emitDirectVideo(packetId, frame.data, frame.meta, cb);\n }\n\n if (queue.size === 0) {\n this.#pendingDirectRecoveredVideoByStream.delete(streamKey);\n }\n }\n\n #enqueueDirectRecoveredVideo(\n packetId: number,\n data: Uint8Array,\n meta: RoutedFrameMeta,\n ): void {\n const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);\n const lastEmitted = this.#lastDirectEmittedVideoTsByStream.get(streamKey);\n if (lastEmitted !== undefined && meta.timestamp <= lastEmitted) return;\n\n const queue = this.#pendingRecoveredQueue(streamKey);\n queue.set(this.#pendingRecoveredKey(meta), { data, meta: { ...meta } });\n\n if (queue.size > DIRECT_RECOVERED_VIDEO_QUEUE_LIMIT) {\n const keep = [...queue.entries()]\n .sort((a, b) => this.#comparePendingRecoveredVideo(a[1], b[1]))\n .slice(-Math.floor(DIRECT_RECOVERED_VIDEO_QUEUE_LIMIT / 2));\n queue.clear();\n for (const [key, frame] of keep) queue.set(key, frame);\n }\n }\n\n #routeDirectSourceVideo(\n packetId: number,\n data: Uint8Array,\n meta: RoutedFrameMeta,\n cb: FrameCallback | null,\n ): void {\n const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);\n const lastSourceTs = this.#lastDirectSourceVideoTsByStream.get(streamKey);\n if (lastSourceTs === undefined || meta.timestamp > lastSourceTs) {\n this.#lastDirectSourceVideoTsByStream.set(streamKey, meta.timestamp);\n }\n\n // Recovered FEC may arrive before the source stream reaches the same media\n // time. Drain those gap fills immediately before the source frame that proves\n // the live cursor caught up, preserving the decoder dependency chain.\n this.#drainDirectRecoveredVideo(streamKey, packetId, frame => frame.timestamp <= meta.timestamp, cb);\n this.#emitDirectVideo(packetId, data, meta, cb);\n }\n\n #routeDirectRecoveredVideo(\n packetId: number,\n data: Uint8Array,\n meta: RoutedFrameMeta,\n cb: FrameCallback | null,\n ): void {\n const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);\n this.#enqueueDirectRecoveredVideo(packetId, data, meta);\n const lastSourceTs = this.#lastDirectSourceVideoTsByStream.get(streamKey);\n this.#drainDirectRecoveredVideo(\n streamKey,\n packetId,\n frame => lastSourceTs === undefined || frame.timestamp <= lastSourceTs,\n cb,\n );\n }\n\n #pendingRecoveredAudioQueue(streamKey: string): Map<string, PendingRecoveredAudioFrame> {\n let queue = this.#pendingDirectRecoveredAudioByStream.get(streamKey);\n if (!queue) {\n queue = new Map();\n this.#pendingDirectRecoveredAudioByStream.set(streamKey, queue);\n }\n return queue;\n }\n\n #directAudioCursor(meta: RoutedFrameMeta): DirectAudioCursor {\n return {\n timestamp: meta.timestamp,\n // A recovered frame without transport sequence metadata must drain before\n // an equal-timestamp source horizon instead of remaining queued forever.\n sequenceNumber: meta.packetSequenceNumber ?? Number.MIN_SAFE_INTEGER,\n };\n }\n\n #compareDirectAudioCursor(a: DirectAudioCursor, b: DirectAudioCursor): number {\n if (a.timestamp !== b.timestamp) return a.timestamp - b.timestamp;\n return a.sequenceNumber - b.sequenceNumber;\n }\n\n #emitDirectAudio(\n packetId: number,\n data: Uint8Array,\n meta: RoutedFrameMeta,\n cb: FrameCallback | null,\n ): boolean {\n const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);\n const cursor = this.#directAudioCursor(meta);\n const lastEmitted = this.#lastDirectEmittedAudioCursorByStream.get(streamKey);\n if (\n lastEmitted !== undefined &&\n this.#compareDirectAudioCursor(cursor, lastEmitted) <= 0\n ) return false;\n this.#lastDirectEmittedAudioCursorByStream.set(streamKey, cursor);\n cb?.(data, meta);\n return true;\n }\n\n #emitDirectSourceAudio(\n packetId: number,\n data: Uint8Array,\n meta: RoutedFrameMeta,\n cb: FrameCallback | null,\n ): void {\n const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);\n const cursor = this.#directAudioCursor(meta);\n const lastEmitted = this.#lastDirectEmittedAudioCursorByStream.get(streamKey);\n const diag = this.#diagFor(packetId, meta.catalogTrackName);\n diag.directAudioSourceEmitted++;\n if (\n lastEmitted !== undefined &&\n this.#compareDirectAudioCursor(cursor, lastEmitted) <= 0\n ) {\n // Source packets already passed transport/parser validation. Preserve\n // them even when unordered multicast completion moves the callback\n // cursor backward; only recovered frames are optional stale gap fills.\n diag.directAudioSourceRegressions++;\n } else {\n this.#lastDirectEmittedAudioCursorByStream.set(streamKey, cursor);\n }\n cb?.(data, meta);\n }\n\n #drainDirectRecoveredAudioUpTo(\n streamKey: string,\n packetId: number,\n horizon: DirectAudioCursor,\n cb: FrameCallback | null,\n ): void {\n const queue = this.#pendingDirectRecoveredAudioByStream.get(streamKey);\n if (!queue || queue.size === 0) return;\n\n const ready = [...queue.values()]\n .filter(frame => this.#compareDirectAudioCursor(\n this.#directAudioCursor(frame.meta),\n horizon,\n ) <= 0)\n .sort((a, b) => this.#compareDirectAudioCursor(\n this.#directAudioCursor(a.meta),\n this.#directAudioCursor(b.meta),\n ));\n\n for (const frame of ready) {\n queue.delete(this.#pendingRecoveredKey(frame.meta));\n if (this.#emitDirectAudio(packetId, frame.data, frame.meta, cb)) {\n this.#diagFor(packetId, frame.meta.catalogTrackName)\n .directAudioRecoveredDrained++;\n } else {\n this.#diagFor(packetId, frame.meta.catalogTrackName)\n .directAudioRecoveredStaleDropped++;\n }\n }\n\n if (queue.size === 0) {\n this.#pendingDirectRecoveredAudioByStream.delete(streamKey);\n }\n }\n\n #routeDirectSourceAudio(\n packetId: number,\n data: Uint8Array,\n meta: RoutedFrameMeta,\n cb: FrameCallback | null,\n ): void {\n const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);\n const cursor = this.#directAudioCursor(meta);\n const lastSource = this.#lastDirectSourceAudioCursorByStream.get(streamKey);\n if (\n lastSource === undefined ||\n this.#compareDirectAudioCursor(cursor, lastSource) > 0\n ) {\n this.#lastDirectSourceAudioCursorByStream.set(streamKey, cursor);\n }\n this.#drainDirectRecoveredAudioUpTo(streamKey, packetId, cursor, cb);\n this.#emitDirectSourceAudio(packetId, data, meta, cb);\n }\n\n #routeDirectRecoveredAudio(\n packetId: number,\n data: Uint8Array,\n meta: RoutedFrameMeta,\n cb: FrameCallback | null,\n ): void {\n const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);\n const cursor = this.#directAudioCursor(meta);\n const lastEmitted = this.#lastDirectEmittedAudioCursorByStream.get(streamKey);\n if (\n lastEmitted !== undefined &&\n this.#compareDirectAudioCursor(cursor, lastEmitted) <= 0\n ) {\n this.#diagFor(packetId, meta.catalogTrackName)\n .directAudioRecoveredStaleDropped++;\n return;\n }\n\n const queue = this.#pendingRecoveredAudioQueue(streamKey);\n queue.set(this.#pendingRecoveredKey(meta), { data, meta: { ...meta } });\n this.#diagFor(packetId, meta.catalogTrackName).directAudioRecoveredQueued++;\n if (queue.size > DIRECT_RECOVERED_AUDIO_QUEUE_LIMIT) {\n const keep = [...queue.entries()]\n .sort((a, b) => this.#compareDirectAudioCursor(\n this.#directAudioCursor(a[1].meta),\n this.#directAudioCursor(b[1].meta),\n ))\n .slice(-Math.floor(DIRECT_RECOVERED_AUDIO_QUEUE_LIMIT / 2));\n queue.clear();\n for (const [key, frame] of keep) queue.set(key, frame);\n }\n\n const lastSource = this.#lastDirectSourceAudioCursorByStream.get(streamKey);\n if (lastSource !== undefined) {\n this.#drainDirectRecoveredAudioUpTo(streamKey, packetId, lastSource, cb);\n }\n }\n\n // \u2500\u2500 Private: FEC recovery routing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Route WASM-recovered block through appropriate path based on container kind. */\n #routeRecoveredBlock(\n blockId: number,\n recovered: Uint8Array,\n ownerPacketId?: number,\n catalogTrackName?: string,\n ): void {\n // MMTP path: use processFecBlock for MMTP-wrapped recovery.\n // Recovered symbols are individual MMTP packets that may be fragments\n // (FI=1/2/3) of a larger MFU. Route through the reassembler so\n // multi-packet MFUs (e.g., keyframes spanning 15+ packets) are\n // properly assembled before reaching the decoder.\n const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);\n if (ownerPacketId === undefined) {\n throw new Error(\"[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile\");\n }\n const blockProfile = this.#blockProfileFor(ownerPacketId);\n if (!(packetSymbolSize > 0)) {\n throw new Error(\n `[MmtpRouter] recovered FEC block for packetId=${ownerPacketId} needs a configured ` +\n `symbol size (got T=${packetSymbolSize})`,\n );\n }\n const symbolSize = packetSymbolSize;\n // An iso-ssbg1-v1 block decodes to K' = SSB_length ssbg_mode1 symbols.\n const result = processFecBlock(recovered, {\n symbolSize,\n ownerPacketId,\n blockProfile,\n });\n this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName);\n }\n\n #routeVerifiedFecSymbols(\n blockId: number,\n result: ProcessFecBlockResult,\n ownerPacketId?: number,\n catalogTrackName?: string,\n ): void {\n if (!this.#manifestVerifier || ownerPacketId == null) {\n this.#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName);\n return;\n }\n if (!this.#resolveManifestIdentity && !this.#resolveManifestIdentityForRoute) {\n console.warn(`[MmtpRouter] manifest identity resolver unavailable; recovered media is fail-open for packetId=${ownerPacketId} block=${blockId}`);\n this.#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName);\n return;\n }\n const queueKey = this.#mediaStreamKey(ownerPacketId, catalogTrackName);\n const queue = this.#manifestRecoveryQueues.get(queueKey) ?? [];\n queue.push({ blockId, result, ownerPacketId, catalogTrackName });\n this.#manifestRecoveryQueues.set(queueKey, queue);\n if (queue.length > 1) return;\n const drain = (): void => {\n const next = queue[0];\n if (!next) {\n this.#manifestRecoveryQueues.delete(queueKey);\n return;\n }\n this.#admitRecoveredResult(\n next.blockId,\n next.result,\n next.ownerPacketId,\n next.catalogTrackName,\n () => {\n queue.shift();\n drain();\n },\n );\n };\n drain();\n }\n\n #admitRecoveredResult(\n blockId: number,\n result: ProcessFecBlockResult,\n ownerPacketId: number,\n catalogTrackName: string | undefined,\n done: () => void,\n ): void {\n const verifier = this.#manifestVerifier;\n const resolveIdentity = this.#resolveManifestIdentity;\n const resolveIdentityForRoute = this.#resolveManifestIdentityForRoute;\n if (!verifier) {\n this.#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName);\n done();\n return;\n }\n if (!resolveIdentity && !resolveIdentityForRoute) throw new Error(\"manifest identity resolver disappeared during admission\");\n const symbols = result.symbols;\n const admitNext = (index: number): void => {\n const symbol = symbols[index];\n if (!symbol) {\n done();\n return;\n }\n const identity = resolveIdentityForRoute\n ? resolveIdentityForRoute({\n packetId: ownerPacketId,\n sbn: blockId,\n esi: symbol.index,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n recovered: true,\n sourceSymbolFormat: \"fec-padded-recovery\",\n })\n : resolveIdentity!(ownerPacketId, blockId, symbol.index, catalogTrackName);\n if (!identity) {\n console.warn(`[MmtpRouter] manifest identity unresolved; recovered symbol is fail-open for packetId=${ownerPacketId} block=${blockId} esi=${symbol.index}`);\n this.#routeProcessedFecSymbols(\n blockId,\n { ...result, symbols: [symbol] },\n ownerPacketId,\n catalogTrackName,\n );\n admitNext(index + 1);\n return;\n }\n const admission = verifier.admitObject(\n {\n ...identity,\n packetId: ownerPacketId,\n },\n symbol.rawSymbol,\n () => {\n if (!this.#disposed) {\n this.#routeProcessedFecSymbols(\n blockId,\n { ...result, symbols: [symbol] },\n ownerPacketId,\n catalogTrackName,\n );\n }\n admitNext(index + 1);\n },\n // A stalled symbol that is later discarded instead of played must still\n // advance this loop; enforce mode never calls the release callback for\n // it (BLO-30095).\n () => admitNext(index + 1),\n );\n if (admission.action === \"drop\") admitNext(index + 1);\n };\n admitNext(0);\n }\n\n #routeProcessedFecSymbols(\n blockId: number,\n result: ProcessFecBlockResult,\n ownerPacketId?: number,\n catalogTrackName?: string,\n ): void {\n // Route recovered symbols to the track's callback. The ownerPacketId tells us\n // which track this block belongs to \u2014 resolves per-track, then legacy fallback.\n if (ownerPacketId == null) return; // no track association\n if (this.#classifyPacketId(ownerPacketId) === null) return; // unknown track\n this.#stream(ownerPacketId, catalogTrackName).duplicates.advance(blockId);\n\n // A recovered symbol is a function of EVERY symbol that entered its block,\n // so its provenance is the block's, not the media path's. Repair symbols\n // routinely arrive over reliable QUIC (`feedRepairPacket`), so a block that\n // recovered with MoQ help produced bytes that never crossed a multicast\n // socket. Without this, recovered frames reached the decoder carrying no\n // attribution at all and were counted as multicast by default.\n // A FEC manager that cannot report block provenance yields 'unknown', not\n // a guess: these bytes are then never counted as physical-delivery\n // evidence. `FECManagerLike` is a pluggable structural interface, so an\n // implementation without this method must degrade, not throw.\n const deliveryProvenance: MmtpDeliveryProvenance =\n this.#fecManager?.blockDeliveryProvenance?.(\n blockId,\n ownerPacketId,\n catalogTrackName,\n ) ?? 'unknown';\n\n for (const sym of result.symbols) {\n this.#routeTrackPacket(sym.rawSymbol, ownerPacketId, sym.packetSequenceNumber, {\n recovered: true,\n skipFecPayloadId: true,\n catalogTrackName,\n deliveryProvenance,\n });\n }\n\n // Feed recovered metadata before routing data. FT=0 initializes the timing\n // resolver and FT=1 publishes the sample table that recovered FT=2 data\n // must resolve against. The parser's callbacks dedupe repeated init\n // delivery, while keeping this path in lockstep with direct source input.\n for (const sym of result.symbols) {\n if (sym.fragmentType !== 0 && sym.fragmentType !== 1) continue;\n this.#feedViaParser(\n sym.rawSymbol,\n { packetId: ownerPacketId, sequenceNumber: sym.packetSequenceNumber },\n undefined,\n {\n skipFecPayloadId: true,\n recovered: true,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n },\n );\n }\n\n // MSE assembler path: route recovered block as pre-assembled frames (skip FT=0)\n // BLO-3035 layer 3: recovered symbols are RAW MFU fragments, not complete\n // frames. A keyframe can span multiple packets (FI=1/2/3); pushing each\n // fragment as a \"frame\" produces garbage moof+mdat that fails decode.\n // Mirror the non-MSE path's logic: FI=0 \u2192 emit single-packet MFU directly\n // (after dedup against source path), FI=1/2/3 \u2192 feed reassembler, emit when\n // the complete frame is available. Take the keyframe bit from the assembled\n // frame's rapFlag (with `#detectKeyframe` as a bitstream fallback for\n // publishers that don't set rapFlag) \u2014 `sym.isKeyframe` is just rapFlag\n // from a single fragment's header (see `block-processor.ts` where\n // `isKeyframe = hdr.rapFlag`) and isn't meaningful for the reassembled-\n // frame keyframe decision when FI\u22600.\n // FEC tracks release recovered frames through the parser's FEC release\n // gate, in authored order with the source frames they fill in for; the\n // frame callback then applies sync gating and MSE/direct dispatch once.\n const gated = this.#isFecTrack(ownerPacketId);\n const assembler = this.#mseAssemblerFor(ownerPacketId, catalogTrackName);\n if (assembler) {\n // A staged switch holds and splices its two streams at the parser's\n // frame callback, and the ungated batch below would bypass it: the\n // incoming stream's recovered frames would be lost from the GOP it\n // replays at the commit, and the outgoing stream's could reach MSE past\n // the splice. Their recovered frames go through the parser instead, as\n // an FEC-gated track's always do.\n const staged = this.#staged;\n const owner = { packetId: ownerPacketId, catalogTrackName };\n const throughParser = gated ||\n (staged !== null && (isStream(owner, staged.from) || isStream(owner, staged.to)));\n const isVideoTrack = this.#classifyPacketId(ownerPacketId) === true;\n const frames: Array<{\n data: Uint8Array;\n timestamp: number;\n keyframe: boolean;\n authoredTiming?: AuthoredTiming;\n packetSequenceNumber?: number;\n packetSequenceNumbers?: number[];\n repairedPacketSequenceNumbers?: number[];\n mmtpTimestamp?: bigint;\n mpuSequenceNumber?: number;\n movieFragmentSequenceNumber?: number;\n sampleNumber?: number;\n catalogTrackName?: string;\n deliveryProvenance?: MmtpDeliveryProvenance;\n sourceDiscontinuity?: boolean;\n }> = [];\n let dedupedAgainstSource = 0;\n let droppedBeforeSync = 0;\n for (const sym of result.symbols) {\n // An in-place switch can retire this owner from inside this loop (a\n // recovered frame that reaches the splice); the rest have no route.\n if (this.#classifyPacketId(ownerPacketId) === null) return;\n if (sym.fragmentType === 0 || sym.fragmentType === 1) continue;\n // FT=0/FT=1 are MPU metadata (initialization/movie fragment), already\n // consumed above to seed the authored timing resolver. Only FT=2 MFU\n // payloads can become media samples here.\n const fi = sym.fragment.fragmentationIndicator;\n if (fi === 0) {\n // Single-packet MFU. Dedup against source path so frames aren't\n // emitted twice. The direct source frame may still be buffered by the\n // parser reorder window, so use source-received sequence numbers.\n if (this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) {\n dedupedAgainstSource++;\n continue;\n }\n const keyframe = isVideoTrack\n ? this.#detectKeyframe(\n ownerPacketId,\n sym.fragment.data,\n isMfuKeyframeMetadata(sym.fragment.rapFlag, sym.fragment.priority),\n )\n : sym.fragment.rapFlag;\n if (throughParser) {\n const gatedTiming = this.#resolveRecoveredTiming(\n ownerPacketId,\n sym.fragment.mpuSequenceNumber,\n sym.fragment.movieFragmentSequenceNumber,\n sym.fragment.sampleNumber,\n sym.fragment.timestamp,\n catalogTrackName,\n );\n if (!gatedTiming) continue;\n this.#parser.routeFrame(sym.fragment.data, {\n timestamp: gatedTiming.timestamp,\n mmtpTimestamp: gatedTiming.mmtpTimestamp,\n ...(gatedTiming.authoredTiming === undefined\n ? {}\n : { authoredTiming: gatedTiming.authoredTiming }),\n keyframe: isVideoTrack ? keyframe : true,\n packetId: ownerPacketId,\n trackType: isVideoTrack ? \"video\" : \"audio\",\n packetSequenceNumber: sym.packetSequenceNumber,\n packetSequenceNumbers: [sym.packetSequenceNumber],\n repairedPacketSequenceNumbers: [sym.packetSequenceNumber],\n mpuSequenceNumber: sym.fragment.mpuSequenceNumber,\n ...(sym.fragment.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber }),\n ...(sym.fragment.sampleNumber === undefined\n ? {}\n : { sampleNumber: sym.fragment.sampleNumber }),\n rapFlag: sym.fragment.rapFlag,\n recovered: true,\n deliveryProvenance,\n fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n });\n continue;\n }\n const sourceDiscontinuity = this.#opensFecSync(\n ownerPacketId,\n keyframe,\n catalogTrackName,\n );\n if (!this.#shouldEmitFrame(ownerPacketId, keyframe, catalogTrackName)) {\n droppedBeforeSync++;\n continue;\n }\n const recoveredTiming = this.#resolveRecoveredTiming(\n ownerPacketId,\n sym.fragment.mpuSequenceNumber,\n sym.fragment.movieFragmentSequenceNumber,\n sym.fragment.sampleNumber,\n sym.fragment.timestamp,\n catalogTrackName,\n );\n if (!recoveredTiming) continue;\n frames.push({\n data: sym.fragment.data,\n timestamp: recoveredTiming.timestamp,\n mmtpTimestamp: recoveredTiming.mmtpTimestamp,\n keyframe,\n authoredTiming: recoveredTiming.authoredTiming,\n packetSequenceNumber: sym.packetSequenceNumber,\n packetSequenceNumbers: [sym.packetSequenceNumber],\n repairedPacketSequenceNumbers: [sym.packetSequenceNumber],\n mpuSequenceNumber: sym.fragment.mpuSequenceNumber,\n ...(sym.fragment.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber }),\n ...(sym.fragment.sampleNumber === undefined\n ? {}\n : { sampleNumber: sym.fragment.sampleNumber }),\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n // A single-packet MFU needs the block verdict just as much as a\n // reassembled one. The multi-fragment branch below gets it via the\n // reassembler fold (`assembled.deliveryProvenance`); this branch\n // never touches the reassembler, so it must carry the block-level\n // verdict directly or the sample reaches MSE unattributed.\n deliveryProvenance,\n ...(sourceDiscontinuity ? { sourceDiscontinuity: true } : {}),\n });\n this.#noteEmittedKeyframe(ownerPacketId, keyframe);\n } else {\n // Multi-packet MFU. Feed to reassembler; emit only when complete.\n // A decoded block contains every source symbol, including fragments\n // already accepted on the direct path. Re-feeding those fragments as\n // recovered would replace their original attribution in the shared\n // reassembler and overstate repairedPacketSequenceNumbers.\n if (this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) {\n dedupedAgainstSource++;\n continue;\n }\n let assembled;\n try {\n assembled = this.#parser.reassembler.addFragment({\n fragmentationIndicator: fi,\n mpuSequenceNumber: sym.fragment.mpuSequenceNumber,\n fragmentCounter: sym.fragment.fragmentCounter,\n data: sym.fragment.data,\n timestamp: sym.fragment.timestamp,\n rapFlag: sym.fragment.rapFlag,\n sampleNumber: sym.fragment.sampleNumber,\n movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber,\n sampleOffset: sym.fragment.sampleOffset,\n priority: sym.fragment.priority,\n packetSequenceNumber: sym.packetSequenceNumber,\n recovered: true,\n // Without this the fold sees a fragment with neither `source`\n // nor a verdict, returns 'unknown', and the block-level finding\n // computed above is lost before the frame reaches any consumer.\n deliveryProvenance,\n packetId: ownerPacketId,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n });\n } catch (error) {\n if (!isMfuReassemblyError(error)) throw error;\n console.warn(\n `[MmtpRouter] dropped rejected recovered MFU fragment: block=${blockId} ` +\n `packetId=${ownerPacketId} code=${error.code}`,\n );\n continue;\n }\n if (assembled) {\n const keyframe = isVideoTrack\n ? this.#detectKeyframe(\n ownerPacketId,\n assembled.data,\n isMfuKeyframeMetadata(assembled.rapFlag, assembled.priority),\n )\n : assembled.rapFlag;\n const recoveredCatalogTrackName = assembled.catalogTrackName ?? catalogTrackName;\n if (throughParser) {\n const gatedTiming = this.#resolveRecoveredTiming(\n ownerPacketId,\n assembled.mpuSequenceNumber,\n assembled.movieFragmentSequenceNumber,\n assembled.sampleNumber,\n assembled.timestamp,\n recoveredCatalogTrackName,\n );\n if (!gatedTiming) continue;\n this.#parser.routeFrame(assembled.data, {\n timestamp: gatedTiming.timestamp,\n mmtpTimestamp: gatedTiming.mmtpTimestamp,\n ...(gatedTiming.authoredTiming === undefined\n ? {}\n : { authoredTiming: gatedTiming.authoredTiming }),\n keyframe: isVideoTrack ? keyframe : true,\n packetId: ownerPacketId,\n trackType: isVideoTrack ? \"video\" : \"audio\",\n packetSequenceNumber: sym.packetSequenceNumber,\n ...(assembled.packetSequenceNumbers === undefined\n ? {}\n : { packetSequenceNumbers: assembled.packetSequenceNumbers }),\n ...(assembled.repairedPacketSequenceNumbers === undefined\n ? {}\n : { repairedPacketSequenceNumbers: assembled.repairedPacketSequenceNumbers }),\n mpuSequenceNumber: assembled.mpuSequenceNumber,\n ...(assembled.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: assembled.movieFragmentSequenceNumber }),\n ...(assembled.sampleNumber === undefined\n ? {}\n : { sampleNumber: assembled.sampleNumber }),\n rapFlag: assembled.rapFlag,\n recovered: true,\n ...(assembled.deliveryProvenance === undefined\n ? {}\n : { deliveryProvenance: assembled.deliveryProvenance }),\n fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),\n ...(recoveredCatalogTrackName === undefined\n ? {}\n : { catalogTrackName: recoveredCatalogTrackName }),\n });\n continue;\n }\n const sourceDiscontinuity = this.#opensFecSync(\n ownerPacketId,\n keyframe,\n recoveredCatalogTrackName,\n );\n if (!this.#shouldEmitFrame(\n ownerPacketId,\n keyframe,\n recoveredCatalogTrackName,\n )) {\n droppedBeforeSync++;\n continue;\n }\n const recoveredTiming = this.#resolveRecoveredTiming(\n ownerPacketId,\n assembled.mpuSequenceNumber,\n assembled.movieFragmentSequenceNumber,\n assembled.sampleNumber,\n assembled.timestamp,\n recoveredCatalogTrackName,\n );\n if (!recoveredTiming) continue;\n frames.push({\n data: assembled.data,\n timestamp: recoveredTiming.timestamp,\n mmtpTimestamp: recoveredTiming.mmtpTimestamp,\n keyframe,\n authoredTiming: recoveredTiming.authoredTiming,\n // The singular delivery identity is the fragment that completed\n // and delivered the MFU, matching the callback/parser path.\n packetSequenceNumber: sym.packetSequenceNumber,\n ...(assembled.packetSequenceNumbers === undefined\n ? {}\n : { packetSequenceNumbers: assembled.packetSequenceNumbers }),\n ...(assembled.repairedPacketSequenceNumbers === undefined\n ? {}\n : {\n repairedPacketSequenceNumbers:\n assembled.repairedPacketSequenceNumbers,\n }),\n mpuSequenceNumber: assembled.mpuSequenceNumber,\n ...(assembled.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: assembled.movieFragmentSequenceNumber }),\n ...(assembled.sampleNumber === undefined\n ? {}\n : { sampleNumber: assembled.sampleNumber }),\n ...(assembled.catalogTrackName === undefined\n ? {}\n : { catalogTrackName: assembled.catalogTrackName }),\n deliveryProvenance: assembled.deliveryProvenance,\n ...(sourceDiscontinuity ? { sourceDiscontinuity: true } : {}),\n });\n this.#noteEmittedKeyframe(ownerPacketId, keyframe);\n }\n }\n }\n if (frames.length > 0) {\n assembler.addRecoveredBlock(blockId, frames);\n // The assembler owns MSE writes, but the normal frame callback still\n // carries transport admission: MulticastManager uses it to confirm\n // video/audio and MediaRouter uses that confirmation to complete the\n // takeover. Recovered frames bypass parser.onFrame, so without this\n // callback MSE can receive samples forever while takeover never fires.\n const cb = this.#getFrameCallback(ownerPacketId);\n for (const frame of frames) {\n cb?.(frame.data, {\n timestamp: frame.timestamp,\n ...(frame.mmtpTimestamp === undefined ? {} : { mmtpTimestamp: frame.mmtpTimestamp }),\n keyframe: frame.keyframe,\n ...(frame.authoredTiming === undefined ? {} : { authoredTiming: frame.authoredTiming }),\n packetId: ownerPacketId,\n ...(frame.packetSequenceNumber === undefined\n ? {}\n : { packetSequenceNumber: frame.packetSequenceNumber }),\n ...(frame.packetSequenceNumbers === undefined\n ? {}\n : { packetSequenceNumbers: frame.packetSequenceNumbers }),\n ...(frame.repairedPacketSequenceNumbers === undefined\n ? {}\n : { repairedPacketSequenceNumbers: frame.repairedPacketSequenceNumbers }),\n ...(frame.mpuSequenceNumber === undefined ? {} : { mpuSequenceNumber: frame.mpuSequenceNumber }),\n ...(frame.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: frame.movieFragmentSequenceNumber }),\n ...(frame.sampleNumber === undefined ? {} : { sampleNumber: frame.sampleNumber }),\n ...(frame.catalogTrackName === undefined ? {} : { catalogTrackName: frame.catalogTrackName }),\n recovered: true,\n deliveryProvenance: frame.deliveryProvenance,\n ...(frame.sourceDiscontinuity ? { sourceDiscontinuity: true } : {}),\n });\n }\n debugRouter(\n `[MmtpRouter] Block ${blockId}: ${result.symbols.length} symbols \u2192 ${frames.length} reassembled frames \u2192 MSE assembler (packetId=${ownerPacketId}, dedupedAgainstSource=${dedupedAgainstSource}, droppedBeforeSync=${droppedBeforeSync})`,\n );\n } else if (result.symbols.length > 0) {\n // Recovery yielded symbols but no complete frames emerged \u2014 either all\n // FI=0 were source-path duplicates, or all FI=1/2/3 are still mid-\n // reassembly. Surface this so an operator debugging \"FEC recovered N\n // symbols but no new frames rendered\" has a signal.\n debugRouter(\n `[MmtpRouter] Block ${blockId}: ${result.symbols.length} symbols recovered \u2192 0 emitted (dedupedAgainstSource=${dedupedAgainstSource}, droppedBeforeSync=${droppedBeforeSync}, pending-reassembly=${result.symbols.length - dedupedAgainstSource - droppedBeforeSync}, packetId=${ownerPacketId})`,\n );\n }\n return;\n }\n\n const cb = this.#getFrameCallback(ownerPacketId);\n const isVideo = this.#classifyPacketId(ownerPacketId) === true;\n let droppedBeforeSync = 0;\n let droppedStale = 0;\n let dedupedAgainstSource = 0;\n\n for (const sym of result.symbols) {\n // An in-place switch can retire this owner from inside this loop (a\n // recovered frame that reaches the splice); the rest have no route.\n if (this.#classifyPacketId(ownerPacketId) === null) return;\n // FT=0 init already delivered above \u2014 skip data routing\n if (sym.fragmentType === 0 || sym.fragmentType === 1) continue;\n // FT=0/FT=1 are metadata, not decoder samples. The metadata pass above\n // has already used them to establish the timed-MFU contract.\n\n if (isVideo) {\n const fi = sym.fragment.fragmentationIndicator;\n if (fi === 0) {\n // Dedup: skip FI=0 frames already received via direct source path.\n // The reassembler deduplicates FI=1/2/3 fragments via #emitted, but\n // FI=0 (single-packet MFU) bypasses it. Without this check, recovery\n // can re-emit a source frame that is only held in the reorder buffer,\n // producing duplicate timestamps when the source path later flushes.\n if (this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) {\n dedupedAgainstSource++;\n continue;\n }\n if (this.#isStaleRecoveredVideo(\n ownerPacketId,\n sym.packetSequenceNumber,\n catalogTrackName,\n )) {\n droppedStale++;\n continue;\n }\n\n const d = sym.fragment.data;\n const keyframe = this.#detectKeyframe(\n ownerPacketId,\n d,\n isMfuKeyframeMetadata(sym.fragment.rapFlag, sym.fragment.priority),\n );\n const recoveredTiming = this.#resolveRecoveredTiming(\n ownerPacketId,\n sym.fragment.mpuSequenceNumber,\n sym.fragment.movieFragmentSequenceNumber,\n sym.fragment.sampleNumber,\n sym.fragment.timestamp,\n catalogTrackName,\n );\n if (!recoveredTiming) continue;\n this.#parser.routeFrame(d, {\n timestamp: recoveredTiming.timestamp,\n mmtpTimestamp: recoveredTiming.mmtpTimestamp,\n authoredTiming: recoveredTiming.authoredTiming,\n keyframe,\n packetId: ownerPacketId,\n trackType: \"video\",\n packetSequenceNumber: sym.packetSequenceNumber,\n packetSequenceNumbers: [sym.packetSequenceNumber],\n repairedPacketSequenceNumbers: [sym.packetSequenceNumber],\n mpuSequenceNumber: sym.fragment.mpuSequenceNumber,\n ...(sym.fragment.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber }),\n ...(sym.fragment.sampleNumber === undefined\n ? {}\n : { sampleNumber: sym.fragment.sampleNumber }),\n rapFlag: sym.fragment.rapFlag,\n recovered: true,\n deliveryProvenance,\n fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n });\n } else {\n // Fragment of a multi-packet MFU \u2014 must go through reassembler\n if (this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) {\n dedupedAgainstSource++;\n continue;\n }\n let assembled;\n try {\n assembled = this.#parser.reassembler.addFragment({\n fragmentationIndicator: fi,\n mpuSequenceNumber: sym.fragment.mpuSequenceNumber,\n fragmentCounter: sym.fragment.fragmentCounter,\n data: sym.fragment.data,\n timestamp: sym.fragment.timestamp,\n rapFlag: sym.fragment.rapFlag,\n sampleNumber: sym.fragment.sampleNumber,\n movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber,\n sampleOffset: sym.fragment.sampleOffset,\n priority: sym.fragment.priority,\n packetSequenceNumber: sym.packetSequenceNumber,\n recovered: true,\n // Without this the fold sees a fragment with neither `source`\n // nor a verdict, returns 'unknown', and the block-level finding\n // computed above is lost before the frame reaches any consumer.\n deliveryProvenance,\n packetId: ownerPacketId,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n });\n } catch (error) {\n if (!isMfuReassemblyError(error)) throw error;\n console.warn(\n `[MmtpRouter] dropped rejected recovered MFU fragment: block=${blockId} ` +\n `packetId=${ownerPacketId} code=${error.code}`,\n );\n continue;\n }\n if (assembled) {\n if (this.#isStaleRecoveredVideo(\n ownerPacketId,\n sym.packetSequenceNumber,\n assembled.catalogTrackName,\n )) {\n droppedStale++;\n continue;\n }\n const keyframe = this.#detectKeyframe(\n ownerPacketId,\n assembled.data,\n isMfuKeyframeMetadata(assembled.rapFlag, assembled.priority),\n );\n const recoveredCatalogTrackName = assembled.catalogTrackName ?? catalogTrackName;\n const recoveredTiming = this.#resolveRecoveredTiming(\n ownerPacketId,\n assembled.mpuSequenceNumber,\n assembled.movieFragmentSequenceNumber,\n assembled.sampleNumber,\n assembled.timestamp,\n recoveredCatalogTrackName,\n );\n if (!recoveredTiming) continue;\n this.#parser.routeFrame(assembled.data, {\n timestamp: recoveredTiming.timestamp,\n mmtpTimestamp: recoveredTiming.mmtpTimestamp,\n authoredTiming: recoveredTiming.authoredTiming,\n keyframe,\n packetId: ownerPacketId,\n trackType: \"video\",\n packetSequenceNumber: sym.packetSequenceNumber,\n ...(assembled.packetSequenceNumbers === undefined\n ? {}\n : { packetSequenceNumbers: assembled.packetSequenceNumbers }),\n ...(assembled.repairedPacketSequenceNumbers === undefined\n ? {}\n : {\n repairedPacketSequenceNumbers:\n assembled.repairedPacketSequenceNumbers,\n }),\n mpuSequenceNumber: assembled.mpuSequenceNumber,\n ...(assembled.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: assembled.movieFragmentSequenceNumber }),\n ...(assembled.sampleNumber === undefined\n ? {}\n : { sampleNumber: assembled.sampleNumber }),\n rapFlag: assembled.rapFlag,\n recovered: true,\n deliveryProvenance,\n fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),\n ...(assembled.catalogTrackName === undefined\n ? {}\n : { catalogTrackName: assembled.catalogTrackName }),\n });\n }\n }\n } else {\n // Audio recovery: dedup single-packet MFUs already emitted via direct source path\n // (parity with video FI=0 path at line 993). Prevents re-delivering audio frames\n // with duplicate timestamps \u2014 doubled samples in the FIFO ring buffer corrupt the\n // 1kHz test tone (pitch/energy instability in FFT spectrum).\n if (this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) {\n dedupedAgainstSource++;\n continue;\n }\n const recoveredTiming = this.#resolveRecoveredTiming(\n ownerPacketId,\n sym.fragment.mpuSequenceNumber,\n sym.fragment.movieFragmentSequenceNumber,\n sym.fragment.sampleNumber,\n sym.fragment.timestamp,\n catalogTrackName,\n );\n if (recoveredTiming === null) continue;\n if (gated) {\n this.#parser.routeFrame(sym.fragment.data, {\n timestamp: recoveredTiming.timestamp,\n mmtpTimestamp: recoveredTiming.mmtpTimestamp,\n ...(recoveredTiming.authoredTiming === undefined\n ? {}\n : { authoredTiming: recoveredTiming.authoredTiming }),\n keyframe: true,\n packetId: ownerPacketId,\n trackType: \"audio\",\n packetSequenceNumber: sym.packetSequenceNumber,\n packetSequenceNumbers: [sym.packetSequenceNumber],\n repairedPacketSequenceNumbers: [sym.packetSequenceNumber],\n mpuSequenceNumber: sym.fragment.mpuSequenceNumber,\n ...(sym.fragment.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber }),\n ...(sym.fragment.sampleNumber === undefined\n ? {}\n : { sampleNumber: sym.fragment.sampleNumber }),\n recovered: true,\n deliveryProvenance,\n fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n });\n continue;\n }\n const meta = {\n timestamp: recoveredTiming.timestamp,\n mmtpTimestamp: recoveredTiming.mmtpTimestamp,\n authoredTiming: recoveredTiming.authoredTiming,\n keyframe: true,\n packetId: ownerPacketId,\n packetSequenceNumber: sym.packetSequenceNumber,\n packetSequenceNumbers: [sym.packetSequenceNumber],\n repairedPacketSequenceNumbers: [sym.packetSequenceNumber],\n mpuSequenceNumber: sym.fragment.mpuSequenceNumber,\n ...(sym.fragment.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber }),\n ...(sym.fragment.sampleNumber === undefined\n ? {}\n : { sampleNumber: sym.fragment.sampleNumber }),\n recovered: true,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n };\n this.#routeDirectRecoveredAudio(\n ownerPacketId,\n sym.fragment.data,\n meta,\n cb,\n );\n }\n }\n if (result.symbols.length > 0) {\n debugRouter(\n `[MmtpRouter] Block ${blockId}: ${result.symbols.length} symbols recovered (packetId=${ownerPacketId}, dedupedAgainstSource=${dedupedAgainstSource}, droppedBeforeSync=${droppedBeforeSync}, droppedStale=${droppedStale})`,\n );\n }\n }\n\n // \u2500\u2500 Private: keyframe detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Codec-aware keyframe detection from bitstream data.\n * H.264 IDR (NAL type 5/7), AV1 OBU_SEQUENCE_HEADER (type 1), VP9 frame_type bit. */\n #detectKeyframe(\n packetId: number,\n data: Uint8Array,\n rapFlag = false,\n ): boolean {\n const codec = this.#trackCodecs.get(packetId) ?? \"\";\n return MmtpContainerParser.isKeyframe(data, rapFlag, codec);\n }\n}\n", "/**\n * MMTP \u2192 CMAF converter per draft-ramadan-moq-mmt \u00A75.3.\n *\n * Converts reassembled MFU frames (raw codec samples from MMTP) into\n * fMP4 segments (moof+mdat) suitable for MSE SourceBuffer.appendBuffer().\n *\n * MPU metadata (fragment_type=0) packets from FFmpeg are complete\n * ftyp+moov init segments and can be used directly as MSE init data.\n *\n * MFU data (fragment_type=2) contains raw AVCC samples that must be\n * wrapped in moof+mdat boxes for MSE consumption.\n *\n * ISO 14496-12 (ISOBMFF) box construction is inline \u2014 no dependencies.\n */\n\n// \u2500\u2500 ISOBMFF box helpers \u2500\u2500\n\nfunction box(type: string, ...children: Uint8Array[]): Uint8Array {\n let size = 8\n for (const c of children) size += c.length\n const buf = new Uint8Array(size)\n new DataView(buf.buffer).setUint32(0, size)\n buf[4] = type.charCodeAt(0)\n buf[5] = type.charCodeAt(1)\n buf[6] = type.charCodeAt(2)\n buf[7] = type.charCodeAt(3)\n let pos = 8\n for (const c of children) {\n buf.set(c, pos)\n pos += c.length\n }\n return buf\n}\n\nfunction fullBox(type: string, version: number, flags: number, ...children: Uint8Array[]): Uint8Array {\n let size = 12\n for (const c of children) size += c.length\n const buf = new Uint8Array(size)\n const dv = new DataView(buf.buffer)\n dv.setUint32(0, size)\n buf[4] = type.charCodeAt(0)\n buf[5] = type.charCodeAt(1)\n buf[6] = type.charCodeAt(2)\n buf[7] = type.charCodeAt(3)\n dv.setUint32(8, (version << 24) | (flags & 0xffffff))\n let pos = 12\n for (const c of children) {\n buf.set(c, pos)\n pos += c.length\n }\n return buf\n}\n\nfunction u32(v: number): Uint8Array {\n const b = new Uint8Array(4)\n new DataView(b.buffer).setUint32(0, v)\n return b\n}\n\n// \u2500\u2500 Sample flags per ISO 14496-12 \u00A78.8.3.1 \u2500\u2500\nconst KEYFRAME_FLAGS = 0x02000000\nconst DELTA_FLAGS = 0x01010000\n\n// \u2500\u2500 Track geometry field ranges per ISO 14496-12 \u2500\u2500\n// tkhd/trex/tfhd track_ID and the mdhd/mvhd timescales are all unsigned 32-bit\n// fields, written below with DataView.setUint32. That call takes the value\n// modulo 2^32 without complaint, so an out-of-range configuration does not\n// fail \u2014 it silently emits different geometry than the caller asked for\n// (trackId 2^32 becomes track 0, which no fragment can ever reference).\nconst UINT32_MAX = 0xffffffff\n// mvhd next_track_ID is emitted as track_ID + 1 by both buildVideoInitSegment\n// and buildAudioInitSegment (ISO 14496-12 \u00A78.2.2.3, \"a non-zero integer\"). At\n// track_ID = UINT32_MAX that sum wraps to 0, which is both invalid and\n// indistinguishable from an unset field, so the largest usable track_ID is one\n// below the field maximum. (Cited by symbol, not line: this repo's docs gate\n// exists because line anchors rot silently \u2014 see rustfmt.yml's citation check.)\nconst MAX_TRACK_ID = UINT32_MAX - 1\nconst INT32_MIN = -0x80000000\nconst INT32_MAX = 0x7fffffff\n\n/**\n * Fail closed on a track_ID the fMP4 writers below cannot serialize faithfully.\n *\n * Called from both low-level encoders (encodeDataSegment,\n * encodeMultiSampleSegment) so every fragment path is covered by construction\n * rather than by enumerating callers. The class wrappers and init builders call\n * it earlier as well \u2014 partly for a clearer label, partly because the encoders\n * receive an already-incremented sequence number, so their throw would gap the\n * sequence on a call that emitted nothing.\n */\nfunction assertTrackId(trackId: number, label: string): void {\n if (!Number.isInteger(trackId) || trackId <= 0 || trackId > MAX_TRACK_ID) {\n throw new RangeError(`${label} must be an integer in 1..${MAX_TRACK_ID}, got ${trackId}`)\n }\n}\n\n/** Fail closed on a timescale the mdhd/mvhd uint32 fields cannot hold. */\nfunction assertTimescale(timescale: number, label: string): void {\n if (!Number.isInteger(timescale) || timescale <= 0 || timescale > UINT32_MAX) {\n throw new RangeError(`${label} must be an integer in 1..${UINT32_MAX}, got ${timescale}`)\n }\n}\n\n/** Fail closed on a signed trun sample_composition_time_offset. */\nfunction assertCompositionTimeOffsets(\n samples: Array<{ compositionTimeOffset?: number }>,\n): void {\n for (let i = 0; i < samples.length; i++) {\n const offset = samples[i]!.compositionTimeOffset\n if (offset !== undefined &&\n (!Number.isInteger(offset) || offset < INT32_MIN || offset > INT32_MAX)) {\n throw new RangeError(\n `sample ${i} compositionTimeOffset must be an integer in ${INT32_MIN}..${INT32_MAX}, got ${offset}`,\n )\n }\n }\n}\n\n/**\n * Wrap multiple samples in a single moof+mdat fMP4 segment.\n *\n * Builds a trun with N sample entries (duration + size + flags each).\n * Used by CmafBlockAssembler to emit one segment per FEC source block.\n *\n * @param samples - Array of samples with data, duration (timescale units), keyframe flag,\n * and an optional signed composition-time offset (same timescale units)\n * @param baseDecodeTime - Decode time of the first sample in timescale units\n * @param sequence - moof sequence number\n * @param trackId - ISO track ID (default 1)\n * @returns moof+mdat bytes ready for SourceBuffer.appendBuffer()\n */\nexport function encodeMultiSampleSegment(\n samples: Array<{\n data: Uint8Array\n duration: number\n keyframe: boolean\n compositionTimeOffset?: number\n }>,\n baseDecodeTime: number,\n sequence: number,\n trackId = 1,\n): Uint8Array {\n if (samples.length === 0) return new Uint8Array(0)\n assertTrackId(trackId, 'trackId')\n assertCompositionTimeOffsets(samples)\n\n const mfhd = fullBox('mfhd', 0, 0, u32(sequence))\n const tfhd = fullBox('tfhd', 0, 0x020000, u32(trackId))\n\n // tfdt version=1 for 64-bit baseMediaDecodeTime\n const tfdtBody = new Uint8Array(8)\n const tfdtView = new DataView(tfdtBody.buffer)\n tfdtView.setUint32(0, Math.floor(baseDecodeTime / 0x100000000))\n tfdtView.setUint32(4, baseDecodeTime >>> 0)\n const tfdt = fullBox('tfdt', 1, 0, tfdtBody)\n\n // A composition-time offset is signed only in trun version 1. When one is\n // present, every sample gets the field; omitted offsets are encoded as zero.\n const hasCompositionTimeOffset = samples.some(sample => sample.compositionTimeOffset !== undefined)\n const trunVersion = hasCompositionTimeOffset ? 1 : 0\n const trunFlags = 0x000701 | (hasCompositionTimeOffset ? 0x000800 : 0)\n // 4 bytes sample_count + 4 bytes data_offset + N * (4+4+4 [+4]) per sample\n const sampleCount = samples.length\n const sampleEntrySize = hasCompositionTimeOffset ? 16 : 12\n const trunBody = new Uint8Array(8 + sampleCount * sampleEntrySize)\n const trunView = new DataView(trunBody.buffer)\n trunView.setUint32(0, sampleCount)\n trunView.setUint32(4, 0) // dataOffset placeholder\n for (let i = 0; i < sampleCount; i++) {\n const s = samples[i]!\n const off = 8 + i * sampleEntrySize\n trunView.setUint32(off, s.duration >>> 0)\n trunView.setUint32(off + 4, s.data.length)\n trunView.setUint32(off + 8, s.keyframe ? KEYFRAME_FLAGS : DELTA_FLAGS)\n if (hasCompositionTimeOffset) {\n trunView.setInt32(off + 12, s.compositionTimeOffset ?? 0)\n }\n }\n const trun = fullBox('trun', trunVersion, trunFlags, trunBody)\n\n const traf = box('traf', tfhd, tfdt, trun)\n const moof = box('moof', mfhd, traf)\n\n // Concatenate all sample data into mdat\n let mdatSize = 0\n for (const s of samples) mdatSize += s.data.length\n const mdatPayload = new Uint8Array(mdatSize)\n let pos = 0\n for (const s of samples) {\n mdatPayload.set(s.data, pos)\n pos += s.data.length\n }\n const mdat = box('mdat', mdatPayload)\n\n // Fix trun.dataOffset: moof size + 8 (mdat box header)\n const dataOffsetPos = 8 + mfhd.length + 8 + tfhd.length + tfdt.length + 12 + 4\n new DataView(moof.buffer).setUint32(dataOffsetPos, moof.length + 8)\n\n const result = new Uint8Array(moof.length + mdat.length)\n result.set(moof, 0)\n result.set(mdat, moof.length)\n return result\n}\n\n// \u2500\u2500 Silence generator \u2500\u2500\n\n/**\n * Generate a minimal silent audio frame for FEC fill.\n *\n * Supports AAC (mp4a) and Opus codecs. Returns raw frame data (no ADTS/OBU\n * header) suitable for fMP4/CMAF wrapping.\n *\n * @param codec - Audio codec string (e.g. 'mp4a.40.2', 'opus')\n * @param sampleRate - Audio sample rate (e.g. 48000)\n * @param channels - Channel count (e.g. 1 or 2)\n */\nexport function generateSilenceFrame(codec: string, sampleRate: number, channels: number): Uint8Array {\n if (codec.startsWith('opus') || codec.startsWith('Opus')) {\n return generateSilenceOpus(channels)\n }\n return generateSilenceAAC(sampleRate, channels)\n}\n\n/** Minimal silent AAC-LC access unit (raw, no ADTS). */\nfunction generateSilenceAAC(sampleRate: number, channels: number): Uint8Array {\n if (channels >= 2) {\n // CPE (channel pair element) + END\n return new Uint8Array([\n 0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80,\n ])\n }\n // SCE (single channel element) + END\n return new Uint8Array([\n 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,\n ])\n}\n\n/** Minimal silent Opus frame.\n * RFC 6716 \u00A73.1: a 1-byte frame with code 0 encodes silence for SILK/CELT. */\nfunction generateSilenceOpus(channels: number): Uint8Array {\n // Opus TOC byte: config=0 (SILK NB 10ms), s=0 (mono/stereo from channel mapping), c=0 (1 frame)\n // Frame data: single zero byte \u2192 silence\n // For stereo, channel mapping in fMP4 init handles routing; the Opus frame itself is the same.\n return new Uint8Array([0xF8, 0xFF, 0xFE])\n}\n\nexport function normalizeAacSampleForCmaf(data: Uint8Array): Uint8Array {\n if (data.length < 7 || data[0] !== 0xff || (data[1]! & 0xf6) !== 0xf0) {\n return data\n }\n const headerLength = (data[1]! & 0x01) ? 7 : 9\n if (data.length <= headerLength) return data\n const frameLength = ((data[3]! & 0x03) << 11) | (data[4]! << 3) | ((data[5]! & 0xe0) >>> 5)\n if (frameLength !== data.length || frameLength <= headerLength) return data\n return data.subarray(headerLength)\n}\n\n/**\n * Wrap a single raw codec sample in a moof+mdat fMP4 segment.\n *\n * @param data - Raw sample bytes (AVCC H.264 or raw AAC)\n * @param timestamp - Decode time in timescale units\n * @param duration - Sample duration in timescale units\n * @param keyframe - true for IDR/sync sample\n * @param sequence - moof sequence number (monotonically incrementing)\n * @param trackId - ISO track ID (default 1)\n * @returns moof+mdat bytes ready for SourceBuffer.appendBuffer()\n */\nexport function encodeDataSegment(\n data: Uint8Array,\n timestamp: number,\n duration: number,\n keyframe: boolean,\n sequence: number,\n trackId = 1,\n compositionTimeOffset?: number,\n): Uint8Array {\n assertTrackId(trackId, 'trackId')\n assertCompositionTimeOffsets([{ compositionTimeOffset }])\n if (compositionTimeOffset !== undefined) {\n return encodeMultiSampleSegment(\n [{ data, duration, keyframe, compositionTimeOffset }],\n timestamp,\n sequence,\n trackId,\n )\n }\n const sampleFlags = keyframe ? KEYFRAME_FLAGS : DELTA_FLAGS\n\n const mfhd = fullBox('mfhd', 0, 0, u32(sequence))\n const tfhd = fullBox('tfhd', 0, 0x020000, u32(trackId))\n\n // tfdt version=1 for 64-bit baseMediaDecodeTime\n const tfdtBody = new Uint8Array(8)\n const tfdtView = new DataView(tfdtBody.buffer)\n tfdtView.setUint32(0, Math.floor(timestamp / 0x100000000))\n tfdtView.setUint32(4, timestamp >>> 0)\n const tfdt = fullBox('tfdt', 1, 0, tfdtBody)\n\n // trun: flags=0x000701 (data-offset + duration + size + flags)\n const trunBody = new Uint8Array(20)\n const trunView = new DataView(trunBody.buffer)\n trunView.setUint32(0, 1) // sample_count\n trunView.setUint32(4, 0) // dataOffset placeholder\n trunView.setUint32(8, duration >>> 0)\n trunView.setUint32(12, data.length)\n trunView.setUint32(16, sampleFlags)\n const trun = fullBox('trun', 0, 0x000701, trunBody)\n\n const traf = box('traf', tfhd, tfdt, trun)\n const moof = box('moof', mfhd, traf)\n const mdat = box('mdat', data)\n\n // Fix trun.dataOffset: moof size + 8 (mdat box header)\n const dataOffsetPos = 8 + mfhd.length + 8 + tfhd.length + tfdt.length + 12 + 4\n new DataView(moof.buffer).setUint32(dataOffsetPos, moof.length + 8)\n\n const result = new Uint8Array(moof.length + mdat.length)\n result.set(moof, 0)\n result.set(mdat, moof.length)\n return result\n}\n\n/**\n * Build an ftyp+moov init segment for H.264 video MSE from an avcC box.\n *\n * @param avcC - Raw avcC box payload (SPS/PPS configuration)\n * @param width - Video width in pixels\n * @param height - Video height in pixels\n * @param timescale - Timescale (default 1_000_000 = microseconds)\n * @param trackId - Track ID (default 1)\n */\nexport function buildVideoInitSegment(\n avcC: Uint8Array,\n width: number,\n height: number,\n timescale = 1_000_000,\n trackId = 1,\n): Uint8Array {\n return buildVisualInitSegment('avc1', 'avcC', avcC, width, height, timescale, trackId)\n}\n\n/**\n * Build an ftyp+moov init segment for H.265/HEVC video MSE.\n *\n * The sample entry is `hev1` or `hvc1` (ISO/IEC 14496-15 \u00A78.4.1.1) carrying an\n * `hvcC` HEVCDecoderConfigurationRecord (\u00A78.3.3.1). Choose the entry from the\n * catalog codec string's first element, never by inspecting the bitstream:\n * `hev1` permits VPS/SPS/PPS to stay in-band in the samples (cast's\n * `repeat-headers=1` stream), `hvc1` requires every parameter set in the\n * `hvcC` and none in the samples.\n *\n * @param sampleEntry - `hev1` or `hvc1`\n * @param hvcC - Raw hvcC record payload (e.g. from `generateHvcCFromParameterSets`)\n * @param width - Video width in pixels\n * @param height - Video height in pixels\n * @param timescale - Media timescale\n * @param trackId - ISO track ID\n */\nexport function buildHevcVideoInitSegment(\n sampleEntry: 'hev1' | 'hvc1',\n hvcC: Uint8Array,\n width: number,\n height: number,\n timescale: number,\n trackId: number,\n): Uint8Array {\n if (sampleEntry !== 'hev1' && sampleEntry !== 'hvc1') {\n throw new Error(`HEVC init segment requires an hev1 or hvc1 sample entry, got ${String(sampleEntry)}`)\n }\n return buildVisualInitSegment(sampleEntry, 'hvcC', hvcC, width, height, timescale, trackId)\n}\n\n/**\n * ftyp+moov for one video track whose VisualSampleEntry (ISO/IEC 14496-12\n * \u00A712.1.3) carries a single codec configuration box. AVC (`avc1`/`avcC`) and\n * HEVC (`hev1`|`hvc1`/`hvcC`) share this layout byte for byte.\n */\nfunction buildVisualInitSegment(\n sampleEntryType: 'avc1' | 'hev1' | 'hvc1',\n configBoxType: 'avcC' | 'hvcC',\n configRecord: Uint8Array,\n width: number,\n height: number,\n timescale: number,\n trackId: number,\n): Uint8Array {\n // Validated here as well as in setVideoTrackParams: the builders are exported\n // from the package index, so a direct caller reaches the same uint32 tkhd /\n // trex / mdhd / mvhd fields without passing through the class.\n assertTrackId(trackId, 'video trackId')\n assertTimescale(timescale, 'video timescale')\n const ftyp = box('ftyp', new Uint8Array([\n 0x69, 0x73, 0x6f, 0x6d, 0, 0, 2, 0,\n 0x69, 0x73, 0x6f, 0x6d,\n 0x69, 0x73, 0x6f, 0x36,\n 0x6d, 0x70, 0x34, 0x31,\n ]))\n\n const configBox = box(configBoxType, configRecord)\n const sampleEntryBody = new Uint8Array(78 + configBox.length)\n const sampleEntryView = new DataView(sampleEntryBody.buffer)\n sampleEntryView.setUint16(6, 1)\n sampleEntryView.setUint16(24, width)\n sampleEntryView.setUint16(26, height)\n sampleEntryView.setUint32(28, 0x00480000)\n sampleEntryView.setUint32(32, 0x00480000)\n sampleEntryView.setUint16(40, 1)\n sampleEntryView.setUint16(74, 0x0018)\n sampleEntryView.setInt16(76, -1)\n sampleEntryBody.set(configBox, 78)\n const visualSampleEntry = box(sampleEntryType, sampleEntryBody)\n\n const stsd = fullBox('stsd', 0, 0, u32(1), visualSampleEntry)\n const stbl = box('stbl', stsd,\n fullBox('stts', 0, 0, u32(0)),\n fullBox('stsc', 0, 0, u32(0)),\n fullBox('stsz', 0, 0, u32(0), u32(0)),\n fullBox('stco', 0, 0, u32(0)),\n )\n const dinf = box('dinf', fullBox('dref', 0, 0, u32(1), fullBox('url ', 0, 1)))\n // ISO/IEC 14496-12 \u00A712.1.2: VideoMediaHeaderBox is a FullBox (flags = 1)\n // followed by graphicsmode (uint16) and opcolor (uint16[3]), all zero for\n // copy mode. WebKit's MSE parser rejects the whole init segment when those\n // 8 bytes are missing (SourceBuffer error on iPhone Safari); Chromium\n // tolerates it, which is how the short box went unnoticed.\n const minf = box('minf', fullBox('vmhd', 0, 1, new Uint8Array(8)), dinf, stbl)\n const hdlr = fullBox('hdlr', 0, 0, new Uint8Array([\n 0, 0, 0, 0, 0x76, 0x69, 0x64, 0x65,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0x56, 0x69, 0x64, 0x65, 0x6f, 0,\n ]))\n const mdhdBody = new Uint8Array(20)\n new DataView(mdhdBody.buffer).setUint32(8, timescale)\n const mdhd = fullBox('mdhd', 0, 0, mdhdBody)\n const mdia = box('mdia', mdhd, hdlr, minf)\n\n const tkhdBody = new Uint8Array(80)\n const tkhdView = new DataView(tkhdBody.buffer)\n tkhdView.setUint32(8, trackId)\n tkhdView.setUint32(36, 0x00010000)\n tkhdView.setUint32(52, 0x00010000)\n tkhdView.setUint32(64, 0x40000000)\n tkhdView.setUint32(72, width << 16)\n tkhdView.setUint32(76, height << 16)\n const tkhd = fullBox('tkhd', 0, 3, tkhdBody)\n const trak = box('trak', tkhd, mdia)\n\n const mvhdBody = new Uint8Array(96)\n const mvhdView = new DataView(mvhdBody.buffer)\n mvhdView.setUint32(8, timescale)\n mvhdView.setUint32(16, 0x00010000)\n mvhdView.setUint16(20, 0x0100)\n mvhdView.setUint32(32, 0x00010000)\n mvhdView.setUint32(48, 0x00010000)\n mvhdView.setUint32(60, 0x40000000)\n mvhdView.setUint32(92, trackId + 1)\n const mvhd = fullBox('mvhd', 0, 0, mvhdBody)\n\n const trexBody = new Uint8Array(20)\n new DataView(trexBody.buffer).setUint32(0, trackId)\n new DataView(trexBody.buffer).setUint32(4, 1)\n const mvex = box('mvex', fullBox('trex', 0, 0, trexBody))\n\n const moov = box('moov', mvhd, trak, mvex)\n const result = new Uint8Array(ftyp.length + moov.length)\n result.set(ftyp, 0)\n result.set(moov, ftyp.length)\n return result\n}\n\n/**\n * Build ftyp+moov init segment for AAC audio MSE.\n */\nexport function buildAudioInitSegment(\n sampleRate: number,\n channels: number,\n asc?: Uint8Array,\n timescale?: number,\n trackId = 1,\n): Uint8Array {\n // Validated here as well as in setAudioTimescale, for the same reason the\n // video builder is: this function is exported from the package index, so a\n // direct caller reaches the same uint32 tkhd / trex / mdhd / mvhd fields\n // without passing through the class.\n assertTrackId(trackId, 'audio trackId')\n const ts = timescale ?? sampleRate\n assertTimescale(ts, 'audio timescale')\n\n if (!asc) {\n const freqTable = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]\n const fi = freqTable.indexOf(sampleRate)\n const freqIdx = fi >= 0 ? fi : 0x0f\n if (freqIdx <= 0x0e) {\n const v = (2 << 11) | (freqIdx << 7) | (channels << 3)\n asc = new Uint8Array([(v >> 8) & 0xff, v & 0xff])\n } else {\n asc = new Uint8Array([\n (2 << 3) | 0x07,\n (0x08 | ((sampleRate >> 20) & 0x07)) & 0xff,\n (sampleRate >> 12) & 0xff,\n (sampleRate >> 4) & 0xff,\n (((sampleRate & 0x0f) << 4) | (channels << 0)) & 0xff,\n ])\n }\n }\n\n // esds: ES_Descriptor \u2192 DecoderConfigDescriptor \u2192 AudioSpecificConfig\n const dsi = new Uint8Array(2 + asc.length)\n dsi[0] = 0x05; dsi[1] = asc.length; dsi.set(asc, 2)\n const dcdBody = new Uint8Array(13 + dsi.length)\n dcdBody[0] = 0x40; dcdBody[1] = 0x15; dcdBody.set(dsi, 13)\n const dcd = new Uint8Array(2 + dcdBody.length)\n dcd[0] = 0x04; dcd[1] = dcdBody.length; dcd.set(dcdBody, 2)\n const esBody = new Uint8Array(3 + dcd.length)\n esBody[0] = 0; esBody[1] = 0x01; esBody[2] = 0; esBody.set(dcd, 3)\n const es = new Uint8Array(2 + esBody.length)\n es[0] = 0x03; es[1] = esBody.length; es.set(esBody, 2)\n const esdsBox = fullBox('esds', 0, 0, es)\n\n const mp4aBody = new Uint8Array(28 + esdsBox.length)\n const mp4aView = new DataView(mp4aBody.buffer)\n mp4aView.setUint16(6, 1)\n mp4aView.setUint16(16, channels)\n mp4aView.setUint16(18, 16)\n mp4aView.setUint32(24, sampleRate << 16)\n mp4aBody.set(esdsBox, 28)\n const mp4a = box('mp4a', mp4aBody)\n\n const stsd = fullBox('stsd', 0, 0, u32(1), mp4a)\n const stbl = box('stbl', stsd,\n fullBox('stts', 0, 0, u32(0)),\n fullBox('stsc', 0, 0, u32(0)),\n fullBox('stsz', 0, 0, u32(0), u32(0)),\n fullBox('stco', 0, 0, u32(0)),\n )\n const dinf = box('dinf', fullBox('dref', 0, 0, u32(1), fullBox('url ', 0, 1)))\n const minf = box('minf', fullBox('smhd', 0, 0, new Uint8Array(4)), dinf, stbl)\n const hdlr = fullBox('hdlr', 0, 0, new Uint8Array([\n 0, 0, 0, 0, 0x73, 0x6f, 0x75, 0x6e,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0x53, 0x6f, 0x75, 0x6e, 0x64, 0,\n ]))\n const mdhdBody = new Uint8Array(20)\n new DataView(mdhdBody.buffer).setUint32(8, ts)\n const mdhd = fullBox('mdhd', 0, 0, mdhdBody)\n const mdia = box('mdia', mdhd, hdlr, minf)\n\n const tkhdBody = new Uint8Array(80)\n const tkhdView = new DataView(tkhdBody.buffer)\n tkhdView.setUint32(8, trackId)\n tkhdView.setUint32(36, 0x00010000)\n tkhdView.setUint32(52, 0x00010000)\n tkhdView.setUint32(64, 0x40000000)\n const tkhd = fullBox('tkhd', 0, 3, tkhdBody)\n const trak = box('trak', tkhd, mdia)\n\n const mvhdBody = new Uint8Array(96)\n const mvhdView = new DataView(mvhdBody.buffer)\n mvhdView.setUint32(8, ts)\n mvhdView.setUint32(16, 0x00010000)\n mvhdView.setUint16(20, 0x0100)\n mvhdView.setUint32(32, 0x00010000)\n mvhdView.setUint32(48, 0x00010000)\n mvhdView.setUint32(60, 0x40000000)\n mvhdView.setUint32(92, trackId + 1)\n const mvhd = fullBox('mvhd', 0, 0, mvhdBody)\n\n const trexBody = new Uint8Array(20)\n new DataView(trexBody.buffer).setUint32(0, trackId)\n new DataView(trexBody.buffer).setUint32(4, 1)\n const mvex = box('mvex', fullBox('trex', 0, 0, trexBody))\n\n const moov = box('moov', mvhd, trak, mvex)\n const ftyp = box('ftyp', new Uint8Array([\n 0x69, 0x73, 0x6f, 0x6d, 0, 0, 2, 0,\n 0x69, 0x73, 0x6f, 0x6d,\n 0x69, 0x73, 0x6f, 0x36,\n 0x6d, 0x70, 0x34, 0x31,\n ]))\n const result = new Uint8Array(ftyp.length + moov.length)\n result.set(ftyp, 0)\n result.set(moov, ftyp.length)\n return result\n}\n\n/**\n * MMTP \u2192 CMAF converter.\n *\n * Accepts reassembled MFU frames (raw codec samples) from MmtpDecoder\n * and produces fMP4 segments for MSE SourceBuffer.\n *\n * Per draft-ramadan-moq-mmt \u00A75.3:\n * MPU metadata (ftyp+moov) \u2192 init segment\n * MFU sample bytes \u2192 moof+mdat data segment\n *\n * Timescale is microseconds (1_000_000) matching MMTP NTP short timestamps\n * converted via mmtpTimestampToMicros().\n */\nexport class MmtpToCmaf {\n #videoSeq = 1\n #audioSeq = 1\n #videoInitSegment: Uint8Array | null = null\n // Known only for locally-built init segments. Wire-supplied MPU metadata is\n // intentionally opaque here, so setVideoTrackParams remains the API that\n // lets a caller declare its track geometry before wrapping samples.\n #videoInitTrackId: number | null = null\n #audioInitSegment: Uint8Array | null = null\n #lastVideoTimestamp = 0\n // Sticky sample duration in video timescale units, updated from caller-\n // supplied durations or adjacent-timestamp deltas. null means \"nothing\n // observed yet\", in which case #videoFrameDuration derives ~30fps from the\n // configured timescale (33333 at the 1_000_000 default, i.e. unchanged).\n #frameDuration: number | null = null\n // Per-track rebase: each track anchors on its OWN first sample so\n // baseMediaDecodeTime is never negative. A shared base was wrong \u2014 if the\n // first sample of one track arrives later than the other's first sample,\n // the earlier track computes `timestamp - sharedBase` as a negative number,\n // which serializes into tfdt as a huge unsigned uint64 (~2^64 - 46) and\n // Chromium rejects the segment with CHUNK_DEMUXER_ERROR_APPEND_FAILED,\n // closing the MediaSource and detaching all SourceBuffers.\n // For A/V alignment, choose a COMMON anchor: set when the first sample of\n // EITHER track arrives, but only if that sample's timestamp is <= any\n // subsequent track's first sample. Implementation: each track rebases to\n // the minimum timestamp seen across tracks on their respective first sample.\n #videoBaseTs = -1\n #audioBaseTs = -1\n\n // Video track geometry. These pair with the video init segment's tkhd\n // track_ID and mdhd/mvhd timescale: every moof this converter emits must\n // reference the same track at the same rate or the browser either drops the\n // fragment (unknown track) or plays it at the wrong rate. They are\n // configuration rather than stream state, so \u2014 unlike the sequence numbers\n // and time bases above \u2014 reset() deliberately preserves them, matching the\n // fact that reset() also preserves #videoInitSegment.\n #videoTrackId = 1\n #videoTimescale = 1_000_000\n\n /**\n * Point the single-sample video wrappers at a specific track and rate.\n *\n * Call this when the init segment came off the wire (setInitSegmentFromMpu)\n * rather than from buildVideoInit, so the wrappers match the publisher's\n * tkhd/mdhd instead of assuming trackId=1 at microsecond rate. Consumers\n * that build their init through buildVideoInit can pass the same values\n * there instead and skip this call.\n *\n * The rate is settable only until the first single-sample wrap \u2014 see the\n * throw below for why.\n */\n setVideoTrackParams(trackId: number, timescale: number) {\n assertTrackId(trackId, 'video trackId')\n assertTimescale(timescale, 'video timescale')\n // #frameDuration, #lastVideoTimestamp and #videoBaseTs are all denominated\n // in #videoTimescale, so moving the rate out from under them reinterprets\n // every one at the new rate. Both consequences are silent:\n // - a duration latched as 33333 (1 MHz ~30fps) re-emits as 33333 ticks of\n // 90 kHz, a 370 ms frame;\n // - a base captured on the old scale rebases new-scale timestamps to a\n // negative relTs, which tfdt serializes as a ~2^64 uint64 and Chromium\n // rejects with CHUNK_DEMUXER_ERROR_APPEND_FAILED, closing the\n // MediaSource (same failure the #videoBaseTs comment above describes).\n // Rescaling instead of refusing would be a guess: the class cannot tell\n // whether the caller's next timestamps continue this timeline in new units\n // or open a fresh epoch. reset() is the exact mechanism for that\n // discontinuity \u2014 it clears all three fields while preserving geometry \u2014\n // so require it rather than infer. Re-asserting the same rate is a no-op.\n // A cached init also fixes the track ID in tkhd/trex; changing only the\n // wrapper setting would make later tfhd IDs disagree with that init.\n if (trackId !== this.#videoTrackId && this.#videoInitTrackId !== null) {\n throw new Error(\n `video track ID cannot change from ${this.#videoTrackId} to ${trackId} ` +\n 'after a video init has been cached; replace the init atomically',\n )\n }\n if (timescale !== this.#videoTimescale && this.#videoBaseTs >= 0) {\n throw new Error(\n `video timescale cannot change from ${this.#videoTimescale} to ${timescale} ` +\n `after video samples have been wrapped; call reset() first`,\n )\n }\n this.#videoTrackId = trackId\n this.#videoTimescale = timescale\n }\n\n /** Set init segment from MPU metadata packet (fragment_type=0).\n * These are complete ftyp+moov bytes from FFmpeg \u2014 use directly. */\n setInitSegmentFromMpu(data: Uint8Array, isAudio: boolean) {\n if (isAudio) {\n this.#audioInitSegment = data\n } else {\n this.#videoInitSegment = data\n this.#videoInitTrackId = null\n }\n }\n\n /** Build init segment from codec config (catalog path \u2014 no MPU metadata available).\n * Passing timescale/trackId also configures the wrappers via\n * setVideoTrackParams, so init and media segments cannot disagree. */\n buildVideoInit(avcC: Uint8Array, width: number, height: number, timescale?: number, trackId?: number) {\n const nextTimescale = timescale ?? this.#videoTimescale\n const nextTrackId = trackId ?? this.#videoTrackId\n assertTrackId(nextTrackId, 'video trackId')\n assertTimescale(nextTimescale, 'video timescale')\n if (nextTimescale !== this.#videoTimescale && this.#videoBaseTs >= 0) {\n throw new Error(\n `video timescale cannot change from ${this.#videoTimescale} to ${nextTimescale} ` +\n 'after video samples have been wrapped; call reset() first',\n )\n }\n // Build first, then publish both geometry and init together. This is the\n // sanctioned path for replacing a cached init with a new track ID.\n const init = buildVideoInitSegment(\n avcC,\n width,\n height,\n nextTimescale,\n nextTrackId,\n )\n this.#videoTimescale = nextTimescale\n this.#videoTrackId = nextTrackId\n this.#videoInitSegment = init\n this.#videoInitTrackId = nextTrackId\n }\n\n buildAudioInit(sampleRate: number, channels: number, asc?: Uint8Array, timescale?: number) {\n const ts = timescale ?? sampleRate\n // Build first, then publish, as the video path does: buildAudioInitSegment\n // validates, so assigning the field first would leave a rejected timescale\n // latched on the instance with no init segment to match it.\n const init = buildAudioInitSegment(sampleRate, channels, asc, ts)\n this.#audioTimescale = ts\n this.#audioInitSegment = init\n }\n\n get videoInitSegment(): Uint8Array | null { return this.#videoInitSegment }\n get audioInitSegment(): Uint8Array | null { return this.#audioInitSegment }\n\n /** Convert a raw video sample to an fMP4 moof+mdat segment. */\n wrapVideoSample(\n data: Uint8Array,\n timestamp: number,\n keyframe: boolean,\n duration?: number,\n compositionTimeOffset?: number,\n ): Uint8Array {\n return this.wrapVideoSampleWithTiming(data, timestamp, keyframe, duration, compositionTimeOffset).segment\n }\n\n /**\n * Convert a raw video sample and return the exact browser media interval\n * encoded into tfdt/trun. Consumers that correlate SourceBuffer appends with\n * requestVideoFrameCallback must use this rebased timestamp, not the wire or\n * caller-owned timestamp passed above.\n *\n * `compositionTimeOffset` is the signed authored PTS-DTS delta, in the same\n * #videoTimescale units as `timestamp` and `duration` \u2014 unlike wrapAudioSample,\n * which takes microseconds and rescales, because every other quantity on this\n * path is already denominated in #videoTimescale (see setVideoTrackParams).\n * Passing it therefore needs no conversion here; rescaling would double-convert.\n * Absent leaves the trun at version 0 with no offset field; a declared 0 is an\n * authored value and still promotes to version 1. Never reconstruct it from\n * arrival time.\n */\n wrapVideoSampleWithTiming(\n data: Uint8Array,\n timestamp: number,\n keyframe: boolean,\n duration?: number,\n compositionTimeOffset?: number,\n ): Readonly<{ segment: Uint8Array; mediaTimestamp: number; duration: number }> {\n // Checked before #videoSeq++ and before the duration/base-timestamp latches\n // below, for the same reason as wrapAudioSample and wrapVideoSamples: the\n // encoder validates too, but by then the sequence number is spent and a\n // caller that corrects the offset and retries emits a gapped mfhd.\n assertCompositionTimeOffsets([{ compositionTimeOffset }])\n // Prefer a caller-provided media duration when the browser append timeline\n // is intentionally decoupled from sparse MMTP wire timestamps. Fall back to\n // estimating from adjacent timestamps for legacy/direct callers. A delta of\n // a full second or more is not a frame interval, so it is rejected as a\n // discontinuity rather than latched.\n if (duration !== undefined && Number.isFinite(duration) && duration > 0) {\n this.#frameDuration = Math.round(duration)\n } else if (this.#lastVideoTimestamp > 0 && timestamp > this.#lastVideoTimestamp) {\n const delta = timestamp - this.#lastVideoTimestamp\n if (delta > 0 && delta < this.#videoTimescale) this.#frameDuration = delta\n }\n this.#lastVideoTimestamp = timestamp\n if (this.#videoBaseTs < 0) this.#videoBaseTs = timestamp\n const relTs = timestamp - this.#videoBaseTs\n const frameDuration = this.#videoFrameDuration\n return Object.freeze({\n segment: encodeDataSegment(\n data,\n relTs,\n frameDuration,\n keyframe,\n this.#videoSeq++,\n this.#videoTrackId,\n compositionTimeOffset,\n ),\n mediaTimestamp: relTs,\n duration: frameDuration,\n })\n }\n\n /** Sticky duration if one has been observed, else ~30fps at the current rate. */\n get #videoFrameDuration(): number {\n return this.#frameDuration ?? Math.max(1, Math.round(this.#videoTimescale / 30))\n }\n\n /** Convert an append-safe video batch using the caller-owned timeline.\n * Durations, baseDecodeTime, and compositionTimeOffset are in the\n * configured video timescale. compositionTimeOffset is the signed PTS-DTS\n * delta; its presence selects a version-1 trun for the whole batch.\n * An explicit trackId is range-checked before #videoSeq++ for the same\n * reason as wrapAudioSamples: the encoder's own check fires too late to\n * keep the sequence ungapped. */\n wrapVideoSamples(\n samples: Array<{\n data: Uint8Array\n duration: number\n keyframe: boolean\n compositionTimeOffset?: number\n }>,\n baseDecodeTime: number,\n trackId = this.#videoTrackId,\n ): Uint8Array {\n assertTrackId(trackId, 'video trackId')\n assertCompositionTimeOffsets(samples)\n return encodeMultiSampleSegment(samples, baseDecodeTime, this.#videoSeq++, trackId)\n }\n\n // Audio timescale from init segment (default 1M \u00B5s; real FFmpeg init uses sampleRate).\n // Set by buildAudioInit or setAudioTimescale.\n #audioTimescale = 1_000_000\n\n /** Set audio timescale to match the init segment's mdhd timescale.\n * Call after appending the real encoder init (FT=0 ftyp+moov).\n * Fails closed on a value the mdhd uint32 cannot hold, as the video paths\n * already do. wrapAudioSample rescales the authored composition offset by\n * this value, so the two failure modes are both silent: a 0 multiplies\n * every authored offset to 0 \u2014 indistinguishable from a feed that authored\n * none \u2014 while a value above uint32 scales the trun to a timescale the\n * mdhd cannot declare, since it is emitted with setUint32 modulo 2^32. */\n setAudioTimescale(timescale: number) {\n assertTimescale(timescale, 'audio timescale')\n this.#audioTimescale = timescale\n }\n\n // Audio sample counter for drift-free baseMediaDecodeTime.\n // AAC frames are exactly 1024 samples at sampleRate. Using sequential\n // counter \u00D7 1024 avoids floating-point rounding drift from timestamp conversion.\n #audioSampleCount = 0\n\n /** AAC access-unit length in samples. ISO/IEC 14496-3: an AAC-LC frame is\n * exactly 1024 samples at every sample rate \u2014 it is not rate-dependent. */\n readonly #aacFrameSamples = 1024\n\n /** Convert a raw audio sample to an fMP4 moof+mdat segment. */\n wrapAudioSample(\n data: Uint8Array,\n timestamp: number,\n sampleDuration = 21333,\n compositionTimeOffset?: number,\n ): Uint8Array {\n assertCompositionTimeOffsets([{ compositionTimeOffset }])\n const sampleData = normalizeAacSampleForCmaf(data)\n if (this.#audioBaseTs < 0) this.#audioBaseTs = timestamp\n\n // When timescale matches sampleRate (e.g. 48000), use integer sample counting\n // for exact baseMediaDecodeTime alignment. This eliminates cumulative rounding\n // drift from Math.round(\u00B5s * sampleRate / 1_000_000) that causes frequency\n // wandering in FFT analysis (42% consistency \u2192 99%+ with exact counting).\n //\n // samplesPerFrame is the AAC access-unit length, NOT a rescaled duration.\n // Deriving it as Math.round(sampleDuration * timescale / 1e6) only lands on\n // 1024 when the rate happens to be 48 kHz (21333 * 48000 / 1e6 = 1023.98),\n // which made the default silently correct at 48 kHz and wrong everywhere\n // else: at 44.1 kHz it yields 941, so every moof declared ~8% short frames\n // and Chrome rejected the first packet with PIPELINE_ERROR_DECODE\n // (\"Failed to send audio packet for decoding\", duration=21337\u00B5s = 941/44100).\n const samplesPerFrame = this.#audioTimescale !== 1_000_000\n ? this.#aacFrameSamples\n : sampleDuration\n const scaledTs = this.#audioTimescale !== 1_000_000\n ? this.#audioSampleCount * samplesPerFrame\n : (timestamp - this.#audioBaseTs)\n this.#audioSampleCount++\n\n // compositionTimeOffset arrives in microsecond ticks, like timestamp and\n // sampleDuration: the block assembler scales authored PTS-DTS into its own\n // timescale, which defaults to 1e6 whenever the caller omits `timescale`\n // (cmaf-block-assembler.ts). The trun it lands in is at #audioTimescale, so\n // on the sample-counting branch it needs the same rescale its two siblings\n // above already get. Without it a 10 ms authored offset is written as\n // 10_000 ticks of a 48 kHz track \u2014 1e6/48000 = 20.83x, ~208 ms of A/V\n // desync \u2014 which is the samplesPerFrame hazard documented above, one field\n // over. Undefined stays undefined so raw feeds keep a v0 trun with no\n // composition-offset field rather than claiming an authored zero.\n //\n // This 1e6 is the assembler's default timescale, not a free constant, so\n // the two move together. Configuring the audio assembler with\n // `timescale: sampleRate` makes its offsets already trun ticks and turns\n // this rescale into a 20.83x *under*-count: an authored 480 arrives as 23.\n //\n // The two bridges currently disagree on that config, so this rescale is\n // correct for exactly one of them at a time. moqtail omits `timescale`\n // (apps/client-js/src/lib/player.ts, audio enableMseAssembler) and needs\n // the rescale. hang passes `timescale: sampleRate` (js/watch/src/element.ts,\n // audio enableMseAssembler) and must not get it. hang is safe today only\n // because it pins a libmmt revision older than this rescale. Bumping that\n // pin past this commit without dropping hang's `timescale` in the same\n // change is the 20.83x under-count, so the pin bump and the hang edit are\n // one atomic change. mmtp-to-cmaf-audio-composition-offset.test.ts pins\n // both configs against a real CmafBlockAssembler so the pairing is checked\n // by CI rather than by this comment.\n const scaledCompositionTimeOffset =\n compositionTimeOffset === undefined || this.#audioTimescale === 1_000_000\n ? compositionTimeOffset\n : Math.round((compositionTimeOffset * this.#audioTimescale) / 1_000_000)\n\n return encodeDataSegment(\n sampleData,\n scaledTs,\n samplesPerFrame,\n true,\n this.#audioSeq++,\n 1,\n scaledCompositionTimeOffset,\n )\n }\n\n /** Convert an ordered audio batch using the caller-owned timeline. */\n wrapAudioSamples(\n samples: Array<{\n data: Uint8Array\n duration: number\n compositionTimeOffset?: number\n }>,\n baseDecodeTime: number,\n trackId = 1,\n ): Uint8Array {\n // Checked before #audioSeq++ below: the encoder validates too, but its\n // throw would land after the counter had already advanced, leaving the\n // sequence gapped by a call that produced no segment.\n assertTrackId(trackId, 'audio trackId')\n assertCompositionTimeOffsets(samples)\n return encodeMultiSampleSegment(\n samples.map(sample => ({\n data: normalizeAacSampleForCmaf(sample.data),\n duration: sample.duration,\n keyframe: true,\n compositionTimeOffset: sample.compositionTimeOffset,\n })),\n baseDecodeTime,\n this.#audioSeq++,\n trackId,\n )\n }\n\n /** Clear per-stream sequencing and time bases.\n * Cached init segments and the video track geometry survive, so a converter\n * reset mid-subscription keeps emitting moofs the existing init can resolve.\n * Clearing the timescale-denominated timing state also re-opens the video\n * rate for change \u2014 this is the sanctioned way to retarget mid-subscription. */\n reset() {\n this.#videoSeq = 1\n this.#audioSeq = 1\n this.#audioSampleCount = 0\n this.#lastVideoTimestamp = 0\n this.#frameDuration = null\n this.#videoBaseTs = -1\n this.#audioBaseTs = -1\n this.#audioTimescale = 1_000_000\n }\n}\n", "/**\n * Sink-side concealment helpers for the keyframe-protected FEC policy.\n *\n * Concealment is hold-last-good-frame. No bitstream is ever synthesised:\n * a generated \"fill\" picture is codec-specific, resets the reference chain\n * (an IDR) or is not a picture at all, and every later delta would decode\n * against it until the next RAP. Instead the sink keeps presenting the last\n * good frame, sized exactly from authored timestamps, until media resumes.\n *\n * - {@link videoConcealModeForCodec} says whether a video decoder can be fed\n * straight through a delta gap (H.264 conceals missing references itself)\n * or must hold until the next RAP.\n * - {@link HoldLastTimeline} gives MSE sinks gapless per-sample durations.\n * - {@link AudioGapConcealer} sizes audio concealment exactly: repeat the last\n * good access unit (MSE) or insert PCM silence (WebCodecs).\n */\n\nimport type { FrameGapDecision } from './block-policy.js'\n\n// \u2500\u2500\u2500 Video conceal mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * `feed-through`: keep feeding deltas after a delta-only gap; the decoder\n * conceals missing references and the canvas keeps the last frame.\n * `hold-to-rap`: drop non-RAP frames until the next RAP (no queue flush, no\n * A/V sync reset).\n */\nexport type VideoConcealMode = 'feed-through' | 'hold-to-rap'\n\n/**\n * AVC decoders conceal a missing reference and resynchronise on their own;\n * HEVC, AV1 and VP9 decoders error or freeze on a broken reference chain.\n * Any codec not known to conceal is held to the next RAP (fail-closed).\n */\nexport function videoConcealModeForCodec(codec: string): VideoConcealMode {\n const lower = codec.toLowerCase()\n if (lower.startsWith('avc1') || lower.startsWith('avc3')) return 'feed-through'\n return 'hold-to-rap'\n}\n\n// \u2500\u2500\u2500 HoldLastTimeline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** One video sample in its media timescale. */\nexport interface TimelineSample<T> {\n readonly payload: T\n readonly dts: bigint\n readonly pts: bigint\n /** Authored duration in media ticks. */\n readonly duration: number\n /** Codec-aware RAP. */\n readonly keyframe: boolean\n readonly mpu?: number\n /** Decision attached by libmmt to the first frame released after a gap. */\n readonly gapBefore?: FrameGapDecision\n}\n\n/** A sample ready to append, with an explicit per-sample duration. */\nexport interface TimelineEmit<T> {\n readonly payload: T\n readonly dts: bigint\n readonly pts: bigint\n readonly duration: number\n readonly keyframe: boolean\n}\n\nexport interface HoldLastTimelineOptions {\n readonly concealMode: VideoConcealMode\n}\n\nexport interface HoldLastTimelineStats {\n /** Non-RAP frames dropped while waiting for a RAP. */\n droppedFrames: number\n /** Held frames whose duration was extended over a gap. */\n extendedHolds: number\n}\n\nconst U32_MAX = 0xffff_ffffn\n\n/**\n * Holds one video sample back so its duration can run to the next emitted\n * sample's dts. That costs exactly one frame of MSE latency (1000/framerate\n * ms, from the catalog) and buys a gapless hold-last without synthesising a\n * bitstream: a gap simply lengthens the last good frame.\n *\n * On a stall, or on any conceal under `hold-to-rap` except a RAP arriving\n * right after the gap, frames are dropped until the next RAP and the held\n * frame stays held until that RAP arrives, then runs exactly to its dts. The\n * element waits on the held frame for the length of the outage, but the\n * buffer never gets a hole, however many RAPs the outage took. Once the\n * stream shows composition offsets (dts \u2260 pts), samples pass straight\n * through with their authored durations and MSE starves until the RAP is\n * appended \u2014 the one documented case where the element also waits on audio.\n */\nexport class HoldLastTimeline<T> {\n readonly #concealMode: VideoConcealMode\n #held: TimelineSample<T> | null = null\n #dropping = false\n #compositionOffsets = false\n readonly #stats: HoldLastTimelineStats = { droppedFrames: 0, extendedHolds: 0 }\n\n constructor(options: HoldLastTimelineOptions) {\n this.#concealMode = options.concealMode\n }\n\n get stats(): Readonly<HoldLastTimelineStats> {\n return { ...this.#stats }\n }\n\n push(sample: TimelineSample<T>): TimelineEmit<T>[] {\n if (sample.dts !== sample.pts) this.#compositionOffsets = true\n\n if (this.#dropping) {\n if (!sample.keyframe) {\n this.#stats.droppedFrames++\n return []\n }\n this.#dropping = false\n return this.#accept(sample)\n }\n\n if (this.#dropsUntilRap(sample)) {\n this.#dropping = true\n this.#stats.droppedFrames++\n // The held frame stays held: the RAP's #accept sizes it exactly.\n return this.#compositionOffsets ? this.#releaseHeld() : []\n }\n\n return this.#accept(sample)\n }\n\n /** Release the held sample with its authored duration (end of stream). */\n flush(): TimelineEmit<T>[] {\n return this.#releaseHeld()\n }\n\n /** Drop all state for a new epoch. */\n reset(): void {\n this.#held = null\n this.#dropping = false\n this.#compositionOffsets = false\n }\n\n #accept(sample: TimelineSample<T>): TimelineEmit<T>[] {\n if (this.#compositionOffsets) {\n const out = this.#releaseHeld()\n out.push(this.#emit(sample, sample.duration))\n return out\n }\n const out: TimelineEmit<T>[] = []\n const held = this.#held\n if (held) out.push(this.#emit(held, this.#durationTo(held, sample.dts)))\n this.#held = sample\n return out\n }\n\n #dropsUntilRap(sample: TimelineSample<T>): boolean {\n const gap = sample.gapBefore\n if (!gap || gap.trackKind !== 'video' || sample.keyframe) return false\n if (gap.action === 'stall') return true\n return this.#concealMode === 'hold-to-rap' && gap.reason !== 'rap-next'\n }\n\n #durationTo(held: TimelineSample<T>, target: bigint): number {\n if (target <= held.dts) return held.duration\n const span = target - held.dts\n if (span > U32_MAX) return held.duration\n const duration = Number(span)\n if (duration !== held.duration) this.#stats.extendedHolds++\n return duration\n }\n\n #releaseHeld(): TimelineEmit<T>[] {\n const held = this.#held\n this.#held = null\n return held ? [this.#emit(held, held.duration)] : []\n }\n\n #emit(sample: TimelineSample<T>, duration: number): TimelineEmit<T> {\n return {\n payload: sample.payload,\n dts: sample.dts,\n pts: sample.pts,\n duration,\n keyframe: sample.keyframe,\n }\n }\n}\n\n// \u2500\u2500\u2500 AudioGapConcealer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * One audio access unit on its track's authored clock: the ISO-BMFF media\n * timescale the `audio-gap` decision was computed in (`decideGap`, from\n * `AuthoredTiming`). An AAC AU is exactly 1024 ticks there. Never pass values\n * rescaled to another timescale: 1024 samples at 48 kHz is 21333.33... \u00B5s,\n * which no integer tick count can hold.\n */\nexport interface AudioAccessUnit<T> {\n readonly payload: T\n /** Authored presentation time in media ticks (`AuthoredTiming.presentationTime`). */\n readonly pts: bigint\n /** Authored duration in media ticks (`AuthoredTiming.durationTicks`): a positive safe integer. */\n readonly duration: number\n}\n\nexport interface AudioGapConcealerStats {\n /**\n * Access units `note()` refused because their duration was not a positive\n * safe integer: not media ticks, so the AU cannot size a gap. The concealer\n * then holds no good AU and the next gap re-anchors. Non-zero means a caller\n * noted a rescaled value (e.g. microseconds) instead of authored ticks.\n */\n rejectedUnits: number\n}\n\nexport type AudioGapPlan<T> =\n | { readonly kind: 'none' }\n | {\n readonly kind: 'conceal'\n /**\n * MSE: the last good AU repeated into each missing slot. Empty when no\n * good AU has been seen yet; an AAC access unit is not portable across\n * AudioSpecificConfigs, so none is ever synthesised.\n */\n readonly repeat: AudioAccessUnit<T>[]\n /** WebCodecs: exactly this much PCM silence, in media ticks. */\n readonly silenceTicks: bigint\n }\n | {\n /** The gap is not a whole number of AUs: re-anchor at `pts`. */\n readonly kind: 're-anchor'\n readonly pts: bigint\n }\n\n/**\n * Sizes an audio gap from the libmmt gap decision. Audio never touches video\n * state: every plan is local to the audio sink.\n */\nexport class AudioGapConcealer<T> {\n #last: AudioAccessUnit<T> | null = null\n readonly #stats: AudioGapConcealerStats = { rejectedUnits: 0 }\n\n get stats(): Readonly<AudioGapConcealerStats> {\n return { ...this.#stats }\n }\n\n /**\n * Record a good access unit as it is emitted. An AU whose duration is not\n * whole media ticks is refused and counted (`stats.rejectedUnits`), and the\n * concealer forgets its previous AU: that one no longer precedes the gap, so\n * sizing from it would misplace every slot. Refusing here keeps `plan()`\n * total, so a bad AU never throws out of the packet path.\n */\n note(au: AudioAccessUnit<T>): void {\n if (!Number.isSafeInteger(au.duration) || au.duration <= 0) {\n this.#stats.rejectedUnits++\n this.#last = null\n return\n }\n this.#last = au\n }\n\n plan(decision: FrameGapDecision | undefined, next: { readonly pts: bigint }): AudioGapPlan<T> {\n if (!decision || decision.trackKind !== 'audio') return { kind: 'none' }\n const missing = decision.missingFrames\n if (\n decision.reason !== 'audio-gap' ||\n missing === null ||\n !Number.isSafeInteger(missing) ||\n missing <= 0\n ) {\n return { kind: 're-anchor', pts: next.pts }\n }\n const last = this.#last\n // Exact sizing needs the preceding AU's duration; a sink that has not\n // emitted one yet has nothing to hold and simply starts at the next AU.\n if (last === null) return { kind: 're-anchor', pts: next.pts }\n const duration = BigInt(last.duration)\n const repeat: AudioAccessUnit<T>[] = []\n for (let slot = 1; slot <= missing; slot++) {\n repeat.push({\n payload: last.payload,\n pts: last.pts + BigInt(slot) * duration,\n duration: last.duration,\n })\n }\n return { kind: 'conceal', repeat, silenceTicks: BigInt(missing) * duration }\n }\n\n reset(): void {\n this.#last = null\n }\n}\n", "/**\n * FEC-aware CMAF sample assembler for MSE multicast.\n *\n * Emits source frames on arrival and FEC-recovered frames in media order.\n * Nothing is synthesised for a lost video block: the sink holds the last good\n * frame (HoldLastTimeline). A lost audio span is concealed only when libmmt's\n * release gate decided it (`gapBefore` with reason `audio-gap`), by repeating\n * the last good access unit into each missing slot (AudioGapConcealer).\n *\n * One assembler instance per track (video or audio). MulticastManager owns\n * these instances and calls them directly with SBN/ESI from the FEC pipeline.\n */\n\nimport {\n encodeDataSegment,\n buildVideoInitSegment,\n buildHevcVideoInitSegment,\n buildAudioInitSegment,\n normalizeAacSampleForCmaf,\n} from './mmtp-to-cmaf.js'\nimport { AudioGapConcealer } from './conceal-timeline.js'\nimport type { FrameGapDecision } from './block-policy.js'\nimport {\n classifyH264Keyframe,\n detectH264Keyframe,\n hevcNalUnitType,\n isAnnexB,\n isCompleteAvccSample,\n prepareHevcCmafSample,\n splitAnnexBNalUnits,\n splitAvccNalUnits,\n splitH264SampleNalUnits,\n videoCodecFamily,\n videoSampleEntryFromCodecString,\n type VideoCodecFamily,\n type VideoSampleEntry,\n} from './codec-utils.js'\nimport {\n scaleAuthoredTicks,\n scaleAuthoredTiming,\n type AuthoredTiming,\n} from './authored-timing.js'\nimport type { MmtpDeliveryProvenance } from './mfu-reassembler.js'\n\nconst INT32_MIN = -0x80000000\nconst INT32_MAX = 0x7fffffff\n\ninterface BlockFrame {\n data: Uint8Array\n timestamp: number\n keyframe: boolean\n /** True only for locally generated concealment frames. */\n synthetic?: boolean\n /** Raw MMTP short-NTP header timestamp; never used for CMAF timing. */\n mmtpTimestamp?: bigint\n /** Exact ISO-BMFF timing retained from the parser for FEC/source ordering. */\n authoredTiming?: AuthoredTiming\n /** MMTP packet_sequence_number identifying the delivered access unit. */\n packetSequenceNumber?: number\n /** Every authoritative MMTP source-packet sequence that formed this sample. */\n packetSequenceNumbers?: number[]\n /** Subset of packetSequenceNumbers reconstructed by FEC. */\n repairedPacketSequenceNumbers?: number[]\n /** Native MMTP MPU group coordinate retained across source/FEC ordering. */\n mpuSequenceNumber?: number\n /** Timed-MFU sample identity; absent when the MFU header is untimed. */\n movieFragmentSequenceNumber?: number\n sampleNumber?: number\n catalogTrackName?: string\n /**\n * Whole-frame delivery verdict folded across every contributing fragment.\n * Absent means no caller declared one; it is never an implied 'multicast'.\n */\n deliveryProvenance?: MmtpDeliveryProvenance\n /** libmmt's stall/conceal decision for the gap before this frame. */\n gapBefore?: FrameGapDecision\n}\n\nexport interface CmafBlockSample {\n data: Uint8Array\n timestamp: number\n duration: number\n keyframe: boolean\n /**\n * True only for locally generated concealment samples. These samples carry\n * codec bytes and a media timestamp, but no authored MMTP identity; object\n * consumers must not validate them as source MFUs.\n */\n synthetic?: boolean\n /**\n * Raw MMTP short-NTP header timestamp retained for timing telemetry.\n * This is intentionally separate from the authored media timestamp above.\n */\n mmtpTimestamp?: bigint\n /** Signed PTS-DTS offset in the configured CMAF timescale. */\n compositionTimeOffset?: number\n /** Exact ISO-BMFF timing in the media track's native timescale. */\n authoredTiming?: AuthoredTiming\n /** MMTP packet_sequence_number identifying the delivered access unit. */\n packetSequenceNumber?: number\n /** Every authoritative MMTP source-packet sequence that formed this sample. */\n packetSequenceNumbers?: number[]\n /** Subset of packetSequenceNumbers reconstructed by FEC. */\n repairedPacketSequenceNumbers?: number[]\n /** Native MMTP MPU group coordinate retained for exact replay selection. */\n mpuSequenceNumber?: number\n /**\n * Timed-MFU sample identity `(movie_fragment_sequence_number,\n * sample_number)` from the MFU data-unit header, carried alongside\n * `mpuSequenceNumber` so a consumer can address the sample by its native\n * MMTP coordinates. Absent when the MFU header is untimed.\n */\n movieFragmentSequenceNumber?: number\n sampleNumber?: number\n /** Immutable catalog identity supplied by the owning ingress subscription. */\n catalogTrackName?: string\n /**\n * Whole-frame delivery verdict, carried so MSE `onSample` consumers can see\n * the same evidence the non-MSE frame path gets. Without it the MSE branch\n * silently loses a computed 'mixed' or 'multicast' finding.\n */\n deliveryProvenance?: MmtpDeliveryProvenance\n /**\n * libmmt's stall/conceal decision for the gap before this sample\n * (`RoutedFrameMeta.gapBefore`). MSE sinks feed it to HoldLastTimeline.\n */\n gapBefore?: FrameGapDecision\n}\n\nexport type CmafSampleOrderPolicy =\n | { mode: 'arrival' }\n | {\n /** Release samples according to media progression, without a wall clock. */\n mode: 'media-window'\n /** Delivery window in the same units as CmafBlockSample.timestamp. */\n deliveryWindow: number\n /** Optional modulo period for wrapped input timestamps, in timestamp units. */\n timestampWrapPeriod?: number\n }\n\nexport interface CmafSampleOrderStats {\n staleSamples: number\n duplicateSamples: number\n preRapDeadlineDrops: number\n noRapFlushDrops: number\n forcedReleases: number\n /**\n * Samples refused because they carried `authoredTiming` while this track's\n * `admitAuthoredTiming` was false. Non-zero means the contract rollback\n * switch is engaged AND the producer is still sending authored batches \u2014\n * a rollback is meant to be paired with that producer reverting to raw.\n */\n unadmittedAuthoredSamples: number\n /**\n * Video samples refused because their bitstream could not be read: HEVC\n * whose framing is neither a complete 4-byte length-prefixed sample nor\n * Annex B, or that holds no NAL unit to write. Writing them would put bytes\n * the hvcC's lengthSizeMinusOne = 3 does not describe into the mdat.\n */\n unreadableVideoSamples: number\n}\n\n/**\n * Cumulative counters of what the assembler emitted and which block events\n * it saw. `emits` counts emit calls (one per direct frame, recovered block,\n * recovered drain or block flush); `samples` and `keyframes` count the\n * samples those calls carried.\n */\nexport interface CmafEmitStats {\n emits: number\n samples: number\n keyframes: number\n addedFrames: number\n recoveredBlocks: number\n completedBlocks: number\n failedBlocks: number\n}\n\n/**\n * Cumulative audio gap concealment counters since construction. Every audio\n * gap decision the assembler sees lands in exactly one of the gap counters.\n */\nexport interface CmafAudioConcealStats {\n /** Last-good AUs written into the slots of `audio-gap` decisions. */\n repeatedUnits: number\n /**\n * Gap decisions re-anchored at the next AU by design: an\n * `audio-discontinuity`, or no good AU emitted yet.\n */\n reanchoredGaps: number\n /**\n * `audio-gap` decisions that could not be sized and were re-anchored\n * instead: the AU after the gap, or the last good AU, carried no authored\n * ISO-BMFF timing, or the two were authored in different media timescales.\n * libmmt only decides an `audio-gap` from authored ticks (`decideGap`), so\n * non-zero means a caller handed the assembler a decision without the\n * timing it was computed from.\n */\n untimedGaps: number\n}\n\nexport interface BlockAssemblerConfig {\n /** Number of source symbols per FEC block */\n k: number\n /** Timescale for CMAF segments (default 1_000_000 = microseconds) */\n timescale?: number\n /** ISO track ID (default 1) */\n trackId?: number\n /** Track type \u2014 determines init segment format and audio concealment */\n trackType: 'video' | 'audio'\n /** Video/audio codec string (e.g. 'avc1.64002a', 'mp4a.40.2'). */\n codec?: string\n // Video config (required when trackType='video')\n /** Raw avcC payload from catalog description \u2014 for the init segment */\n avcC?: Uint8Array\n /** Raw hvcC payload for an HEVC init segment (e.g. built from in-band\n * VPS/SPS/PPS by `generateHvcCFromParameterSets`). Requires `codec` to name\n * the `hev1` or `hvc1` sample entry. */\n hvcC?: Uint8Array\n /** Video width in pixels */\n width?: number\n /** Video height in pixels */\n height?: number\n // Audio config (required when trackType='audio')\n /** Audio codec string (e.g. 'mp4a.40.2', 'opus') */\n audioCodec?: string\n /** Audio sample rate (e.g. 48000) */\n sampleRate?: number\n /** Audio channel count (e.g. 1 or 2) */\n channels?: number\n /** Raw AudioSpecificConfig from catalog */\n asc?: Uint8Array\n // Shared\n /** Frames per second \u2014 fallback for duration estimation */\n fps?: number\n /** Explicit sample ordering policy. Datagram/FEC callers use media-window. */\n sampleOrder?: CmafSampleOrderPolicy\n /**\n * Bounded sample reorder depth for callback mode.\n *\n * Default 0 preserves immediate emission. When set on video tracks with\n * onSample enabled, samples are sorted by timestamp and the oldest sample is\n * emitted only after this many newer samples are available.\n * @deprecated Use sampleOrder with an explicit policy.\n */\n sampleReorderWindow?: number\n /**\n * Authored-timing admission for this track (default `true` \u2014 admitted).\n *\n * This is the rollback switch named by the authored-audio-CTS contract's\n * removal gate (BLO-30397). Assemblers are keyed on\n * `(packetId, catalogTrackName)` (`mmtp-router.ts` `#mseAssemblerKey`), so\n * this switch is per **track**. A producer feeding several tracks must be\n * de-admitted on each of them: nothing applies it across a track set\n * (BLO-34821).\n *\n * Set `false` and a sample carrying `authoredTiming` is **rejected**, not\n * zeroed: the contract forbids rolling back \"by zeroing already-authored\n * offsets\", because a zeroed offset is byte-indistinguishable from a feed\n * that authored nothing (BLO-30401 measured both as 0 A/V skew). Raw samples\n * \u2014 those with no `authoredTiming` at all \u2014 are unaffected and keep emitting\n * a v0 trun with no composition-offset field.\n */\n admitAuthoredTiming?: boolean\n}\n\ninterface OrderedSample extends CmafBlockSample {\n advancesHorizon: boolean\n}\n\nexport class CmafBlockAssembler {\n #config: Required<\n Pick<\n BlockAssemblerConfig,\n 'k' | 'timescale' | 'trackId' | 'trackType' | 'admitAuthoredTiming'\n >\n >\n #pendingRecovered = new Map<number, BlockFrame>()\n #sequence = 1\n #baseTs = -1\n /**\n * Last decoder-proven audio AU, repeated into slots a gap decision names.\n * It is noted and planned on the AU's authored clock (`AuthoredTiming`\n * ticks), never on this assembler's CMAF timescale: on the default\n * 1_000_000 an AAC AU is 1024e6 / sampleRate \u00B5s, which is not an integer.\n */\n #audioConcealer = new AudioGapConcealer<Uint8Array>()\n /**\n * Media timescale of the AU the concealer holds; `'untimed'` when the last\n * emitted audio AU carried no authored timing, `null` before any AU.\n */\n #audioConcealClock: number | 'untimed' | null = null\n #audioConcealStats: CmafAudioConcealStats = {\n repeatedUnits: 0,\n reanchoredGaps: 0,\n untimedGaps: 0,\n }\n #frameDuration: number\n #lastEmittedTs = -1\n #lastSourceTs = -1\n #sampleReorderWindow = 0\n #sampleOrder: CmafSampleOrderPolicy = { mode: 'arrival' }\n #sampleOrderCapacity = 0\n #sampleReorderQueue: OrderedSample[] = []\n #recoveredHorizonTs = -1\n #sampleOrderStats: CmafSampleOrderStats = {\n staleSamples: 0,\n duplicateSamples: 0,\n preRapDeadlineDrops: 0,\n noRapFlushDrops: 0,\n forcedReleases: 0,\n unadmittedAuthoredSamples: 0,\n unreadableVideoSamples: 0,\n }\n /** Codec family whose bitstream keyframe rule overrides the metadata flag\n * (AVC IDR, HEVC IRAP), or null when the bitstream is not validated. */\n #videoKeyframeFamily: VideoCodecFamily | null = null\n /** HEVC sample entry chosen from the catalog codec string. */\n #hevcSampleEntry: 'hev1' | 'hvc1' | null = null\n #keyframeRejectDumpCount = 0\n\n /** Callback: emits a complete moof+mdat CMAF segment */\n onSegment: ((segment: Uint8Array) => void) | null = null\n /** Callback: emits a gated/deduped raw sample for callers that already own the MSE timeline. */\n onSample: ((sample: CmafBlockSample) => void) | null = null\n /** Callback: emits an init segment (ftyp+moov) */\n onInitSegment: ((init: Uint8Array) => void) | null = null\n\n // Video keyframe gate (BLO-3035 layer 3): Chrome's CHUNK_DEMUXER rejects a\n // moof+mdat whose first sample is non-sync with no prior reference frames\n // (\"CHUNK_DEMUXER_ERROR_APPEND_FAILED: Failed to prepare video sample for\n // decode\") and detaches the SourceBuffer for the rest of the session.\n // Mid-stream join almost guarantees the first FEC blocks are mid-GOP \u2014 all\n // delta frames. Gate emission until the first block containing an IDR;\n // within that block, splice samples to start at the IDR index. Audio passes\n // unconditionally \u2014 valid for the codecs supported here (AAC, Opus) where\n // every frame is independently decodable. SBR-only AAC, USAC, or HE-AAC v2\n // with PCE re-entry would need a similar gate.\n #armedVideo = false\n\n /** Truncate `frames` to start at the first keyframe for video tracks before\n * the first emission. Returns the (possibly spliced) frames, or null to\n * drop the block entirely. Audio + already-armed video: identity. */\n #applyVideoKeyframeGate<T extends { data: Uint8Array; keyframe: boolean }>(frames: T[]): T[] | null {\n const keyedFrames = this.#normalizeVideoKeyframes(frames)\n if (this.#config.trackType !== 'video' || this.#armedVideo) return keyedFrames\n const firstKfIdx = keyedFrames.findIndex(f => f.keyframe)\n if (firstKfIdx < 0) return null\n return firstKfIdx === 0 ? keyedFrames : keyedFrames.slice(firstKfIdx)\n }\n\n #normalizeVideoKeyframes<T extends { data: Uint8Array; keyframe: boolean }>(frames: T[]): T[] {\n if (this.#config.trackType !== 'video' || this.#videoKeyframeFamily === null) return frames\n const out: T[] = []\n for (const frame of frames) {\n const effective = this.#withEffectiveVideoKeyframe(frame)\n if (effective !== null) out.push(effective)\n }\n return out\n }\n\n /** The frame with its bitstream keyframe (and, for HEVC, mdat framing);\n * null when the frame is refused and must not be emitted. */\n #withEffectiveVideoKeyframe<T extends { data: Uint8Array; keyframe: boolean }>(frame: T): T | null {\n if (this.#config.trackType !== 'video' || this.#videoKeyframeFamily === null) return frame\n if (this.#videoKeyframeFamily === 'hevc') {\n // HEVC keyframe = IRAP (H.265 Table 7-1). The mdat carries 4-byte\n // length-prefixed NAL units to match the hvcC's lengthSizeMinusOne = 3;\n // normalization is idempotent, so a frame seen twice is unchanged. A\n // frame that cannot be read is refused, not written as-is and not\n // passed off as a delta frame.\n const prepared = prepareHevcCmafSample(frame.data, this.#hevcSampleEntry!)\n if (prepared === null) {\n this.#sampleOrderStats.unreadableVideoSamples++\n return null\n }\n const { keyframe, data } = prepared\n if (!keyframe) this.#dumpRejectedVideoKeyframe(frame.data, frame.keyframe)\n return keyframe === frame.keyframe && data === frame.data ? frame : { ...frame, keyframe, data }\n }\n const keyframe = detectH264Keyframe(frame.data)\n if (!keyframe) this.#dumpRejectedVideoKeyframe(frame.data, frame.keyframe)\n return keyframe === frame.keyframe ? frame : { ...frame, keyframe }\n }\n\n #dumpRejectedVideoKeyframe(data: Uint8Array, metadataKeyframe: boolean): void {\n if (this.#keyframeRejectDumpCount >= 12) return\n this.#keyframeRejectDumpCount++\n if (this.#videoKeyframeFamily === 'hevc') {\n const nals = isCompleteAvccSample(data)\n ? splitAvccNalUnits(data)\n : isAnnexB(data) ? splitAnnexBNalUnits(data) : []\n console.log(\n `[L4-KF-GATE] reject#${this.#keyframeRejectDumpCount} len=${data.length} ` +\n `metadataKf=${metadataKeyframe} family=hevc ` +\n `nalTypes=[${nals.slice(0, 16).map((nal) => (nal.length >= 2 ? hevcNalUnitType(nal) : -1)).join(',')}]`,\n )\n return\n }\n const sample = splitH264SampleNalUnits(data)\n const nalTypes = describeH264NalTypes(sample.nals)\n const classify = classifyH264Keyframe(data)\n const hex = Array.from(data.subarray(0, Math.min(48, data.length)))\n .map(b => (b as number).toString(16).padStart(2, '0'))\n .join(' ')\n console.log(\n `[L4-KF-GATE] reject#${this.#keyframeRejectDumpCount} len=${data.length} ` +\n `metadataKf=${metadataKeyframe} classify=${classify} format=${sample.format} ` +\n `nalTypes=[${nalTypes.join(',')}] first48=[${hex}]`,\n )\n }\n\n /** Mark video as already synchronized by an external decoder path.\n * Used by MoQ\u2192multicast MSE continuity when MoQ has already appended a\n * keyframe to the same SourceBuffer timeline before multicast takeover. */\n markVideoSynced(): void {\n if (this.#config.trackType === 'video') this.#armedVideo = true\n }\n\n /** Emit each frame as its own single-sample moof+mdat segment. Matches the\n * case-20 path (`MmtpToCmaf.wrapVideoSample` \u2192 `encodeDataSegment`) byte-\n * for-byte per frame. Working hypothesis (pending Chromium-source\n * validation against `media/filters/chunk_demuxer.cc`):\n * `encodeMultiSampleSegment` blobs are accepted by ChunkDemuxer in this\n * pipeline but only the first sample renders for live, possibly because\n * the pipeline expects per-frame moof boundaries. Track in BLO-3035.\n *\n * Stale-tfdt defense-in-depth: drops any frame whose timestamp is older\n * than `#lastEmittedTs`. The SBN watermark already prevents out-of-order\n * FEC-block emissions, but `addBypassFrame` (oversized MFU / no-FEC path)\n * doesn't touch the watermark \u2014 a bypass frame at t=T followed by a FEC\n * block whose frames precede T would otherwise inject earlier tfdt and\n * detach the SourceBuffer. */\n #ordersSamples(): boolean {\n return this.#sampleOrder.mode === 'media-window' || this.#sampleReorderWindow > 0\n }\n\n #unwrapTimestamp(timestamp: number): number {\n const wrapPeriod = this.#sampleOrder.mode === 'media-window'\n ? this.#sampleOrder.timestampWrapPeriod\n : undefined\n if (!wrapPeriod) return timestamp\n const reference = this.#lastSourceTs >= 0\n ? this.#lastSourceTs\n : this.#recoveredHorizonTs >= 0\n ? this.#recoveredHorizonTs\n : this.#lastEmittedTs\n if (reference < 0) return timestamp\n return timestamp + Math.round((reference - timestamp) / wrapPeriod) * wrapPeriod\n }\n\n #emitSample(\n data: Uint8Array,\n timestamp: number,\n duration: number,\n keyframe: boolean,\n advancesHorizon: boolean,\n catalogTrackName: string | undefined,\n mpuSequenceNumber: number | undefined,\n packetSequenceNumber: number | undefined,\n packetSequenceNumbers: number[] | undefined,\n repairedPacketSequenceNumbers: number[] | undefined,\n authoredTiming: AuthoredTiming | undefined,\n // Required, not optional: an omitted optional argument is silently\n // undefined, which is exactly how this field went missing from the\n // recovered path in the first place. Callers with nothing to declare pass\n // `undefined` on purpose.\n deliveryProvenance: MmtpDeliveryProvenance | undefined,\n /** Raw MMTP short-NTP header timestamp for telemetry only. */\n mmtpTimestamp: bigint | undefined,\n // Same rule as deliveryProvenance: required so a caller cannot drop the\n // timed-MFU identity by omission. Untimed MFUs pass `undefined`.\n movieFragmentSequenceNumber: number | undefined,\n sampleNumber: number | undefined,\n /** True only for locally generated concealment samples. */\n synthetic: boolean | undefined,\n /** libmmt's decision for the gap before this sample, carried to sinks. */\n gapBefore: FrameGapDecision | undefined,\n ): void {\n // Authored-timing admission \u2014 the rollback switch the BLO-30397 removal\n // gate names. Refuse the sample outright rather than stripping its offset:\n // the contract forbids rolling back \"by zeroing already-authored offsets\",\n // and BLO-30401 measured why \u2014 a zeroed offset and a never-authored one are\n // behaviourally identical downstream (same buffered ranges, same duration,\n // no diagnostic), so a silent strip would look exactly like success while\n // audio ran early forever. A de-admitted producer is expected to revert to\n // raw batches, which are untouched by this branch.\n if (authoredTiming !== undefined && !this.#config.admitAuthoredTiming) {\n this.#sampleOrderStats.unadmittedAuthoredSamples++\n return\n }\n const scaledTiming = authoredTiming === undefined\n ? null\n : scaleAuthoredTiming(authoredTiming, this.#config.timescale)\n if (authoredTiming !== undefined && scaledTiming === null) return\n const authoredTimestamp = scaledTiming?.timestamp ?? timestamp\n const authoredDuration = scaledTiming?.duration ?? duration\n let compositionTimeOffset: number | undefined\n if (authoredTiming !== undefined) {\n const scaledOffset = scaleAuthoredTicks(\n authoredTiming.presentationTime - authoredTiming.decodeTime,\n authoredTiming.mediaTimescale,\n this.#config.timescale,\n )\n if (scaledOffset === null) return\n // trun stores composition offsets as integer target-timescale ticks.\n // Preserve the nearest representable tick, then enforce the signed\n // int32 serialization range on the rounded value.\n const roundedOffset = Math.round(scaledOffset)\n if (\n !Number.isSafeInteger(roundedOffset) ||\n roundedOffset < INT32_MIN ||\n roundedOffset > INT32_MAX\n ) return\n compositionTimeOffset = roundedOffset\n }\n const unwrappedTimestamp = this.#unwrapTimestamp(authoredTimestamp)\n if (unwrappedTimestamp <= this.#lastEmittedTs) {\n this.#sampleOrderStats.staleSamples++\n return\n }\n\n const sample = this.#withEffectiveVideoKeyframe({\n data,\n timestamp: unwrappedTimestamp,\n duration: authoredDuration,\n keyframe,\n ...(mmtpTimestamp === undefined ? {} : { mmtpTimestamp }),\n ...(compositionTimeOffset === undefined ? {} : { compositionTimeOffset }),\n advancesHorizon,\n ...(authoredTiming === undefined ? {} : { authoredTiming }),\n ...(packetSequenceNumber === undefined ? {} : { packetSequenceNumber }),\n ...(packetSequenceNumbers === undefined ? {} : { packetSequenceNumbers }),\n ...(repairedPacketSequenceNumbers === undefined\n ? {}\n : { repairedPacketSequenceNumbers }),\n ...(mpuSequenceNumber === undefined ? {} : { mpuSequenceNumber }),\n ...(movieFragmentSequenceNumber === undefined ? {} : { movieFragmentSequenceNumber }),\n ...(sampleNumber === undefined ? {} : { sampleNumber }),\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n ...(deliveryProvenance === undefined ? {} : { deliveryProvenance }),\n ...(synthetic === undefined ? {} : { synthetic }),\n ...(gapBefore === undefined ? {} : { gapBefore }),\n })\n if (sample === null) return\n if (advancesHorizon && unwrappedTimestamp > this.#lastSourceTs) {\n this.#lastSourceTs = unwrappedTimestamp\n } else if (!advancesHorizon && unwrappedTimestamp > this.#recoveredHorizonTs) {\n this.#recoveredHorizonTs = unwrappedTimestamp\n }\n\n if (this.#ordersSamples()) {\n this.#queueSample(sample)\n return\n }\n this.#emitArrivalOrdered(sample)\n }\n\n #emitArrivalOrdered(sample: OrderedSample): void {\n if (this.#config.trackType === 'video' && !this.#armedVideo && !sample.keyframe) {\n this.#sampleOrderStats.preRapDeadlineDrops++\n return\n }\n this.#emitSampleNow(sample)\n }\n\n #queueSample(sample: OrderedSample): void {\n if (this.#sampleReorderQueue.some(s => s.timestamp === sample.timestamp)) {\n this.#sampleOrderStats.duplicateSamples++\n return\n }\n this.#sampleReorderQueue.push(sample)\n this.#sampleReorderQueue.sort((a, b) => a.timestamp - b.timestamp)\n this.#drainSampleReorder(false)\n this.#enforceSampleOrderCapacity()\n }\n\n #releaseHorizon(): number {\n return this.#lastSourceTs >= 0 ? this.#lastSourceTs : this.#recoveredHorizonTs\n }\n\n #sampleIsReleasable(sample: OrderedSample, flushAll: boolean): boolean {\n if (flushAll) return true\n if (this.#sampleOrder.mode === 'media-window') {\n const horizon = this.#releaseHorizon()\n return horizon >= 0 && sample.timestamp < horizon - this.#sampleOrder.deliveryWindow\n }\n return this.#sampleReorderQueue.length > this.#sampleReorderWindow\n }\n\n #dropPreRapSamples(flushAll: boolean): boolean {\n if (this.#config.trackType !== 'video' || this.#armedVideo) return false\n\n const firstRap = this.#sampleReorderQueue.findIndex(\n sample => sample.keyframe && this.#sampleIsReleasable(sample, flushAll),\n )\n if (firstRap >= 0) {\n if (firstRap > 0) {\n this.#sampleOrderStats.preRapDeadlineDrops += firstRap\n this.#sampleReorderQueue.splice(0, firstRap)\n }\n return false\n }\n\n let dropped = 0\n while (\n this.#sampleReorderQueue[0] &&\n this.#sampleIsReleasable(this.#sampleReorderQueue[0]!, flushAll)\n ) {\n this.#sampleReorderQueue.shift()\n dropped++\n }\n if (flushAll) this.#sampleOrderStats.noRapFlushDrops += dropped\n else this.#sampleOrderStats.preRapDeadlineDrops += dropped\n return dropped > 0\n }\n\n #drainSampleReorder(flushAll: boolean): void {\n while (this.#sampleReorderQueue.length > 0) {\n if (this.#dropPreRapSamples(flushAll) && this.#sampleReorderQueue.length === 0) return\n const sample = this.#sampleReorderQueue[0]\n if (!sample || !this.#sampleIsReleasable(sample, flushAll)) return\n this.#sampleReorderQueue.shift()\n this.#emitQueuedSample(sample)\n }\n }\n\n #emitQueuedSample(sample: OrderedSample): void {\n const next = this.#sampleReorderQueue.find(s => s.timestamp > sample.timestamp)\n const duration = sample.authoredTiming\n ? sample.duration\n : next ? next.timestamp - sample.timestamp : sample.duration\n this.#emitSampleNow({\n ...sample,\n duration: duration > 0\n ? duration\n : sample.authoredTiming ? sample.duration : this.#frameDuration,\n })\n }\n\n #enforceSampleOrderCapacity(): void {\n if (this.#sampleOrder.mode !== 'media-window') return\n while (this.#sampleReorderQueue.length > this.#sampleOrderCapacity) {\n const sample = this.#sampleReorderQueue.shift()\n if (!sample) return\n this.#sampleOrderStats.forcedReleases++\n console.warn(\n `[CmafBlockAssembler] ${this.#config.trackType} media-window queue exceeded ` +\n `${this.#sampleOrderCapacity} samples; force-releasing timestamp=${sample.timestamp}`,\n )\n if (this.#config.trackType === 'video' && !this.#armedVideo && !sample.keyframe) {\n this.#sampleOrderStats.preRapDeadlineDrops++\n continue\n }\n this.#emitQueuedSample(sample)\n }\n }\n\n #emitSampleNow(sample: CmafBlockSample): void {\n if (sample.timestamp <= this.#lastEmittedTs) {\n this.#sampleOrderStats.staleSamples++\n return\n }\n this.#concealAudioGapBefore(sample)\n this.#writeSampleNow(sample, true)\n }\n\n #writeSampleNow(sample: CmafBlockSample, rememberAudio: boolean): void {\n const { timestamp, duration, keyframe } = sample\n this.#lastEmittedTs = timestamp\n if (this.#config.trackType === 'video' && keyframe) this.#armedVideo = true\n const data = this.#normalizeSampleData(sample.data)\n if (this.#config.trackType === 'audio' && rememberAudio && data.length > 0) {\n // Keep decoder-proven codec data for concealment of a later gap that\n // libmmt's release gate decided, on the AU's own authored clock.\n this.#noteAudioAccessUnit(data, sample.authoredTiming)\n }\n if (this.onSample) {\n this.onSample(data === sample.data ? sample : { ...sample, data })\n } else {\n const baseTime = this.#rebase(timestamp)\n const segment = encodeDataSegment(\n data,\n baseTime,\n duration,\n keyframe,\n this.#sequence++,\n this.#config.trackId,\n sample.compositionTimeOffset,\n )\n this.onSegment?.(segment)\n }\n }\n\n /**\n * Record an emitted audio AU for concealment in its authored media ticks \u2014\n * the clock libmmt's `audio-gap` decision was computed in, where an AAC AU\n * is exactly 1024 ticks. An AU with no authored timing has no exact clock,\n * so nothing is held for it and a later `audio-gap` is counted as untimed.\n */\n #noteAudioAccessUnit(data: Uint8Array, timing: AuthoredTiming | undefined): void {\n if (timing === undefined) {\n this.#audioConcealer.reset()\n this.#audioConcealClock = 'untimed'\n return\n }\n this.#audioConcealer.note({\n payload: data,\n pts: timing.presentationTime,\n duration: timing.durationTicks,\n })\n this.#audioConcealClock = timing.mediaTimescale\n }\n\n /**\n * Repeat the last good AU into exactly the slots an `audio-gap` decision\n * names. A synthesised AAC access unit is not portable across every\n * AudioSpecificConfig, so only known-good encoded audio is repeated; with\n * no good AU yet, or on `audio-discontinuity`, the sink re-anchors at the\n * next sample and nothing is written.\n *\n * The gap is sized in authored ticks and each slot is mapped onto this\n * assembler's timescale by `scaleAuthoredTiming`, the conversion every\n * source AU goes through, so a slot lands exactly where the AU it replaces\n * would have. Nothing here can throw into the packet path: a gap that\n * cannot be sized exactly is re-anchored and counted.\n */\n #concealAudioGapBefore(sample: CmafBlockSample): void {\n const decision = sample.gapBefore\n if (this.#config.trackType !== 'audio' || !decision || decision.trackKind !== 'audio') return\n const timing = sample.authoredTiming\n const clock = this.#audioConcealClock\n if (\n timing === undefined ||\n clock === 'untimed' ||\n (clock !== null && clock !== timing.mediaTimescale)\n ) {\n // No single authored clock covers both this AU and the one held, so\n // the gap has no exact size in ticks.\n if (decision.reason === 'audio-gap') this.#audioConcealStats.untimedGaps++\n else this.#audioConcealStats.reanchoredGaps++\n return\n }\n const plan = this.#audioConcealer.plan(decision, { pts: timing.presentationTime })\n if (plan.kind !== 'conceal') {\n this.#audioConcealStats.reanchoredGaps++\n return\n }\n for (const slot of plan.repeat) {\n // Every input here is one this sample's own timing already scaled\n // successfully (same media timescale and duration, an earlier pts), so\n // the conversion cannot fail for a slot.\n const scaled = scaleAuthoredTiming({\n decodeTime: slot.pts,\n presentationTime: slot.pts,\n durationTicks: slot.duration,\n mediaTimescale: timing.mediaTimescale,\n }, this.#config.timescale)\n if (scaled === null) continue\n const slotTimestamp = this.#unwrapTimestamp(scaled.timestamp)\n if (slotTimestamp <= this.#lastEmittedTs || slotTimestamp >= sample.timestamp) continue\n this.#writeSampleNow({\n data: slot.payload.slice(),\n timestamp: slotTimestamp,\n duration: scaled.duration,\n keyframe: true,\n synthetic: true,\n }, false)\n this.#audioConcealStats.repeatedUnits++\n }\n }\n\n #emitPerSample(\n frames: BlockFrame[],\n audioDurationOverride?: number,\n advancesHorizon = false,\n ): void {\n for (let i = 0; i < frames.length; i++) {\n const f = frames[i]!\n // Per-frame duration from delta to next frame, or fallback.\n let duration: number\n if (this.#config.trackType === 'audio') {\n duration = audioDurationOverride ?? this.#frameDuration\n } else if (i < frames.length - 1) {\n duration = frames[i + 1]!.timestamp - f.timestamp\n } else if (i > 0) {\n duration = f.timestamp - frames[i - 1]!.timestamp\n } else {\n duration = audioDurationOverride ?? this.#frameDuration\n }\n if (duration <= 0) duration = audioDurationOverride ?? this.#frameDuration\n this.#emitSample(\n f.data,\n f.timestamp,\n duration,\n f.keyframe,\n advancesHorizon,\n f.catalogTrackName,\n f.mpuSequenceNumber,\n f.packetSequenceNumber,\n f.packetSequenceNumbers,\n f.repairedPacketSequenceNumbers,\n f.authoredTiming,\n f.deliveryProvenance,\n f.mmtpTimestamp,\n f.movieFragmentSequenceNumber,\n f.sampleNumber,\n f.synthetic,\n f.gapBefore,\n )\n }\n }\n\n // Cumulative emit counters (see emitStats). They replace the BLO-3035 L4\n // diagnostics, which hashed and hex-dumped every video keyframe and ran a\n // 1 s setInterval console aggregate on the MSE path (BLO-36599).\n #emitStats: CmafEmitStats = {\n emits: 0,\n samples: 0,\n keyframes: 0,\n addedFrames: 0,\n recoveredBlocks: 0,\n completedBlocks: 0,\n failedBlocks: 0,\n }\n\n #countEmit(samples: ReadonlyArray<{ keyframe: boolean }>): void {\n this.#emitStats.emits++\n this.#emitStats.samples += samples.length\n for (const sample of samples) if (sample.keyframe) this.#emitStats.keyframes++\n }\n\n constructor(config: BlockAssemblerConfig) {\n this.#config = {\n k: config.k,\n timescale: config.timescale ?? 1_000_000,\n trackId: config.trackId ?? 1,\n trackType: config.trackType,\n admitAuthoredTiming: config.admitAuthoredTiming ?? true,\n }\n\n // Compute default frame duration from fps (updated from timestamp deltas at runtime)\n if (config.fps && config.fps > 0) {\n this.#frameDuration = Math.round(this.#config.timescale / config.fps)\n } else if (config.trackType === 'audio' && config.sampleRate) {\n this.#frameDuration = Math.round(this.#config.timescale * 1024 / config.sampleRate)\n } else {\n // No fps info \u2014 will be refined from actual timestamp deltas\n this.#frameDuration = 33333\n }\n\n const sampleReorderWindow = config.sampleReorderWindow ?? 0\n if (!Number.isFinite(sampleReorderWindow) || sampleReorderWindow < 0) {\n throw new Error('[CmafBlockAssembler] sampleReorderWindow must be finite and non-negative')\n }\n if (config.sampleOrder && sampleReorderWindow > 0) {\n throw new Error('[CmafBlockAssembler] configure sampleOrder or sampleReorderWindow, not both')\n }\n this.#sampleReorderWindow = Math.floor(sampleReorderWindow)\n this.#sampleOrder = config.sampleOrder ?? { mode: 'arrival' }\n if (this.#sampleOrder.mode === 'media-window') {\n const { deliveryWindow, timestampWrapPeriod } = this.#sampleOrder\n if (!Number.isSafeInteger(deliveryWindow) || deliveryWindow <= 0) {\n throw new Error(\n '[CmafBlockAssembler] media-window deliveryWindow must be a positive safe integer',\n )\n }\n if (\n timestampWrapPeriod !== undefined &&\n (!Number.isSafeInteger(timestampWrapPeriod) || timestampWrapPeriod <= deliveryWindow * 2)\n ) {\n throw new Error(\n '[CmafBlockAssembler] timestampWrapPeriod must be a safe integer larger than two delivery windows',\n )\n }\n const samplesPerSecond = config.trackType === 'audio' && config.sampleRate\n ? config.sampleRate / 1024\n : config.fps\n if (!samplesPerSecond || !Number.isFinite(samplesPerSecond) || samplesPerSecond <= 0) {\n throw new Error(\n '[CmafBlockAssembler] media-window ordering requires catalog-derived media cadence',\n )\n }\n this.#sampleOrderCapacity =\n Math.ceil(deliveryWindow * samplesPerSecond / this.#config.timescale) + 1\n }\n const videoSampleEntry = config.trackType === 'video' ? resolveVideoSampleEntry(config) : null\n this.#videoKeyframeFamily = videoSampleEntry === null\n ? null\n : videoSampleEntry === 'unvalidated' ? null : videoCodecFamily(videoSampleEntry)\n this.#hevcSampleEntry = videoSampleEntry === 'hev1' || videoSampleEntry === 'hvc1' ? videoSampleEntry : null\n if (config.trackType === 'video') {\n console.log(\n `[L4-KF-CONFIG] validate=${this.#videoKeyframeFamily ?? false} ` +\n `codec=${config.codec ?? ''} avcC=${config.avcC?.length ?? 0} hvcC=${config.hvcC?.length ?? 0}`,\n )\n }\n\n // Store config for init segment generation\n this._videoConfig = config.trackType === 'video' && config.avcC\n ? { avcC: config.avcC, width: config.width ?? 0, height: config.height ?? 0 }\n : config.trackType === 'video' && config.hvcC && this.#hevcSampleEntry\n ? {\n hvcC: config.hvcC,\n hevcSampleEntry: this.#hevcSampleEntry,\n width: config.width ?? 0,\n height: config.height ?? 0,\n }\n : null\n this._audioConfig = config.trackType === 'audio' && config.sampleRate && config.channels\n ? { sampleRate: config.sampleRate, channels: config.channels, asc: config.asc }\n : null\n }\n\n private _videoConfig: {\n avcC?: Uint8Array\n hvcC?: Uint8Array\n hevcSampleEntry?: 'hev1' | 'hvc1'\n width: number\n height: number\n } | null\n private _audioConfig: { sampleRate: number; channels: number; asc?: Uint8Array } | null\n\n get sampleOrderStats(): Readonly<CmafSampleOrderStats> {\n return { ...this.#sampleOrderStats }\n }\n\n /** Cumulative emit and block-event counters since construction. */\n get emitStats(): Readonly<CmafEmitStats> {\n return { ...this.#emitStats }\n }\n\n /** Cumulative audio gap concealment counters since construction. */\n get audioConcealStats(): Readonly<CmafAudioConcealStats> {\n return { ...this.#audioConcealStats }\n }\n\n /** Emit a single frame immediately as a one-sample moof+mdat segment.\n *\n * BLO-3035 layer 4 (4b): the prior SBN-grouped buffering scheme stranded\n * frames whenever a multi-packet frame spanned FEC block boundaries \u2014\n * intermediate SBNs had packets-but-no-frames and got `completeBlock`\n * events that landed on empty `#blocks` (\u2192 `pendingFlush` leak), while\n * the SBN where the multi-packet frame finally completed received the\n * `addFrame` but its `trackBlock` count never reached K (\u2192 `pendingBlocks`\n * leak). Result: ~50% of received frames never reached MSE.\n *\n * This path now mirrors `addBypassFrame` \u2014 emit on arrival, no buffering.\n * FEC recovery (`addRecoveredBlock`) keeps its SBN-watermark dedup against\n * the same SBN being re-emitted from two paths. Out-of-order regression\n * is guarded by the per-frame `lastEmittedTs` check (also enforced in\n * `#emitPerSample`). The `sbn`/`esi` parameters are kept for the call-site\n * signature but no longer affect routing. */\n addFrame(\n sbn: number,\n esi: number,\n data: Uint8Array,\n timestamp: number,\n keyframe: boolean,\n catalogTrackName?: string,\n mpuSequenceNumber?: number,\n packetSequenceNumber?: number,\n packetSequenceNumbers?: number[],\n repairedPacketSequenceNumbers?: number[],\n authoredTiming?: AuthoredTiming,\n deliveryProvenance?: MmtpDeliveryProvenance,\n mmtpTimestamp?: bigint,\n movieFragmentSequenceNumber?: number,\n sampleNumber?: number,\n gapBefore?: FrameGapDecision,\n ): void {\n void sbn; void esi\n this.#emitStats.addedFrames++\n\n if (!this.#ordersSamples()) this.#drainRecoveredUpTo(timestamp)\n\n // Per-frame duration: default to catalog-derived `#frameDuration`. For\n // variable-rate content Chrome refines duration from the next sample's\n // DTS at append time, so a fallback here is safe.\n this.#emitSample(\n data,\n timestamp,\n this.#frameDuration,\n keyframe,\n true,\n catalogTrackName,\n mpuSequenceNumber,\n packetSequenceNumber,\n packetSequenceNumbers,\n repairedPacketSequenceNumbers,\n authoredTiming,\n deliveryProvenance,\n mmtpTimestamp,\n movieFragmentSequenceNumber,\n sampleNumber,\n undefined,\n gapBefore,\n )\n\n this.#countEmit([{ keyframe }])\n }\n\n /** Mark a block as complete (all K source symbols received or FEC recovery succeeded).\n * If all frames are already buffered, flushes immediately. Otherwise sets pending-flush. */\n completeBlock(sbn: number): void {\n void sbn\n this.#emitStats.completedBlocks++\n // Source samples are emitted directly from addFrame(). A clean FEC block\n // completion is still useful for stats, but it must not enqueue stale\n // pending flushes from the retired block-buffered path.\n }\n\n /**\n * Record an unrecoverable block. Nothing is synthesised: the release gate\n * attaches the stall/conceal decision to the next released frame, and the\n * sink holds the last good frame (video) or repeats the last good AU into\n * the decided slots (audio).\n */\n failBlock(sbn: number): void {\n void sbn\n this.#emitStats.failedBlocks++\n }\n\n /** Add all frames from a FEC-recovered block at once and emit immediately. */\n addRecoveredBlock(sbn: number, frames: BlockFrame[]): void {\n void sbn\n this.#emitStats.recoveredBlocks++\n\n if (frames.length === 0) return\n const normalized = this.#normalizeVideoKeyframes(frames)\n if (normalized.length === 0) return\n this.#countEmit(normalized)\n if (this.#ordersSamples()) {\n this.#emitPerSample(normalized, undefined, false)\n return\n }\n\n // Arrival mode preserves the legacy protection against future recovered\n // samples advancing past source media. Explicit media-window mode routes\n // both paths through one queue instead.\n if (this.#lastSourceTs >= 0) {\n this.#enqueueRecovered(normalized)\n this.#drainRecoveredUpTo(this.#lastSourceTs)\n } else {\n const gated = this.#applyVideoKeyframeGate(normalized)\n if (gated) this.#emitPerSample(gated, undefined, false)\n }\n }\n\n /** Emit a single frame immediately as a one-sample segment (for oversized packets bypassing FEC). */\n addBypassFrame(\n data: Uint8Array,\n timestamp: number,\n keyframe: boolean,\n catalogTrackName?: string,\n mpuSequenceNumber?: number,\n packetSequenceNumber?: number,\n packetSequenceNumbers?: number[],\n repairedPacketSequenceNumbers?: number[],\n authoredTiming?: AuthoredTiming,\n deliveryProvenance?: MmtpDeliveryProvenance,\n mmtpTimestamp?: bigint,\n movieFragmentSequenceNumber?: number,\n sampleNumber?: number,\n gapBefore?: FrameGapDecision,\n ): void {\n if (!this.#ordersSamples()) this.#drainRecoveredUpTo(timestamp)\n this.#emitSample(\n data,\n timestamp,\n this.#frameDuration,\n keyframe,\n true,\n catalogTrackName,\n mpuSequenceNumber,\n packetSequenceNumber,\n packetSequenceNumbers,\n repairedPacketSequenceNumbers,\n authoredTiming,\n deliveryProvenance,\n mmtpTimestamp,\n movieFragmentSequenceNumber,\n sampleNumber,\n undefined,\n gapBefore,\n )\n }\n\n /** Build and emit init segment from catalog config. */\n emitInit(): void {\n let init: Uint8Array | null = null\n if (this._videoConfig?.avcC) {\n init = buildVideoInitSegment(\n this._videoConfig.avcC,\n this._videoConfig.width,\n this._videoConfig.height,\n this.#config.timescale,\n this.#config.trackId,\n )\n } else if (this._videoConfig?.hvcC && this._videoConfig.hevcSampleEntry) {\n init = buildHevcVideoInitSegment(\n this._videoConfig.hevcSampleEntry,\n this._videoConfig.hvcC,\n this._videoConfig.width,\n this._videoConfig.height,\n this.#config.timescale,\n this.#config.trackId,\n )\n } else if (this._audioConfig) {\n init = buildAudioInitSegment(\n this._audioConfig.sampleRate,\n this._audioConfig.channels,\n this._audioConfig.asc,\n this.#config.timescale,\n this.#config.trackId,\n )\n }\n if (init) this.onInitSegment?.(init)\n }\n\n /** Drain all ordered samples at end-of-stream or before reconfiguration. */\n flush(): void {\n if (this.#ordersSamples()) {\n this.#drainSampleReorder(true)\n return\n }\n if (this.#pendingRecovered.size === 0) return\n const frames = [...this.#pendingRecovered.values()].sort((a, b) => a.timestamp - b.timestamp)\n this.#pendingRecovered.clear()\n const gated = this.#applyVideoKeyframeGate(frames)\n if (gated) this.#emitPerSample(gated, undefined, false)\n else if (this.#config.trackType === 'video') {\n this.#sampleOrderStats.noRapFlushDrops += frames.length\n }\n }\n\n /** Drop all queued state and begin a fresh timestamp epoch. */\n reset(): void {\n this.#pendingRecovered.clear()\n this.#sequence = 1\n this.#baseTs = -1\n this.#lastEmittedTs = -1\n this.#lastSourceTs = -1\n this.#recoveredHorizonTs = -1\n this.#audioConcealer.reset()\n this.#audioConcealClock = null\n this.#armedVideo = false\n this.#sampleReorderQueue = []\n }\n\n dispose(): void {\n this.reset()\n this.onSegment = null\n this.onSample = null\n this.onInitSegment = null\n }\n\n // \u2500\u2500 Private \u2500\u2500\n\n /** Rebase an absolute timestamp to relative media time (starting near 0). */\n #rebase(timestamp: number): number {\n if (this.#baseTs < 0) this.#baseTs = timestamp\n return timestamp - this.#baseTs\n }\n\n #normalizeSampleData(data: Uint8Array): Uint8Array {\n return this.#config.trackType === 'audio' ? normalizeAacSampleForCmaf(data) : data\n }\n\n #enqueueRecovered(frames: BlockFrame[]): void {\n for (const frame of frames) {\n if (this.#lastEmittedTs >= 0 && frame.timestamp <= this.#lastEmittedTs) {\n this.#sampleOrderStats.staleSamples++\n continue\n }\n if (this.#pendingRecovered.has(frame.timestamp)) {\n this.#sampleOrderStats.duplicateSamples++\n continue\n }\n this.#pendingRecovered.set(frame.timestamp, frame)\n }\n }\n\n #drainRecoveredUpTo(horizonTs: number): void {\n if (this.#pendingRecovered.size === 0) return\n const ready = [...this.#pendingRecovered.values()]\n .filter(frame => frame.timestamp <= horizonTs)\n .sort((a, b) => a.timestamp - b.timestamp)\n\n for (const frame of ready) {\n this.#pendingRecovered.delete(frame.timestamp)\n this.#emitSample(\n frame.data,\n frame.timestamp,\n this.#frameDuration,\n frame.keyframe,\n false,\n frame.catalogTrackName,\n frame.mpuSequenceNumber,\n frame.packetSequenceNumber,\n frame.packetSequenceNumbers,\n frame.repairedPacketSequenceNumbers,\n frame.authoredTiming,\n frame.deliveryProvenance,\n frame.mmtpTimestamp,\n frame.movieFragmentSequenceNumber,\n frame.sampleNumber,\n frame.synthetic,\n frame.gapBefore,\n )\n this.#countEmit([frame])\n }\n }\n}\n\n/**\n * Video sample entry whose bitstream keyframe rule the assembler enforces,\n * selected from the catalog codec string (never sniffed):\n *\n * - `avc1`/`avc3`/`hev1`/`hvc1` from `config.codec`.\n * - No codec string: `avc1` (the legacy H.264 contract, which an avcC\n * confirms). An hvcC without a codec string is refused because the sample\n * entry (hev1 vs hvc1) cannot be chosen without it.\n * - Any other video codec (AV1, VP9): 'unvalidated' \u2014 its metadata keyframe\n * flag is trusted, as before.\n *\n * A configuration record that contradicts the codec family is refused.\n */\nfunction resolveVideoSampleEntry(config: BlockAssemblerConfig): VideoSampleEntry | 'unvalidated' {\n if (!config.codec) {\n if (config.hvcC) {\n throw new Error('[CmafBlockAssembler] hvcC requires a catalog codec string naming hev1 or hvc1')\n }\n return 'avc1'\n }\n const fourCc = config.codec.split('.')[0]\n if (fourCc !== 'avc1' && fourCc !== 'avc3' && fourCc !== 'hev1' && fourCc !== 'hvc1') {\n if (config.avcC || config.hvcC) {\n throw new Error(`[CmafBlockAssembler] codec ${config.codec} contradicts the supplied avcC/hvcC`)\n }\n return 'unvalidated'\n }\n const sampleEntry = videoSampleEntryFromCodecString(config.codec)\n const family = videoCodecFamily(sampleEntry)\n if ((family === 'avc' && config.hvcC) || (family === 'hevc' && config.avcC)) {\n throw new Error(`[CmafBlockAssembler] codec ${config.codec} contradicts the supplied avcC/hvcC`)\n }\n return sampleEntry\n}\n\nfunction describeH264NalTypes(nals: Uint8Array[]): number[] {\n return nals.slice(0, 16).map((nal) => (\n nal[0] !== undefined ? nal[0] & 0x1f : -1\n ))\n}\n", "/**\n * @mmt/container - Sample-level combining of subsample data units (moqcast #37)\n *\n * {@link MFUReassembler} reassembles exactly one data unit -- a whole-sample\n * DU, or one subsample DU of a partitioned sample. When a timed sample\n * exceeds one data unit's 256-payload capacity, the origin packetizer\n * partitions it into multiple subsample data units (each its own FI chain,\n * its own authored base offset). This module combines those\n * already-reassembled data units back into one contiguous sample.\n *\n * Container-blind, matching {@link MFUReassembler}'s own doctrine: it never\n * parses ISOBMFF itself. The authored sample size a subsample-partitioned\n * sample must total to comes from the host's own FT=1 (movie-fragment-\n * metadata) parse, supplied via {@link SampleCombiner.registerAuthoredSize}.\n * Completion is a protocol event -- every constituent data unit reassembled,\n * byte-adjacent from zero, totaling the registered size -- never a\n * timer-based guess; on lossy paths the existing per-data-unit deadline in\n * {@link MFUReassembler} already bounds each constituent, so this module adds\n * no second timer tier, matching moqcast #37 section 5.2.1.\n *\n * A whole-sample data unit whose length already equals the registered\n * authored size passes through immediately, with no buffering. When\n * `requireAuthoredSize` is false, an offset-zero data unit with no registered\n * size at all also passes through immediately -- the pre-amendment shape, for\n * a host that does not drive FT=1. When true, that same data unit instead\n * buffers and waits for a size to be registered: FT=1 always publishes before\n * any FT=2 sample referencing it (moqcast #37), but on a lossy path FT=1 can\n * still *arrive* after an offset-zero data unit (reordering, or FT=1's own DU\n * FEC-recovered late) -- treating \"no size yet\" as \"whole sample\" in that\n * window would silently truncate a subsample-partitioned sample.\n *\n * Both `#pending` (incomplete samples) and `#authoredSizes` (registered but\n * not yet consumed) are bounded. `#pending` is bounded by `maxBuffered`\n * samples and `maxSegmentsPerSample` data units per sample; a full table\n * evicts its oldest sample by insertion order rather than throwing, so one\n * FEC-lost subsample DU cannot wedge every future sample. `#authoredSizes` is\n * bounded by retiring each key the moment its sample completes, keeping only a\n * bounded recent-retirement set so a late duplicate or FEC-recovered\n * retransmission for that same sample is dropped rather than silently\n * re-emitted or misread as a fresh whole sample.\n *\n * @packageDocumentation\n */\n\nimport type {\n MmtpSourcePath,\n ReassembledMFU,\n MmtpDeliveryProvenance,\n} from './mfu-reassembler.js'\n\n/** Identifies one authored sample: `(catalogTrackName, mpu, movieFragmentSequenceNumber, sampleNumber)`. */\ntype SampleKey = string\n\nfunction sampleKey(\n catalogTrackName: string | undefined,\n mpuSequenceNumber: number,\n movieFragmentSequenceNumber: number,\n sampleNumber: number,\n): SampleKey {\n return JSON.stringify([catalogTrackName ?? null, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber])\n}\n\n/** The `catalogTrackName` a key was built from, or undefined when it had none. */\nfunction trackNameOfKey(key: SampleKey): string | undefined {\n const name = (JSON.parse(key) as [string | null, number, number, number])[0]\n return name ?? undefined\n}\n\n/** The full authored-sample identity a key was built from. */\nfunction identityOfKey(key: SampleKey): AbandonedSample {\n const [name, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber] =\n JSON.parse(key) as [string | null, number, number, number]\n return {\n catalogTrackName: name ?? undefined,\n mpuSequenceNumber,\n movieFragmentSequenceNumber,\n sampleNumber,\n }\n}\n\n/**\n * An authored sample that provably will not be delivered, reported to the\n * owner of any per-sample state keyed on this identity so that state can be\n * released. See {@link SampleCombinerOptions.onAbandon}.\n */\nexport interface AbandonedSample {\n catalogTrackName: string | undefined\n mpuSequenceNumber: number\n movieFragmentSequenceNumber: number\n sampleNumber: number\n}\n\n/** One authored sample combined from its constituent data unit(s). */\nexport interface CombinedSample {\n mpuSequenceNumber: number\n movieFragmentSequenceNumber: number\n sampleNumber: number\n /** The full sample's media bytes, in ascending-offset order. */\n data: Uint8Array\n /** Raw MMTP short-NTP header timestamp for the offset-zero data unit. */\n mmtpTimestamp?: bigint\n timestamp: bigint\n /**\n * The sample's RAP flag is the RAP flag of the FI=0 or FI=1 object of its\n * offset-0 data unit (moqcast #37).\n */\n rapFlag: boolean\n priority?: number\n /** Every authoritative source-packet sequence across all constituent data units, in offset order. */\n packetSequenceNumbers?: number[]\n /** Subset of packetSequenceNumbers reconstructed by FEC. */\n repairedPacketSequenceNumbers?: number[]\n catalogTrackName?: string\n /** Number of subsample data units combined (1 for a whole-sample DU or a non-timed passthrough). */\n subsampleCount: number\n /** MMTP fragments that formed the sample, summed across constituent data units. */\n fragmentCount: number\n /**\n * Transport that supplied the offset-zero data unit's head fragment. Taken\n * from that data unit alone -- like {@link CombinedSample.rapFlag} -- rather\n * than from whichever sibling happened to complete last.\n */\n source?: MmtpSourcePath\n /**\n * Strict provenance folded across EVERY constituent data unit, and within\n * each of them across every MMTP fragment. Unlike {@link CombinedSample.source},\n * which is the offset-zero DU's routing tag, this is whole-sample evidence:\n * only 'multicast' proves that no byte of this sample arrived another way.\n */\n deliveryProvenance: MmtpDeliveryProvenance\n}\n\nexport const SAMPLE_COMBINE_ERROR_CODES = [\n 'duplicate-subsample-offset',\n 'overlapping-subsample',\n 'exceeds-authored-size',\n 'too-many-subsample-segments',\n] as const\n\nexport type SampleCombineErrorCode = typeof SAMPLE_COMBINE_ERROR_CODES[number]\n\n/**\n * Fail-closed rejection reasons for subsample combining. Distinct from\n * {@link MfuReassemblyError}, which governs one data unit's own fragment\n * chain; these govern combining already-reassembled data units into a\n * sample.\n */\nexport class SampleCombineError extends Error {\n readonly name = 'SampleCombineError'\n\n constructor(readonly code: SampleCombineErrorCode, message: string) {\n super(message)\n }\n}\n\nexport interface SampleCombinerStats {\n combinedSamples: number\n passthroughSamples: number\n timedOutSamples: number\n /** Incomplete samples discarded to enforce `maxBuffered`. */\n evictedSamples: number\n}\n\nexport interface SampleCombinerOptions {\n /**\n * Max concurrent incomplete samples. Enforced by evicting the oldest by\n * insertion order, never by throwing, so a host that never calls\n * {@link SampleCombiner.cleanupTimeouts} still recovers from a permanently\n * lost subsample data unit instead of wedging.\n */\n maxBuffered: number\n /**\n * When true, an offset-zero data unit with no registered authored size\n * buffers and waits for one rather than passing through immediately. See\n * the module doc.\n */\n requireAuthoredSize: boolean\n /**\n * Max subsample data units held for one sample at once. Bounds both memory\n * and the per-insert overlap scan; a legitimate subsample-partitioned\n * sample needs at most `ceil(sampleSize / (256 * maxPayload))` data units.\n */\n maxSegmentsPerSample: number\n /**\n * Called once per sample discarded by eviction or timeout -- the two paths\n * where a sample is abandoned while its stream keeps running.\n *\n * Completion and passthrough are deliberately excluded: those samples were\n * delivered, and their consumer releases whatever per-sample state it held.\n * Wholesale resets (`flush`, `resetStream`) are excluded too, because they\n * already discard that state in bulk.\n *\n * Exists because per-sample state elsewhere -- notably the ISO-BMFF\n * timing-sample budget -- is otherwise only released on delivery, making it\n * a one-way ratchet that wedges the stream once it saturates. The combiner\n * is where abandonment is actually detected, so it is where the signal has\n * to come from; inferring it downstream would require a timer or an age\n * bound, neither of which is derivable from the catalog.\n */\n onAbandon?: (sample: AbandonedSample) => void\n}\n\ninterface PendingSample {\n /** Held data units keyed by their authored base offset. */\n segments: Map<number, ReassembledMFU>\n startedAt: number\n}\n\n/** Combines subsample data units of one sample into a contiguous whole. */\nexport class SampleCombiner {\n #maxBuffered: number\n #requireAuthoredSize: boolean\n #maxSegmentsPerSample: number\n #onAbandon: ((sample: AbandonedSample) => void) | undefined\n #authoredSizes: Map<SampleKey, number> = new Map()\n /** Map iteration is insertion-ordered, so the first key is the oldest. */\n #pending: Map<SampleKey, PendingSample> = new Map()\n /**\n * Bounded, insertion-ordered set of recently-completed sample keys, so a\n * late duplicate or FEC-recovered retransmission is dropped rather than\n * re-emitted or misread as a fresh whole sample.\n */\n #retired: Set<SampleKey> = new Set()\n #stats: SampleCombinerStats = {\n combinedSamples: 0,\n passthroughSamples: 0,\n timedOutSamples: 0,\n evictedSamples: 0,\n }\n\n constructor(options: SampleCombinerOptions) {\n const { maxBuffered, requireAuthoredSize, maxSegmentsPerSample, onAbandon } = options\n if (!Number.isInteger(maxBuffered) || maxBuffered <= 0) {\n throw new Error('[SampleCombiner] maxBuffered must be a positive integer')\n }\n if (!Number.isInteger(maxSegmentsPerSample) || maxSegmentsPerSample <= 0) {\n throw new Error('[SampleCombiner] maxSegmentsPerSample must be a positive integer')\n }\n this.#maxBuffered = maxBuffered\n this.#requireAuthoredSize = requireAuthoredSize\n this.#maxSegmentsPerSample = maxSegmentsPerSample\n this.#onAbandon = onAbandon\n }\n\n /** Report one abandoned sample to the owner of any state keyed on it. */\n #abandon(key: SampleKey): void {\n this.#onAbandon?.(identityOfKey(key))\n }\n\n /**\n * Register the authored sample size from the host's own FT=1 parse. Per\n * moqcast #37, FT=1 for a movie fragment publishes before any FT=2 sample\n * referencing it, so a host driving FT=1 as it arrives registers each\n * sample's size before that sample's own data unit(s) arrive.\n *\n * On a lossy path FT=1 can still *arrive* late, after data units this\n * combiner has already buffered. Registering a size therefore re-drives the\n * completion check and returns the combined sample when the buffered data\n * units already satisfy the newly-known size. Callers MUST consume the\n * return value: for a sample carried by a single data unit there is no\n * later {@link SampleCombiner.addMfu} to re-enter, so discarding it\n * silently drops the sample.\n *\n * Registering also clears the key from the retired set. Retirement exists\n * to drop data units that arrive *passively* after a sample completed \u2014 a\n * late duplicate, an FEC-recovered retransmission. A host calling this\n * method is instead asserting that a sample with this identity is authored\n * now, which is what happens when a publisher restarts and its MPU\n * sequence numbers begin again, or when MPU sequence wraps. Without this,\n * such a stream would silently drop every sample whose identity collided\n * with a retired one until the bounded set aged the key out.\n */\n registerAuthoredSize(\n catalogTrackName: string | undefined,\n mpuSequenceNumber: number,\n movieFragmentSequenceNumber: number,\n sampleNumber: number,\n size: number,\n ): CombinedSample | null {\n const key = sampleKey(catalogTrackName, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber)\n this.#retired.delete(key)\n this.#authoredSizes.set(key, size)\n return this.#tryComplete(key, size)\n }\n\n /**\n * Add one reassembled data unit. Returns the combined sample once every\n * constituent data unit has arrived and totals the authored size.\n */\n addMfu(mfu: ReassembledMFU): CombinedSample | null {\n if (mfu.movieFragmentSequenceNumber === undefined || mfu.sampleNumber === undefined) {\n // Non-timed (Item) MFU: sample combining does not apply.\n this.#stats.passthroughSamples++\n return fromPassthrough(mfu)\n }\n const offset = mfu.sampleOffset ?? 0\n const key = sampleKey(\n mfu.catalogTrackName,\n mfu.mpuSequenceNumber,\n mfu.movieFragmentSequenceNumber,\n mfu.sampleNumber,\n )\n\n if (this.#retired.has(key)) {\n // Duplicate or late data unit for an already-retired sample (a\n // FEC-recovered retransmission, reordering after completion, or a\n // sample discarded by eviction/timeout). Drop it rather than\n // re-emitting a duplicate downstream or misreading a stray fragment as\n // a fresh whole sample.\n return null\n }\n\n const dataLen = mfu.data.byteLength\n const authoredSize = this.#authoredSizes.get(key)\n\n if (offset === 0 && !this.#pending.has(key)) {\n const passesThrough = authoredSize === undefined\n ? !this.#requireAuthoredSize\n : dataLen === authoredSize\n if (passesThrough) {\n this.#stats.passthroughSamples++\n this.#retire(key)\n return fromPassthrough(mfu)\n }\n // Either shorter than the authored total (a subsample), or awaiting a\n // size under requireAuthoredSize -- buffer below.\n }\n\n // Reject BEFORE mutating any state. An earlier revision allocated the\n // pending entry (evicting the oldest sample to make room) and only then\n // validated, so a data unit the combiner went on to reject could destroy\n // an unrelated in-flight sample and leave an empty entry in its slot.\n const thisEnd = offset + dataLen\n if (authoredSize !== undefined && thisEnd > authoredSize) {\n throw new SampleCombineError(\n 'exceeds-authored-size',\n 'combined subsample data would exceed the registered authored sample size',\n )\n }\n const existing = this.#pending.get(key)\n if (existing !== undefined) {\n if (existing.segments.has(offset)) {\n throw new SampleCombineError(\n 'duplicate-subsample-offset',\n 'subsample data unit duplicates an offset already held for this sample',\n )\n }\n if (existing.segments.size >= this.#maxSegmentsPerSample) {\n throw new SampleCombineError(\n 'too-many-subsample-segments',\n 'a sample already holds the maximum number of subsample data units',\n )\n }\n for (const [segOffset, seg] of existing.segments) {\n const segEnd = segOffset + seg.data.byteLength\n if (offset < segEnd && segOffset < thisEnd) {\n throw new SampleCombineError(\n 'overlapping-subsample',\n \"subsample data unit overlaps a preceding data unit's byte range\",\n )\n }\n }\n }\n\n let pending = existing\n if (pending === undefined) {\n if (this.#pending.size >= this.#maxBuffered) this.#evictOldestPending()\n pending = { segments: new Map(), startedAt: Date.now() }\n this.#pending.set(key, pending)\n }\n pending.segments.set(offset, mfu)\n\n if (authoredSize === undefined) return null\n return this.#tryComplete(key, authoredSize)\n }\n\n /**\n * Emit the sample when its held data units are byte-adjacent from zero and\n * total `size`. Completion is a protocol event, never a timer.\n */\n #tryComplete(key: SampleKey, size: number): CombinedSample | null {\n const pending = this.#pending.get(key)\n if (pending === undefined) return null\n let expected = 0\n const sortedOffsets = [...pending.segments.keys()].sort((a, b) => a - b)\n for (const segOffset of sortedOffsets) {\n if (segOffset !== expected) return null // gap: not yet contiguous from zero.\n expected += pending.segments.get(segOffset)!.data.byteLength\n }\n if (expected !== size) return null // contiguous so far, short of the authored total.\n\n this.#pending.delete(key)\n this.#stats.combinedSamples++\n this.#retire(key)\n return fromSegments(pending.segments, sortedOffsets)\n }\n\n /**\n * Retire a sample's key: free its registered authored size and remember the\n * key in a bounded recency set. Called on every path that removes a sample\n * -- completion, passthrough, eviction, and timeout -- so `#authoredSizes`\n * cannot accumulate entries for samples that never complete, and so a later\n * data unit for a discarded sample is dropped rather than starting a fresh\n * buffer or being misread as a whole sample.\n *\n * The recency set holds `maxBuffered` keys. A duplicate delayed by more\n * than that many retirements escapes it; on a reliable path there is no\n * such duplicate, and on a lossy path the per-data-unit deadline in\n * {@link MFUReassembler} bounds the delay well inside this window.\n */\n #retire(key: SampleKey): void {\n this.#authoredSizes.delete(key)\n this.#retired.add(key)\n while (this.#retired.size > this.#maxBuffered) {\n const oldest = this.#retired.values().next().value\n if (oldest === undefined) break\n this.#retired.delete(oldest)\n }\n }\n\n /**\n * Discard the oldest pending sample by insertion order. This is the only\n * backstop against one FEC-lost subsample data unit wedging\n * {@link SampleCombiner.addMfu} for every future sample once `maxBuffered`\n * is reached, for a host that never drives\n * {@link SampleCombiner.cleanupTimeouts}.\n */\n #evictOldestPending(): void {\n const oldest = this.#pending.keys().next().value\n if (oldest === undefined) return\n this.#pending.delete(oldest)\n this.#stats.evictedSamples++\n this.#retire(oldest)\n this.#abandon(oldest)\n }\n\n /**\n * Discard samples whose oldest held segment has waited past `timeoutMs`.\n * Mirrors {@link MFUReassembler}'s clock-driven shape; the per-data-unit\n * deadline inside {@link MFUReassembler} already bounds each constituent,\n * so this is a backstop against a host that registers an authored size but\n * never supplies every subsample DU.\n */\n cleanupTimeouts(timeoutMs: number): void {\n const now = Date.now()\n for (const [key, pending] of [...this.#pending]) {\n if (now - pending.startedAt > timeoutMs) {\n this.#pending.delete(key)\n this.#stats.timedOutSamples++\n this.#retire(key)\n this.#abandon(key)\n }\n }\n }\n\n flush(): void {\n this.#pending.clear()\n this.#authoredSizes.clear()\n this.#retired.clear()\n }\n\n /**\n * Drop one stream's in-flight, registered-size, and retired state while\n * preserving every other stream (mirrors\n * {@link MFUReassembler.resetStream}).\n */\n resetStream(catalogTrackName: string): void {\n for (const key of this.#pending.keys()) {\n if (trackNameOfKey(key) === catalogTrackName) this.#pending.delete(key)\n }\n for (const key of this.#authoredSizes.keys()) {\n if (trackNameOfKey(key) === catalogTrackName) this.#authoredSizes.delete(key)\n }\n for (const key of this.#retired) {\n if (trackNameOfKey(key) === catalogTrackName) this.#retired.delete(key)\n }\n }\n\n get stats(): SampleCombinerStats {\n return { ...this.#stats }\n }\n\n get pendingCount(): number {\n return this.#pending.size\n }\n\n /**\n * Registered authored sizes still held. Diagnostic only: it exists so tests\n * and health reporting can assert this table stays bounded, since an\n * unbounded one is invisible from `pendingCount` alone.\n */\n get debugAuthoredSizeCount(): number {\n return this.#authoredSizes.size\n }\n\n /** Retired keys still remembered. Diagnostic only, bounded by `maxBuffered`. */\n get debugRetiredCount(): number {\n return this.#retired.size\n }\n}\n\nfunction fromPassthrough(mfu: ReassembledMFU): CombinedSample {\n return {\n mpuSequenceNumber: mfu.mpuSequenceNumber,\n movieFragmentSequenceNumber: mfu.movieFragmentSequenceNumber ?? 0,\n sampleNumber: mfu.sampleNumber ?? 0,\n data: mfu.data,\n mmtpTimestamp: mfu.timestamp,\n timestamp: mfu.timestamp,\n rapFlag: mfu.rapFlag,\n priority: mfu.priority,\n packetSequenceNumbers: mfu.packetSequenceNumbers,\n repairedPacketSequenceNumbers: mfu.repairedPacketSequenceNumbers,\n catalogTrackName: mfu.catalogTrackName,\n subsampleCount: 1,\n fragmentCount: mfu.fragmentCount,\n source: mfu.source,\n // One data unit in, one sample out: its provenance IS the sample's.\n deliveryProvenance: mfu.deliveryProvenance\n }\n}\n\nfunction fromSegments(\n segments: Map<number, ReassembledMFU>,\n sortedOffsets: number[],\n): CombinedSample {\n const totalSize = sortedOffsets.reduce((sum, offset) => sum + segments.get(offset)!.data.byteLength, 0)\n const data = new Uint8Array(totalSize)\n let packetSequenceNumbers: number[] | undefined\n let repairedPacketSequenceNumbers: number[] | undefined\n let writeOffset = 0\n let first: ReassembledMFU | undefined\n let fragmentCount = 0\n // Fold across data units the same way the reassembler folds across the\n // fragments inside one: a sample is multicast-exclusive only if every\n // subsample that formed it was.\n let combinedProvenance: MmtpDeliveryProvenance =\n sortedOffsets.length === 0 ? 'unknown' : 'multicast'\n for (const offset of sortedOffsets) {\n const seg = segments.get(offset)!\n combinedProvenance =\n combinedProvenance === 'mixed' || seg.deliveryProvenance === 'mixed'\n ? 'mixed'\n : combinedProvenance === 'unknown' || seg.deliveryProvenance === 'unknown'\n ? 'unknown'\n : 'multicast'\n data.set(seg.data, writeOffset)\n writeOffset += seg.data.byteLength\n fragmentCount += seg.fragmentCount\n if (seg.packetSequenceNumbers !== undefined) {\n (packetSequenceNumbers ??= []).push(...seg.packetSequenceNumbers)\n }\n if (seg.repairedPacketSequenceNumbers !== undefined) {\n (repairedPacketSequenceNumbers ??= []).push(...seg.repairedPacketSequenceNumbers)\n }\n if (first === undefined) first = seg\n }\n return {\n mpuSequenceNumber: first!.mpuSequenceNumber,\n movieFragmentSequenceNumber: first!.movieFragmentSequenceNumber!,\n sampleNumber: first!.sampleNumber!,\n data,\n mmtpTimestamp: first!.timestamp,\n timestamp: first!.timestamp,\n rapFlag: first!.rapFlag,\n priority: first!.priority,\n packetSequenceNumbers,\n repairedPacketSequenceNumbers,\n catalogTrackName: first!.catalogTrackName,\n subsampleCount: segments.size,\n fragmentCount,\n source: first!.source,\n deliveryProvenance: combinedProvenance,\n }\n}\n\n// =============================================================================\n// Inline Tests (vitest)\n// =============================================================================\n\nif (import.meta.vitest) {\n const { describe, test, expect } = import.meta.vitest\n\n const combiner = (overrides: Partial<SampleCombinerOptions> = {}) =>\n new SampleCombiner({\n maxBuffered: 10,\n requireAuthoredSize: false,\n maxSegmentsPerSample: 8,\n ...overrides,\n })\n\n const mfu = (\n mpu: number,\n movie: number,\n sample: number,\n offset: number,\n data: number[],\n timestamp: bigint,\n rapFlag: boolean,\n deliveryProvenance: MmtpDeliveryProvenance = 'multicast',\n ): ReassembledMFU => ({\n mpuSequenceNumber: mpu,\n data: new Uint8Array(data),\n timestamp,\n fragmentCount: 1,\n rapFlag,\n sampleNumber: sample,\n movieFragmentSequenceNumber: movie,\n sampleOffset: offset,\n packetSequenceNumbers: [offset],\n deliveryProvenance,\n })\n\n describe('SampleCombiner', () => {\n test('whole-sample DU passes through immediately with no registered size', () => {\n const c = combiner()\n const out = c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]))\n expect(out!.subsampleCount).toBe(1)\n expect(c.pendingCount).toBe(0)\n })\n\n test('whole-sample DU passes through when it matches the registered size', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 1, 9, 17, 5)\n const out = c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))\n expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]))\n })\n\n test('subsample data units combine once the authored size is met', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 1, 9, 17, 10)\n\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()\n expect(c.pendingCount).toBe(1)\n\n const out = c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))\n expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))\n expect(out?.subsampleCount).toBe(2)\n expect(out?.rapFlag).toBe(true) // offset-0 DU's RAP flag, per moqcast #37.\n expect(out?.timestamp).toBe(1000n)\n expect(c.pendingCount).toBe(0)\n expect(c.stats.combinedSamples).toBe(1)\n })\n\n test('subsample data units combine out of order', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 1, 9, 17, 10)\n\n expect(c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))).toBeNull()\n const out = c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))\n expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))\n })\n\n test('subsample without a registered size never completes', () => {\n // A non-zero-offset DU always buffers (it can never be a whole\n // sample). Without a registered authored size there is nothing to\n // combine against, so it -- and a sibling that arrives after it --\n // stay pending rather than being guessed complete.\n const c = combiner()\n expect(c.addMfu(mfu(1, 9, 17, 3, [1, 2, 3], 1001n, false))).toBeNull()\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3], 1000n, true))).toBeNull()\n expect(c.pendingCount).toBe(1)\n })\n\n test('requireAuthoredSize buffers an offset-zero DU instead of guessing complete', () => {\n // A moqcast-#37-aware host opts into fail-closed buffering: on a lossy\n // path FT=1 can arrive after an offset-zero data unit (reordering, or\n // FT=1's own DU recovered late by FEC). Treating \"no size yet\" as\n // \"whole sample\" would silently truncate a partitioned sample.\n const c = combiner({ requireAuthoredSize: true })\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()\n expect(c.pendingCount).toBe(1)\n\n c.registerAuthoredSize(undefined, 1, 9, 17, 10)\n const out = c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))\n expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))\n })\n\n test('duplicate offset is rejected', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 1, 9, 17, 10)\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()\n expect(() => c.addMfu(mfu(1, 9, 17, 0, [9, 9, 9, 9, 9], 2000n, true)))\n .toThrow(SampleCombineError)\n })\n\n test('overlapping subsample is rejected', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 1, 9, 17, 10)\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()\n expect(() => c.addMfu(mfu(1, 9, 17, 3, [9, 9, 9, 9, 9], 2000n, true)))\n .toThrow(SampleCombineError)\n })\n\n test('exceeding the authored size is rejected', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 1, 9, 17, 8)\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()\n expect(() => c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 2000n, false)))\n .toThrow(SampleCombineError)\n })\n\n test('exceeding maxSegmentsPerSample is rejected', () => {\n const c = combiner({ maxSegmentsPerSample: 2 })\n c.registerAuthoredSize(undefined, 1, 9, 17, 100)\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2], 1000n, true))).toBeNull()\n expect(c.addMfu(mfu(1, 9, 17, 2, [3, 4], 1001n, false))).toBeNull()\n expect(() => c.addMfu(mfu(1, 9, 17, 4, [5, 6], 1002n, false)))\n .toThrow(SampleCombineError)\n })\n\n test('reports every abandoned sample so its per-sample state can be freed', () => {\n // Regression (BLO-31103): eviction and timeout are the two paths where\n // a sample is abandoned while its stream keeps running. State keyed on\n // that sample elsewhere -- notably the ISO-BMFF timing-sample budget --\n // is otherwise released only on delivery, so an abandoned sample holds\n // its slot forever and the budget becomes a one-way ratchet that wedges\n // the stream at saturation.\n const abandoned: AbandonedSample[] = []\n const c = combiner({ maxBuffered: 2, onAbandon: s => abandoned.push(s) })\n c.registerAuthoredSize(undefined, 1, 9, 17, 10)\n c.registerAuthoredSize(undefined, 1, 9, 18, 10)\n c.registerAuthoredSize(undefined, 1, 9, 19, 10)\n\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()\n expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()\n expect(abandoned).toEqual([])\n\n // Eviction reports the evicted identity, not the incoming one.\n expect(c.addMfu(mfu(1, 9, 19, 0, [3, 3, 3, 3, 3], 1002n, true))).toBeNull()\n expect(abandoned).toEqual([{\n catalogTrackName: undefined,\n mpuSequenceNumber: 1,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n }])\n\n // Timeout reports too; both survivors are still incomplete here.\n c.cleanupTimeouts(-1)\n expect(abandoned.map(s => s.sampleNumber)).toEqual([17, 18, 19])\n\n // A delivered sample is NOT reported: its consumer releases that state.\n const d = combiner({ maxBuffered: 2, onAbandon: s => abandoned.push(s) })\n d.registerAuthoredSize(undefined, 1, 9, 20, 10)\n expect(d.addMfu(mfu(1, 9, 20, 0, [4, 4, 4, 4, 4], 1003n, true))).toBeNull()\n expect(d.addMfu(mfu(1, 9, 20, 5, [5, 5, 5, 5, 5], 1004n, false))).not.toBeNull()\n expect(abandoned.map(s => s.sampleNumber)).toEqual([17, 18, 19])\n })\n\n test('a full combiner evicts the oldest pending sample instead of throwing', () => {\n // Eviction is by insertion order, which is also the only backstop\n // against one permanently-lost subsample DU wedging every future\n // sample once maxBuffered incomplete samples accumulate.\n const c = combiner({ maxBuffered: 2 })\n c.registerAuthoredSize(undefined, 1, 9, 17, 10)\n c.registerAuthoredSize(undefined, 1, 9, 18, 10)\n c.registerAuthoredSize(undefined, 1, 9, 19, 10)\n\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()\n expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()\n expect(c.pendingCount).toBe(2)\n\n // A third distinct incomplete sample evicts sample 17, the oldest.\n expect(c.addMfu(mfu(1, 9, 19, 0, [3, 3, 3, 3, 3], 1002n, true))).toBeNull()\n expect(c.pendingCount).toBe(2)\n expect(c.stats.evictedSamples).toBe(1)\n\n // The surviving samples are undisturbed and still complete normally.\n expect(c.addMfu(mfu(1, 9, 18, 5, [8, 8, 8, 8, 8], 2001n, false))?.data)\n .toEqual(new Uint8Array([2, 2, 2, 2, 2, 8, 8, 8, 8, 8]))\n expect(c.addMfu(mfu(1, 9, 19, 5, [9, 9, 9, 9, 9], 2002n, false))?.data)\n .toEqual(new Uint8Array([3, 3, 3, 3, 3, 9, 9, 9, 9, 9]))\n\n // The combiner still accepts new work rather than being wedged.\n c.registerAuthoredSize(undefined, 1, 9, 20, 10)\n expect(c.addMfu(mfu(1, 9, 20, 0, [4, 4, 4, 4, 4], 3000n, true))).toBeNull()\n expect(c.addMfu(mfu(1, 9, 20, 5, [7, 7, 7, 7, 7], 3001n, false))?.data)\n .toEqual(new Uint8Array([4, 4, 4, 4, 4, 7, 7, 7, 7, 7]))\n })\n\n test('a completed sample frees its authored size and drops a late duplicate', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 1, 9, 17, 10)\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()\n expect(c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))).not.toBeNull()\n\n // A FEC-recovered retransmission of the completed sample's offset-0\n // data unit must not be re-emitted as a second complete sample.\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 2000n, true))).toBeNull()\n })\n\n test('resetStream drops one track and preserves the others', () => {\n const c = combiner()\n c.registerAuthoredSize('video/720p', 1, 9, 17, 10)\n c.registerAuthoredSize('audio', 1, 9, 17, 10)\n const video = { ...mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true), catalogTrackName: 'video/720p' }\n const audio = { ...mfu(1, 9, 17, 0, [2, 2, 2, 2, 2], 1000n, true), catalogTrackName: 'audio' }\n expect(c.addMfu(video)).toBeNull()\n expect(c.addMfu(audio)).toBeNull()\n\n c.resetStream('video/720p')\n expect(c.pendingCount).toBe(1)\n\n const audioTail = { ...mfu(1, 9, 17, 5, [3, 3, 3, 3, 3], 1001n, false), catalogTrackName: 'audio' }\n expect(c.addMfu(audioTail)?.data).toEqual(new Uint8Array([2, 2, 2, 2, 2, 3, 3, 3, 3, 3]))\n })\n\n test('flush clears registered authored sizes', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 1, 9, 17, 5)\n c.flush()\n // A now-unregistered size means an offset-zero DU passes through\n // immediately again (requireAuthoredSize is false here) rather than\n // waiting on a size flush() already discarded.\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))?.data)\n .toEqual(new Uint8Array([1, 2, 3, 4, 5]))\n })\n\n test('a healthy stream accumulates no bookkeeping', () => {\n // Regression: retirement ran only on completion paths, so an\n // abandoned sample's registered size stayed resident forever.\n const c = combiner({ maxBuffered: 4, requireAuthoredSize: true })\n for (let i = 0; i < 1000; i++) {\n expect(c.registerAuthoredSize(undefined, 1, 9, i, 10)).toBeNull()\n expect(c.addMfu(mfu(1, 9, i, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()\n expect(c.addMfu(mfu(1, 9, i, 5, [2, 2, 2, 2, 2], 1001n, false))).not.toBeNull()\n }\n expect(c.stats.combinedSamples).toBe(1000)\n expect(c.pendingCount).toBe(0)\n expect(c.debugAuthoredSizeCount).toBe(0)\n expect(c.debugRetiredCount).toBeLessThanOrEqual(4)\n })\n\n test('samples that never complete release their authored size', () => {\n const c = combiner({ maxBuffered: 4, requireAuthoredSize: true })\n for (let i = 0; i < 1000; i++) {\n expect(c.registerAuthoredSize(undefined, 1, 9, i, 10)).toBeNull()\n expect(c.addMfu(mfu(1, 9, i, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()\n // Deliberately never supply the tail.\n }\n c.cleanupTimeouts(-1) // expire everything still held\n expect(c.pendingCount).toBe(0)\n expect(c.debugAuthoredSizeCount).toBe(0)\n })\n\n test('a late authored size completes an already-buffered sample', () => {\n // Regression: with requireAuthoredSize the completion check ran only\n // inside addMfu, so a sample carried by a SINGLE data unit whose FT=1\n // arrived afterwards had no later data unit to re-enter on and was\n // silently lost.\n const c = combiner({ requireAuthoredSize: true })\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()\n expect(c.pendingCount).toBe(1)\n\n const out = c.registerAuthoredSize(undefined, 1, 9, 17, 5)\n expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]))\n expect(out?.subsampleCount).toBe(1)\n expect(c.pendingCount).toBe(0)\n })\n\n test('a rejected data unit does not evict a live sample', () => {\n // Regression: the pending slot was allocated (evicting the oldest\n // sample to make room) BEFORE validation, so a data unit the combiner\n // went on to reject destroyed an unrelated in-flight sample.\n const c = combiner({ maxBuffered: 2 })\n for (const s of [17, 18, 19]) c.registerAuthoredSize(undefined, 1, 9, s, 10)\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()\n expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()\n expect(c.pendingCount).toBe(2)\n\n // 8 + 5 = 13 exceeds sample 19's authored size of 10.\n expect(() => c.addMfu(mfu(1, 9, 19, 8, [9, 9, 9, 9, 9], 2000n, false)))\n .toThrow(SampleCombineError)\n expect(c.stats.evictedSamples).toBe(0)\n expect(c.pendingCount).toBe(2)\n\n // Sample 17 is untouched and still completes.\n expect(c.addMfu(mfu(1, 9, 17, 5, [3, 3, 3, 3, 3], 2001n, false))?.data)\n .toEqual(new Uint8Array([1, 1, 1, 1, 1, 3, 3, 3, 3, 3]))\n })\n\n test('an evicted sample is retired rather than reopened', () => {\n const c = combiner({ maxBuffered: 1 })\n for (const s of [17, 18]) c.registerAuthoredSize(undefined, 1, 9, s, 10)\n expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()\n expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()\n expect(c.stats.evictedSamples).toBe(1)\n\n // Sample 17's tail arrives after its eviction; it must not reopen.\n expect(c.addMfu(mfu(1, 9, 17, 5, [3, 3, 3, 3, 3], 2000n, false))).toBeNull()\n expect(c.pendingCount).toBe(1)\n })\n })\n\n describe('sample-level delivery provenance', () => {\n test('one non-multicast subsample makes the whole sample non-exclusive', () => {\n // A 4K/8K sample is split across subsample DUs. If any one of them\n // arrived over QUIC, the assembled sample contains bytes that never\n // crossed a multicast socket, so the sample is not physical evidence\n // even though every other DU was multicast.\n const c = combiner()\n c.registerAuthoredSize(undefined, 1, 1, 1, 4)\n expect(c.addMfu(mfu(1, 1, 1, 0, [1, 2], 100n, true, 'multicast'))).toBeNull()\n const done = c.addMfu(mfu(1, 1, 1, 2, [3, 4], 100n, false, 'mixed'))\n expect(done).not.toBeNull()\n expect(done!.subsampleCount).toBe(2)\n expect(done!.deliveryProvenance).toBe('mixed')\n })\n\n test('an unattributed subsample degrades the sample to unknown', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 2, 1, 1, 4)\n expect(c.addMfu(mfu(2, 1, 1, 0, [1, 2], 100n, true, 'multicast'))).toBeNull()\n const done = c.addMfu(mfu(2, 1, 1, 2, [3, 4], 100n, false, 'unknown'))\n expect(done).not.toBeNull()\n expect(done!.deliveryProvenance).toBe('unknown')\n })\n\n test('an all-multicast sample stays multicast-exclusive', () => {\n const c = combiner()\n c.registerAuthoredSize(undefined, 3, 1, 1, 4)\n expect(c.addMfu(mfu(3, 1, 1, 0, [1, 2], 100n, true, 'multicast'))).toBeNull()\n const done = c.addMfu(mfu(3, 1, 1, 2, [3, 4], 100n, false, 'multicast'))\n expect(done).not.toBeNull()\n expect(done!.deliveryProvenance).toBe('multicast')\n })\n\n test('a passthrough sample inherits its data unit provenance verbatim', () => {\n const c = combiner()\n const done = c.addMfu(mfu(4, 1, 1, 0, [1, 2], 100n, true, 'mixed'))\n expect(done).not.toBeNull()\n expect(done!.subsampleCount).toBe(1)\n expect(done!.deliveryProvenance).toBe('mixed')\n })\n })\n}\n", "export type MfuReassemblyPolicy =\n | ReliableMoqReassemblyPolicy\n | { transport: 'datagram'; deadlineMs: number }\n\nexport interface ReliableMoqReassemblyPolicy {\n transport: 'reliable-moq'\n startsAtRandomAccessGroup?: true\n}\n\nexport type MmtpTrackType = 'video' | 'audio' | null\n\nexport interface ReliableMoqReassemblyOptions {\n /**\n * The transport starts at a complete independently decodable media group.\n * The parser still requires an observed RAP before decoder startup.\n */\n startsAtRandomAccessGroup?: boolean\n}\n\nfunction validateReliableMoqOptionShape(value: Record<string, unknown>): void {\n const unsupported = Object.keys(value).filter(\n key => key !== 'transport' && key !== 'startsAtRandomAccessGroup',\n )\n if (unsupported.length > 0) {\n if (unsupported.includes('maxRacingGroups')) {\n throw new Error(\n '[MFU reassembly] maxRacingGroups was removed; reliable MoQ completion is transport-owned',\n )\n }\n throw new Error(\n `[MFU reassembly] unsupported reliable MoQ option(s): ${unsupported.join(', ')}`,\n )\n }\n const startsAtRandomAccessGroup = value['startsAtRandomAccessGroup']\n if (\n typeof startsAtRandomAccessGroup !== 'boolean' &&\n startsAtRandomAccessGroup !== undefined\n ) {\n throw new Error('[MFU reassembly] startsAtRandomAccessGroup must be boolean')\n }\n}\n\nexport function reliableMoqMfuReassembly(\n options: ReliableMoqReassemblyOptions = {},\n): ReliableMoqReassemblyPolicy {\n if (\n arguments.length > 1 ||\n typeof options !== 'object' ||\n options === null ||\n Array.isArray(options)\n ) {\n throw new Error(\n '[MFU reassembly] the positional maxRacingGroups API was removed; pass a reliable MoQ options object',\n )\n }\n validateReliableMoqOptionShape(options as Record<string, unknown>)\n return options.startsAtRandomAccessGroup\n ? {\n transport: 'reliable-moq',\n startsAtRandomAccessGroup: true,\n }\n : { transport: 'reliable-moq' }\n}\n\nfunction assertValidDatagramDeadline(deadlineMs: unknown): asserts deadlineMs is number {\n if (typeof deadlineMs !== 'number' || !Number.isFinite(deadlineMs) || deadlineMs <= 0) {\n throw new Error('[MFU reassembly] datagram deadlineMs must be a finite number > 0')\n }\n}\n\nexport function datagramMfuReassembly(deadlineMs: number): MfuReassemblyPolicy {\n assertValidDatagramDeadline(deadlineMs)\n return { transport: 'datagram', deadlineMs }\n}\n\n/** Validate the complete runtime shape even when a caller bypasses the policy factories. */\nexport function assertValidMfuReassemblyPolicy(policy: MfuReassemblyPolicy): void {\n if (typeof policy !== 'object' || policy === null || Array.isArray(policy)) {\n throw new Error('[MFU reassembly] policy must be an object')\n }\n\n const value = policy as unknown as Record<string, unknown>\n if (value['transport'] === 'reliable-moq') {\n validateReliableMoqOptionShape(value)\n if (value['startsAtRandomAccessGroup'] === false) {\n throw new Error(\n '[MFU reassembly] startsAtRandomAccessGroup must be true when present in a policy',\n )\n }\n return\n }\n if (value['transport'] === 'datagram') {\n const unsupported = Object.keys(value).filter(\n key => key !== 'transport' && key !== 'deadlineMs',\n )\n if (unsupported.length > 0) {\n throw new Error(\n `[MFU reassembly] unsupported datagram option(s): ${unsupported.join(', ')}`,\n )\n }\n assertValidDatagramDeadline(value['deadlineMs'])\n return\n }\n throw new Error(\n `[MFU reassembly] unsupported transport: ${String(value['transport'])}`,\n )\n}\n\nexport function mfuReassemblyTimeoutMs(policy: MfuReassemblyPolicy): number | null {\n assertValidMfuReassemblyPolicy(policy)\n return policy.transport === 'reliable-moq' ? null : policy.deadlineMs\n}\n\n/** Resolve the parser reorder window without inferring reliable-stream completion. */\nexport function mmtpFrameReorderWindowFrames(\n policy: MfuReassemblyPolicy,\n configuredWindow = 0,\n): number {\n assertValidMfuReassemblyPolicy(policy)\n if (!Number.isFinite(configuredWindow) || configuredWindow < 0) {\n throw new Error('[MFU reassembly] configuredWindow must be a finite number >= 0')\n }\n\n const requested = Math.ceil(configuredWindow)\n if (policy.transport === 'reliable-moq') {\n if (requested > 0) {\n throw new Error(\n '[MFU reassembly] reliable MoQ paths do not accept a frame reorder window in the parser',\n )\n }\n return 0\n }\n return requested\n}\n", "/**\n * Strict ISO-BMFF timing extraction for the timed-MFU browser path.\n *\n * This intentionally implements only the current single-track MMT profile:\n * one media track in FT=0, and one matching traf/trun in each FT=1. It does\n * not infer timing from packet arrival, MMTP send timestamps, or cadence.\n */\n\nconst U64_MAX = 0xffff_ffff_ffff_ffffn\nconst I64_MIN = -0x8000_0000_0000_0000n\nconst I64_MAX = 0x7fff_ffff_ffff_ffffn\nconst MICROSECONDS_PER_SECOND = 1_000_000n\n\nconst TFHD_BASE_DATA_OFFSET_PRESENT = 0x000001\nconst TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT = 0x000002\nconst TFHD_DEFAULT_SAMPLE_DURATION_PRESENT = 0x000008\nconst TFHD_DEFAULT_SAMPLE_SIZE_PRESENT = 0x000010\nconst TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT = 0x000020\nconst TFHD_DURATION_IS_EMPTY = 0x010000\nconst TFHD_DEFAULT_BASE_IS_MOOF = 0x020000\nconst TFHD_KNOWN_FLAGS =\n TFHD_BASE_DATA_OFFSET_PRESENT |\n TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT |\n TFHD_DEFAULT_SAMPLE_DURATION_PRESENT |\n TFHD_DEFAULT_SAMPLE_SIZE_PRESENT |\n TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT |\n TFHD_DURATION_IS_EMPTY |\n TFHD_DEFAULT_BASE_IS_MOOF\n\nconst TRUN_DATA_OFFSET_PRESENT = 0x000001\nconst TRUN_FIRST_SAMPLE_FLAGS_PRESENT = 0x000004\nconst TRUN_SAMPLE_DURATION_PRESENT = 0x000100\nconst TRUN_SAMPLE_SIZE_PRESENT = 0x000200\nconst TRUN_SAMPLE_FLAGS_PRESENT = 0x000400\nconst TRUN_SAMPLE_CTS_OFFSET_PRESENT = 0x000800\nconst TRUN_KNOWN_FLAGS =\n TRUN_DATA_OFFSET_PRESENT |\n TRUN_FIRST_SAMPLE_FLAGS_PRESENT |\n TRUN_SAMPLE_DURATION_PRESENT |\n TRUN_SAMPLE_SIZE_PRESENT |\n TRUN_SAMPLE_FLAGS_PRESENT |\n TRUN_SAMPLE_CTS_OFFSET_PRESENT\n\nexport interface IsoBmffTrackTiming {\n trackId: number\n timescale: number\n defaultSampleDuration?: number\n defaultSampleSize?: number\n}\n\nexport interface IsoBmffSampleTiming {\n /** Exact decode time in mdhd timescale units. */\n decodeTime: bigint\n /** Exact presentation time in mdhd timescale units, including signed CTS. */\n presentationTime: bigint\n /** Exact authored duration in mdhd timescale units. */\n duration: number\n timescale: number\n /** Exact elementary-stream sample size declared by trun/tfhd/trex. */\n size: number\n /** Byte position of the sample relative to the beginning of the moof. */\n bytePosition: bigint\n}\n\nexport interface IsoBmffMovieFragmentTiming {\n movieFragmentSequenceNumber: number\n samples: readonly IsoBmffSampleTiming[]\n}\n\nexport type IsoBmffMovieFragmentRejection = 'invalid' | 'budget'\n\ninterface IsoBox {\n type: string\n start: number\n bodyStart: number\n end: number\n}\n\ninterface FullBox {\n version: number\n flags: number\n bodyStart: number\n}\n\ninterface ParsedTrack {\n trackId: number\n handlerType: string\n timescale: number\n hasEdits: boolean\n}\n\ninterface ParsedTfhd {\n trackId: number\n defaultSampleDuration?: number\n defaultSampleSize?: number\n}\n\ninterface MdatLayout {\n dataStart: bigint\n declaredEnd: bigint\n}\n\nfunction view(data: Uint8Array): DataView {\n return new DataView(data.buffer, data.byteOffset, data.byteLength)\n}\n\nfunction boxType(data: Uint8Array, offset: number): string {\n return String.fromCharCode(\n data[offset]!,\n data[offset + 1]!,\n data[offset + 2]!,\n data[offset + 3]!,\n )\n}\n\nfunction readU64(data: Uint8Array, offset: number): bigint | null {\n if (offset < 0 || offset + 8 > data.length) return null\n const dataView = view(data)\n return (BigInt(dataView.getUint32(offset, false)) << 32n) |\n BigInt(dataView.getUint32(offset + 4, false))\n}\n\nfunction parseBox(data: Uint8Array, start: number, limit: number): IsoBox | null {\n if (start < 0 || limit > data.length || start + 8 > limit) return null\n const dataView = view(data)\n const shortSize = dataView.getUint32(start, false)\n let headerSize = 8\n let size: number\n if (shortSize === 0) {\n size = limit - start\n } else if (shortSize === 1) {\n if (start + 16 > limit) return null\n const extendedSize = readU64(data, start + 8)\n if (extendedSize === null || extendedSize > BigInt(Number.MAX_SAFE_INTEGER)) return null\n headerSize = 16\n size = Number(extendedSize)\n } else {\n size = shortSize\n }\n if (size < headerSize || start + size > limit) return null\n return {\n type: boxType(data, start + 4),\n start,\n bodyStart: start + headerSize,\n end: start + size,\n }\n}\n\nfunction parseBoxes(data: Uint8Array, start: number, end: number): IsoBox[] | null {\n if (start < 0 || end < start || end > data.length) return null\n const boxes: IsoBox[] = []\n let offset = start\n while (offset < end) {\n const box = parseBox(data, offset, end)\n if (!box || box.end <= offset) return null\n boxes.push(box)\n offset = box.end\n }\n return offset === end ? boxes : null\n}\n\nfunction children(data: Uint8Array, parent: IsoBox): IsoBox[] | null {\n return parseBoxes(data, parent.bodyStart, parent.end)\n}\n\nfunction exactlyOne(boxes: readonly IsoBox[], type: string): IsoBox | null {\n const matches = boxes.filter(box => box.type === type)\n return matches.length === 1 ? matches[0]! : null\n}\n\nfunction parseFullBox(data: Uint8Array, box: IsoBox): FullBox | null {\n if (box.bodyStart + 4 > box.end) return null\n const versionAndFlags = view(data).getUint32(box.bodyStart, false)\n return {\n version: versionAndFlags >>> 24,\n flags: versionAndFlags & 0x00ff_ffff,\n bodyStart: box.bodyStart + 4,\n }\n}\n\nfunction parseTkhdTrackId(data: Uint8Array, box: IsoBox): number | null {\n const full = parseFullBox(data, box)\n if (!full || full.flags & ~0x00000f) return null\n const bodyLength = box.end - full.bodyStart\n const trackIdOffset = full.version === 0\n ? full.bodyStart + 8\n : full.version === 1\n ? full.bodyStart + 16\n : -1\n const expectedBodyLength = full.version === 0 ? 80 : full.version === 1 ? 92 : -1\n if (trackIdOffset < 0 || bodyLength !== expectedBodyLength) return null\n return view(data).getUint32(trackIdOffset, false)\n}\n\nfunction parseMdhdTimescale(data: Uint8Array, box: IsoBox): number | null {\n const full = parseFullBox(data, box)\n if (!full || full.flags !== 0) return null\n const bodyLength = box.end - full.bodyStart\n const timescaleOffset = full.version === 0\n ? full.bodyStart + 8\n : full.version === 1\n ? full.bodyStart + 16\n : -1\n const expectedBodyLength = full.version === 0 ? 20 : full.version === 1 ? 32 : -1\n if (timescaleOffset < 0 || bodyLength !== expectedBodyLength) return null\n return view(data).getUint32(timescaleOffset, false)\n}\n\nfunction parseHandlerType(data: Uint8Array, box: IsoBox): string | null {\n const full = parseFullBox(data, box)\n if (!full || full.version !== 0 || full.flags !== 0 || full.bodyStart + 20 > box.end) {\n return null\n }\n return boxType(data, full.bodyStart + 4)\n}\n\nfunction editListHasEntries(data: Uint8Array, box: IsoBox): boolean | null {\n const full = parseFullBox(data, box)\n if (!full || full.flags !== 0 || (full.version !== 0 && full.version !== 1)) return null\n if (full.bodyStart + 4 > box.end) return null\n const entryCount = view(data).getUint32(full.bodyStart, false)\n const entrySize = full.version === 0 ? 12 : 20\n const expectedEnd = full.bodyStart + 4 + entryCount * entrySize\n if (!Number.isSafeInteger(expectedEnd) || expectedEnd !== box.end) return null\n return entryCount !== 0\n}\n\nfunction parseTrack(data: Uint8Array, trak: IsoBox): ParsedTrack | null {\n const trakChildren = children(data, trak)\n if (!trakChildren) return null\n const tkhd = exactlyOne(trakChildren, 'tkhd')\n const mdia = exactlyOne(trakChildren, 'mdia')\n if (!tkhd || !mdia) return null\n const trackId = parseTkhdTrackId(data, tkhd)\n if (trackId === null || trackId === 0) return null\n\n const mdiaChildren = children(data, mdia)\n if (!mdiaChildren) return null\n const mdhd = exactlyOne(mdiaChildren, 'mdhd')\n const hdlr = exactlyOne(mdiaChildren, 'hdlr')\n if (!mdhd || !hdlr) return null\n const timescale = parseMdhdTimescale(data, mdhd)\n const handlerType = parseHandlerType(data, hdlr)\n if (timescale === null || timescale === 0 || handlerType === null) return null\n\n const edtsBoxes = trakChildren.filter(box => box.type === 'edts')\n if (edtsBoxes.length > 1) return null\n let hasEdits = false\n if (edtsBoxes.length === 1) {\n const edtsChildren = children(data, edtsBoxes[0]!)\n if (!edtsChildren) return null\n const editLists = edtsChildren.filter(box => box.type === 'elst')\n if (editLists.length > 1) return null\n if (editLists.length === 1) {\n const result = editListHasEntries(data, editLists[0]!)\n if (result === null) return null\n hasEdits = result\n }\n }\n\n return { trackId, handlerType, timescale, hasEdits }\n}\n\nfunction parseTrex(data: Uint8Array, box: IsoBox): IsoBmffTrackTiming | null {\n const full = parseFullBox(data, box)\n if (!full || full.version !== 0 || full.flags !== 0 || box.end - full.bodyStart !== 20) {\n return null\n }\n const dataView = view(data)\n const trackId = dataView.getUint32(full.bodyStart, false)\n const defaultSampleDuration = dataView.getUint32(full.bodyStart + 8, false)\n const defaultSampleSize = dataView.getUint32(full.bodyStart + 12, false)\n return {\n trackId,\n timescale: 0,\n ...(defaultSampleDuration === 0 ? {} : { defaultSampleDuration }),\n ...(defaultSampleSize === 0 ? {} : { defaultSampleSize }),\n }\n}\n\nexport function parseIsoBmffInitializationTiming(\n data: Uint8Array,\n): IsoBmffTrackTiming | null {\n const topLevel = parseBoxes(data, 0, data.length)\n if (!topLevel) return null\n const ftyp = exactlyOne(topLevel, 'ftyp')\n const moov = exactlyOne(topLevel, 'moov')\n if (!ftyp || !moov || ftyp.start > moov.start) return null\n if (topLevel.some(box => box.type === 'moof' || box.type === 'mdat')) return null\n\n const moovChildren = children(data, moov)\n if (!moovChildren) return null\n const parsedTracks: ParsedTrack[] = []\n for (const trak of moovChildren.filter(box => box.type === 'trak')) {\n const parsed = parseTrack(data, trak)\n if (!parsed) return null\n parsedTracks.push(parsed)\n }\n const mediaTracks = parsedTracks.filter(\n track => track.handlerType === 'vide' || track.handlerType === 'soun',\n )\n if (mediaTracks.length !== 1) return null\n const mediaTrack = mediaTracks[0]!\n if (mediaTrack.hasEdits) return null\n\n const mvex = exactlyOne(moovChildren, 'mvex')\n if (!mvex) return null\n const mvexChildren = children(data, mvex)\n if (!mvexChildren) return null\n const trexEntries: IsoBmffTrackTiming[] = []\n for (const trexBox of mvexChildren.filter(box => box.type === 'trex')) {\n const trex = parseTrex(data, trexBox)\n if (!trex) return null\n trexEntries.push(trex)\n }\n const matchingTrex = trexEntries.filter(trex => trex.trackId === mediaTrack.trackId)\n if (matchingTrex.length !== 1) return null\n const trex = matchingTrex[0]!\n return {\n trackId: mediaTrack.trackId,\n timescale: mediaTrack.timescale,\n ...(trex.defaultSampleDuration === undefined\n ? {}\n : { defaultSampleDuration: trex.defaultSampleDuration }),\n ...(trex.defaultSampleSize === undefined\n ? {}\n : { defaultSampleSize: trex.defaultSampleSize }),\n }\n}\n\nfunction parseMfhdSequence(data: Uint8Array, box: IsoBox): number | null {\n const full = parseFullBox(data, box)\n if (!full || full.version !== 0 || full.flags !== 0 || box.end - full.bodyStart !== 4) {\n return null\n }\n return view(data).getUint32(full.bodyStart, false)\n}\n\nfunction parseTfhd(data: Uint8Array, box: IsoBox): ParsedTfhd | null {\n const full = parseFullBox(data, box)\n if (!full || full.version !== 0 || (full.flags & ~TFHD_KNOWN_FLAGS) !== 0) return null\n if ((full.flags & TFHD_BASE_DATA_OFFSET_PRESENT) !== 0 ||\n (full.flags & TFHD_DEFAULT_BASE_IS_MOOF) === 0 ||\n (full.flags & TFHD_DURATION_IS_EMPTY) !== 0) {\n return null\n }\n const dataView = view(data)\n let offset = full.bodyStart\n if (offset + 4 > box.end) return null\n const trackId = dataView.getUint32(offset, false)\n offset += 4\n if ((full.flags & TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT) !== 0) {\n if (offset + 4 > box.end) return null\n offset += 4\n }\n let defaultSampleDuration: number | undefined\n if ((full.flags & TFHD_DEFAULT_SAMPLE_DURATION_PRESENT) !== 0) {\n if (offset + 4 > box.end) return null\n defaultSampleDuration = dataView.getUint32(offset, false)\n offset += 4\n }\n let defaultSampleSize: number | undefined\n if ((full.flags & TFHD_DEFAULT_SAMPLE_SIZE_PRESENT) !== 0) {\n if (offset + 4 > box.end) return null\n defaultSampleSize = dataView.getUint32(offset, false)\n offset += 4\n }\n if ((full.flags & TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT) !== 0) {\n if (offset + 4 > box.end) return null\n offset += 4\n }\n if (offset !== box.end) return null\n return {\n trackId,\n ...(defaultSampleDuration === undefined ? {} : { defaultSampleDuration }),\n ...(defaultSampleSize === undefined ? {} : { defaultSampleSize }),\n }\n}\n\nfunction parseTfdt(data: Uint8Array, box: IsoBox): bigint | null {\n const full = parseFullBox(data, box)\n if (!full || full.flags !== 0) return null\n if (full.version === 0 && box.end - full.bodyStart === 4) {\n return BigInt(view(data).getUint32(full.bodyStart, false))\n }\n if (full.version === 1 && box.end - full.bodyStart === 8) {\n return readU64(data, full.bodyStart)\n }\n return null\n}\n\nfunction parseMdatLayout(data: Uint8Array, offset: number): MdatLayout | null {\n if (offset < 0 || offset + 8 > data.length || boxType(data, offset + 4) !== 'mdat') return null\n const dataView = view(data)\n const shortSize = dataView.getUint32(offset, false)\n let headerSize = 8\n let declaredSize: bigint\n if (shortSize === 0) {\n return null\n } else if (shortSize === 1) {\n const extended = readU64(data, offset + 8)\n if (extended === null) return null\n headerSize = 16\n declaredSize = extended\n } else {\n declaredSize = BigInt(shortSize)\n }\n if (declaredSize < BigInt(headerSize) || declaredSize < BigInt(data.length - offset)) {\n return null\n }\n const declaredEnd = BigInt(offset) + declaredSize\n if (declaredEnd > U64_MAX) return null\n return { dataStart: BigInt(offset + headerSize), declaredEnd }\n}\n\nfunction resolvedDefault(\n entryValue: number | undefined,\n tfhdValue: number | undefined,\n trexValue: number | undefined,\n): number | null {\n const value = entryValue ?? tfhdValue ?? trexValue\n return value === undefined || value === 0 ? null : value\n}\n\nfunction parseTrunSamples(\n data: Uint8Array,\n box: IsoBox,\n moofStart: number,\n baseDecodeTime: bigint,\n tfhd: ParsedTfhd,\n track: IsoBmffTrackTiming,\n mdat: MdatLayout,\n maxSamples: number,\n): IsoBmffSampleTiming[] | null {\n const full = parseFullBox(data, box)\n if (!full || (full.version !== 0 && full.version !== 1) ||\n (full.flags & ~TRUN_KNOWN_FLAGS) !== 0 ||\n (full.flags & TRUN_DATA_OFFSET_PRESENT) === 0 ||\n ((full.flags & TRUN_FIRST_SAMPLE_FLAGS_PRESENT) !== 0 &&\n (full.flags & TRUN_SAMPLE_FLAGS_PRESENT) !== 0)) {\n return null\n }\n const dataView = view(data)\n let offset = full.bodyStart\n if (offset + 8 > box.end) return null\n const sampleCount = dataView.getUint32(offset, false)\n offset += 4\n if (sampleCount === 0 || sampleCount > maxSamples) return null\n const relativeDataOffset = dataView.getInt32(offset, false)\n offset += 4\n if (relativeDataOffset < 0) return null\n const dataOffset = BigInt(relativeDataOffset)\n const absoluteDataOffset = BigInt(moofStart) + dataOffset\n if (absoluteDataOffset > BigInt(data.length) || absoluteDataOffset < mdat.dataStart) return null\n if ((full.flags & TRUN_FIRST_SAMPLE_FLAGS_PRESENT) !== 0) {\n if (offset + 4 > box.end) return null\n offset += 4\n }\n\n let decodeTime = baseDecodeTime\n let bytePosition = dataOffset\n const samples: IsoBmffSampleTiming[] = []\n for (let index = 0; index < sampleCount; index++) {\n let entryDuration: number | undefined\n if ((full.flags & TRUN_SAMPLE_DURATION_PRESENT) !== 0) {\n if (offset + 4 > box.end) return null\n entryDuration = dataView.getUint32(offset, false)\n offset += 4\n }\n let entrySize: number | undefined\n if ((full.flags & TRUN_SAMPLE_SIZE_PRESENT) !== 0) {\n if (offset + 4 > box.end) return null\n entrySize = dataView.getUint32(offset, false)\n offset += 4\n }\n if ((full.flags & TRUN_SAMPLE_FLAGS_PRESENT) !== 0) {\n if (offset + 4 > box.end) return null\n offset += 4\n }\n let compositionOffset = 0\n if ((full.flags & TRUN_SAMPLE_CTS_OFFSET_PRESENT) !== 0) {\n if (offset + 4 > box.end) return null\n compositionOffset = full.version === 1\n ? dataView.getInt32(offset, false)\n : dataView.getUint32(offset, false)\n offset += 4\n }\n if (offset > box.end) return null\n\n const duration = resolvedDefault(\n entryDuration,\n tfhd.defaultSampleDuration,\n track.defaultSampleDuration,\n )\n const size = resolvedDefault(entrySize, tfhd.defaultSampleSize, track.defaultSampleSize)\n if (duration === null || size === null) return null\n const sampleEnd = bytePosition + BigInt(size)\n const absoluteSampleEnd = BigInt(moofStart) + sampleEnd\n if (absoluteSampleEnd > mdat.declaredEnd || absoluteSampleEnd > U64_MAX) return null\n const presentationTime = decodeTime + BigInt(compositionOffset)\n if (presentationTime < I64_MIN || presentationTime > I64_MAX) return null\n samples.push({\n decodeTime,\n presentationTime,\n duration,\n timescale: track.timescale,\n size,\n bytePosition,\n })\n decodeTime += BigInt(duration)\n if (decodeTime > U64_MAX) return null\n bytePosition = sampleEnd\n }\n return offset === box.end ? samples : null\n}\n\nexport function parseIsoBmffMovieFragmentTiming(\n data: Uint8Array,\n track: IsoBmffTrackTiming,\n maxSamples: number,\n): IsoBmffMovieFragmentTiming | null {\n if (!Number.isInteger(maxSamples) || maxSamples <= 0) return null\n const first = parseBox(data, 0, data.length)\n if (!first) return null\n const moof = first.type === 'styp'\n ? parseBox(data, first.end, data.length)\n : first\n if (!moof || moof.type !== 'moof' || moof.end >= data.length) return null\n const moofChildren = children(data, moof)\n if (!moofChildren) return null\n const mfhd = exactlyOne(moofChildren, 'mfhd')\n const traf = exactlyOne(moofChildren, 'traf')\n if (!mfhd || !traf) return null\n const movieFragmentSequenceNumber = parseMfhdSequence(data, mfhd)\n if (movieFragmentSequenceNumber === null || movieFragmentSequenceNumber === 0) return null\n\n const trafChildren = children(data, traf)\n if (!trafChildren) return null\n const tfhdBox = exactlyOne(trafChildren, 'tfhd')\n const tfdtBox = exactlyOne(trafChildren, 'tfdt')\n const trunBox = exactlyOne(trafChildren, 'trun')\n if (!tfhdBox || !tfdtBox || !trunBox) return null\n const tfhd = parseTfhd(data, tfhdBox)\n const baseDecodeTime = parseTfdt(data, tfdtBox)\n if (!tfhd || tfhd.trackId !== track.trackId || baseDecodeTime === null) return null\n const mdat = parseMdatLayout(data, moof.end)\n if (!mdat) return null\n const samples = parseTrunSamples(\n data,\n trunBox,\n moof.start,\n baseDecodeTime,\n tfhd,\n track,\n mdat,\n maxSamples,\n )\n if (!samples) return null\n return { movieFragmentSequenceNumber, samples }\n}\n\nfunction sameTrack(a: IsoBmffTrackTiming, b: IsoBmffTrackTiming): boolean {\n return a.trackId === b.trackId &&\n a.timescale === b.timescale &&\n a.defaultSampleDuration === b.defaultSampleDuration &&\n a.defaultSampleSize === b.defaultSampleSize\n}\n\nfunction sameSample(a: IsoBmffSampleTiming, b: IsoBmffSampleTiming): boolean {\n return a.decodeTime === b.decodeTime &&\n a.presentationTime === b.presentationTime &&\n a.duration === b.duration &&\n a.timescale === b.timescale &&\n a.size === b.size &&\n a.bytePosition === b.bytePosition\n}\n\ntype PendingMovieFragment = Array<IsoBmffSampleTiming | null>\n\n/** Bounded per-track FT=0/FT=1 timing state. */\nexport class IsoBmffTimingResolver {\n #track: IsoBmffTrackTiming | null = null\n #fragments = new Map<string, PendingMovieFragment>()\n #pendingSamples = 0\n #evictedSamples = 0\n #lastMovieFragmentRejection: IsoBmffMovieFragmentRejection | null = null\n\n constructor(readonly maxPendingSamples: number) {\n if (!Number.isInteger(maxPendingSamples) || maxPendingSamples <= 0) {\n throw new Error('[IsoBmffTimingResolver] maxPendingSamples must be a positive integer')\n }\n }\n\n get pendingSampleCount(): number {\n return this.#pendingSamples\n }\n\n /**\n * Slots reclaimed by window displacement rather than by delivery or\n * abandonment. Deliberately separate from the combiner's own release count:\n * \"nobody ever saw this sample\" and \"the combiner gave up on it\" are\n * different failures with different owners, and one number cannot say which.\n */\n get evictedSampleCount(): number {\n return this.#evictedSamples\n }\n\n /** Typed reason for the most recent rejected movie fragment. */\n get lastMovieFragmentRejection(): IsoBmffMovieFragmentRejection | null {\n return this.#lastMovieFragmentRejection\n }\n\n addInitialization(data: Uint8Array): boolean {\n const timing = parseIsoBmffInitializationTiming(data)\n if (!timing) return false\n if (this.#track) return sameTrack(this.#track, timing)\n this.#track = timing\n return true\n }\n\n addMovieFragment(\n mpuSequenceNumber: number,\n data: Uint8Array,\n additionalSampleLimit = this.maxPendingSamples,\n ): boolean {\n this.#lastMovieFragmentRejection = null\n if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {\n this.#lastMovieFragmentRejection = 'invalid'\n return false\n }\n if (!this.#track || !isU32(mpuSequenceNumber)) {\n this.#lastMovieFragmentRejection = 'invalid'\n return false\n }\n const parsed = parseIsoBmffMovieFragmentTiming(\n data,\n this.#track,\n this.maxPendingSamples,\n )\n if (!parsed) {\n this.#lastMovieFragmentRejection = 'invalid'\n return false\n }\n const key = fragmentKey(mpuSequenceNumber, parsed.movieFragmentSequenceNumber)\n const existing = this.#fragments.get(key)\n if (existing) {\n return existing.length === parsed.samples.length && existing.every(\n (sample, index) => sample !== null && sameSample(sample, parsed.samples[index]!),\n )\n }\n // Sliding window, not a one-way ratchet. `consumeSample` and\n // `releaseSample` between them cover every sample some component observed\n // -- but a sample whose data units never arrive at all is observed by\n // nobody, so neither path ever runs and its slot is held until `reset()`.\n // Under sustained loss those orphans accumulate until the budget is full\n // of samples that can never arrive, at which point every later moof is\n // refused and media stops permanently with no error.\n //\n // Displacement is the signal no component can raise: once the budget is\n // full and a newer fragment is in hand, the oldest pending fragment's\n // samples are older than the whole window and cannot still be in flight.\n // Reclaiming them keeps the timing that is arriving now and discards the\n // timing that is not; refusing the new fragment does exactly the reverse,\n // which is what wedged the stream. Evicted timing is not silent loss --\n // a late data unit then finds no entry and is dropped against a named\n // reason, which is attributable, bounded, and recoverable.\n let freed = 0\n while (\n this.#fragments.size > 0 &&\n (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples ||\n parsed.samples.length - freed > additionalSampleLimit)\n ) {\n freed += this.#evictOldestFragment()\n }\n // Net new charge against the caller's pool is what it must afford: `freed`\n // slots were already counted there and are handed back by this call.\n if (parsed.samples.length - freed > additionalSampleLimit) {\n this.#lastMovieFragmentRejection = 'budget'\n return false\n }\n if (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples) {\n this.#lastMovieFragmentRejection = 'budget'\n return false\n }\n this.#fragments.set(key, [...parsed.samples])\n this.#pendingSamples += parsed.samples.length\n return true\n }\n\n sampleTiming(\n mpuSequenceNumber: number,\n movieFragmentSequenceNumber: number,\n sampleNumber: number,\n ): IsoBmffSampleTiming | null {\n if (!isU32(mpuSequenceNumber) || !isU32(movieFragmentSequenceNumber) ||\n movieFragmentSequenceNumber === 0 || !isU32(sampleNumber) || sampleNumber === 0) {\n return null\n }\n const samples = this.#fragments.get(\n fragmentKey(mpuSequenceNumber, movieFragmentSequenceNumber),\n )\n return samples?.[sampleNumber - 1] ?? null\n }\n\n consumeSample(\n mpuSequenceNumber: number,\n movieFragmentSequenceNumber: number,\n sampleNumber: number,\n expected: IsoBmffSampleTiming,\n ): boolean {\n const samples = this.#fragments.get(\n fragmentKey(mpuSequenceNumber, movieFragmentSequenceNumber),\n )\n const sample = samples?.[sampleNumber - 1]\n if (!sample || !sameSample(sample, expected)) return false\n return this.#clearSlot(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber)\n }\n\n /**\n * Free the budget slot of a sample that will never be delivered.\n *\n * `consumeSample` is the delivery path and is the only decrement on a\n * healthy stream. A sample whose data units never complete is never\n * delivered; window displacement in `addMovieFragment` eventually reclaims\n * it, but only after it has aged out of the whole window. This path frees\n * the slot as soon as the combiner knows the sample is gone, so the budget\n * tracks what is actually in flight rather than what has merely aged.\n *\n * The caller is whoever abandoned the sample, so the release is driven by\n * the abandonment itself rather than by a timer, an LRU, or an age bound --\n * the slot is freed exactly when its sample provably cannot arrive, which\n * is a protocol event the combiner already detects and counts.\n *\n * Deliberately takes no `expected` timing: the caller is discarding, not\n * matching, and requiring it would make the release fail exactly when the\n * timing entry is the thing being reclaimed. Returns false when the slot\n * was already freed, so a double release cannot drive the count negative.\n */\n releaseSample(\n mpuSequenceNumber: number,\n movieFragmentSequenceNumber: number,\n sampleNumber: number,\n ): boolean {\n return this.#clearSlot(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber)\n }\n\n /**\n * Drop the oldest pending fragment whole and return the slots it held.\n *\n * Oldest is insertion order, which for a moof stream is arrival order, so\n * this is the least-recently-declared timing -- the entries least likely to\n * still have data units in flight.\n */\n #evictOldestFragment(): number {\n const oldest = this.#fragments.keys().next().value\n if (oldest === undefined) return 0\n const samples = this.#fragments.get(oldest)!\n let live = 0\n for (const sample of samples) if (sample !== null) live++\n this.#fragments.delete(oldest)\n this.#pendingSamples -= live\n this.#evictedSamples += live\n return live\n }\n\n /** Null one slot, decrement the budget, and drop the fragment when empty. */\n #clearSlot(\n mpuSequenceNumber: number,\n movieFragmentSequenceNumber: number,\n sampleNumber: number,\n ): boolean {\n if (!isU32(mpuSequenceNumber) || !isU32(movieFragmentSequenceNumber) ||\n movieFragmentSequenceNumber === 0 || !isU32(sampleNumber) || sampleNumber === 0) {\n return false\n }\n const key = fragmentKey(mpuSequenceNumber, movieFragmentSequenceNumber)\n const samples = this.#fragments.get(key)\n const index = sampleNumber - 1\n if (!samples || !samples[index]) return false\n samples[index] = null\n this.#pendingSamples--\n if (samples.every(value => value === null)) this.#fragments.delete(key)\n return true\n }\n\n reset(): void {\n this.#track = null\n this.#fragments.clear()\n this.#pendingSamples = 0\n }\n}\n\nfunction fragmentKey(mpuSequenceNumber: number, movieFragmentSequenceNumber: number): string {\n return `${mpuSequenceNumber}:${movieFragmentSequenceNumber}`\n}\n\nfunction isU32(value: number): boolean {\n return Number.isInteger(value) && value >= 0 && value <= 0xffff_ffff\n}\n\n/** Convert exact media ticks to microseconds without first narrowing the ticks. */\nexport function mediaTicksToMicroseconds(ticks: bigint, timescale: number): number | null {\n if (!Number.isInteger(timescale) || timescale <= 0) return null\n const numerator = ticks * MICROSECONDS_PER_SECOND\n const scale = BigInt(timescale)\n const whole = numerator / scale\n const remainder = numerator % scale\n if (whole < BigInt(Number.MIN_SAFE_INTEGER) || whole > BigInt(Number.MAX_SAFE_INTEGER)) {\n return null\n }\n const result = Number(whole) + Number(remainder) / timescale\n return Number.isFinite(result) ? result : null\n}\n", "import {\n parseMMTPHeader,\n extractMFUPayloadWithHeader,\n sourceFecPayloadIdOf,\n extractAltaTrailer,\n PACKET_TYPE_MPU,\n PACKET_TYPE_REPAIR,\n type MMTPHeader,\n} from '@blockcast/mmt-base'\nimport {\n MFUReassembler,\n MfuReassemblyError,\n isMfuReassemblyError,\n type MfuReassemblerBackend,\n type MmtpSourcePath,\n type MmtpDeliveryProvenance,\n type ReassembledMFU,\n} from './mfu-reassembler.js'\nimport {\n SampleCombiner,\n SampleCombineError,\n type AbandonedSample,\n type CombinedSample,\n type SampleCombinerOptions,\n type SampleCombinerStats,\n} from './sample-combiner.js'\nimport {\n classifyH264Keyframe,\n isHevcIrapNalUnitType,\n mmtpTimestampToMicros,\n normalizeMmtpInitSegment,\n} from './codec-utils.js'\nimport {\n mmtpFrameReorderWindowFrames,\n mfuReassemblyTimeoutMs,\n type MfuReassemblyPolicy,\n} from './mfu-reassembly-policy.js'\nimport {\n IsoBmffTimingResolver,\n mediaTicksToMicroseconds,\n type IsoBmffSampleTiming,\n} from './iso-bmff-timing.js'\nimport type { AuthoredTiming } from './authored-timing.js'\nimport type { FrameGapDecision } from './block-policy.js'\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface TrackInfo {\n type: 'video' | 'audio'\n codec: string\n /** Catalog/in-band frame cadence available to downstream media stages. */\n framerate?: number\n /** Catalog MMTP group duration available to downstream media stages. */\n groupDurationMs?: number\n}\n\nexport interface MmtpParserConfig {\n tracks?: Map<number, TrackInfo>\n reorderWindow?: number\n /** MFU reassembly timeout (ms). Derive from FEC interleaveDepthMs or frame duration.\n * Pass null for live clock-free operation; memory remains bounded by maxBuffered.\n * Defaults to 1000ms if not provided. */\n mfuTimeoutMs?: number | null\n /** Explicit transport policy. Reliable MoQ is clock-free; datagrams require\n * a catalog/in-band-derived deadline. Mutually exclusive with mfuTimeoutMs. */\n reassemblyPolicy?: MfuReassemblyPolicy\n /** Max concurrent active MFUs. A new first fragment is rejected at the bound. */\n maxBufferedMfus?: number\n /**\n * Max subsample data units held for one partitioned sample (moqcast #37).\n * A sample needs `ceil(sampleSize / (256 * maxPayload))` data units; at the\n * ~1300-byte payloads this producer emits, one data unit spans ~327 KB, so\n * the default admits a ~5 MB sample \u2014 beyond any 8K intra frame \u2014 while\n * still bounding the per-insert overlap scan. Raise it only alongside a\n * larger authored frame size, never to paper over a lost data unit.\n */\n maxSubsampleSegments?: number\n /** Alternate backend. The TypeScript and Rust/WASM implementations enforce\n * the same authored active-DU contract. */\n reassembler?: MfuReassemblerBackend\n /** Default track info for auto-discovered packetIds.\n * When the parser receives an MMTP packet with an unregistered packetId,\n * it creates a new track entry using this info. Required for correct\n * keyframe detection with HEVC/AV1/VP9 (without it, defaults to H.264). */\n defaultTrack?: TrackInfo\n /** In-order release policy for FEC-protected streams (see FecReleaseGate). */\n fecReleaseGate?: FecReleaseGate\n}\n\n/** Structured result from parseHeader(). Used by MmtpRouter for two-phase parsing:\n * 1. parseHeader() \u2192 get packetId + fecPayloadId for FEC feeding\n * 2. feed() \u2192 MFU extraction, reassembly, keyframe detection, emit */\nexport interface PacketInfo {\n packetId: number\n packetType: number\n timestamp: bigint\n /** MMTP timestamp converted to monotonic microseconds for pacing/sync users. */\n timestampUs: number\n sequenceNumber: number\n rapFlag: boolean\n fecType: number\n /** FEC source symbol ID (from 4-byte FEC Payload ID at packet end). Null if no FEC. */\n fecPayloadId: { ssId: number } | null\n /** True if this is a repair packet (packetType=0x03). */\n isRepair: boolean\n}\n\n/**\n * One parse of an MMTP packet's header, as returned by\n * MmtpContainerParser.parsePacket(). Hand it back to feedParsed() so the same\n * packet's header is not parsed again (BLO-36599).\n */\nexport interface ParsedMmtpPacket {\n readonly info: PacketInfo\n /** parseMMTPHeader() of the packet this was parsed from. */\n readonly header: MMTPHeader\n /** The skipFecPayloadId option `info.fecPayloadId` was resolved under. */\n readonly skipFecPayloadId: boolean\n}\n\nexport interface FrameMeta {\n /** Authored ISO-BMFF presentation time converted to microseconds. */\n timestamp: number\n /** Authored ISO-BMFF decode time converted to microseconds. */\n decodeTimestamp?: number\n /** Authored ISO-BMFF sample duration converted to microseconds. */\n duration?: number\n /** Exact authored decode time in mediaTimescale units. */\n decodeTime?: bigint\n /** Exact authored presentation time in mediaTimescale units. */\n presentationTime?: bigint\n /** Exact authored duration in mediaTimescale units. */\n durationTicks?: number\n /** mdhd media timescale for the exact timing fields. */\n mediaTimescale?: number\n /** Exact authored ISO-BMFF timing carried without narrowing to microseconds. */\n authoredTiming?: AuthoredTiming\n keyframe: boolean\n packetId: number\n trackType: 'video' | 'audio' | null\n packetSequenceNumber?: number\n /** Every authoritative MMTP source-packet sequence that formed this frame. */\n packetSequenceNumbers?: number[]\n /** Subset of packetSequenceNumbers reconstructed by FEC. */\n repairedPacketSequenceNumbers?: number[]\n /** Immutable catalog identity supplied by the owning ingress subscription. */\n catalogTrackName?: string\n /** Raw MMTP short-NTP timestamp retained for adapters that own the timeline. */\n mmtpTimestamp?: bigint\n mpuSequenceNumber?: number\n movieFragmentSequenceNumber?: number\n sampleNumber?: number\n fragmentCount?: number\n rapFlag?: boolean\n priority?: number\n recovered?: boolean\n /** Starts a new publisher/source epoch for ordering and timestamp state. */\n sourceDiscontinuity?: boolean\n /** Source of the accepted FI=1/FI=0 frame path. Absent for recovery. */\n source?: MmtpSourcePath\n /**\n * Strict whole-frame delivery provenance. Unlike `source`, which names the\n * head fragment's transport for routing, this is folded over every\n * contributing fragment (and, for FEC-recovered frames, over the transports\n * that supplied the block's symbols). Only 'multicast' is evidence of\n * multicast-exclusive delivery. Absent means no producer on this path\n * recorded provenance \u2014 consumers MUST treat that as unproven, never as\n * multicast.\n */\n deliveryProvenance?: MmtpDeliveryProvenance\n /**\n * FEC source block (SBN) this frame completed in, when it came from an\n * FEC-protected packet or an FEC-recovered block. Drives in-order release\n * on FEC-gated streams.\n */\n fecSbn?: number\n /**\n * Set on the first frame released after a gap: the blocks between it and\n * the previous release resolved with lost media, or authored decode time\n * shows a missing sample. The sink must stall or conceal exactly as decided.\n */\n gapBefore?: FrameGapDecision\n}\n\n/**\n * Per-packet MPU and data-unit identity, read without reassembly. The router\n * uses it as classification evidence for the FEC block the packet belongs to.\n */\nexport interface SourcePacketIdentity {\n mpuSequenceNumber: number\n /** 1-based MFU DU-header sample_number, when this packet carries a DU header. */\n sampleNumber?: number\n movieFragmentSequenceNumber?: number\n /** DU-header offset: the data unit's authored byte position in its sample. */\n offset?: number\n}\n\n/**\n * In-order release policy for FEC-protected streams. For such a stream a\n * frame after a gap is released only once every FEC block that could hold\n * the missing frames has resolved (completed or failed); the frame-count\n * reorder window applies only to streams without FEC.\n */\nexport interface FecReleaseGate {\n /** True when this physical stream's frames are FEC-gated. */\n gates(packetId: number, catalogTrackName?: string): boolean\n /**\n * Resolution of the blocks `[fromSbn, toSbn]`: null while any of them can\n * still complete, otherwise the SBNs whose media was lost (empty when no\n * block failed; the release still carries a decision when authored decode\n * time shows a missing sample). `releasedSbn` is the block of the frame\n * released before the gap: that frame arrived, so its block is not lost\n * merely because it has since retired.\n */\n resolvedLoss(\n packetId: number,\n catalogTrackName: string | undefined,\n fromSbn: number,\n toSbn: number,\n releasedSbn: number,\n ): readonly number[] | null\n /**\n * Longest a frame may wait behind unresolved blocks, in milliseconds from\n * its arrival: by then every block that could hold the frames before it\n * must have resolved. Past it the frame is released as a discontinuity.\n * It must not exceed how long a resolved block stays readable as resolved.\n */\n holdLimitMs(packetId: number, catalogTrackName?: string): number\n /** Decide stall/conceal for the first frame released after a gap. */\n decide(\n before: { data: Uint8Array; meta: FrameMeta },\n after: { data: Uint8Array; meta: FrameMeta },\n lostBlocks: readonly number[],\n discontinuity: boolean,\n ): FrameGapDecision\n /** Every frame released in order from a gated stream. */\n onReleased?(data: Uint8Array, meta: FrameMeta): void\n}\n\n/** Typed ISO-BMFF timing-sample budget counters. See `timingBudgetStats`. */\nexport interface MmtpTimingBudgetStats {\n /** Slots currently charged to declared-but-undelivered samples. */\n pending: number\n /** Total slots available; `pending === capacity` rejects every new moof. */\n capacity: number\n /** Slots returned by samples the combiner abandoned (evicted or timed out). */\n reclaimed: number\n /**\n * Slots reclaimed by window displacement \u2014 samples no component ever saw,\n * so no abandonment event could be raised for them. Separate from\n * `reclaimed` because the owners differ: `reclaimed` is the combiner giving\n * up, `evicted` is loss that was never observable at all.\n */\n evicted: number\n /** Movie fragments refused because the budget had no room for their samples. */\n rejectedMovieFragments: number\n}\n\nexport interface MmtpReorderStats {\n droppedBeforeStartupKeyframe: number\n droppedIncompleteStartupInterval: number\n droppedStale: number\n forcedWindowFlushes: number\n droppedOnLiveDispose: number\n publisherResets: number\n droppedOnPublisherReset: number\n bufferedFrames: number\n}\n\nexport interface MmtpReorderFrameDiagnostics {\n timestampUs: number\n keyframe: boolean\n sequenceNumber: number\n packetSequenceNumber?: number\n movieFragmentSequenceNumber?: number\n sampleNumber?: number\n fragmentCount?: number\n}\n\nexport interface MmtpReorderStreamDiagnostics {\n packetId: number\n bufferedFrames: number\n lastEmitted: MmtpReorderFrameDiagnostics | null\n startupKeyframe: MmtpReorderFrameDiagnostics | null\n head: MmtpReorderFrameDiagnostics[]\n tail: MmtpReorderFrameDiagnostics[]\n keyframes: MmtpReorderFrameDiagnostics[]\n}\n\nexport type MmtpParserRecord =\n | {\n kind: 'repair' | 'non-media'\n packet: PacketInfo\n byteLength: number\n }\n | {\n kind: 'init' | 'fragment'\n packet: PacketInfo\n data: Uint8Array\n mpuSequenceNumber: number\n fragmentationIndicator: number\n fragmentCounter: number\n }\n | {\n kind: 'pending'\n packet: PacketInfo\n mpuSequenceNumber: number\n fragmentationIndicator: number\n fragmentCounter: number\n }\n | {\n kind: 'rejected'\n packet: PacketInfo\n mpuSequenceNumber: number\n fragmentationIndicator: number\n fragmentCounter: number\n error: {\n /**\n * Either a single data unit's own fragment chain was rejected, or\n * combining reassembled data units into a sample was (moqcast #37).\n * Both are fail-closed drops of the same packet, so they share this\n * record; the code says which class it came from.\n */\n code: MfuReassemblyError['code'] | SampleCombineError['code']\n message: string\n }\n }\n | {\n kind: 'frame'\n packet: PacketInfo\n data: Uint8Array\n meta: FrameMeta\n mpuSequenceNumber: number\n fragmentCount: number\n rapFlag: boolean\n priority?: number\n }\n\n/**\n * Why feed() returned no record. Every `return null` inside feed() names one\n * of these, so a packet that produced nothing is attributable to the stage\n * that rejected it rather than to one collapsed failure bucket.\n *\n * The distinction is what makes a wedged receive path readable: a media track\n * that stops producing records looks identical from outside whether the header\n * failed to parse, MFU extraction failed, or the timing resolver refused the\n * sample \u2014 and those name three different components.\n */\nexport type MmtpFeedDropReason =\n /** feed() after dispose(); the host is still pushing at a torn-down parser. */\n | 'disposed'\n | 'header-parse-failed'\n | 'mfu-extract-failed'\n /** MPU present but not a timed MFU, so no media payload to reassemble. */\n | 'untimed-mpu'\n | 'init-unexpected-fragmentation'\n | 'init-normalize-failed'\n /** No timing resolver, or it refused the initialization segment. */\n | 'init-timing-refused'\n | 'fragment-unexpected-fragmentation'\n | 'fragment-no-resolver'\n /** addMovieFragment refused \u2014 includes the pending-timing-sample budget. */\n | 'fragment-timing-refused'\n | 'unknown-fragment-type'\n | 'mfu-no-resolver'\n | 'mfu-empty-payload'\n | 'mfu-du-header-missing'\n | 'mfu-sample-timing-missing'\n | 'mfu-offset-out-of-range'\n | 'mfu-size-mismatch'\n | 'assembled-identity-missing'\n | 'assembled-timing-missing'\n | 'assembled-mpu-mismatch'\n /**\n * Refusals inside the combined-sample delivery step, which runs after the\n * combiner has already produced a whole sample. These are distinct from\n * their `assembled-*` counterparts: those reject a data unit on the way in,\n * these reject a sample that was fully reassembled and then found\n * undeliverable, so collapsing the two would hide a combiner that completes\n * samples nothing can present.\n */\n | 'combined-timing-missing'\n | 'combined-size-mismatch'\n /** Frame metadata could not be built \u2014 an fMP4 box arrived as a timed MFU\n * payload, or the authored timing converts to a non-positive duration. */\n | 'combined-meta-build-failed'\n | 'combined-timing-consume-refused'\n /** feed() threw. Counted so the tally still sums to fedObjects; the error\n * is rethrown, never swallowed. */\n | 'threw'\n\n/**\n * Every key `outcomes` can carry: one per record kind, one per drop reason.\n *\n * Derived from the two unions rather than written out, so a new record kind or\n * drop reason cannot be counted under a key the type does not admit, and a\n * consumer can enumerate the vocabulary from the type instead of from prose.\n */\nexport type MmtpFeedOutcomeKey =\n | MmtpParserRecord['kind']\n | `drop:${MmtpFeedDropReason}`\n\n/**\n * Feed tallies owned by the parser.\n *\n * These live here rather than on MmtpDecodePipeline because the two players\n * sit at different layers of this library: hang-mmt-fec drives the pipeline,\n * while moqtail constructs MmtpContainerParser directly and never builds the\n * wrapper. feed() is the one chokepoint both cross, so a tally maintained here\n * instruments both by construction.\n *\n * Logs are diagnostic output, not an acceptance API \u2014 this is the typed\n * counter a harness should read instead of scraping console histograms.\n */\nexport interface MmtpParserFeedStats {\n /** Every packet handed to feed(), including post-dispose calls. */\n fedObjects: number\n /**\n * Mutually exclusive outcome tally: record kinds under their own `kind`,\n * drops under `drop:<reason>`. The values sum to exactly `fedObjects`, which\n * is the property that makes a frozen media bucket beside a still-climbing\n * non-media bucket a readable signal rather than two unrelated numbers.\n *\n * Partial because a key appears only once it has been counted; absent and\n * zero mean the same thing to a reader.\n */\n outcomes: Partial<Record<MmtpFeedOutcomeKey, number>>\n /**\n * Refusals from a completed older sample delivered while registering a\n * later packet's authored size. Kept separate because that side effect is\n * not the packet's own feed outcome.\n */\n deferredOutcomes: Partial<Record<MmtpFeedDropReason, number>>\n}\n\ntype FrameCallback = (data: Uint8Array, meta: FrameMeta) => void\ntype InitCallback = (data: Uint8Array, packetId: number, catalogTrackName?: string) => void\n\n/**\n * Result of one combined-sample delivery attempt: either the record to return\n * or the reason the sample could not be delivered.\n *\n * Internal to the parser. A bare `null` would be indistinguishable from the\n * other refusals `feed()` can take, and the whole point of the drop-reason\n * vocabulary is that a stage which stops producing frames says which stage it\n * was.\n */\ntype CombinedSampleOutcome =\n | { readonly record: MmtpParserRecord; readonly reason?: undefined }\n | { readonly record?: undefined; readonly reason: MmtpFeedDropReason }\n\nexport function isMfuKeyframeMetadata(rapFlag: boolean, priority?: number): boolean {\n return rapFlag || (priority ?? 0) > 0x80\n}\n\n/** Per-packet detached ALTA authenticator trailer (08-07 Path 2).\n * - `auth`: the authenticator bytes (length-prefixed at packet end).\n * - `payloadEnd`: byte offset within the source packet where signed\n * payload ends \u2014 consumers reconstruct `payload = packet[0..payloadEnd]`\n * for verify_detached. */\nexport interface ParsedAltaTrailer {\n auth: Uint8Array\n payloadEnd: number\n}\n\n/** Source-symbol exposure for FEC consumers.\n *\n * Fires from {@link MmtpContainerParser.feed} once per FEC-protected source\n * packet \u2014 after parseHeader confirms fec_payload_id is present, before MFU\n * extraction.\n *\n * Arguments:\n * - `packetId` \u2013 MMTP header `packet_id` (track identifier)\n * - `ssId` \u2013 flat Source Symbol ID from the 4-byte fec_payload_id trailer\n * - `rawSymbol` \u2013 packet bytes minus the 4-byte fec_payload_id trailer.\n * NOT zero-padded to FEC symbol size \u2014 consumer pads if needed\n * (FEC symbol size is FEC-config, not parser-config).\n * - `alta` \u2013 detached ALTA authenticator if present, else undefined\n *\n * Designed so consumers (player, Shaka shim, upstream moqtail) can wire\n * source-symbol delivery to whatever FEC pipeline they own, without depending\n * on libmmt's MulticastManager/MmtpRouter/FECManager stack. */\ntype SourceSymbolCallback = (\n packetId: number,\n ssId: number,\n rawSymbol: Uint8Array,\n alta: ParsedAltaTrailer | undefined,\n) => void\n\nexport interface ParseHeaderOptions {\n /** Recovered source symbols already had the trailing Source FEC Payload ID stripped. */\n skipFecPayloadId?: boolean\n /** Mark frames emitted from this packet as FEC-recovered. */\n recovered?: boolean\n /** Exact catalog track bound to this packet by its ingress subscription. */\n catalogTrackName?: string\n /** Source transport for parser-owned frame attribution. */\n source?: MmtpSourcePath\n /**\n * Explicit strict provenance for the fragments this packet produces.\n * The router sets it for FEC-recovered packets, which carry no `source`\n * but whose FEC block's symbol provenance is known.\n */\n deliveryProvenance?: MmtpDeliveryProvenance\n /** FEC source block this packet belongs to (FEC-protected packets only). */\n fecSbn?: number\n}\n\n// \u2500\u2500 FrameSequencer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface PendingFrame {\n data: Uint8Array\n meta: FrameMeta\n seq: number\n}\n\ninterface SequencerStream {\n buf: PendingFrame[]\n lastEmitted: PendingFrame | null\n}\n\nfunction reorderFrameDiagnostics(frame: PendingFrame): MmtpReorderFrameDiagnostics {\n return {\n timestampUs: frame.meta.timestamp,\n keyframe: frame.meta.keyframe,\n sequenceNumber: frame.seq,\n ...(frame.meta.packetSequenceNumber === undefined\n ? {}\n : { packetSequenceNumber: frame.meta.packetSequenceNumber }),\n ...(frame.meta.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: frame.meta.movieFragmentSequenceNumber }),\n ...(frame.meta.sampleNumber === undefined ? {} : { sampleNumber: frame.meta.sampleNumber }),\n ...(frame.meta.fragmentCount === undefined\n ? {}\n : { fragmentCount: frame.meta.fragmentCount }),\n }\n}\n\ntype SequencerStat = Exclude<keyof MmtpReorderStats, 'bufferedFrames'>\n\nfunction serialSequenceDelta(a: number, b: number): number {\n return (a - b) | 0\n}\n\nfunction compareFrameOrder(\n a: PendingFrame,\n b: PendingFrame,\n): number {\n if (a.meta.decodeTime !== undefined && b.meta.decodeTime !== undefined &&\n a.meta.mediaTimescale === b.meta.mediaTimescale) {\n if (a.meta.decodeTime < b.meta.decodeTime) return -1\n if (a.meta.decodeTime > b.meta.decodeTime) return 1\n } else {\n const decodeDelta = (a.meta.decodeTimestamp ?? a.meta.timestamp) -\n (b.meta.decodeTimestamp ?? b.meta.timestamp)\n if (decodeDelta !== 0) return decodeDelta\n }\n const movieDelta = serialSequenceDelta(\n a.meta.movieFragmentSequenceNumber ?? 0,\n b.meta.movieFragmentSequenceNumber ?? 0,\n )\n if (movieDelta !== 0) return movieDelta\n const sampleDelta = (a.meta.sampleNumber ?? 0) - (b.meta.sampleNumber ?? 0)\n return sampleDelta !== 0 ? sampleDelta : serialSequenceDelta(a.seq, b.seq)\n}\n\nclass FrameSequencer {\n #streams = new Map<number, SequencerStream>()\n #windowSize: number\n #emit: (data: Uint8Array, meta: FrameMeta) => void\n #recordStat: (stat: SequencerStat, count?: number) => void\n\n constructor(\n windowSize: number,\n emit: (data: Uint8Array, meta: FrameMeta) => void,\n recordStat: (stat: SequencerStat, count?: number) => void = () => {},\n ) {\n this.#windowSize = windowSize\n this.#emit = emit\n this.#recordStat = recordStat\n }\n\n push(data: Uint8Array, meta: FrameMeta, seq: number): void {\n if (this.#windowSize <= 0) {\n this.#emit(data, meta)\n return\n }\n const frame: PendingFrame = { data, meta: { ...meta }, seq }\n const stream = this.#streamFor(meta.packetId)\n if (meta.sourceDiscontinuity) {\n this.#recordStat('publisherResets')\n this.#recordStat('droppedOnPublisherReset', stream.buf.length)\n stream.buf = []\n stream.lastEmitted = null\n frame.meta.sourceDiscontinuity = true\n }\n if (this.#isStale(stream, frame)) {\n this.#recordStat('droppedStale')\n return\n }\n if (meta.keyframe && stream.lastEmitted === null) {\n // The first RAP establishes decoder sync. Discard any pre-RAP media and\n // emit it immediately so startup does not pay the reorder-window delay.\n this.#recordStat('droppedBeforeStartupKeyframe', stream.buf.length)\n stream.buf = []\n this.#emitFrame(stream, frame)\n return\n }\n let lo = 0, hi = stream.buf.length\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (compareFrameOrder(stream.buf[mid]!, frame) < 0) lo = mid + 1\n else hi = mid\n }\n stream.buf.splice(lo, 0, frame)\n while (stream.buf.length > this.#windowSize) this.#flushHead(stream)\n }\n\n flush(): void {\n for (const packetId of [...this.#streams.keys()].sort((a, b) => a - b)) {\n const stream = this.#streams.get(packetId)\n while (stream && stream.buf.length > 0) this.#flushHead(stream)\n }\n }\n\n get buffered(): number {\n let total = 0\n for (const stream of this.#streams.values()) total += stream.buf.length\n return total\n }\n\n get diagnostics(): Omit<MmtpReorderStreamDiagnostics, 'packetId'>[] {\n return [...this.#streams.values()].map(stream => ({\n bufferedFrames: stream.buf.length,\n lastEmitted: stream.lastEmitted ? reorderFrameDiagnostics(stream.lastEmitted) : null,\n startupKeyframe: null,\n head: stream.buf.slice(0, 4).map(reorderFrameDiagnostics),\n tail: stream.buf.slice(-4).map(reorderFrameDiagnostics),\n keyframes: stream.buf.filter(frame => frame.meta.keyframe).slice(0, 8)\n .map(reorderFrameDiagnostics),\n }))\n }\n\n discard(): void {\n for (const stream of this.#streams.values()) {\n this.#recordStat('droppedOnLiveDispose', stream.buf.length)\n stream.buf = []\n }\n this.#streams.clear()\n }\n\n #streamFor(packetId: number): SequencerStream {\n let stream = this.#streams.get(packetId)\n if (!stream) {\n stream = { buf: [], lastEmitted: null }\n this.#streams.set(packetId, stream)\n }\n return stream\n }\n\n #flushHead(stream: SequencerStream): boolean {\n const f = stream.buf.shift()\n if (!f) return false\n return this.#emitFrame(stream, f)\n }\n\n #isStale(stream: SequencerStream, frame: PendingFrame): boolean {\n return stream.lastEmitted !== null &&\n compareFrameOrder(frame, stream.lastEmitted) <= 0\n }\n\n #emitFrame(stream: SequencerStream, frame: PendingFrame): boolean {\n if (this.#isStale(stream, frame)) {\n this.#recordStat('droppedStale')\n return false\n }\n stream.lastEmitted = frame\n this.#emit(frame.data, frame.meta)\n return true\n }\n}\n\n// \u2500\u2500 FecReleaseSequencer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Authored decode-time adjacency: the next frame starts where this one ends. */\nfunction authoredAdjacent(before: FrameMeta, after: FrameMeta): boolean {\n const a = before.authoredTiming\n const b = after.authoredTiming\n if (!a || !b || a.mediaTimescale !== b.mediaTimescale) return false\n return b.decodeTime === a.decodeTime + BigInt(a.durationTicks)\n}\n\n/**\n * Authored decode time proves media is missing between two frames: the next\n * frame starts after this one ends. ISO-BMFF decode times of one track are\n * contiguous, so this is positive evidence of a missing sample whatever the\n * reason it went missing (a failed FEC block, or a frame the parser or the\n * recovery path could not deliver although its block completed).\n */\nfunction authoredGap(before: FrameMeta, after: FrameMeta): boolean {\n const a = before.authoredTiming\n const b = after.authoredTiming\n if (!a || !b || a.mediaTimescale !== b.mediaTimescale) return false\n return b.decodeTime > a.decodeTime + BigInt(a.durationTicks)\n}\n\ninterface GatedFrame extends PendingFrame {\n /** performance.now() at push; measures the hold against the gate's limit. */\n arrivedAtMs: number\n}\n\ninterface GatedStream {\n packetId: number\n catalogTrackName?: string\n buf: GatedFrame[]\n last: PendingFrame | null\n lastSbn: number | undefined\n discontinuity: boolean\n}\n\n/**\n * In-order release for FEC-protected streams, driven by block resolution\n * instead of a frame-count window. A frame adjacent (in authored decode time)\n * to the last released frame is released immediately. A frame after a gap\n * waits until every block between the last released frame's block and its\n * own has resolved; a D-28 repair or a late in-band recovery can therefore\n * still fill the gap. When the resolved blocks lost media, or authored decode\n * time shows a missing sample, the release carries the gate's stall/conceal\n * decision as `gapBefore`.\n *\n * The wait is bounded here, not by the blocks: a frame held longer than the\n * gate's `holdLimitMs` since it arrived is released with the gate's\n * discontinuity decision (a stall), so a block that never resolves cannot\n * grow the buffer without limit. There is no timer: the check runs on the\n * next push or poke, so after a total outage held frames are released by the\n * first packet that arrives, not at the ceiling itself.\n */\nclass FecReleaseSequencer {\n #streams = new Map<string, GatedStream>()\n readonly #gate: FecReleaseGate\n readonly #emit: (data: Uint8Array, meta: FrameMeta) => void\n readonly #recordStat: (stat: SequencerStat, count?: number) => void\n\n constructor(\n gate: FecReleaseGate,\n emit: (data: Uint8Array, meta: FrameMeta) => void,\n recordStat: (stat: SequencerStat, count?: number) => void,\n ) {\n this.#gate = gate\n this.#emit = emit\n this.#recordStat = recordStat\n }\n\n get buffered(): number {\n let total = 0\n for (const stream of this.#streams.values()) total += stream.buf.length\n return total\n }\n\n push(streamKey: string, data: Uint8Array, meta: FrameMeta, seq: number): void {\n const frame: GatedFrame = { data, meta: { ...meta }, seq, arrivedAtMs: performance.now() }\n const stream = this.#streamFor(streamKey, meta)\n if (meta.sourceDiscontinuity) {\n this.#recordStat('publisherResets')\n this.#recordStat('droppedOnPublisherReset', stream.buf.length)\n stream.buf = []\n stream.last = null\n stream.lastSbn = undefined\n stream.discontinuity = false\n }\n if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {\n this.#recordStat('droppedStale')\n return\n }\n if (stream.last === null) {\n // The first release establishes decoder sync: video must start on a\n // RAP; earlier deltas are undecodable and are dropped.\n if (meta.trackType === 'video' && !meta.keyframe) {\n this.#recordStat('droppedBeforeStartupKeyframe')\n return\n }\n this.#release(stream, frame, undefined)\n this.#drain(stream)\n return\n }\n let lo = 0\n let hi = stream.buf.length\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (compareFrameOrder(stream.buf[mid]!, frame) < 0) lo = mid + 1\n else hi = mid\n }\n stream.buf.splice(lo, 0, frame)\n this.#drain(stream)\n }\n\n /** Re-evaluate held gaps after FEC state changed. */\n poke(streamKey?: string): void {\n if (streamKey !== undefined) {\n const stream = this.#streams.get(streamKey)\n if (stream) this.#drain(stream)\n return\n }\n for (const stream of this.#streams.values()) this.#drain(stream)\n }\n\n /**\n * The source restarted: the next release stalls with `discontinuity`.\n * Only marks the stream. Frames held behind the restart are released by\n * the next `poke` (or push), so the caller decides when the drain runs; a\n * drain from inside FEC bookkeeping would re-enter it mid-update.\n */\n markDiscontinuity(streamKey: string): void {\n const stream = this.#streams.get(streamKey)\n if (!stream || stream.last === null) return\n stream.discontinuity = true\n }\n\n /** Release everything buffered, in order (finite inputs). */\n flush(): void {\n for (const stream of this.#streams.values()) {\n this.#drain(stream)\n while (stream.buf.length > 0) {\n const head = stream.buf[0]!\n stream.buf.shift()\n this.#release(stream, head, undefined)\n }\n }\n }\n\n discardStream(streamKey: string): void {\n const stream = this.#streams.get(streamKey)\n if (!stream) return\n this.#recordStat('droppedOnLiveDispose', stream.buf.length)\n // Empty the buffer too: a discard can run from inside this stream's own\n // drain (a released frame whose sink retires the stream), and the drain\n // loop reads `buf` on every iteration. Deleting the entry alone would let\n // it keep releasing frames of a stream that no longer exists.\n stream.buf = []\n this.#streams.delete(streamKey)\n }\n\n /** Release one packet's buffered frames in order and forget its streams. */\n flushPacket(packetId: number): void {\n for (const [key, stream] of this.#streams) {\n if (stream.packetId !== packetId) continue\n this.#drain(stream)\n while (stream.buf.length > 0) {\n const head = stream.buf[0]!\n stream.buf.shift()\n this.#release(stream, head, undefined)\n }\n this.#streams.delete(key)\n }\n }\n\n /**\n * The packet's FEC ended, so no block will resolve a held gap: release its\n * frames in order, each gap stalled as a discontinuity, and forget its\n * streams, so FEC configured on it again starts from a fresh one.\n */\n endPacket(packetId: number): void {\n for (const [key, stream] of this.#streams) {\n if (stream.packetId !== packetId) continue\n // Frames are held only behind a released one, so `last` is set; each\n // pass releases at least the head.\n while (stream.buf.length > 0 && stream.last !== null) {\n stream.discontinuity = true\n this.#drain(stream)\n }\n this.#streams.delete(key)\n }\n }\n\n discardPacket(packetId: number): void {\n for (const [key, stream] of this.#streams) {\n if (stream.packetId !== packetId) continue\n this.#recordStat('droppedOnLiveDispose', stream.buf.length)\n stream.buf = []\n this.#streams.delete(key)\n }\n }\n\n discard(): void {\n for (const stream of this.#streams.values()) {\n this.#recordStat('droppedOnLiveDispose', stream.buf.length)\n stream.buf = []\n }\n this.#streams.clear()\n }\n\n #streamFor(streamKey: string, meta: FrameMeta): GatedStream {\n let stream = this.#streams.get(streamKey)\n if (!stream) {\n stream = {\n packetId: meta.packetId,\n ...(meta.catalogTrackName === undefined ? {} : { catalogTrackName: meta.catalogTrackName }),\n buf: [],\n last: null,\n lastSbn: undefined,\n discontinuity: false,\n }\n this.#streams.set(streamKey, stream)\n }\n return stream\n }\n\n #drain(stream: GatedStream): void {\n while (stream.buf.length > 0 && stream.last !== null) {\n const head = stream.buf[0]!\n const last = stream.last\n if (stream.discontinuity) {\n stream.discontinuity = false\n stream.buf.shift()\n this.#release(stream, head, this.#gate.decide(last, head, [], true))\n continue\n }\n if (authoredAdjacent(last.meta, head.meta)) {\n stream.buf.shift()\n this.#release(stream, head, undefined)\n continue\n }\n const headSbn = head.meta.fecSbn\n const from = stream.lastSbn ?? headSbn\n const to = headSbn ?? stream.lastSbn\n let lost: readonly number[] = []\n // Neither side carries a Source FEC Payload ID: on a catalog-declared\n // FEC track that is the trailer-stripped profile violation, and there\n // are no blocks to wait on. The gap is still a gap: never release across\n // it undecided (fail closed), let the gate decide with no known blocks.\n const unanchored = from === undefined && to === undefined\n if (from !== undefined && to !== undefined) {\n const resolved = this.#gate.resolvedLoss(\n stream.packetId,\n stream.catalogTrackName,\n Math.min(from, to),\n Math.max(from, to),\n from,\n )\n if (resolved === null) {\n const heldMs = performance.now() - head.arrivedAtMs\n if (heldMs <= this.#gate.holdLimitMs(stream.packetId, stream.catalogTrackName)) return\n // Past the point where these blocks must have resolved: nothing\n // is known about the gap, so stall rather than hold forever.\n this.#recordStat('forcedWindowFlushes')\n stream.buf.shift()\n this.#release(stream, head, this.#gate.decide(last, head, [], true))\n continue\n }\n lost = resolved\n }\n stream.buf.shift()\n // Every gap in the frame sequence gets a decision, not only one whose\n // blocks failed: a frame can go missing although its block completed\n // (recovered media whose authored timing is gone, a parser drop), and\n // releasing the next frame undecided would feed the decoder past a\n // hole with no stall or hold.\n this.#release(\n stream,\n head,\n lost.length > 0 || unanchored || authoredGap(last.meta, head.meta)\n ? this.#gate.decide(last, head, lost, false)\n : undefined,\n )\n }\n }\n\n #release(stream: GatedStream, frame: PendingFrame, decision: FrameGapDecision | undefined): void {\n if (decision) frame.meta.gapBefore = decision\n stream.last = frame\n if (frame.meta.fecSbn !== undefined) stream.lastSbn = frame.meta.fecSbn\n this.#gate.onReleased?.(frame.data, frame.meta)\n this.#emit(frame.data, frame.meta)\n }\n}\n\n// \u2500\u2500 fMP4 box detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst FMPU_BOX_TYPES = new Set(['ftyp', 'moov', 'moof'])\n\nfunction isFmp4Box(data: Uint8Array): boolean {\n if (data.length < 8) return false\n const type = String.fromCharCode(data[4]!, data[5]!, data[6]!, data[7]!)\n return FMPU_BOX_TYPES.has(type)\n}\n\ninterface TimingStreamState {\n packetId: number\n resolver: IsoBmffTimingResolver\n}\n\n// \u2500\u2500 MmtpContainerParser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class MmtpContainerParser {\n #tracks = new Map<number, TrackInfo>()\n #defaultTrack: TrackInfo | null = null\n #reassembler: MfuReassemblerBackend\n /**\n * Combines subsample data units back into whole samples (moqcast #37), one\n * combiner per parser stream. Per-stream is required, not an optimization:\n * {@link SampleCombiner} identifies a sample by\n * `(catalogTrackName, mpu, movieFragment, sampleNumber)`, so two streams\n * that carry no catalog identity would collide on every sample number and\n * each would see the other's data units as duplicates of its own. The\n * parser already isolates timing and reordering by this same stream key.\n *\n * Inert until a producer partitions \u2014 which it does the moment a frame\n * exceeds one data unit's 256-payload capacity (~327 KB here, routinely\n * exceeded by 4K/8K intra frames).\n */\n #sampleCombiners = new Map<string, SampleCombiner>()\n #sampleCombinerOptions: SampleCombinerOptions\n /** Counters folded in from combiners dropped by a track reset, so the\n * totals below stay monotonic across a publisher change. */\n #retiredSampleCombinerStats: SampleCombinerStats = {\n combinedSamples: 0,\n passthroughSamples: 0,\n timedOutSamples: 0,\n evictedSamples: 0,\n }\n #timingStreams = new Map<string, TimingStreamState>()\n #maxPendingTimingSamples: number\n #pendingTimingSamples = 0\n #reclaimedTimingSamples = 0\n #evictedTimingSamples = 0\n #rejectedMovieFragments = 0\n #sequencers = new Map<string, { packetId: number; sequencer: FrameSequencer }>()\n #fecGate: FecReleaseGate | null = null\n #fecSequencer: FecReleaseSequencer | null = null\n #reorderWindow = 0\n #reassemblyPolicy: MfuReassemblyPolicy | null = null\n #frameCb: FrameCallback | null = null\n #initCb: InitCallback | null = null\n #sourceSymbolCb: SourceSymbolCallback | null = null\n #trackFrameCbs = new Map<number, FrameCallback>()\n #reorderStats: Omit<MmtpReorderStats, 'bufferedFrames'> = {\n droppedBeforeStartupKeyframe: 0,\n droppedIncompleteStartupInterval: 0,\n droppedStale: 0,\n forcedWindowFlushes: 0,\n droppedOnLiveDispose: 0,\n publisherResets: 0,\n droppedOnPublisherReset: 0,\n }\n /** Streams (keyed by physical parser stream, value is packetId) that have\n * observed their first RAP under a reliable-moq policy. Reliable MoQ\n * resolves to a zero parser reorder window (transport ordering is\n * protocol-owned), so #sequencerFor returns null and its own startup gate\n * never runs; this map preserves an order-independent \"discard delta\n * frames before the first RAP\" gate for that path without reintroducing\n * reordering into the parser. */\n #reliableMoqStartupGate = new Map<string, number>()\n #disposed = false\n #fedObjects = 0\n #reassemblerFragmentsFed = 0\n #reassemblerDataUnitsReturned = 0\n #feedOutcomes = new Map<MmtpFeedOutcomeKey, number>()\n #deferredFeedOutcomes = new Map<MmtpFeedDropReason, number>()\n #lastOutcome: MmtpFeedOutcomeKey | null = null\n #lastOutcomeCount = 0\n\n #streamKey(packetId: number, catalogTrackName?: string): string {\n return JSON.stringify([packetId, catalogTrackName ?? null])\n }\n\n #sampleCombinerFor(streamKey: string): SampleCombiner {\n let combiner = this.#sampleCombiners.get(streamKey)\n if (combiner === undefined) {\n combiner = new SampleCombiner({\n ...this.#sampleCombinerOptions,\n onAbandon: sample => this.#releaseTimingSlot(streamKey, sample),\n })\n this.#sampleCombiners.set(streamKey, combiner)\n }\n return combiner\n }\n\n /**\n * Return the timing budget slot of a sample the combiner abandoned.\n *\n * Without this, `#pendingTimingSamples` is charged for every sample a moof\n * declares but credited only when that sample is delivered, so each sample\n * lost to eviction or timeout burns a slot until the window displaces it.\n * Window displacement bounds that leak but only reclaims the OLDEST entries;\n * this path returns a slot the moment the combiner knows the sample is gone,\n * which keeps the live window as wide as the stream actually needs.\n *\n * The parser total is decremented only when the resolver reports it\n * actually freed a slot, so a resolver already dropped by a track reset, or\n * a sample released twice, cannot drive the accounting negative.\n */\n #releaseTimingSlot(streamKey: string, sample: AbandonedSample): void {\n const resolver = this.#timingStreams.get(streamKey)?.resolver\n if (!resolver) return\n if (!resolver.releaseSample(\n sample.mpuSequenceNumber,\n sample.movieFragmentSequenceNumber,\n sample.sampleNumber,\n )) return\n this.#pendingTimingSamples--\n this.#reclaimedTimingSamples++\n if (this.#pendingTimingSamples < 0) {\n throw new Error('[MmtpContainerParser] pending timing sample accounting underflow')\n }\n }\n\n /** Drop one stream's combiner, folding its counters into the retained\n * totals so a reset cannot make the exposed stats run backwards. */\n #dropSampleCombiner(streamKey: string): void {\n const combiner = this.#sampleCombiners.get(streamKey)\n if (combiner === undefined) return\n const stats = combiner.stats\n this.#retiredSampleCombinerStats.combinedSamples += stats.combinedSamples\n this.#retiredSampleCombinerStats.passthroughSamples += stats.passthroughSamples\n this.#retiredSampleCombinerStats.timedOutSamples += stats.timedOutSamples\n this.#retiredSampleCombinerStats.evictedSamples += stats.evictedSamples\n this.#sampleCombiners.delete(streamKey)\n }\n\n constructor(config?: MmtpParserConfig) {\n if (config?.reassemblyPolicy && config.mfuTimeoutMs !== undefined) {\n throw new Error('[MmtpContainerParser] use reassemblyPolicy or mfuTimeoutMs, not both')\n }\n const reassemblyPolicy = config?.reassemblyPolicy ?? null\n const requestedReorderWindow = config?.reorderWindow ?? 0\n if (!Number.isFinite(requestedReorderWindow) || requestedReorderWindow < 0) {\n throw new Error('[MmtpContainerParser] reorderWindow must be a finite number >= 0')\n }\n const normalizedReorderWindow = Math.ceil(requestedReorderWindow)\n const requestedMfuTimeout = reassemblyPolicy\n ? mfuReassemblyTimeoutMs(reassemblyPolicy)\n : config?.mfuTimeoutMs\n if (reassemblyPolicy) {\n mmtpFrameReorderWindowFrames(reassemblyPolicy, normalizedReorderWindow)\n }\n if (\n requestedMfuTimeout !== undefined &&\n requestedMfuTimeout !== null &&\n requestedMfuTimeout <= 0\n ) {\n throw new Error('[MmtpContainerParser] mfuTimeoutMs must be > 0')\n }\n const maxPendingTimingSamples = config?.maxBufferedMfus ?? 100\n if (!Number.isInteger(maxPendingTimingSamples) || maxPendingTimingSamples <= 0) {\n throw new Error('[MmtpContainerParser] maxBufferedMfus must be a positive integer')\n }\n this.#maxPendingTimingSamples = maxPendingTimingSamples\n\n const maxSubsampleSegments = config?.maxSubsampleSegments ?? 16\n if (!Number.isInteger(maxSubsampleSegments) || maxSubsampleSegments <= 0) {\n throw new Error('[MmtpContainerParser] maxSubsampleSegments must be a positive integer')\n }\n\n // Everything above only reads `config` \u2014 the full policy/window\n // combination is validated before this point, so it is now safe to\n // mutate this instance or an injected, caller-owned reassembler.\n this.#reassemblyPolicy = reassemblyPolicy\n if (config?.reassembler) {\n this.#reassembler = config.reassembler\n // An injected backend owns its existing transport policy unless this\n // parser was given an explicit policy/legacy timeout override.\n if (requestedMfuTimeout !== undefined) {\n this.#reassembler.timeoutMs = requestedMfuTimeout\n }\n } else {\n this.#reassembler = new MFUReassembler({\n timeoutMs: requestedMfuTimeout === undefined ? 1000 : requestedMfuTimeout,\n ...(config?.maxBufferedMfus === undefined ? {} : { maxBuffered: config.maxBufferedMfus }),\n })\n }\n this.#reorderWindow = normalizedReorderWindow\n this.#sampleCombinerOptions = {\n maxBuffered: maxPendingTimingSamples,\n // The parser resolves every timed sample's authored size from FT=1\n // before its data units reach the combiner, so \"no size yet\" always\n // means the size has not arrived -- never that the host declines to\n // supply one. Waiting is therefore correct: treating an offset-zero\n // data unit as a whole sample in that window would truncate a\n // partitioned sample instead of completing it.\n requireAuthoredSize: true,\n maxSegmentsPerSample: maxSubsampleSegments,\n }\n this.#defaultTrack = config?.defaultTrack ?? null\n if (config?.fecReleaseGate) this.setFecReleaseGate(config.fecReleaseGate)\n if (this.#defaultTrack) this.#validateReassemblyPolicy()\n if (config?.tracks) {\n for (const [id, info] of config.tracks) {\n this.#validateReassemblyPolicy()\n this.#tracks.set(id, info)\n }\n }\n }\n\n /** The MFU reassembler that buffers fragmented MFUs fed via feed().\n * Exposed so a co-operating FEC-recovery layer (MmtpRouter) feeds recovered\n * fragments into the SAME reassembler: a frame split between source-received\n * and FEC-recovered fragments must share one buffer to complete (BLO-8151).\n * Before this was shared, recovered video symbols reported \"0 emitted,\n * pending-reassembly=N\" because they landed in a separate instance. */\n get reassembler(): MfuReassemblerBackend {\n return this.#reassembler\n }\n\n /**\n * Subsample-combining counters (moqcast #37). `combinedSamples` counts\n * samples reassembled from two or more data units and `passthroughSamples`\n * those that arrived whole; the two are deliberately separate, so a\n * producer that silently stopped partitioning cannot hide behind a single\n * \"samples delivered\" total. This is the typed acceptance signal for\n * large-frame playback \u2014 never parse the console for it.\n */\n get sampleCombinerStats(): Readonly<SampleCombinerStats> {\n const total = { ...this.#retiredSampleCombinerStats }\n for (const combiner of this.#sampleCombiners.values()) {\n const stats = combiner.stats\n total.combinedSamples += stats.combinedSamples\n total.passthroughSamples += stats.passthroughSamples\n total.timedOutSamples += stats.timedOutSamples\n total.evictedSamples += stats.evictedSamples\n }\n return total\n }\n\n /**\n * Typed view of the ISO-BMFF timing-sample budget.\n *\n * This is the acceptance signal for the budget ratchet: `pending` walking\n * to `capacity` while `rejectedMovieFragments` climbs is the stream wedging,\n * and it is observable here rather than only as media silently stopping.\n * Per repo policy this is a producer-owned counter -- never infer budget\n * state by parsing logs.\n */\n get timingBudgetStats(): Readonly<MmtpTimingBudgetStats> {\n return {\n pending: this.#pendingTimingSamples,\n capacity: this.#maxPendingTimingSamples,\n reclaimed: this.#reclaimedTimingSamples,\n evicted: this.#evictedTimingSamples,\n rejectedMovieFragments: this.#rejectedMovieFragments,\n }\n }\n\n get reorderStats(): Readonly<MmtpReorderStats> {\n let bufferedFrames = this.#fecSequencer?.buffered ?? 0\n for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered\n return { ...this.#reorderStats, bufferedFrames }\n }\n\n /**\n * Install (or remove) the in-order release policy for FEC-protected\n * streams. Streams the gate claims are released by block resolution; all\n * others keep the frame-count reorder window.\n */\n setFecReleaseGate(gate: FecReleaseGate | null): void {\n this.#fecSequencer?.flush()\n this.#fecGate = gate\n this.#fecSequencer = gate\n ? new FecReleaseSequencer(\n gate,\n (data, meta) => this.#emitFrame(data, meta),\n (stat, count = 1) => {\n this.#reorderStats[stat] += count\n },\n )\n : null\n }\n\n /** Re-evaluate held gaps after FEC block state changed. */\n pokeFecRelease(packetId?: number, catalogTrackName?: string): void {\n if (!this.#fecSequencer) return\n this.#fecSequencer.poke(\n packetId === undefined ? undefined : this.#streamKey(packetId, catalogTrackName),\n )\n }\n\n /**\n * The FEC source of a stream restarted: stall the next release. Marks\n * only; follow with `pokeFecRelease` to release frames held behind it.\n */\n markFecDiscontinuity(packetId: number, catalogTrackName?: string): void {\n this.#fecSequencer?.markDiscontinuity(this.#streamKey(packetId, catalogTrackName))\n }\n\n /**\n * The FEC of a packet ID ended: release the frames the FEC release gate\n * holds for it in order, each gap stalled, and forget its gated streams.\n */\n endFecRelease(packetId: number): void {\n this.#fecSequencer?.endPacket(packetId)\n }\n\n /**\n * MPU and data-unit identity of one MMTP packet, parsed in place (no copy,\n * no reassembly). Null for non-MPU packets and packets that do not parse.\n * `header` is the packet's already-parsed MMTP header (parsePacket()), so\n * the header is not parsed a second time (BLO-36599).\n */\n static sourcePacketIdentity(packet: Uint8Array, header: MMTPHeader): SourcePacketIdentity | null {\n try {\n const extraction = extractMFUPayloadWithHeader(packet, header)\n if (!extraction) return null\n const identity: SourcePacketIdentity = {\n mpuSequenceNumber: extraction.mpu.mpuSequenceNumber,\n }\n if (extraction.mfuDuHeader) {\n identity.sampleNumber = extraction.mfuDuHeader.sampleNumber\n identity.movieFragmentSequenceNumber = extraction.mfuDuHeader.movieFragmentSequenceNumber\n identity.offset = extraction.mfuDuHeader.offset\n }\n return identity\n } catch {\n return null\n }\n }\n\n /**\n * Feed tallies for this parser. A fresh snapshot per call, not a live view,\n * so a caller can hold one and compare it against a later read.\n *\n * Survives dispose() \u2014 the counters are the parser's own state and are not\n * cleared by teardown, because a wedged receive path is usually read after\n * the fact.\n */\n get feedStats(): Readonly<MmtpParserFeedStats> {\n return {\n fedObjects: this.#fedObjects,\n outcomes: Object.fromEntries(this.#feedOutcomes),\n deferredOutcomes: Object.fromEntries(this.#deferredFeedOutcomes),\n }\n }\n\n /**\n * Outcome token of the most recent feed(), or null before the first call.\n *\n * Exposed alongside `lastFeedOutcomeCount` as two scalars rather than one\n * object because a wrapper reads them on every packet, and feedStats()\n * allocates a fresh snapshot per call.\n */\n get lastFeedOutcome(): MmtpFeedOutcomeKey | null {\n return this.#lastOutcome\n }\n\n /** How many times `lastFeedOutcome` has now been seen. 1 on its first\n * occurrence, which is what a caller logging once per novel outcome needs. */\n get lastFeedOutcomeCount(): number {\n return this.#lastOutcomeCount\n }\n\n /**\n * MFU fragments feed() handed to the reassembler, including rejected ones.\n * A plain scalar a per-packet caller can difference; reading the\n * reassembler's own stats is a wasm crossing on the Rust backend\n * (BLO-36599).\n */\n get reassemblerFragmentsFed(): number {\n return this.#reassemblerFragmentsFed\n }\n\n /** Data units the reassembler returned complete to feed(). */\n get reassemblerDataUnitsReturned(): number {\n return this.#reassemblerDataUnitsReturned\n }\n\n #countOutcome(token: MmtpFeedOutcomeKey): void {\n const count = (this.#feedOutcomes.get(token) ?? 0) + 1\n this.#feedOutcomes.set(token, count)\n this.#lastOutcome = token\n this.#lastOutcomeCount = count\n }\n\n #countDeferredOutcome(reason: MmtpFeedDropReason): void {\n this.#deferredFeedOutcomes.set(\n reason,\n (this.#deferredFeedOutcomes.get(reason) ?? 0) + 1,\n )\n }\n\n /**\n * Count a feed() drop and return null in one expression, so every early\n * return inside feed() states the reason it took instead of collapsing into\n * a single failure bucket. Returns null so call sites stay `return this.#drop(...)`.\n */\n #drop(reason: MmtpFeedDropReason): null {\n this.#countOutcome(`drop:${reason}`)\n return null\n }\n\n get reorderDiagnostics(): ReadonlyArray<MmtpReorderStreamDiagnostics> {\n const streams: MmtpReorderStreamDiagnostics[] = []\n for (const { packetId, sequencer } of this.#sequencers.values()) {\n for (const diagnostics of sequencer.diagnostics) streams.push({ packetId, ...diagnostics })\n }\n return streams\n }\n\n /**\n * Reclaim samples abandoned by the sample combiner's protocol deadline.\n *\n * This is deliberately driven by the parser's existing packet/lifecycle\n * cleanup path rather than by another timer. The reassembler timeout is the\n * same catalog/FEC-derived deadline that bounds each constituent MFU.\n */\n cleanupTimeouts(): void {\n const timeoutMs = this.#reassembler.timeoutMs\n if (timeoutMs === null) return\n for (const combiner of this.#sampleCombiners.values()) {\n combiner.cleanupTimeouts(timeoutMs)\n }\n }\n\n /**\n * Phase 1 of two-phase parsing. Returns structured header info for FEC feeding.\n * Call this before feed() when you need to extract FEC payload ID and feed\n * symbols to FECManager before MFU extraction.\n * Returns null for malformed packets.\n */\n parseHeader(packet: Uint8Array, options?: ParseHeaderOptions): PacketInfo | null {\n return this.parsePacket(packet, options)?.info ?? null\n }\n\n /**\n * parseHeader() that also keeps the parsed MMTP header, so a caller that\n * goes on to feedParsed() the same packet parses its header exactly once.\n * The MMTP header is parsed once here and the Source FEC Payload ID is read\n * from it; parseHeader() used to parse the header three times for an FEC\n * source packet, and feed() repeated all of it (BLO-36599).\n */\n parsePacket(packet: Uint8Array, options?: ParseHeaderOptions): ParsedMmtpPacket | null {\n try {\n const header = parseMMTPHeader(packet)\n const isRepair = header.packetType === PACKET_TYPE_REPAIR\n const skipFecPayloadId = options?.skipFecPayloadId === true\n const fecPayloadId =\n !skipFecPayloadId && !isRepair ? sourceFecPayloadIdOf(packet, header) : null\n return {\n info: {\n packetId: header.packetId,\n packetType: header.packetType,\n timestamp: header.timestamp,\n timestampUs: mmtpTimestampToMicros(header.timestamp),\n sequenceNumber: header.sequenceNumber,\n rapFlag: header.rapFlag,\n fecType: header.fecType,\n fecPayloadId,\n isRepair,\n },\n header,\n skipFecPayloadId,\n }\n } catch {\n return null\n }\n }\n\n /** Feed a complete MMTP packet. For two-phase use, call parseHeader() first. */\n feed(packet: Uint8Array, options?: ParseHeaderOptions): MmtpParserRecord | null {\n return this.#feedCounted(packet, options, undefined)\n }\n\n /**\n * feed() for a packet this caller already ran through parsePacket(): the\n * header parse is reused instead of repeated. `parsed` must come from\n * parsePacket() of this same, unmodified packet. If it was resolved under a\n * different skipFecPayloadId option, the packet is parsed again.\n */\n feedParsed(\n packet: Uint8Array,\n parsed: ParsedMmtpPacket,\n options?: ParseHeaderOptions,\n ): MmtpParserRecord | null {\n return this.#feedCounted(packet, options, parsed)\n }\n\n #feedCounted(\n packet: Uint8Array,\n options: ParseHeaderOptions | undefined,\n parsed: ParsedMmtpPacket | undefined,\n ): MmtpParserRecord | null {\n this.#fedObjects += 1\n let record: MmtpParserRecord | null\n try {\n record = this.#feed(packet, options, parsed)\n } catch (error) {\n // Count before rethrowing: a throw is an outcome, and leaving it\n // uncounted would silently break the sum-to-fedObjects invariant that\n // makes this tally readable.\n this.#countOutcome('drop:threw')\n throw error\n }\n // Drops are counted at their own return sites via #drop(), which is what\n // preserves the reason. Only successful records are counted here.\n if (record) this.#countOutcome(record.kind)\n return record\n }\n\n #feed(\n packet: Uint8Array,\n options: ParseHeaderOptions | undefined,\n preParsed: ParsedMmtpPacket | undefined,\n ): MmtpParserRecord | null {\n if (this.#disposed) return this.#drop('disposed')\n\n this.cleanupTimeouts()\n\n const parsed =\n preParsed !== undefined &&\n preParsed.skipFecPayloadId === (options?.skipFecPayloadId === true)\n ? preParsed\n : this.parsePacket(packet, options)\n if (!parsed) return this.#drop('header-parse-failed')\n const info = parsed.info\n\n if (info.packetType !== PACKET_TYPE_MPU) {\n return {\n kind: info.isRepair ? 'repair' : 'non-media',\n packet: info,\n byteLength: packet.byteLength,\n }\n }\n\n // \u2500\u2500 FEC source-symbol exposure \u2500\u2500\n // Fire onSourceSymbol BEFORE MFU extraction so the consumer-side FEC\n // pipeline (e.g. FecWorkerClient.feedSource) starts as early as possible.\n // Source-only: parseHeader already gates fecPayloadId on !isRepair.\n if (info.fecPayloadId !== null && this.#sourceSymbolCb) {\n // Strip the 4-byte fec_payload_id trailer per ISO 23008-1 \u00A7C.5.2.\n // Symbol-size zero-padding is the consumer's responsibility \u2014 FEC\n // symbol size is a FEC-config concern, not a parser concern.\n const rawSymbol = packet.subarray(0, packet.length - 4)\n const altaTrailer = extractAltaTrailer(packet)\n const alta = altaTrailer\n ? { auth: altaTrailer.auth, payloadEnd: altaTrailer.signedEnd }\n : undefined\n try {\n this.#sourceSymbolCb(info.packetId, info.fecPayloadId.ssId, rawSymbol, alta)\n } catch {\n // Source-symbol consumer threw \u2014 never block MFU extraction on\n // FEC-pipeline errors. The frame path stays viable; the consumer\n // is responsible for its own error surfacing.\n }\n }\n\n const extraction = extractMFUPayloadWithHeader(packet, parsed.header, {\n skipFecId: options?.skipFecPayloadId,\n })\n if (!extraction) return this.#drop('mfu-extract-failed')\n\n const { mpu, fragmentType, data, mfuDuHeader } = extraction\n if (mpu.timedFlag !== 1) return this.#drop('untimed-mpu')\n const streamKey = this.#streamKey(info.packetId, options?.catalogTrackName)\n\n if (fragmentType === 0) {\n if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)\n return this.#drop('init-unexpected-fragmentation')\n // ISO MMTP MPU metadata may carry an `mmpu` box between the ftyp and\n // moov boxes. That box is valid MMTP metadata but is not a legal\n // top-level MSE initialization box. Normalize at the parser boundary so\n // every container consumer (including MSE and rendition handoff paths)\n // receives append-safe ftyp+moov bytes rather than having to remember\n // this wire-format detail independently.\n const normalizedInit = normalizeMmtpInitSegment(data)\n if (!normalizedInit) return this.#drop('init-normalize-failed')\n const resolver = this.#timingResolver(streamKey, info.packetId, true)\n if (!resolver?.addInitialization(normalizedInit)) return this.#drop('init-timing-refused')\n this.#initCb?.(normalizedInit, info.packetId, options?.catalogTrackName)\n return {\n kind: 'init',\n packet: info,\n data: normalizedInit,\n mpuSequenceNumber: mpu.mpuSequenceNumber,\n fragmentationIndicator: mpu.fragmentationIndicator,\n fragmentCounter: mpu.fragmentCounter,\n }\n }\n\n if (fragmentType === 1) {\n if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)\n return this.#drop('fragment-unexpected-fragmentation')\n const resolver = this.#timingResolver(streamKey, info.packetId, false)\n if (!resolver) return this.#drop('fragment-no-resolver')\n const pendingBefore = resolver.pendingSampleCount\n const evictedBefore = resolver.evictedSampleCount\n const remaining = this.#maxPendingTimingSamples - this.#pendingTimingSamples\n const admitted = resolver.addMovieFragment(mpu.mpuSequenceNumber, data, remaining)\n // Sync on BOTH paths: the resolver can displace stale timing and still\n // refuse the fragment in hand, and a global count that missed those\n // freed slots would keep refusing on a budget that is no longer full.\n this.#pendingTimingSamples += resolver.pendingSampleCount - pendingBefore\n this.#evictedTimingSamples += resolver.evictedSampleCount - evictedBefore\n if (!admitted) {\n if (resolver.lastMovieFragmentRejection === 'budget') {\n this.#rejectedMovieFragments++\n }\n return this.#drop('fragment-timing-refused')\n }\n return {\n kind: 'fragment',\n packet: info,\n data,\n mpuSequenceNumber: mpu.mpuSequenceNumber,\n fragmentationIndicator: mpu.fragmentationIndicator,\n fragmentCounter: mpu.fragmentCounter,\n }\n }\n if (fragmentType !== 2) return this.#drop('unknown-fragment-type')\n\n const resolver = this.#timingResolver(streamKey, info.packetId, false)\n if (!resolver) return this.#drop('mfu-no-resolver')\n if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)\n return this.#drop('mfu-empty-payload')\n if (mpu.fragmentationIndicator === 0 || mpu.fragmentationIndicator === 1) {\n if (!mfuDuHeader || mfuDuHeader.movieFragmentSequenceNumber === 0 ||\n mfuDuHeader.sampleNumber === 0) {\n return this.#drop('mfu-du-header-missing')\n }\n const headTiming = resolver.sampleTiming(\n mpu.mpuSequenceNumber,\n mfuDuHeader.movieFragmentSequenceNumber,\n mfuDuHeader.sampleNumber,\n )\n if (!headTiming) return this.#drop('mfu-sample-timing-missing')\n // A data unit's authored base offset is its position within the sample\n // (moqcast #37): 0 for a whole-sample DU, nonzero for one subsample DU\n // among siblings. Sizes below are therefore relative to that base, not\n // to the sample's start -- comparing a subsample DU against the whole\n // sample's size is what dropped every partitioned 4K/8K frame.\n const offset = mfuDuHeader.offset\n if (!Number.isSafeInteger(offset) || offset < 0 || offset >= headTiming.size)\n return this.#drop('mfu-offset-out-of-range')\n const remaining = headTiming.size - offset\n // FI=0 carries its data unit whole, in a single payload. Only a\n // partitioned sample's FINAL data unit can be shorter than a full data\n // unit -- every earlier one spans exactly 256 payloads and so is always\n // fragmented -- which makes a whole-carried DU exactly the bytes left\n // from its base. At offset 0 this is the pre-amendment check unchanged.\n if (mpu.fragmentationIndicator === 0 && data.byteLength !== remaining)\n return this.#drop('mfu-size-mismatch')\n if (mpu.fragmentationIndicator === 1 && data.byteLength >= remaining)\n return this.#drop('mfu-size-mismatch')\n\n // Publish the authored size before this data unit can complete, so the\n // combiner never has to guess whether an offset-zero DU is a whole\n // sample. Registration can complete a previously buffered sample; its\n // refusal is a side effect of this feed, not this packet's own outcome,\n // so expose it in deferredOutcomes without double-counting outcomes.\n const readyOnRegister = this.#sampleCombinerFor(streamKey).registerAuthoredSize(\n options?.catalogTrackName,\n mpu.mpuSequenceNumber,\n mfuDuHeader.movieFragmentSequenceNumber,\n mfuDuHeader.sampleNumber,\n headTiming.size,\n )\n if (readyOnRegister) {\n const deferred = this.#emitCombinedSample(\n readyOnRegister,\n info,\n resolver,\n options?.recovered === true,\n options?.fecSbn,\n )\n if (deferred.record === undefined) this.#countDeferredOutcome(deferred.reason)\n }\n }\n\n let assembled: ReassembledMFU | null\n this.#reassemblerFragmentsFed++\n try {\n assembled = this.#reassembler.addFragment({\n fragmentationIndicator: mpu.fragmentationIndicator,\n mpuSequenceNumber: mpu.mpuSequenceNumber,\n fragmentCounter: mpu.fragmentCounter,\n data,\n timestamp: info.timestamp,\n rapFlag: info.rapFlag,\n priority: mfuDuHeader?.priority,\n sampleNumber: mfuDuHeader?.sampleNumber,\n movieFragmentSequenceNumber: mfuDuHeader?.movieFragmentSequenceNumber,\n sampleOffset: mfuDuHeader?.offset,\n packetSequenceNumber: info.sequenceNumber,\n recovered: options?.recovered === true,\n source: options?.source,\n deliveryProvenance: options?.deliveryProvenance,\n catalogTrackName: options?.catalogTrackName,\n packetId: info.packetId,\n })\n } catch (error) {\n if (!isMfuReassemblyError(error)) throw error\n console.warn(\n `[MmtpContainerParser] rejected MFU fragment: code=${error.code} ` +\n `mpu=${mpu.mpuSequenceNumber} fi=${mpu.fragmentationIndicator} ` +\n `counter=${mpu.fragmentCounter}`,\n )\n return {\n kind: 'rejected',\n packet: info,\n mpuSequenceNumber: mpu.mpuSequenceNumber,\n fragmentationIndicator: mpu.fragmentationIndicator,\n fragmentCounter: mpu.fragmentCounter,\n error: { code: error.code, message: error.message },\n }\n }\n\n if (!assembled) {\n return {\n kind: 'pending',\n packet: info,\n mpuSequenceNumber: mpu.mpuSequenceNumber,\n fragmentationIndicator: mpu.fragmentationIndicator,\n fragmentCounter: mpu.fragmentCounter,\n }\n }\n this.#reassemblerDataUnitsReturned++\n\n const movieFragmentSequenceNumber = assembled.movieFragmentSequenceNumber\n const sampleNumber = assembled.sampleNumber\n if (movieFragmentSequenceNumber === undefined || sampleNumber === undefined)\n return this.#drop('assembled-identity-missing')\n // Resolve authored timing BEFORE the combiner sees this data unit. An\n // assembled data unit whose sample identity has no authored timing is not\n // a sample this stream declared, so it must be dropped outright -- letting\n // it buffer would occupy a combiner slot and could evict a legitimate\n // partitioned sample waiting on a sibling.\n if (!resolver.sampleTiming(\n assembled.mpuSequenceNumber,\n movieFragmentSequenceNumber,\n sampleNumber,\n )) return this.#drop('assembled-timing-missing')\n if (assembled.mpuSequenceNumber !== mpu.mpuSequenceNumber)\n return this.#drop('assembled-mpu-mismatch')\n\n // One reassembled data unit is not necessarily one whole sample. A\n // partitioned sample completes only once every sibling data unit has\n // arrived and they are byte-adjacent from zero to the authored size; an\n // unpartitioned sample passes straight through with no buffering.\n let combined: CombinedSample | null\n try {\n combined = this.#sampleCombinerFor(streamKey).addMfu(assembled)\n } catch (error) {\n if (!(error instanceof SampleCombineError)) throw error\n console.warn(\n `[MmtpContainerParser] rejected subsample data unit: code=${error.code} ` +\n `mpu=${assembled.mpuSequenceNumber} sample=${sampleNumber} ` +\n `offset=${assembled.sampleOffset ?? 0}`,\n )\n return {\n kind: 'rejected',\n packet: info,\n mpuSequenceNumber: mpu.mpuSequenceNumber,\n fragmentationIndicator: mpu.fragmentationIndicator,\n fragmentCounter: mpu.fragmentCounter,\n error: { code: error.code, message: error.message },\n }\n }\n if (!combined) {\n return {\n kind: 'pending',\n packet: info,\n mpuSequenceNumber: mpu.mpuSequenceNumber,\n fragmentationIndicator: mpu.fragmentationIndicator,\n fragmentCounter: mpu.fragmentCounter,\n }\n }\n\n const outcome = this.#emitCombinedSample(\n combined,\n info,\n resolver,\n options?.recovered === true,\n options?.fecSbn,\n )\n if (outcome.record !== undefined) return outcome.record\n return this.#drop(outcome.reason)\n }\n\n /**\n * Deliver one combined sample: resolve its authored timing, build frame\n * metadata, retire the timing entry, and route it. Shared by the ordinary\n * completion path and by the rarer completion that falls out of registering\n * an authored size for data units that arrived before it.\n *\n * Reports a refusal as a reason rather than counting it, because the two\n * call sites owe different accounting. On `#feed()`'s tail return the\n * refusal *is* that feed()'s outcome and must be attributed; on the\n * register-completion path it belongs to an earlier sample while this\n * packet's own fragment continues to its own outcome, so counting it there\n * would push `outcomes` past `fedObjects` \u2014 the very invariant the\n * attribution exists to keep readable. Leaving the choice at the call site\n * is what keeps both true.\n */\n #emitCombinedSample(\n combined: CombinedSample,\n info: PacketInfo,\n resolver: IsoBmffTimingResolver,\n recovered: boolean,\n fecSbn: number | undefined,\n ): CombinedSampleOutcome {\n const timing = resolver.sampleTiming(\n combined.mpuSequenceNumber,\n combined.movieFragmentSequenceNumber,\n combined.sampleNumber,\n )\n if (!timing) return { reason: 'combined-timing-missing' }\n // The combiner already completes against the registered authored size;\n // re-checking here keeps the unpartitioned passthrough path fail-closed\n // on the same invariant rather than trusting it by construction.\n if (combined.data.byteLength !== timing.size) return { reason: 'combined-size-mismatch' }\n\n const meta = this.#buildFrameMeta(\n combined,\n info.packetId,\n info.sequenceNumber,\n timing,\n recovered,\n )\n if (!meta) return { reason: 'combined-meta-build-failed' }\n if (fecSbn !== undefined) meta.fecSbn = fecSbn\n if (!resolver.consumeSample(\n combined.mpuSequenceNumber,\n combined.movieFragmentSequenceNumber,\n combined.sampleNumber,\n timing,\n )) return { reason: 'combined-timing-consume-refused' }\n this.#pendingTimingSamples--\n this.routeFrame(combined.data, meta)\n return {\n record: {\n kind: 'frame',\n packet: info,\n data: combined.data,\n meta,\n mpuSequenceNumber: combined.mpuSequenceNumber,\n fragmentCount: combined.fragmentCount,\n rapFlag: combined.rapFlag,\n ...(combined.priority === undefined ? {} : { priority: combined.priority }),\n },\n }\n }\n\n onInit(cb: InitCallback): void {\n this.#initCb = cb\n }\n\n onFrame(cb: FrameCallback): void {\n this.#frameCb = cb\n }\n\n /** Register a per-packet FEC source-symbol callback. Fires for each MMTP\n * source MPU packet that carries a Source FEC Payload ID (ISO 23008-1 \u00A7C.5.2).\n *\n * Consumer responsibilities:\n * - Zero-pad `rawSymbol` to the FEC `symbolSize` if shorter (FEC config is\n * out-of-band \u2014 typically from the CMSF catalog's `fec.symbolSize`).\n * - Decide oversized handling (rawSymbol.length > symbolSize is not FEC-\n * decodable; consumer typically skips that block's source-feed).\n * - Look up the right FEC consumer (e.g. FecWorkerClient) by packetId.\n *\n * Designed for spec-clean reuse: Shaka, upstream moqtail, or any consumer\n * that uses MmtpContainerParser can wire FEC source delivery without\n * pulling in MulticastManager/MmtpRouter/FECManager. */\n onSourceSymbol(cb: SourceSymbolCallback): void {\n this.#sourceSymbolCb = cb\n }\n\n onTrackFrame(packetId: number, cb: FrameCallback): void {\n this.#trackFrameCbs.set(packetId, cb)\n }\n\n setTrack(packetId: number, info: TrackInfo): void {\n this.#validateReassemblyPolicy()\n for (const [key, entry] of this.#sequencers) {\n if (entry.packetId !== packetId) continue\n entry.sequencer.flush()\n this.#sequencers.delete(key)\n }\n this.#fecSequencer?.flushPacket(packetId)\n for (const [key, state] of this.#timingStreams) {\n if (state.packetId === packetId) this.#dropTimingStream(key)\n }\n for (const key of [...this.#sampleCombiners.keys()]) {\n if ((JSON.parse(key) as [number, string | null])[0] === packetId) {\n this.#dropSampleCombiner(key)\n }\n }\n for (const [key, gatedPacketId] of this.#reliableMoqStartupGate) {\n if (gatedPacketId === packetId) this.#reliableMoqStartupGate.delete(key)\n }\n this.#tracks.set(packetId, info)\n }\n\n /** Drop one physical stream's ordering and MFU state for retained replay. */\n resetTrack(packetId: number, catalogTrackName: string): void {\n if (!catalogTrackName) {\n throw new Error('[MmtpContainerParser] catalogTrackName is required for track reset')\n }\n for (const [key, entry] of this.#sequencers) {\n if (key !== this.#streamKey(packetId, catalogTrackName)) continue\n entry.sequencer.discard()\n this.#sequencers.delete(key)\n }\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n this.#fecSequencer?.discardStream(streamKey)\n this.#dropTimingStream(streamKey)\n this.#reliableMoqStartupGate.delete(streamKey)\n this.#reassembler.resetStream(catalogTrackName)\n // Drop half-combined samples too: a partitioned sample whose siblings\n // straddle the reset would otherwise complete against a stale authored\n // size and hand the decoder bytes from two different publisher epochs.\n this.#dropSampleCombiner(streamKey)\n }\n\n /**\n * Retire one packet ID, drop-only: discard every stream of it (reorder and\n * FEC release buffers, authored timing, half-combined samples, startup\n * gates) and its track registration, and reset the MFU reassembler for each\n * catalog stream it carried. Nothing buffered is emitted, as on live\n * dispose. `catalogTrackNames` names the streams the caller routed on this\n * packet ID; the parser adds any it holds state for itself. Reassembly\n * resets are catalog-scoped, as for resetTrack: a stream that never carried\n * a catalog name keeps its reassembler buffers until they time out or are\n * evicted.\n */\n removeTrack(packetId: number, catalogTrackNames: Iterable<string>): void {\n const names = new Set<string>(catalogTrackNames)\n for (const [key, entry] of [...this.#sequencers]) {\n if (entry.packetId !== packetId) continue\n entry.sequencer.discard()\n this.#sequencers.delete(key)\n }\n this.#fecSequencer?.discardPacket(packetId)\n for (const [key, state] of [...this.#timingStreams]) {\n if (state.packetId !== packetId) continue\n const name = (JSON.parse(key) as [number, string | null])[1]\n if (name !== null) names.add(name)\n this.#dropTimingStream(key)\n }\n for (const key of [...this.#sampleCombiners.keys()]) {\n const [keyPacketId, name] = JSON.parse(key) as [number, string | null]\n if (keyPacketId !== packetId) continue\n if (name !== null) names.add(name)\n this.#dropSampleCombiner(key)\n }\n for (const [key, gatedPacketId] of [...this.#reliableMoqStartupGate]) {\n if (gatedPacketId === packetId) this.#reliableMoqStartupGate.delete(key)\n }\n for (const name of names) this.#reassembler.resetStream(name)\n this.#tracks.delete(packetId)\n this.#trackFrameCbs.delete(packetId)\n }\n\n setMfuTimeoutMs(ms: number | null): void {\n if (ms !== null && ms <= 0) throw new Error('[MmtpContainerParser] mfuTimeoutMs must be > 0')\n this.#reassembler.timeoutMs = ms\n }\n\n setReassemblyPolicy(policy: MfuReassemblyPolicy): void {\n const timeoutMs = mfuReassemblyTimeoutMs(policy)\n this.#validateReassemblyPolicy(policy)\n this.#flushSequencers()\n this.#reassemblyPolicy = policy\n this.#reliableMoqStartupGate.clear()\n this.setMfuTimeoutMs(timeoutMs)\n }\n\n setReorderWindow(windowSize: number): void {\n if (!Number.isFinite(windowSize) || windowSize < 0) {\n throw new Error('[MmtpContainerParser] reorderWindow must be a finite number >= 0')\n }\n const nextWindow = Math.ceil(windowSize)\n this.#validateReassemblyPolicy(this.#reassemblyPolicy, nextWindow)\n this.#flushSequencers()\n this.#reorderWindow = nextWindow\n if (this.#reorderWindow <= 0) this.#sequencers.clear()\n }\n\n /** Route an already-assembled frame through the same reorder/callback path\n * used by source packets. FEC recovery uses this to merge recovered frames\n * with direct source frames before the decoder sees them. */\n routeFrame(data: Uint8Array, meta: FrameMeta): void {\n const seq = meta.packetSequenceNumber\n if (this.#fecSequencer && this.#fecGate?.gates(meta.packetId, meta.catalogTrackName)) {\n this.#fecSequencer.push(\n this.#streamKey(meta.packetId, meta.catalogTrackName),\n data,\n meta,\n seq !== undefined && Number.isFinite(seq) ? seq : 0,\n )\n return\n }\n const sequencer = this.#sequencerFor(meta.packetId, meta.trackType, meta.catalogTrackName)\n if (sequencer && seq !== undefined && Number.isFinite(seq)) {\n sequencer.push(data, meta, seq)\n return\n }\n if (this.#reassemblyPolicy?.transport === 'reliable-moq' &&\n meta.trackType === 'video' &&\n !this.#passReliableMoqStartupGate(meta)) {\n return\n }\n this.#emitFrame(data, meta)\n }\n\n /**\n * Claim and return the authored timing for a recovered sample. Recovery does\n * not pass through the ordinary `feed()` completion path, so it must retire\n * the same bounded resolver entry once the router accepts the recovered\n * sample. A missing entry is a fail-closed signal: callers must not replace\n * it with an MMTP send timestamp or cadence guess.\n */\n consumeAuthoredTimingFor(\n packetId: number,\n mpuSequenceNumber: number,\n movieFragmentSequenceNumber: number | undefined,\n sampleNumber: number | undefined,\n catalogTrackName?: string,\n ): AuthoredTiming | null {\n if (movieFragmentSequenceNumber === undefined || sampleNumber === undefined) return null\n const resolver = this.#timingResolver(\n this.#streamKey(packetId, catalogTrackName),\n packetId,\n false,\n )\n const timing = resolver?.sampleTiming(\n mpuSequenceNumber,\n movieFragmentSequenceNumber,\n sampleNumber,\n )\n if (!timing || !resolver || !resolver.consumeSample(\n mpuSequenceNumber,\n movieFragmentSequenceNumber,\n sampleNumber,\n timing,\n )) return null\n this.#pendingTimingSamples--\n if (this.#pendingTimingSamples < 0) {\n throw new Error('[MmtpContainerParser] pending timing sample accounting underflow')\n }\n return {\n decodeTime: timing.decodeTime,\n presentationTime: timing.presentationTime,\n durationTicks: timing.duration,\n mediaTimescale: timing.timescale,\n }\n }\n\n /** Order-independent reliable-moq startup gate: discard delta frames for a\n * stream until its first RAP is observed, without buffering/reordering. */\n #passReliableMoqStartupGate(meta: FrameMeta): boolean {\n const streamKey = this.#streamKey(meta.packetId, meta.catalogTrackName)\n if (meta.sourceDiscontinuity) this.#reliableMoqStartupGate.delete(streamKey)\n if (this.#reliableMoqStartupGate.has(streamKey)) return true\n if (!meta.keyframe) {\n this.#reorderStats.droppedBeforeStartupKeyframe++\n return false\n }\n this.#reliableMoqStartupGate.set(streamKey, meta.packetId)\n return true\n }\n\n /** Emit all ordered frames currently buffered by the parser. Finite-stream\n * consumers may call this before dispose(); live cancellation should not. */\n flush(): void {\n if (this.#disposed) return\n this.#flushSequencers()\n this.#fecSequencer?.flush()\n }\n\n dispose(): void {\n this.#disposed = true\n for (const { sequencer } of this.#sequencers.values()) sequencer.discard()\n this.#sequencers.clear()\n this.#fecSequencer?.discard()\n this.#timingStreams.clear()\n this.#pendingTimingSamples = 0\n this.#reliableMoqStartupGate.clear()\n this.#reassembler.reset()\n this.#frameCb = null\n this.#initCb = null\n this.#trackFrameCbs.clear()\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #timingResolver(\n streamKey: string,\n packetId: number,\n create: boolean,\n ): IsoBmffTimingResolver | null {\n const existing = this.#timingStreams.get(streamKey)\n if (existing) return existing.resolver\n if (!create) return null\n const resolver = new IsoBmffTimingResolver(this.#maxPendingTimingSamples)\n this.#timingStreams.set(streamKey, { packetId, resolver })\n return resolver\n }\n\n #dropTimingStream(streamKey: string): void {\n const state = this.#timingStreams.get(streamKey)\n if (!state) return\n this.#timingStreams.delete(streamKey)\n this.#pendingTimingSamples -= state.resolver.pendingSampleCount\n if (this.#pendingTimingSamples < 0) {\n throw new Error('[MmtpContainerParser] pending timing sample accounting underflow')\n }\n }\n\n #validateReassemblyPolicy(\n policy: MfuReassemblyPolicy | null = this.#reassemblyPolicy,\n reorderWindow = this.#reorderWindow,\n ): void {\n if (!policy) return\n mmtpFrameReorderWindowFrames(policy, reorderWindow)\n }\n\n #buildFrameMeta(\n assembled: CombinedSample,\n packetId: number,\n seq: number,\n timing: IsoBmffSampleTiming,\n recovered = false,\n ): FrameMeta | null {\n const { data, rapFlag, priority } = assembled\n\n if (isFmp4Box(data)) return null\n const presentationTimestamp = mediaTicksToMicroseconds(\n timing.presentationTime,\n timing.timescale,\n )\n const decodeTimestamp = mediaTicksToMicroseconds(timing.decodeTime, timing.timescale)\n const duration = mediaTicksToMicroseconds(BigInt(timing.duration), timing.timescale)\n if (presentationTimestamp === null || decodeTimestamp === null || duration === null ||\n duration <= 0) {\n return null\n }\n\n let track = this.#tracks.get(packetId)\n if (!track && this.#defaultTrack) {\n track = this.#defaultTrack\n this.#tracks.set(packetId, track)\n }\n const codec = track?.codec ?? ''\n\n const keyframe = MmtpContainerParser.isKeyframe(data, isMfuKeyframeMetadata(rapFlag, priority), codec)\n\n const meta: FrameMeta = {\n timestamp: presentationTimestamp,\n decodeTimestamp,\n duration,\n decodeTime: timing.decodeTime,\n presentationTime: timing.presentationTime,\n durationTicks: timing.duration,\n mediaTimescale: timing.timescale,\n authoredTiming: {\n decodeTime: timing.decodeTime,\n presentationTime: timing.presentationTime,\n durationTicks: timing.duration,\n mediaTimescale: timing.timescale,\n },\n keyframe,\n packetId,\n trackType: track?.type ?? null,\n packetSequenceNumber: seq,\n ...(assembled.packetSequenceNumbers === undefined\n ? {}\n : { packetSequenceNumbers: assembled.packetSequenceNumbers }),\n ...(assembled.repairedPacketSequenceNumbers === undefined\n ? {}\n : { repairedPacketSequenceNumbers: assembled.repairedPacketSequenceNumbers }),\n ...(assembled.catalogTrackName === undefined\n ? {}\n : { catalogTrackName: assembled.catalogTrackName }),\n ...(assembled.mmtpTimestamp === undefined\n ? {}\n : { mmtpTimestamp: assembled.mmtpTimestamp }),\n mpuSequenceNumber: assembled.mpuSequenceNumber,\n ...(assembled.movieFragmentSequenceNumber === undefined\n ? {}\n : { movieFragmentSequenceNumber: assembled.movieFragmentSequenceNumber }),\n ...(assembled.sampleNumber === undefined ? {} : { sampleNumber: assembled.sampleNumber }),\n fragmentCount: assembled.fragmentCount,\n rapFlag,\n ...(priority === undefined ? {} : { priority }),\n ...(assembled.source === undefined ? {} : { source: assembled.source }),\n deliveryProvenance: assembled.deliveryProvenance,\n }\n if (recovered || (assembled.repairedPacketSequenceNumbers?.length ?? 0) > 0) {\n meta.recovered = true\n }\n\n return meta\n }\n\n #sequencerFor(\n packetId: number,\n _trackType: FrameMeta['trackType'],\n catalogTrackName?: string,\n ): FrameSequencer | null {\n const windowSize = this.#reassemblyPolicy\n ? mmtpFrameReorderWindowFrames(this.#reassemblyPolicy, this.#reorderWindow)\n : this.#reorderWindow\n if (windowSize <= 0) return null\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n let sequencer = this.#sequencers.get(streamKey)?.sequencer\n if (!sequencer) {\n sequencer = new FrameSequencer(windowSize, (data, meta) => {\n this.#emitFrame(data, meta)\n }, (stat, count = 1) => {\n this.#reorderStats[stat] += count\n })\n this.#sequencers.set(streamKey, { packetId, sequencer })\n }\n return sequencer\n }\n\n #flushSequencers(): void {\n for (const { sequencer } of this.#sequencers.values()) sequencer.flush()\n this.#sequencers.clear()\n }\n\n /** Authored presentation timestamp of the last emitted frame, in microseconds. */\n lastFrameTimestamp: number | undefined = undefined\n\n #emitFrame(data: Uint8Array, meta: FrameMeta): void {\n this.lastFrameTimestamp = meta.timestamp\n const trackCb = this.#trackFrameCbs.get(meta.packetId)\n trackCb?.(data, meta)\n this.#frameCb?.(data, meta)\n }\n\n // \u2500\u2500 Static utilities \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n static isKeyframe(data: Uint8Array, rapFlag: boolean, codec = ''): boolean {\n const payloadKeyframe = MmtpContainerParser.#scanForKeyframe(data, codec)\n if (payloadKeyframe !== undefined) return payloadKeyframe\n return rapFlag\n }\n\n static #scanForKeyframe(data: Uint8Array, codec: string): boolean | undefined {\n const c = codec.toLowerCase()\n if (data.length === 0) return undefined\n\n if (c.startsWith('av01')) {\n return scanAv1ObusForKeyframe(data)\n }\n\n if (c.startsWith('vp09') || c.startsWith('vp8')) {\n return (data[0]! & 0x04) === 0\n }\n\n const isHevc = c.startsWith('hev1') || c.startsWith('hvc1')\n if (isHevc) {\n if (data.length < 5) return undefined\n if (startsWithAnnexB(data)) {\n return MmtpContainerParser.#scanAnnexBForKeyframe(data, true)\n }\n return MmtpContainerParser.#scanLengthPrefixedHevcForKeyframe(data)\n }\n\n if (c === '' || c.startsWith('avc')) {\n return classifyH264Keyframe(data, { allowPartialAvcc: true })\n }\n\n return undefined\n }\n\n static #scanLengthPrefixedHevcForKeyframe(data: Uint8Array): boolean | undefined {\n let sawNal = false\n let offset = 0\n while (offset + 4 < data.length) {\n const nalLen =\n (data[offset]! << 24) |\n (data[offset + 1]! << 16) |\n (data[offset + 2]! << 8) |\n data[offset + 3]!\n offset += 4\n if (nalLen <= 0) return sawNal ? false : undefined\n // The router's sync probe may see only the first fragment of a large\n // length-prefixed NAL. The length can exceed this packet, but the NAL\n // header is still enough to classify IRAP (H.265 Table 7-1, 16..=23)\n // and open the sync gate.\n if (offset >= data.length) return sawNal ? false : undefined\n sawNal = true\n const hevcType = (data[offset]! >> 1) & 0x3f\n if (isHevcIrapNalUnitType(hevcType)) return true\n if (offset + nalLen > data.length) break\n offset += nalLen\n }\n return sawNal ? false : undefined\n }\n\n static #scanAnnexBForKeyframe(data: Uint8Array, isHevc: boolean): boolean | undefined {\n let offset = 0\n let sawNal = false\n while (offset < data.length) {\n const start = findAnnexBStart(data, offset)\n if (!start) return sawNal ? false : undefined\n const headerOffset = start.offset + start.length\n if (headerOffset >= data.length) return sawNal ? false : undefined\n sawNal = true\n if (isHevc) {\n if (headerOffset + 1 >= data.length) return sawNal ? false : undefined\n const hevcType = (data[headerOffset]! >> 1) & 0x3f\n if (isHevcIrapNalUnitType(hevcType)) return true\n } else {\n const nalType = data[headerOffset]! & 0x1f\n if (nalType === 5) return true\n }\n offset = headerOffset + 1\n }\n return sawNal ? false : undefined\n }\n}\n\nfunction scanAv1ObusForKeyframe(data: Uint8Array): boolean | undefined {\n let offset = 0\n let sawSequenceHeader = false\n let sawFrame = false\n\n while (offset < data.length) {\n const obu = readAv1Obu(data, offset)\n if (!obu) return sawFrame ? false : undefined\n offset = obu.nextOffset\n\n if (obu.type === 1) {\n sawSequenceHeader = true\n continue\n }\n if (obu.type !== 3 && obu.type !== 6) continue\n\n sawFrame = true\n if (!sawSequenceHeader) {\n continue\n }\n\n const keyframe = readAv1FrameHeaderKeyframe(data.subarray(obu.payloadOffset, obu.payloadEnd))\n if (keyframe !== undefined) return keyframe\n }\n\n return sawFrame ? false : undefined\n}\n\nfunction readAv1Obu(\n data: Uint8Array,\n offset: number,\n): { type: number; payloadOffset: number; payloadEnd: number; nextOffset: number } | null {\n if (offset >= data.length) return null\n const header = data[offset]!\n if ((header & 0x80) !== 0 || (header & 0x01) !== 0) return null\n\n const type = (header >> 3) & 0x0f\n const extensionFlag = (header & 0x04) !== 0\n const hasSizeField = (header & 0x02) !== 0\n let cursor = offset + 1\n if (extensionFlag) cursor += 1\n if (cursor > data.length) return null\n\n let payloadSize = data.length - cursor\n if (hasSizeField) {\n const leb = readLeb128(data, cursor)\n if (!leb) return null\n payloadSize = leb.value\n cursor = leb.nextOffset\n }\n\n const payloadEnd = cursor + payloadSize\n if (payloadEnd > data.length) return null\n return { type, payloadOffset: cursor, payloadEnd, nextOffset: payloadEnd }\n}\n\nfunction readLeb128(data: Uint8Array, offset: number): { value: number; nextOffset: number } | null {\n let value = 0\n for (let i = 0; i < 8; i++) {\n if (offset + i >= data.length) return null\n const byte = data[offset + i]!\n value += (byte & 0x7f) * (2 ** (i * 7))\n if ((byte & 0x80) === 0) return { value, nextOffset: offset + i + 1 }\n }\n return null\n}\n\nfunction readAv1FrameHeaderKeyframe(payload: Uint8Array): boolean | undefined {\n if (payload.length === 0) return undefined\n const first = payload[0]!\n const showExistingFrame = (first & 0x80) !== 0\n if (showExistingFrame) return false\n const frameType = (first >> 5) & 0x03\n return frameType === 0\n}\n\nfunction startsWithAnnexB(data: Uint8Array): boolean {\n return (\n (data.length >= 4 && data[0] === 0 && data[1] === 0 && data[2] === 1) ||\n (data.length >= 5 && data[0] === 0 && data[1] === 0 && data[2] === 0 && data[3] === 1)\n )\n}\n\nfunction findAnnexBStart(data: Uint8Array, offset: number): { offset: number; length: 3 | 4 } | null {\n for (let i = offset; i + 3 <= data.length; i++) {\n if (data[i] !== 0 || data[i + 1] !== 0) continue\n if (data[i + 2] === 1) return { offset: i, length: 3 }\n if (i + 3 < data.length && data[i + 2] === 0 && data[i + 3] === 1) {\n return { offset: i, length: 4 }\n }\n }\n return null\n}\n", "/**\n * MmtpRouter's per-stream duplicate ledgers, retained for the stream's FEC\n * horizon (BLO-36599).\n *\n * The router remembers three kinds of wire number per physical stream, so a\n * second copy of a packet is not parsed or delivered twice:\n *\n * - packet_sequence_numbers received on the direct source path, which a\n * recovered FEC block must not deliver again;\n * - packet_sequence_numbers already handed to the track packet callback;\n * - Source FEC Payload IDs (SS_ID) already parsed for playback, which dedup an\n * overlap copy against its multicast twin.\n *\n * Each of these is useful for as long as FECManager can still re-deliver the\n * number's packet, which ends when its FEC block goes terminal; the ledger\n * keeps one further block lifetime of margin. A block's symbols all arrive\n * within its span, the catalog's `interleaveDepthMs` (draft-ramadan-moq-fec\n * \u00A75.1). After its last symbol FECManager makes the block terminal within one\n * block lifetime (the inactivity fail bound plus the D-28 repair grace, the\n * value `blockLifetimeMsForPacket()` returns). FECManager keeps the terminal\n * record longer than that (`terminalRetentionMsForPacket()`, for the in-order\n * release gate), and that record itself counts late symbols as duplicates, so\n * the ledger need not match it. Measured from a number's first arrival that is\n *\n * retention = interleaveDepthMs + blockLifetime + blockLifetime\n *\n * The bound is a time, not a count, on purpose. A count would need the\n * stream's symbol rate, and the catalog does not give one: the producer closes\n * a block after D media units or after K source symbols, whichever comes\n * first, so one span can hold several blocks (every large keyframe does) and K\n * per span is not an upper bound on the rate. The fixed 2000-entry window this\n * replaces held about 4 s of the 467 packet/s AMT stream and about 40 s of a\n * 47 packet/s audio stream; this one holds each stream for its own horizon.\n *\n * The clock is read only when a stream moves to a new FEC block (or for a\n * packet without an FEC coordinate), never per packet, and no timer is armed:\n * the ledgers rotate lazily.\n *\n * A stream whose horizon cannot be derived while FEC still runs (the FEC\n * manager reports no per-stream geometry, or the catalog gives no block span,\n * in which case FECManager falls back to its fail bound and still recovers\n * blocks) is still re-delivered packets by recovery, so it keeps the count\n * window the router kept for every stream before this change: each ledger\n * holds its newest COUNT_BOUNDED_GENERATION to 2 * COUNT_BOUNDED_GENERATION\n * numbers. Only a router without an FEC manager keeps no ledger: nothing\n * re-delivers a packet.\n */\n\n/**\n * Generation length, in numbers, of a ledger whose stream has no derivable FEC\n * horizon. This is not a new bound: it is the window every stream kept before\n * BLO-36599 (trim to the newest 2000 once past 4000), kept for exactly the\n * streams the FEC horizon cannot describe.\n */\nexport const COUNT_BOUNDED_GENERATION = 2000\n\n/**\n * RFC 1982 serial-number order on a 32-bit wire counter (MMTP\n * packet_sequence_number, SS_ID): true when `a` precedes `b`. The counters\n * wrap, so plain `<` would report every wrap as a regression.\n */\nexport function serial32Precedes(a: number, b: number): boolean {\n const distance = (b - a) >>> 0\n return distance !== 0 && distance < 0x80000000\n}\n\n/** The two FECManager values a stream's duplicate retention is derived from. */\nexport interface DuplicateHorizonGeometry {\n /** Catalog `fec.interleaveDepthMs`: the full FEC block span, in ms. */\n readonly blockSpanMs: number\n /**\n * FECManager's `blockLifetimeMsForPacket()`: the longest a block stays open\n * after its last symbol. Its terminal record is kept at least this long.\n */\n readonly blockLifetimeMs: number\n}\n\n/**\n * How long a stream's duplicate ledgers keep a number, or why no FEC horizon\n * is derivable. `'no-fec-manager'`: the router decodes no FEC, so nothing\n * re-delivers a packet and the stream keeps no ledger. `'no-fec-geometry'`:\n * the FEC manager cannot report per-stream geometry. `'no-block-span'`: the\n * stream has no catalog FEC block span (FECManager reports interleaveDepthMs\n * 0, which is also what a stream without FEC reports). FEC may still recover\n * blocks in the last two cases, so they keep count-bounded ledgers.\n */\nexport type DuplicateHorizon =\n | { readonly kind: 'fec-horizon'; readonly retentionMs: number }\n | {\n readonly kind: 'none'\n readonly reason: 'no-fec-manager' | 'no-fec-geometry' | 'no-block-span'\n }\n\nfunction assertPositiveMs(name: keyof DuplicateHorizonGeometry, value: number): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(\n `[DuplicateLedger] ${name}=${value}. Derive a positive ${name} from the ` +\n 'stream catalog through FECManager.',\n )\n }\n}\n\n/** Derive a stream's duplicate retention from its FEC geometry; see the module comment. */\nexport function duplicateRetentionMs(geometry: DuplicateHorizonGeometry): number {\n assertPositiveMs('blockSpanMs', geometry.blockSpanMs)\n assertPositiveMs('blockLifetimeMs', geometry.blockLifetimeMs)\n const untilTerminalMs = geometry.blockLifetimeMs\n const terminalRecordMs = geometry.blockLifetimeMs\n return geometry.blockSpanMs + untilTerminalMs + terminalRecordMs\n}\n\n/**\n * A set of wire numbers kept in two generations. Rotating retires the older\n * generation and starts a new one, so a number stays a member from the moment\n * it is added until the second rotation after that: at least one full\n * generation. A number already present keeps its first-arrival generation, as\n * a duplicate must be timed from the original. Additions are dropped while\n * `gate.retaining` is false. While `gate.countBounded` is true the set rotates\n * itself each time its newer generation reaches COUNT_BOUNDED_GENERATION.\n *\n * One Set holds both generations. A number is added once, so the Set's\n * insertion order is generation order and the older generation is exactly its\n * first `#olderCount` members; rotating deletes those in place. Two Sets with\n * a clear() per rotation would reallocate and regrow a Set every generation.\n */\nexport class GenerationalNumberSet {\n readonly #members = new Set<number>()\n #olderCount = 0\n #newerCount = 0\n #rotations = 0\n #floor: number | undefined = undefined\n readonly #gate: { readonly retaining: boolean; readonly countBounded: boolean }\n\n constructor(gate: { readonly retaining: boolean; readonly countBounded: boolean }) {\n this.#gate = gate\n }\n\n has(value: number): boolean {\n return this.#members.has(value)\n }\n\n add(value: number): void {\n if (!this.#gate.retaining || this.#members.has(value)) return\n this.#members.add(value)\n if (++this.#newerCount >= COUNT_BOUNDED_GENERATION && this.#gate.countBounded) this.rotate()\n }\n\n /** Retire the older generation; the current one becomes the older. */\n rotate(): void {\n let retiring = this.#olderCount\n if (retiring > 0) {\n for (const value of this.#members) {\n this.#members.delete(value)\n if (--retiring === 0) break\n }\n }\n this.#olderCount = this.#newerCount\n this.#newerCount = 0\n this.#rotations++\n // Members are only appended between rotations, so the earliest-arrived\n // survivor is fixed until the next rotation; read it once here rather than\n // allocating an iterator per lookup.\n this.#floor = undefined\n for (const value of this.#members) {\n this.#floor = value\n break\n }\n }\n\n clear(): void {\n this.#members.clear()\n this.#olderCount = 0\n this.#newerCount = 0\n this.restartAging()\n }\n\n /**\n * Keep every member but forget how many rotations have passed, so no floor\n * is reported until two more rotations. A stream that starts timing its\n * generations calls this: generations rotated by count say nothing about\n * how long ago their members arrived.\n */\n restartAging(): void {\n this.#rotations = 0\n this.#floor = undefined\n }\n\n /**\n * The earliest-arrived member, once it is known to have arrived at least one\n * full generation ago; `undefined` before that.\n *\n * A rotation is triggered by an arriving packet, which is normally the\n * first member of the new generation (a live stream adds its next packet\n * within one packet interval otherwise). From the second rotation on, the\n * older generation therefore began at a rotation, and its first member\n * arrived about one generation length before now. Before the second\n * rotation the first member may be a number that simply arrived early out of\n * order, so no floor is reported.\n */\n get horizonFloor(): number | undefined {\n if (this.#rotations < 2 || this.#olderCount === 0) return undefined\n return this.#floor\n }\n\n get size(): number {\n return this.#members.size\n }\n}\n\n/**\n * One physical stream's three duplicate ledgers and their shared generation\n * clock. `advance()` runs once per packet (and once per recovered block) before\n * any lookup, `predatesHorizon()` included. It does work only when the packet\n * belongs to a different FEC block than the previous one, or carries no FEC\n * coordinate on a stream with an FEC horizon; everything else is a Set lookup.\n */\nexport class StreamDuplicateLedgers {\n /** packet_sequence_numbers received on the direct source path. */\n readonly sourceSeqs: GenerationalNumberSet\n /** packet_sequence_numbers already handed to the track packet callback. */\n readonly routedSeqs: GenerationalNumberSet\n /** SS_IDs already parsed for playback. */\n readonly parsedSymbols: GenerationalNumberSet\n\n readonly #horizon: () => DuplicateHorizon\n readonly #now: () => number\n #retentionMs: number | null = null\n #countBounded = false\n /** Whether the horizon was read since construction or the last clear(). */\n #horizonRead = false\n #generationStartMs = 0\n #lastSbn: number | undefined = undefined\n\n /**\n * @param horizon resolves the stream's current horizon from its FEC\n * geometry. It is read when the stream moves to a new block, so a catalog\n * change applies from the next block.\n * @param now the clock FECManager's deadlines use.\n */\n constructor(horizon: () => DuplicateHorizon, now: () => number = () => performance.now()) {\n this.#horizon = horizon\n this.#now = now\n this.sourceSeqs = new GenerationalNumberSet(this)\n this.routedSeqs = new GenerationalNumberSet(this)\n this.parsedSymbols = new GenerationalNumberSet(this)\n }\n\n /**\n * Note the FEC block (SBN) of the packet about to be looked up, or\n * `undefined` for a packet without a Source FEC Payload ID. Returns whether\n * the stream keeps ledgers; while it does not, lookups find nothing and\n * additions are dropped. The horizon is read on the first packet after\n * construction or clear(), and again whenever the block changes; a packet\n * without a coordinate never re-reads it, so a stream without FEC\n * coordinates resolves its horizon once, not per packet.\n */\n advance(sbn: number | undefined): boolean {\n if (sbn !== undefined) {\n if (sbn === this.#lastSbn) return this.retaining\n this.#lastSbn = sbn\n } else if (this.#horizonRead) {\n // A packet outside every FEC block (MPU metadata, for one) does not move\n // the stream to a new block, but it still ages timed generations, so a\n // stream that stops carrying FEC coordinates cannot grow its ledgers.\n // Count-bounded ledgers age on their own.\n if (this.#retentionMs !== null) this.#rotateIfDue(this.#now())\n return this.retaining\n }\n this.#horizonRead = true\n const horizon = this.#horizon()\n if (horizon.kind === 'none') {\n if (horizon.reason === 'no-fec-manager') {\n this.#forget()\n } else {\n // FEC still runs, but its horizon is not derivable: keep what the\n // ledgers hold and bound them by count from here on.\n this.#retentionMs = null\n this.#countBounded = true\n }\n return this.retaining\n }\n const nowMs = this.#now()\n this.#countBounded = false\n if (this.#retentionMs === null) {\n this.#retentionMs = horizon.retentionMs\n this.#generationStartMs = nowMs\n this.sourceSeqs.restartAging()\n this.routedSeqs.restartAging()\n this.parsedSymbols.restartAging()\n return true\n }\n this.#retentionMs = horizon.retentionMs\n this.#rotateIfDue(nowMs)\n return true\n }\n\n /**\n * Whether a packet with these wire numbers predates every number the stream\n * has kept for at least one full FEC horizon (BLO-36599).\n *\n * Within one publisher epoch this cannot happen to a usable packet: it\n * would have been sent before a packet that arrived a whole horizon ago, so\n * no FEC block could still recover it and no reorder window could still place\n * it. A publisher that restarts resets packet_sequence_number and SS_ID,\n * and its first packets land exactly here. The evidence is in-band: two\n * wire counters compared against numbers the stream actually received, with\n * the horizon derived from the catalog. No timer is armed and no count\n * threshold is guessed.\n *\n * Limits, by construction: a stream without an FEC horizon (count-bounded\n * ledgers, or none) reports nothing, and so does a stream younger than two\n * timed generations; a restart whose new counters land inside the retained\n * range is not evidence, and its old numbers age out of the ledgers within\n * one horizon.\n */\n predatesHorizon(packetSequenceNumber: number, ssId: number | undefined): boolean {\n if (this.#retentionMs === null) return false\n const seqFloor = this.sourceSeqs.horizonFloor\n if (\n seqFloor !== undefined &&\n serial32Precedes(packetSequenceNumber, seqFloor) &&\n !this.sourceSeqs.has(packetSequenceNumber)\n ) {\n return true\n }\n if (ssId === undefined) return false\n const ssIdFloor = this.parsedSymbols.horizonFloor\n return (\n ssIdFloor !== undefined &&\n serial32Precedes(ssId, ssIdFloor) &&\n !this.parsedSymbols.has(ssId)\n )\n }\n\n /** Whether the stream keeps ledgers, for its FEC horizon or by count. */\n get retaining(): boolean {\n return this.#retentionMs !== null || this.#countBounded\n }\n\n /** Whether the ledgers are bounded by count because no FEC horizon is derivable. */\n get countBounded(): boolean {\n return this.#countBounded\n }\n\n /** Current retention in ms, or null while the stream has no FEC horizon. */\n get retentionMs(): number | null {\n return this.#retentionMs\n }\n\n /** Forget every number (a track reset); the next packet re-derives the horizon. */\n clear(): void {\n this.#forget()\n this.#lastSbn = undefined\n this.#horizonRead = false\n }\n\n #forget(): void {\n this.sourceSeqs.clear()\n this.routedSeqs.clear()\n this.parsedSymbols.clear()\n this.#retentionMs = null\n this.#countBounded = false\n }\n\n #rotateIfDue(nowMs: number): void {\n if (this.#retentionMs === null || nowMs - this.#generationStartMs < this.#retentionMs) return\n this.sourceSeqs.rotate()\n this.routedSeqs.rotate()\n this.parsedSymbols.rotate()\n this.#generationStartMs = nowMs\n }\n}\n", "/**\n * @blockcast/transport - Type Definitions\n *\n * Shared interfaces for unified transport (WebTransport, SSM, AMT, DRIAD) and FEC.\n */\n\n/** Native IP-multicast request for the window.multicast gateway. */\nexport interface WindowMulticastSubscribeConfigBytes {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n\tconsume: \"bytes\";\n\torigin?: \"ip-multicast\";\n\t/** MMTP packet IDs to forward before data enters the page transport queue. */\n\tpacketIds?: readonly number[];\n\t/** Catalog track identities corresponding to the selected packet IDs. */\n\ttracks?: ReadonlyArray<{ name: string; packetId: number }>;\n}\n\n/** AMT relay request for the window.multicast gateway. */\nexport interface WindowMulticastSubscribeConfigAmt {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n\tconsume: \"bytes\";\n\torigin: \"amt\";\n\trelay: string;\n\trelayPort: number;\n\t/** MMTP packet IDs to forward before data enters the page transport queue. */\n\tpacketIds?: readonly number[];\n}\n\n/** Canonical window.multicast config for byte subscriptions. */\nexport type WindowMulticastSubscribeConfig =\n\t| WindowMulticastSubscribeConfigBytes\n\t| WindowMulticastSubscribeConfigAmt;\n\nexport type WindowMulticastSubscriptionState = \"connecting\" | \"connected\" | \"reconnecting\" | \"closed\";\n\nexport interface WindowMulticastSubscriptionEventMap {\n\tbytes: CustomEvent<ArrayBuffer>;\n\t/**\n\t * Provider extension, not part of the frozen v1 contract (BLO-36599): every\n\t * packet the provider decoded in one dispatch batch, in arrival order. Each\n\t * element is exactly what one `bytes` event would have carried. Emitted\n\t * only by a subscription whose `eventTypes` lists `\"packets\"`; see\n\t * {@link subscriptionDeliversPacketBatches}. A consumer listens to exactly\n\t * one of `bytes` and `packets`, because both carry the same packets.\n\t */\n\tpackets: CustomEvent<readonly ArrayBuffer[]>;\n\tstate: CustomEvent<WindowMulticastSubscriptionState>;\n\terror: CustomEvent<unknown>;\n}\n\n/**\n * How a window.multicast subscription hands packets to the page.\n *\n * - `\"datagram\"`: packets are independent datagrams; a pause in arrivals may\n * mean loss.\n * - `\"ordered-relay\"`: the provider relays one receiving socket over a\n * reliable, ordered stream (for example a BCDS bridge WebTransport uni\n * stream). The relay may drop packets (a bounded queue discarding its\n * oldest on overflow) but never reorders them: what arrives is a\n * subsequence of the socket's receive order, so a pause in arrivals\n * carries no loss information.\n *\n * A provider declares `\"ordered-relay\"` only when every packet of the\n * subscription travels that one ordered stream for the life of the\n * subscription. Absent means no ordering promise, which consumers treat as\n * `\"datagram\"`.\n */\nexport type WindowMulticastDelivery = \"datagram\" | \"ordered-relay\";\n\n/** window.multicast subscription returned by the IWA gateway surface. */\nexport interface WindowMulticastSubscription {\n\treadonly id?: string;\n\treadonly state?: WindowMulticastSubscriptionState;\n\t/** How this subscription delivers packets; see WindowMulticastDelivery. */\n\treadonly delivery?: WindowMulticastDelivery;\n\t/**\n\t * Events this subscription emits, when the provider advertises them. A\n\t * provider without the field emits the v1 events only (`bytes`, `state`,\n\t * `error`).\n\t */\n\treadonly eventTypes?: readonly string[];\n\taddEventListener<K extends keyof WindowMulticastSubscriptionEventMap>(\n\t\ttype: K,\n\t\tlistener: (ev: WindowMulticastSubscriptionEventMap[K]) => void,\n\t\toptions?: AddEventListenerOptions | boolean,\n\t): void;\n\tremoveEventListener<K extends keyof WindowMulticastSubscriptionEventMap>(\n\t\ttype: K,\n\t\tlistener: (ev: WindowMulticastSubscriptionEventMap[K]) => void,\n\t\toptions?: EventListenerOptions | boolean,\n\t): void;\n\tunsubscribe: () => Promise<void> | void;\n}\n\nexport interface WindowMulticastCapabilities {\n\treadonly consumeModes?: ReadonlyArray<\"bytes\" | string>;\n}\n\n/** Lowercase window.multicast gateway surface. */\nexport interface WindowMulticastGateway {\n\tsubscribe?: (config: WindowMulticastSubscribeConfig) => Promise<WindowMulticastSubscription>;\n\tcapabilities?: WindowMulticastCapabilities | (() => Promise<WindowMulticastCapabilities>);\n}\n\ndeclare global {\n\tinterface Window {\n\t\tmulticast?: WindowMulticastGateway;\n\t\tUDPSocket?: unknown;\n\t}\n}\n\n/** Transport configuration for multicast subscriptions */\nexport interface TransportConfig {\n\t/** Multicast group address (232.x.x.x for SSM) */\n\tgroup: string;\n\t/** Source address for SSM filtering */\n\tsource: string;\n\t/** UDP port */\n\tport: number;\n\t/** Stream type */\n\tstreamType: \"video\" | \"audio\";\n\t/** Optional AMT relay host for unicast tunneling */\n\tamtRelay?: string;\n\t/** Optional AMT relay port. */\n\tamtRelayPort?: number;\n\t/** Browser gateway transport preference. */\n\tmode?: \"auto\" | \"native\" | \"tunnel\";\n\t/** DRIAD discovery enabled */\n\tdriadEnabled?: boolean;\n}\n\n/** What `TransportManager.connect()` needs beyond the subscription itself. */\nexport interface TransportConnectConfig extends TransportConfig {\n\t/**\n\t * The stream's renditions the ABR controller adapts over, e.g. its catalog\n\t * video tracks. Required: there is no built-in ladder. `connect()` checks\n\t * it (`validateQualityLadder`) and `initialQuality` before tearing anything\n\t * down; a config that fails is refused through the error callback and\n\t * `connect()` resolves `false`, leaving a live subscription untouched.\n\t * `catalogQualityLadder(catalog)` builds it from a catalog.\n\t */\n\tabrLadder: readonly QualityLevel[];\n\t/**\n\t * Ladder rendition to start on, matched by `name` against `abrLadder`; the\n\t * lowest-bitrate rung when omitted. A name `abrLadder` does not carry is\n\t * not a fallback to that rung: `TransportManager.connect()` refuses the\n\t * config (error callback, resolves `false`) before tearing anything down.\n\t */\n\tinitialQuality?: QualityLevel;\n}\n\nexport interface QualityLevel {\n\tname: string;\n\t/** Bits per second: what the ABR controller ranks and costs a rendition by. */\n\tbitrate: number;\n\t/**\n\t * Dimensions and frame rate as the stream's catalog states them; absent\n\t * when it does not. ABR never reads them, so none is defaulted.\n\t */\n\twidth?: number;\n\theight?: number;\n\tfps?: number;\n}\n\nexport interface TransportCapabilities {\n\tnativeSSM: boolean;\n\tnativeIGMP: boolean;\n\tdirectSockets: boolean;\n\tamt: boolean;\n\tdriad: boolean;\n\tfecDecode: boolean;\n}\n\nexport type TransportMode = \"multicast\" | \"amt\" | \"moq\" | \"disconnected\";\n\nexport interface TransportState {\n\tmode: TransportMode;\n\tconnected: boolean;\n\tgroup?: string;\n\tsource?: string;\n\tamtRelay?: string;\n\tquality?: QualityLevel;\n\tpacketsReceived: number;\n\tbytesReceived: number;\n\tlastPacketTime: number;\n}\n\n/** Transport subscription handle */\nexport interface TransportSubscription {\n\tid: string;\n\tconfig: TransportConfig;\n\tonpacket?: (data: ArrayBuffer) => void;\n\tonerror?: (error: Error) => void;\n\tonqualitychange?: (quality: QualityLevel) => void;\n\tclose: () => void;\n}\n\n/** DRIAD Relay information (RFC 8777) */\nexport interface DRIADRelay {\n\thost: string;\n\tport: number;\n\tpriority: number;\n\tweight: number;\n\tasn: number;\n\trtt?: number;\n}\n\n/** AMT Relay information */\nexport interface AmtRelayInfo {\n\taddress: string;\n\tport: number;\n\tdiscoveryMethod: \"driad\" | \"manual\";\n\tdiscoveredAt: number;\n}\n\n/** Group key for multicast subscriptions */\nexport interface GroupKey {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n}\n\n/** Packet metadata */\nexport interface PacketMetadata {\n\tsourceAddress: string;\n\tsourcePort: number;\n\ttransport: \"native\" | \"tunnel\" | \"websocket\";\n\ttimestamp: number;\n}\n\n/** Time types (microseconds) */\nexport type Micro = number & { readonly __micro: unique symbol };\n\n/** Sync decision for A/V synchronization */\nexport type SyncDecision = \"render\" | \"hold\" | \"discard\";\n\n// ============================================================\n// Multicast Bridge Types (consolidated from @blockcast/multicast)\n// ============================================================\n\n/** Transport mode for multicast subscription (auto-selection) */\nexport type MulticastTransportMode = \"auto\" | \"native\" | \"tunnel\";\n\n/** Multicast subscription configuration */\nexport interface MulticastConfig {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n\tmode?: MulticastTransportMode;\n\tonPacket: (data: ArrayBuffer, source: PacketSource) => void;\n\tonStateChange?: (state: SubscriptionState) => void;\n\tonError?: (error: Error) => void;\n\tamtRelay?: string;\n\tamtRelayPort?: number;\n\tnativeFallbackTimeout?: number;\n}\n\n/** Packet source information */\nexport interface PacketSource {\n\taddress: string;\n\tport: number;\n\ttransport: \"native\" | \"tunnel\";\n\trecovered?: boolean;\n}\n\n/** Subscription state */\nexport type SubscriptionState = \"initializing\" | \"joining\" | \"active\" | \"suspended\" | \"closed\" | \"error\";\n\n/** Subscription statistics */\nexport interface SubscriptionStats {\n\tpacketsReceived: number;\n\tbytesReceived: number;\n\tpacketsLost: number;\n\tpacketsRecovered: number;\n\tbitrate: number;\n\tuptime: number;\n}\n\n/** Multicast subscription handle */\nexport interface MulticastSubscriptionHandle {\n\treadonly state: SubscriptionState;\n\treadonly group: string;\n\treadonly source?: string;\n\treadonly port: number;\n\treadonly transport: \"native\" | \"tunnel\";\n\treadonly stats: SubscriptionStats;\n\tclose(): Promise<void>;\n}\n\n/** Main Multicast Bridge API */\nexport interface MulticastBridge {\n\tsubscribe(config: MulticastConfig): Promise<MulticastSubscriptionHandle>;\n\tgetSubscriptions(): Promise<MulticastSubscriptionHandle[]>;\n\tisNativeSupported(): Promise<boolean>;\n}\n\n// ============================================================\n// FEC Constants and Utilities per draft-ramadan-moq-fec-00\n// ============================================================\n\n/**\n * Build a conventional repair track path by appending \"repair\" to a source\n * track path.\n *\n * Track names carry no protocol meaning. A repair track's layer, its source\n * and its symbol budget are resolved from the catalog via `resolveRepairLayers`\n * in `@blockcast/mmt-container`, never from a name or a name suffix. This\n * helper is a naming convenience for producers only.\n *\n * @deprecated Naming convenience only. Never classify a track or resolve a\n * repair layer from its path; use `resolveRepairLayers` from\n * `@blockcast/mmt-container`.\n *\n * @param sourceTrack - The source track path (e.g., \"live/video\")\n * @returns The repair track path (e.g., \"live/video/repair\")\n *\n * @example\n * repairTrackPath(\"live/video\") // returns \"live/video/repair\"\n * repairTrackPath(\"broadcast/video/1080p\") // returns \"broadcast/video/1080p/repair\"\n */\nexport function repairTrackPath(sourceTrack: string): string {\n\tif (!sourceTrack || sourceTrack.length === 0) {\n\t\treturn \"repair\";\n\t}\n\treturn `${sourceTrack}/repair`;\n}\n\n/**\n * MoQ FEC Configuration per draft-ramadan-moq-fec-00\n *\n * These parameters are signalled in each track's catalog `fec` block;\n * no FEC_CONFIG control message is sent or parsed.\n */\nexport interface MoqFecConfig {\n\t/** Subscribe ID this config applies to */\n\tsubscribeId: number;\n\t/** Whether FEC is enabled */\n\tenabled: boolean;\n\t/** FEC algorithm (0=none, 1=XOR, 2=RaptorQ) */\n\talgorithm: MoqFecAlgorithm;\n\t/** Number of source symbols per FEC block (K) */\n\tsourceSymbolsPerBlock: number;\n\t/** Number of repair symbols per FEC block (P) */\n\trepairSymbolsPerBlock: number;\n\t/** Interleave depth for burst loss protection */\n\tinterleaveDepth: number;\n\t/** Symbol size in bytes */\n\tsymbolSize: number;\n\t/** RFC 6330 Object Transmission Information (12 bytes) - only for RaptorQ */\n\toti?: Uint8Array;\n}\n\n/**\n * FEC algorithm identifier per draft-ramadan-moq-fec-00\n */\nexport enum MoqFecAlgorithm {\n\t/** No FEC (disabled) */\n\tNone = 0,\n\t/** Simple XOR parity */\n\tXor = 1,\n\t/** RaptorQ (RFC 6330) - recommended for burst loss */\n\tRaptorQ = 2,\n}\n\n/**\n * Repair object header per draft-ramadan-moq-fec-00 Section 6.1\n *\n * Each object on a repair track contains repair symbols for one source block.\n * The object payload begins with this header, followed by concatenated repair symbols.\n */\nexport interface RepairObjectHeader {\n\t/** Source block number being protected (matches group_id of source objects) */\n\tsourceBlockNumber: number;\n\t/** First Repair Symbol ESI (Encoding Symbol ID) - for RaptorQ, repair ESIs >= K */\n\tfirstRepairEsi: number;\n\t/** Number of repair symbols in this object */\n\tnumRepairSymbols: number;\n}\n", "/**\n * Zero-dependency helper for extracting the detached ALTA authenticator trailer\n * from an MMTP source packet.\n *\n * Lives in its own leaf package so both `@blockcast/mmt-container` (wire-path\n * parsing) and `@blockcast/fec-worker` (FEC-recovered-source parsing) can share\n * one implementation without creating a package cycle between them.\n *\n * Spec: ISO/IEC 23008-1:2023 + draft-ramadan-moq-mmt \u00A712 (detached trailer, Path 2).\n */\n\n// --- Private MMTP/MPU/MFU constants (ISO/IEC 23008-1 \u00A79.2.2\u20139.2.3) ---\n// Duplicated here as immutable spec values so this package stays a leaf with\n// zero `@blockcast/*` dependencies. Re-deriving from `mmt-container` would\n// reintroduce the package cycle this split exists to avoid.\n\nconst MMTP_HEADER_SIZE = 12\nconst MPU_HEADER_SIZE = 8\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst TIMED_MFU_DU_HEADER_SIZE = 14\nconst NONTIMED_MFU_DU_HEADER_SIZE = 4\nconst PACKET_TYPE_MPU = 0x00\n\n// --- Public ALTA trailer constants ---\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\nexport const MMTP_EXT_TYPE_ALTA_AUTH = 0xf0a1\n\n/** Fixed size of the trailer's `[ext_type][ext_length]` prefix (2 + 2 BE). */\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = 4\n\n/** Result of extracting the detached ALTA authenticator from an MMTP packet. */\nexport interface AltaTrailer {\n /** Authenticator bytes (no ext_type/ext_length prefix, no trailing padding). */\n auth: Uint8Array\n /**\n * Wire-packet byte offset where the signed payload ends, i.e. where the\n * trailer begins. Caller uses this to produce\n * `payload = wirePacket.subarray(0, signedEnd)` for verify_detached.\n */\n signedEnd: number\n}\n\n/**\n * Extract the detached ALTA authenticator trailer from an MMTP source packet.\n *\n * Wire layout:\n * `[MMTP hdr][length(2)][remaining MPU hdr(6)][DU?][media][TRAILER][SS_ID?]`\n * `payload_length = 6 + DU bytes + media bytes`\n *\n * Returns `null` when:\n * - the packet is too small / malformed,\n * - `payload_length` cannot contain the remaining six MPU-header bytes,\n * - the declared payload overruns the packet,\n * - no bytes remain between `payload_length` and the SS_ID,\n * - the trailer's `ext_type` isn't {@link MMTP_EXT_TYPE_ALTA_AUTH},\n * - the declared `ext_length` would extend past the SS_ID boundary.\n *\n * Non-MPU packets (repair, signaling) always return `null`.\n *\n * @param options.skipFecId When true, do NOT reserve 4 trailing bytes for the\n * Source FEC Payload ID. Use on FEC-recovered symbols where the PID has\n * already been stripped upstream.\n */\nexport function extractAltaTrailer(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): AltaTrailer | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n // ISO/IEC 23008-1:2023 Figure 8 places V=0 packet_type in the low six bits.\n const packetType = mmtpPacket[1]! & 0x3f\n if (packetType !== PACKET_TYPE_MPU) return null\n\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n let headerLength = MMTP_HEADER_SIZE\n if (fecType < 2 && (mmtpPacket[0]! & 0x20) !== 0) headerLength += 4\n if (fecType < 2 && (mmtpPacket[0]! & 0x02) !== 0) {\n if (headerLength + 4 > mmtpPacket.length) return null\n const extensionLength = new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + headerLength + 2,\n 2,\n ).getUint16(0, false)\n headerLength += 4 + extensionLength\n }\n if (headerLength + MPU_HEADER_SIZE > mmtpPacket.length) return null\n\n const payload = mmtpPacket.subarray(headerLength)\n\n // Inline a minimal MPU-header read \u2014 only the fields extractAltaTrailer needs.\n // payloadLength: 16 BE at bytes 0-1.\n // Byte 2 packs FT(4) | T(1) | FI(2) | A(1).\n const payloadLength = (payload[0]! << 8) | payload[1]!\n const flagsByte = payload[2]!\n const fragmentType = (flagsByte >> 4) & 0x0f\n const timed = ((flagsByte >> 3) & 0x01) === 1\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n\n // Only MFU packets (FT=2) carry the detached trailer.\n if (fragmentType !== 2) return null\n\n // FI=0/1 structurally carries a DU header; FI=2/3 does not. Require at\n // least one media byte after that deterministic layout so the bytes at the\n // declared boundary cannot impersonate ALTA for a header-only fragment.\n const dataUnitHeaderSize = fragmentationIndicator <= 1\n ? (timed ? TIMED_MFU_DU_HEADER_SIZE : NONTIMED_MFU_DU_HEADER_SIZE)\n : 0\n if (payloadLength <= MPU_HEADER_REMAINDER_SIZE + dataUnitHeaderSize) return null\n\n if (fecType > 1) return null\n const hasFecId = !options?.skipFecId && fecType === 1\n const payloadEnd = hasFecId ? payload.length - 4 : payload.length\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (declaredEnd > payloadEnd) return null\n\n if (declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE > payloadEnd) return null\n const extType = (payload[declaredEnd]! << 8) | payload[declaredEnd + 1]!\n if (extType !== MMTP_EXT_TYPE_ALTA_AUTH) return null\n const extLen = (payload[declaredEnd + 2]! << 8) | payload[declaredEnd + 3]!\n\n const authStart = declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE\n if (authStart + extLen !== payloadEnd) return null\n\n return {\n auth: payload.subarray(authStart, payloadEnd),\n signedEnd: headerLength + declaredEnd,\n }\n}\n", "import {\n MMTP_EXT_TYPE_ALTA_AUTH as _LEAF_MMTP_EXT_TYPE_ALTA_AUTH,\n MMTP_EXT_TRAILER_HEADER_SIZE as _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE,\n extractAltaTrailer as _leafExtractAltaTrailer,\n type AltaTrailer as _LeafAltaTrailer,\n} from '@blockcast/mmt-alta-parse'\n\n/**\n * @mmt/container - MMTP Parser\n *\n * MMTP (MMT Protocol) packet parser based on ISO/IEC 23008-1:2023 Section 9.2\n *\n * MMTP header format (12 bytes):\n * - Byte 0: V(2) | C(1) | FEC_type(2) | r(1) | X(1) | R(1)\n * - Byte 1: reserved(2) | packet_type(6)\n * - Bytes 2-3: packet_id (big-endian)\n * - Bytes 4-7: timestamp (big-endian, 32-bit)\n * - Bytes 8-11: sequence_number (big-endian)\n *\n * FEC_type values:\n * - 0: No FEC / source packet\n * - 1: FEC mode 0 (source with FEC payload ID)\n * - 2: FEC repair packet mode 0\n * - 3: FEC repair packet mode 1\n *\n * @packageDocumentation\n */\n\nexport interface MMTPHeader {\n version: number\n /** Packet type: 0x00=MPU, 0x02=Signaling, 0x03=Repair */\n packetType: number\n packetId: number\n timestamp: bigint\n sequenceNumber: number\n payloadLength: number\n fecType: number\n /** C flag: header carries a 32-bit packet_counter. */\n packetCounterFlag: boolean\n packetCounter?: number\n /** X flag: header carries an extension header and value. */\n extensionFlag: boolean\n extension?: { type: number; data: Uint8Array }\n /** Byte offset at which the MMTP payload begins. */\n headerLength: number\n /** Random Access Point flag - indicates keyframe (ISO 23008-1, byte 0 bit 0) */\n rapFlag: boolean\n}\n\nexport interface MPUHeader {\n mpuSequenceNumber: number\n fragmentationIndicator: number // FI: 0=complete, 1=first, 2=middle, 3=last\n /** 8-bit MPU fragment_counter from byte 3. */\n fragmentCounter: number\n /** T flag: 1=timed media (14-byte MFU DU header), 0=non-timed (4-byte header) */\n timedFlag: number\n /** A flag: aggregated MPU payload. This strict profile requires A=0. */\n aggregationFlag: boolean\n /** MPU payload_length (bytes 0-1): bytes after the length field, including\n * the remaining six MPU-header bytes and all data-unit bytes. */\n payloadLength: number\n}\n\nexport interface MFUDuHeader {\n movieFragmentSequenceNumber: number\n sampleNumber: number\n offset: number\n priority: number\n dependencyCounter: number\n}\n\nexport interface MMTPPacket {\n header: MMTPHeader\n payload: Uint8Array\n}\n\nexport const MMTP_HEADER_SIZE = 12\nexport const MPU_HEADER_SIZE = 8\nexport const MFU_DU_HEADER_SIZE_TIMED = 14 // T=1: timed media (video/audio samples)\nexport const MFU_DU_HEADER_SIZE_NONTIMED = 4 // T=0: non-timed media (item_id only)\n\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst SOURCE_FEC_PAYLOAD_ID_SIZE = 4\n\n// MMTP Packet Types (ISO/IEC 23008-1:2023 Section 9.2.2)\nexport const PACKET_TYPE_MPU = 0x00\nexport const PACKET_TYPE_SIGNALING = 0x02\nexport const PACKET_TYPE_REPAIR = 0x03\n\n// AL-FEC Signaling Message ID (ISO/IEC 23008-1:2023 Amendment 1:2025)\nexport const ALFEC_MESSAGE_ID = 0x0203\n\n/**\n * MMTP `packet_id` of the signaling flow, which carries PACKET_TYPE_SIGNALING\n * packets such as the AL-FEC message (ALFEC_MESSAGE_ID). The FFmpeg `moq_mmt`\n * muxer sends that message on this packet_id into the same multicast group as\n * the media, and moqcast-draft (PR #44) makes advertising it for a track a\n * MUST NOT. A track's packet_id is therefore 1..MMTP_PACKET_ID_MAX.\n */\nexport const MMTP_SIGNALING_PACKET_ID = 0\n\n/** Largest value of the 16-bit MMTP `packet_id` header field. */\nexport const MMTP_PACKET_ID_MAX = 0xffff\n\n// FEC Code IDs\nexport const FEC_CODE_XOR = 1\nexport const FEC_CODE_RAPTORQ = 2\n\n/**\n * Parse MMTP header from buffer\n */\nexport function parseMMTPHeader(buffer: Uint8Array): MMTPHeader {\n if (buffer.length < MMTP_HEADER_SIZE) {\n throw new Error(`MMTP packet too small: ${buffer.length} bytes`)\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n\n const version = (buffer[0]! >> 6) & 0x03\n if (version !== 0) {\n throw new Error(`Unsupported MMTP version: ${version}`)\n }\n\n // Extract FEC type from byte 0, bits 3-4\n const fecType = (buffer[0]! >> 3) & 0x03\n const packetCounterFlag = (buffer[0]! & 0x20) !== 0\n const extensionFlag = (buffer[0]! & 0x02) !== 0\n\n // Extract RAP flag from byte 0, bit 0 (R bit - Random Access Point)\n const rapFlag = (buffer[0]! & 0x01) === 1\n\n // ISO/IEC 23008-1:2023 Figure 8 (V=0) places reserved(2) before\n // packet_type(6), so packet_type occupies the low six bits of byte 1.\n const packetType = buffer[1]! & 0x3F\n\n let offset = 2\n\n // Packet ID (2 bytes, big-endian)\n const packetId = view.getUint16(offset, false)\n offset += 2\n\n // Timestamp (4 bytes, big-endian)\n const timestamp = BigInt(view.getUint32(offset, false))\n offset += 4\n\n // Sequence number (4 bytes, big-endian)\n const sequenceNumber = view.getUint32(offset, false)\n offset += 4\n\n let packetCounter: number | undefined\n if (packetCounterFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in packet_counter')\n packetCounter = view.getUint32(offset, false)\n offset += 4\n }\n\n let extension: { type: number; data: Uint8Array } | undefined\n if (extensionFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in header extension')\n const type = view.getUint16(offset, false)\n const length = view.getUint16(offset + 2, false)\n offset += 4\n if (offset + length > buffer.length) throw new Error('MMTP packet truncated in header extension value')\n extension = { type, data: buffer.subarray(offset, offset + length) }\n offset += length\n }\n\n return {\n version,\n packetType,\n packetId,\n timestamp,\n sequenceNumber,\n payloadLength: buffer.length - offset,\n fecType,\n packetCounterFlag,\n packetCounter,\n extensionFlag,\n extension,\n headerLength: offset,\n rapFlag,\n }\n}\n\n/**\n * The MMTP `packet_id` of `buffer`, or null exactly when parseMMTPHeader would\n * throw for it (short buffer, V != 0, truncated packet_counter or header\n * extension).\n *\n * This is the per-packet filter a receiver runs before any other work, so it\n * builds no header object, DataView or BigInt timestamp (BLO-36599). Use\n * parseMMTPHeader when any other header field is needed.\n */\nexport function readMMTPPacketId(buffer: Uint8Array): number | null {\n if (buffer.length < MMTP_HEADER_SIZE) return null\n const flags = buffer[0]!\n if (((flags >> 6) & 0x03) !== 0) return null\n let offset = MMTP_HEADER_SIZE\n if ((flags & 0x20) !== 0) {\n if (offset + 4 > buffer.length) return null\n offset += 4\n }\n if ((flags & 0x02) !== 0) {\n if (offset + 4 > buffer.length) return null\n const length = (buffer[offset + 2]! << 8) | buffer[offset + 3]!\n if (offset + 4 + length > buffer.length) return null\n }\n return (buffer[2]! << 8) | buffer[3]!\n}\n\n/**\n * Unwrap MMTP packet to extract payload\n */\nexport function unwrapMMTP(buffer: Uint8Array): MMTPPacket {\n const header = parseMMTPHeader(buffer)\n const payload = buffer.subarray(header.headerLength)\n\n return { header, payload }\n}\n\n/**\n * Parse MPU header from payload\n * ISO/IEC 23008-1:2023 Section 9.2.3.3\n *\n * MPU Header format (8 bytes) as written by FFmpeg/libmmt:\n * - Bytes 0-1: payload_length (16 bits, big-endian)\n * - Byte 2: FT(4) | T(1) | FI(2) | A(1)\n * - Byte 3: fragment_counter (8 bits)\n * - Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n */\nexport function parseMPUHeader(payload: Uint8Array): MPUHeader {\n if (payload.length < MPU_HEADER_SIZE) {\n throw new Error(`Payload too small for MPU header: ${payload.length} bytes`)\n }\n\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)\n\n // Bytes 0-1: payload_length (16 bits, big-endian)\n const payloadLength = view.getUint16(0, false)\n\n // Byte 2: FT(4) | T(1) | FI(2) | A(1)\n const flagsByte = payload[2]!\n const timedFlag = (flagsByte >> 3) & 0x01\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n const aggregationFlag = (flagsByte & 0x01) !== 0\n\n // Byte 3: fragment_counter (8 bits). The high bit is part of the counter in\n // the current libmmt/FFmpeg writer ABI.\n const fragmentCounter = payload[3]!\n\n // Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n const mpuSequenceNumber = view.getUint32(4, false)\n\n return {\n mpuSequenceNumber,\n fragmentationIndicator,\n fragmentCounter,\n timedFlag,\n aggregationFlag,\n payloadLength,\n }\n}\n\nexport function parseTimedMfuDuHeader(payload: Uint8Array, offset: number): MFUDuHeader | null {\n if (offset < 0 || offset + MFU_DU_HEADER_SIZE_TIMED > payload.length) return null\n const view = new DataView(payload.buffer, payload.byteOffset + offset, MFU_DU_HEADER_SIZE_TIMED)\n return {\n movieFragmentSequenceNumber: view.getUint32(0, false),\n sampleNumber: view.getUint32(4, false),\n offset: view.getUint32(8, false),\n priority: payload[offset + 12]!,\n dependencyCounter: payload[offset + 13]!,\n }\n}\n\n/**\n * Why `extractMFUPayload` refused a packet.\n *\n * These identifiers are part of the public contract: callers surface them in\n * diagnostics, so renaming one is a breaking change.\n */\nexport type MFURejectionGate =\n | 'packet-shorter-than-mmtp-plus-mpu-header'\n | 'mmtp-header-unparseable'\n | 'not-an-mpu-packet'\n | 'payload-shorter-than-mpu-header'\n | 'aggregation-flag-set'\n | 'mpu-payload-length-below-header-remainder'\n | 'mpu-declared-end-exceeds-payload'\n | 'mfu-offset-at-or-past-declared-end'\n | 'source-fec-payload-id-width-mismatch'\n | 'declared-trailers-unresolved'\n\nexport interface MFURejection {\n gate: MFURejectionGate\n /** Human-readable detail, including the byte arithmetic that decided it. */\n detail: string\n}\n\ninterface MfuPayloadLayout {\n hasMfuDuHeader: boolean\n mfuOffset: number\n dataEnd: number\n}\n\nfunction resolveMfuPayloadLayout(\n mpu: MPUHeader,\n fragmentType: number,\n payload: Uint8Array,\n): { layout: MfuPayloadLayout } | { rejection: MFURejection } {\n if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE) {\n return {\n rejection: {\n gate: 'mpu-payload-length-below-header-remainder',\n detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE}`,\n },\n }\n }\n\n // ISO/IEC 23008-1 defines payload_length from immediately after its own\n // two-byte field. It therefore includes the remaining six MPU-header bytes.\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength\n if (declaredEnd > payload.length) {\n return {\n rejection: {\n gate: 'mpu-declared-end-exceeds-payload',\n detail:\n `payload_length=${mpu.payloadLength} implies end=${declaredEnd}, ` +\n `payload is ${payload.length} bytes`,\n },\n }\n }\n\n const hasHeader = fragmentType === 2 && mpu.fragmentationIndicator <= 1\n const mfuHeaderSize = mpu.timedFlag ? MFU_DU_HEADER_SIZE_TIMED : MFU_DU_HEADER_SIZE_NONTIMED\n const mfuOffset = MPU_HEADER_SIZE + (hasHeader ? mfuHeaderSize : 0)\n if (mfuOffset >= declaredEnd) {\n return {\n rejection: {\n gate: 'mfu-offset-at-or-past-declared-end',\n detail: `mfuOffset=${mfuOffset} >= declaredEnd=${declaredEnd}`,\n },\n }\n }\n\n return {\n layout: {\n hasMfuDuHeader: hasHeader,\n mfuOffset,\n dataEnd: declaredEnd,\n },\n }\n}\n\nfunction isEmptyOrExactAltaTrailer(payload: Uint8Array, start: number, end: number): boolean {\n if (start === end) return true\n if (start < 0 || end > payload.length || start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE > end) {\n return false\n }\n\n const view = new DataView(\n payload.buffer,\n payload.byteOffset + start,\n end - start,\n )\n if (view.getUint16(0, false) !== _LEAF_MMTP_EXT_TYPE_ALTA_AUTH) return false\n\n const authLength = view.getUint16(2, false)\n return start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE + authLength === end\n}\n\ninterface DeclaredTrailerLayout {\n hasFecId: boolean\n}\n\nfunction resolveDeclaredTrailers(\n payload: Uint8Array,\n declaredEnd: number,\n fecType: number,\n options?: { skipFecId?: boolean },\n): DeclaredTrailerLayout | null {\n if (declaredEnd > payload.length) return null\n\n if (options?.skipFecId) {\n return isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)\n ? { hasFecId: false }\n : null\n }\n\n if (fecType === 1) {\n const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n if (fecStart < declaredEnd || !isEmptyOrExactAltaTrailer(payload, declaredEnd, fecStart)) {\n return null\n }\n return { hasFecId: true }\n }\n if (fecType !== 0) return null\n\n if (isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)) {\n return { hasFecId: false }\n }\n\n return null\n}\n\n/**\n * Extract NAL unit payload from MMTP packet\n */\nexport function extractNALUnit(mmtpPacket: Uint8Array): Uint8Array {\n const extraction = extractMFUPayload(mmtpPacket)\n if (!extraction) throw new Error('Malformed MMTP MPU payload')\n return extraction.data\n}\n\n/** Result of extracting MFU payload from an MMTP packet */\nexport interface MFUExtraction {\n /** MMTP header fields */\n header: MMTPHeader\n /** MPU header fields */\n mpu: MPUHeader\n /** Fragment type (FT field, 4 bits): 0=metadata, 2=MFU data */\n fragmentType: number\n /** MFU payload data (video/audio bytes, headers stripped, FEC ID excluded) */\n data: Uint8Array\n /** Parsed timed MFU DU header when present, otherwise null */\n mfuDuHeader: MFUDuHeader | null\n /** Non-timed MFU Item_ID when the 4-byte DU header is present. */\n itemId: number | null\n /** Source FEC Payload ID if present (`FEC_type == 1`), or null */\n fecPayloadId: { ssId: number } | null\n}\n\n/**\n * Extract MFU payload from a raw MMTP packet.\n *\n * Handles all the byte arithmetic for:\n * - MMTP header (12 bytes)\n * - MPU sub-header (8 bytes, payloadLength field)\n * - MFU DU header (14 bytes timed / 4 bytes non-timed, only for FI \u2264 1)\n * - Source FEC Payload ID (4 bytes at end, only when `FEC_type == 1`)\n *\n * `payloadLength` is the ISO length-after-field value: six remaining MPU-header\n * bytes, then the MFU DU header when present, then media. ALTA and Source FEC\n * trailers are outside that declared span and must consume the exact suffix.\n *\n * @param mmtpPacket Raw MMTP packet (from multicast or MoQ)\n * @param options.skipFecId When true, do NOT subtract the 4-byte Source FEC Payload ID\n * from the payload boundary, even if fecType >= 1 in the MMTP header. Use this for\n * FEC-recovered symbols where the FEC PID was already stripped before WASM decoding\n * but the fecType bits in byte 0 still reflect the original packet (FEC Type=1 per\n * draft-ramadan-moq-mmt-00 \u00A73.1).\n * @returns Extracted MFU data + metadata, or null if packet is too small\n */\ninterface MfuPlan {\n header: MMTPHeader\n payload: Uint8Array\n mpu: MPUHeader\n fragmentType: number\n layout: MfuPayloadLayout\n trailerLayout: DeclaredTrailerLayout\n}\n\n/**\n * Single source of truth for whether a packet yields an MFU payload.\n *\n * `extractMFUPayload` and `explainMFURejection` both delegate here, so the\n * reason reported to operators can never drift from the decision the parser\n * actually made.\n */\nfunction planMfuExtraction(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n parsedHeader?: MMTPHeader,\n): { plan: MfuPlan } | { rejection: MFURejection } {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'packet-shorter-than-mmtp-plus-mpu-header',\n detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE + MPU_HEADER_SIZE}`,\n },\n }\n }\n\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch (error) {\n return {\n rejection: {\n gate: 'mmtp-header-unparseable',\n detail: error instanceof Error ? error.message : String(error),\n },\n }\n }\n }\n if (header.packetType !== PACKET_TYPE_MPU) {\n return {\n rejection: {\n gate: 'not-an-mpu-packet',\n detail: `packetType=0x${header.packetType.toString(16).padStart(2, '0')}`,\n },\n }\n }\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'payload-shorter-than-mpu-header',\n detail: `${payload.length} bytes < ${MPU_HEADER_SIZE}`,\n },\n }\n }\n\n const mpu = parseMPUHeader(payload)\n if (mpu.aggregationFlag) {\n return {\n rejection: { gate: 'aggregation-flag-set', detail: 'strict profile requires A=0' },\n }\n }\n\n // Fragment type from byte 2 upper nibble (not returned by parseMPUHeader)\n const fragmentType = (payload[2]! >> 4) & 0x0f\n\n const layoutResult = resolveMfuPayloadLayout(mpu, fragmentType, payload)\n if ('rejection' in layoutResult) return layoutResult\n const { layout } = layoutResult\n\n const trailerLayout = resolveDeclaredTrailers(\n payload,\n layout.dataEnd,\n header.fecType,\n options,\n )\n if (!trailerLayout) {\n // The most common cause is a producer whose Source FEC Payload ID width\n // disagrees with ISO/IEC 23008-1 \u00A7C.5.2. Name that explicitly rather than\n // leaving operators to infer it from a generic trailer error.\n if (!options?.skipFecId && header.fecType === 1) {\n const observed = payload.length - layout.dataEnd\n if (observed !== SOURCE_FEC_PAYLOAD_ID_SIZE) {\n return {\n rejection: {\n gate: 'source-fec-payload-id-width-mismatch',\n detail:\n `producer wrote a ${observed}-byte Source FEC Payload ID, ` +\n `ISO/IEC 23008-1 \u00A7C.5.2 requires ${SOURCE_FEC_PAYLOAD_ID_SIZE} ` +\n `(payload=${payload.length}, declaredEnd=${layout.dataEnd})`,\n },\n }\n }\n }\n return {\n rejection: {\n gate: 'declared-trailers-unresolved',\n detail:\n `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty ` +\n `nor an exact ALTA trailer (fecType=${header.fecType})`,\n },\n }\n }\n\n return { plan: { header, payload, mpu, fragmentType, layout, trailerLayout } }\n}\n\n/**\n * Report why `extractMFUPayload` would refuse this packet, or null if it would\n * accept it.\n *\n * `extractMFUPayload` returns bare `null` on ten distinct conditions, which\n * makes a producer/consumer framing disagreement indistinguishable from a\n * publisher that never started \u2014 both render as silence. Use this wherever\n * that distinction matters: diagnostics, contract tests, and operator-facing\n * logs.\n */\nexport function explainMFURejection(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFURejection | null {\n const result = planMfuExtraction(mmtpPacket, options)\n return 'rejection' in result ? result.rejection : null\n}\n\nexport function extractMFUPayload(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options))\n}\n\n/**\n * extractMFUPayload for a caller that already holds this packet's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet; the\n * result is then identical to `extractMFUPayload(mmtpPacket, options)`.\n */\nexport function extractMFUPayloadWithHeader(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options, header))\n}\n\nfunction extractPlannedMfu(\n mmtpPacket: Uint8Array,\n result: { plan: MfuPlan } | { rejection: MFURejection },\n): MFUExtraction | null {\n if ('rejection' in result) return null\n const { header, payload, mpu, fragmentType, layout, trailerLayout } = result.plan\n\n const mfuDuHeaderPresent = layout.hasMfuDuHeader\n const mfuOffset = layout.mfuOffset\n const dataEnd = layout.dataEnd\n\n const mfuDuHeader =\n mfuDuHeaderPresent && mpu.timedFlag === 1\n ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE)\n : null\n const itemId =\n mfuDuHeaderPresent && mpu.timedFlag === 0\n ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false)\n : null\n\n const data = payload.subarray(mfuOffset, dataEnd)\n\n const fecPayloadId = trailerLayout.hasFecId\n ? {\n ssId: new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE,\n SOURCE_FEC_PAYLOAD_ID_SIZE,\n ).getUint32(0, false),\n }\n : null\n\n return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId }\n}\n\n/**\n * Extract timestamp from MMTP packet\n */\nexport function extractTimestamp(mmtpPacket: Uint8Array): bigint {\n const { header } = unwrapMMTP(mmtpPacket)\n return header.timestamp\n}\n\n/**\n * Check if MMTP packet is an FEC repair packet\n * FEC_type 2 and 3 are repair packets\n */\nexport function isFecRepairPacket(mmtpPacket: Uint8Array): boolean {\n if (mmtpPacket.length < 1) return false\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n return fecType >= 2\n}\n\n/**\n * Extract Source FEC Payload ID (SS_ID) from the last 4 bytes of an MMTP\n * source packet per ISO/IEC 23008-1:2023 Section C.5.2 Figure C.10.\n *\n * SS_ID is a flat 32-bit monotonic counter (incremented by 1 per packet\n * for ssbg_mode0 and ssbg_mode1). Block membership and ESI use the\n * configured K; repair SS_Start identifies the first SS_ID of the block and\n * SSB_length records the authored source-symbol count:\n * SBN = floor(SS_ID / K), ESI = SS_ID % K\n * SS_Start = SBN * K (first SS_ID of the block)\n *\n * This matches FFmpeg moqenc_mmt.c, the drafts (draft-ramadan-moq-fec \u00A76.2),\n * and ATSC A/331 \u00A78.1.2.5 which defers to ISO 23008-1 Annex C.\n */\nexport function extractFecPayloadId(mmtpPacket: Uint8Array): { ssId: number } | null {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header ? sourceFecPayloadIdOf(mmtpPacket, header) : null\n}\n\n/**\n * Return true when FEC_type=1 and the exact suffix after the ISO-declared MPU\n * payload contains a four-byte Source FEC Payload ID.\n */\nexport function hasSourceFecPayloadId(mmtpPacket: Uint8Array): boolean {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header !== null && sourceFecPayloadIdOf(mmtpPacket, header) !== null\n}\n\nfunction parseHeaderForSourceFecId(mmtpPacket: Uint8Array): MMTPHeader | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n try {\n return parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n}\n\n/**\n * The Source FEC Payload ID of a packet whose MMTP header the caller already\n * parsed, or null when the packet carries none: present only when FEC_type=1\n * and the exact suffix after the ISO-declared MPU payload holds the four-byte\n * ID (ISO/IEC 23008-1 \u00A7C.5.2). Same answer as extractFecPayloadId, without a\n * second header parse (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet.\n */\nexport function sourceFecPayloadIdOf(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n): { ssId: number } | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n if (header.packetType !== PACKET_TYPE_MPU) return null\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) return null\n const payloadLength = (payload[0]! << 8) | payload[1]!\n if (payloadLength < MPU_HEADER_REMAINDER_SIZE) return null\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (!resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId) return null\n\n // Flat 4-byte SS_ID: encoder writes block_id * K + symbol_id\n const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n return {\n ssId:\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0,\n }\n}\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\n// Re-export the ALTA trailer helper from the zero-dep leaf package. The leaf\n// owns the implementation so container and FEC consumers share one parser\n// without creating a package cycle.\nexport const MMTP_EXT_TYPE_ALTA_AUTH = _LEAF_MMTP_EXT_TYPE_ALTA_AUTH\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE\nexport type AltaTrailer = _LeafAltaTrailer\nexport const extractAltaTrailer = _leafExtractAltaTrailer\n\n/** Size of Repair FEC Payload ID per ISO/IEC 23008-1 Section C.5.3 */\nexport const REPAIR_FEC_PID_SIZE = 13\n\n/**\n * Parse the 13-byte Repair FEC Payload ID after the complete dynamic MMTP header.\n * `parsedHeader`, when given, must be `parseMMTPHeader(mmtpPacket)` of this\n * same packet; it spares the second header parse (BLO-36599).\n */\nfunction readRepairFecPayloadIdFields(mmtpPacket: Uint8Array, parsedHeader?: MMTPHeader): {\n packetId: number; ssStart: number; rsbLength: number; rsId: number; ssbLength: number; dataOffset: number\n} | null {\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n }\n if (header.packetType !== PACKET_TYPE_REPAIR || header.fecType !== 2) return null\n\n const off = header.headerLength\n if (mmtpPacket.length < off + REPAIR_FEC_PID_SIZE) return null\n const ssStart =\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0 // 32 bits\n const rsbLength = (mmtpPacket[off + 4]! << 16) | (mmtpPacket[off + 5]! << 8) | mmtpPacket[off + 6]! // 24 bits\n const rsId = (mmtpPacket[off + 7]! << 16) | (mmtpPacket[off + 8]! << 8) | mmtpPacket[off + 9]! // 24 bits\n const ssbLength = (mmtpPacket[off + 10]! << 16) | (mmtpPacket[off + 11]! << 8) | mmtpPacket[off + 12]! // 24 bits\n return {\n packetId: header.packetId,\n ssStart,\n rsbLength,\n rsId,\n ssbLength,\n dataOffset: off + REPAIR_FEC_PID_SIZE,\n }\n}\n\n/**\n * Extract Repair FEC Payload ID from a raw-multicast MMTP repair packet.\n * Per ISO/IEC 23008-1:2023 Section C.5.3 (ssbg_mode0, one-stage, no FFSRP_TS):\n * [MMTP header][SS_Start:4][RSB_length:3][RS_ID:3][SSB_length:3][repair data]\n * OTI is delivered via AL-FEC signaling message (Table C.3), not per-packet.\n *\n * Byte 1 is reserved(2)|packet_type(6) for V=0, so repair type 0x03 is 0x03.\n * Pre-migration high-bits bytes (0x0c) are rejected \u2014 no compatibility decode.\n */\nexport function extractRepairFecPayloadId(mmtpPacket: Uint8Array): {\n ssStart: number; rsbLength: number; rsId: number; ssbLength: number\n} | null {\n const fields = readRepairFecPayloadIdFields(mmtpPacket)\n if (!fields) return null\n return {\n ssStart: fields.ssStart,\n rsbLength: fields.rsbLength,\n rsId: fields.rsId,\n ssbLength: fields.ssbLength,\n }\n}\n\n/** Parsed MoQ repair frame per ISO/IEC 23008-1 Section C.4.3 + C.5.3 */\nexport interface MoqRepairFrame {\n /** MMTP packetId (1=video, 2=audio) \u2014 for routing to correct FEC decoder */\n packetId: number\n /** SS_Start (32 bits): SS_ID of first source symbol in block */\n ssStart: number\n /** RSB_length (24 bits): number of repair symbols (P) */\n rsbLength: number\n /** RS_ID (24 bits): repair symbol index (0-based) */\n rsId: number\n /** SSB_length (24 bits): authored source-symbol count (1..=configured K) */\n ssbLength: number\n /** Repair symbol data after the dynamic MMTP header and 13-byte FEC payload ID. */\n symbolData: Uint8Array\n}\n\n/**\n * Parse a MoQ repair frame (ISO/IEC 23008-1 Annex C format).\n *\n * Wire format per ISO 23008-1 \u00A7C.4.3 + \u00A7C.5.3:\n * MMTP Header: fixed 12 bytes plus C/X optional fields\n * then SS_Start (4 bytes) \u2192 block identifier\n * then RSB_length (3 bytes) \u2192 P\n * then RS_ID (3 bytes) \u2192 ESI = K + RS_ID\n * then SSB_length (3 bytes) \u2192 authored source-symbol count (1..=K)\n * then Repair Symbol Data (T bytes)\n *\n * @param frame - Raw MoQ repair frame bytes\n * @returns Parsed frame or null if invalid\n */\nexport function parseMoqRepairFrame(frame: Uint8Array): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame))\n}\n\n/**\n * parseMoqRepairFrame for a caller that already holds this frame's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599). The router's\n * multicast repair branch uses it: one repair packet in every K + P otherwise\n * paid two header parses.\n *\n * `header` must be `parseMMTPHeader(frame)` of this same frame; the result is\n * then identical to `parseMoqRepairFrame(frame)`.\n */\nexport function parseMoqRepairFrameWithHeader(\n frame: Uint8Array,\n header: MMTPHeader,\n): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame, header))\n}\n\nfunction moqRepairFrameFrom(\n frame: Uint8Array,\n repairId: ReturnType<typeof readRepairFecPayloadIdFields>,\n): MoqRepairFrame | null {\n if (!repairId) return null\n\n const symbolData = frame.subarray(repairId.dataOffset)\n if (\n repairId.rsbLength === 0 ||\n repairId.ssbLength === 0 ||\n repairId.rsId >= repairId.rsbLength ||\n symbolData.length === 0\n ) return null\n\n return {\n packetId: repairId.packetId,\n ssStart: repairId.ssStart,\n rsbLength: repairId.rsbLength,\n rsId: repairId.rsId,\n ssbLength: repairId.ssbLength,\n symbolData,\n }\n}\n\n// The signalling-message parsers (9.3.4.2 payload header, AL-FEC message,\n// PA/MPT) live in ./signalling.ts.\n", "/**\n * AMT well-known UDP port (RFC 7450 \u00A74).\n *\n * The single definition for the whole repo. It lives here, in the one package\n * both `@blockcast/mmt-transport` and `@blockcast/mmt-container` already\n * depend on, because the two apply it on opposite sides of a boundary that\n * does not otherwise share code:\n *\n * - container resolves it where `networkSource` semantics live, so a catalog\n * relay that names no port is reachable instead of handing a join a\n * portless relay that fails.\n * - transport applies it when reading a catalog, dialling a gateway, and\n * answering DRIAD \u2014 but deliberately not at every boundary. Which call\n * sites do and do not take the default is transport's business; the\n * reasoning lives beside those call sites, not here.\n */\nexport const DEFAULT_AMT_RELAY_PORT = 2268;\n", "/**\n * NTP short format (RFC 5905 \u00A76): an unsigned 32-bit, 16.16 fixed-point count\n * of seconds. The MMTP header `timestamp` field (ISO/IEC 23008-1 \u00A79.2.2)\n * carries each packet's UTC sending instant in this format, so the value wraps\n * to 0 every 2^16 s (~18.2 h).\n *\n * Plain numeric order on these values breaks at every wrap: a retained\n * `Math.max` stays at the pre-wrap value and every later instant compares as\n * older. Order send times only with {@link compareNtpShort}, which applies\n * RFC 1982 serial-number arithmetic over the field's width.\n */\n\n/** Width in bits of the MMTP `timestamp` field (NTP short). */\nexport const NTP_SHORT_BITS = 32\n\n/** Number of distinct NTP-short values; the field wraps to 0 here. */\nexport const NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS\n\n/**\n * RFC 1982 \u00A73.2's `2^(SERIAL_BITS - 1)`. Two values whose forward distance is\n * below this are ordered; exactly this far apart they are not ordered at all.\n * Two instants must be less than this far apart (2^15 s, ~9.1 h of send time)\n * for the comparison to give their true order.\n */\nexport const NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1)\n\n/**\n * Serial order of one NTP-short value relative to another.\n *\n * `'unordered'` is RFC 1982's undefined case: the values are exactly\n * {@link NTP_SHORT_HALF_RANGE} apart, so neither precedes the other. It is\n * never folded into `'before'` or `'after'`; each caller decides what an\n * unorderable pair means for it.\n */\nexport type NtpShortOrder = 'before' | 'equal' | 'after' | 'unordered'\n\n/** True when `value` is an NTP-short value: an integer in [0, NTP_SHORT_MODULUS). */\nexport function isNtpShort(value: number): boolean {\n return Number.isInteger(value) && value >= 0 && value < NTP_SHORT_MODULUS\n}\n\n/**\n * RFC 1982 serial comparison of NTP-short `a` against `b`: `'after'` when `a`\n * is the later instant, `'before'` when it is the earlier one.\n *\n * The answer is the true order only while the two instants are less than\n * {@link NTP_SHORT_HALF_RANGE} apart. Past that a 32-bit field cannot tell\n * later from earlier, and the result names whichever direction is nearer.\n * This is not a total order (it is not transitive across the whole space), so\n * it must never be used to sort.\n *\n * @throws {RangeError} if either operand is not an NTP-short value\n */\nexport function compareNtpShort(a: number, b: number): NtpShortOrder {\n if (!isNtpShort(a) || !isNtpShort(b)) {\n throw new RangeError(\n `NTP-short values must be integers in [0, 2^${NTP_SHORT_BITS}), got ${String(a)} and ${String(b)}`,\n )\n }\n if (a === b) return 'equal'\n // Forward distance from b to a, reduced into [1, NTP_SHORT_MODULUS). Both\n // operands are below 2^32, so every step is exact in a double.\n const forward = (a - b + NTP_SHORT_MODULUS) % NTP_SHORT_MODULUS\n if (forward < NTP_SHORT_HALF_RANGE) return 'after'\n if (forward > NTP_SHORT_HALF_RANGE) return 'before'\n return 'unordered'\n}\n", "/**\n * FEC block profile (BLO-35597, ISO/IEC 23008-1 Annex C).\n *\n * Every catalog `fec` object that declares an FEC instance names its block\n * profile in `fec.blockProfile`. A receiver applies all of a profile's rules\n * or none of them, so the token is the one explicit per-stream discriminator:\n * it is never defaulted and never inferred from the wire.\n *\n * draft-ramadan-moq-fec \u00A75.1 defines exactly one value, and it is the only\n * profile this library implements:\n *\n * - `iso-ssbg1-v1`: ssbg_mode1 (C.3.3): a source symbol is\n * `u16 length || protected packet || zero padding` to T; a block is a\n * K' = SSB_length RaptorQ block (C.5.3) and repair ESI = SSB_length + RS_ID;\n * SS_ID increments by one per FEC source packet and wraps at 2^32 (C.5.2),\n * so a block is located by membership in `[SS_Start, SS_Start + SSB_length)`\n * under RFC 1982 serial arithmetic; the MoQ repair group id is the 62-bit\n * wrap-epoch-extended SS_Start. The AL-FEC coding byte for this profile is\n * 0x14 (one-stage, ssbg_mode1, fec_payload_id_mode 0, Amd 1:2025 Table C.3).\n *\n * A track whose `blockProfile` is absent or names any other token (the\n * retired Blockcast-private `k-padded-v1` included) has no FEC: a receiver\n * consumes its source without FEC and joins none of its repair tracks\n * (draft-ramadan-moq-fec \u00A75.1). `parseFecBlockProfile` throws for such a\n * token; the per-track catalog resolver turns the same rule into a typed\n * per-track \"FEC unavailable\" outcome instead of failing the session.\n */\nimport { serial32Add, serial32Distance, serial32Precedes } from './serial-number.js'\n\n/** The block profile a catalog `fec.blockProfile` may name: the one this library implements. */\nexport type FecBlockProfile = 'iso-ssbg1-v1'\n\n/** Every accepted `fec.blockProfile` token. */\nexport const FEC_BLOCK_PROFILES: readonly FecBlockProfile[] = Object.freeze([\n 'iso-ssbg1-v1',\n])\n\n/** True for a block-profile token this library implements. */\nexport function isFecBlockProfile(value: unknown): value is FecBlockProfile {\n return value === 'iso-ssbg1-v1'\n}\n\n/**\n * Read a required `fec.blockProfile`. Throws on an absent token and on any\n * token other than `iso-ssbg1-v1`: there is no default and nothing is\n * inferred from the wire (draft-ramadan-moq-fec \u00A75.1).\n *\n * @param where the catalog path of the field, for the error message.\n */\nexport function parseFecBlockProfile(value: unknown, where: string): FecBlockProfile {\n if (isFecBlockProfile(value)) return value\n if (value === undefined) {\n throw new Error(`${where} is required: name the FEC block profile (\"iso-ssbg1-v1\")`)\n }\n throw new Error(`${where} must be \"iso-ssbg1-v1\" (got ${JSON.stringify(value)})`)\n}\n\n/** ISO/IEC 23008-1 C.3.3 ssbg_mode1: the two-octet packet length field. */\nexport const SSBG1_LENGTH_FIELD_BYTES = 2\n\n/** The largest protected packet an ssbg_mode1 symbol of size T can carry. */\nexport function ssbg1MaxPacketLength(symbolSize: number): number {\n return symbolSize - SSBG1_LENGTH_FIELD_BYTES\n}\n\n/** Whether a protected packet of `packetLength` bytes fits one T-byte ssbg_mode1 symbol. */\nexport function ssbg1FitsSymbol(packetLength: number, symbolSize: number): boolean {\n return (\n Number.isSafeInteger(packetLength) &&\n packetLength >= 0 &&\n packetLength <= ssbg1MaxPacketLength(symbolSize) &&\n packetLength <= 0xffff\n )\n}\n\n/**\n * Build one ssbg_mode1 source symbol (C.3.3): the protected packet's length\n * in two octets, network byte order, then the packet, then zero octets up to\n * T. The protected packet is the MMTP packet before its Source FEC Payload ID\n * is appended (C.4.2).\n *\n * @throws when `length + 2 > T`: the packet cannot be protected under this\n * profile, and silently truncating it would corrupt every repair symbol.\n */\nexport function buildSsbg1SourceSymbol(packet: Uint8Array, symbolSize: number): Uint8Array {\n if (!Number.isSafeInteger(symbolSize) || symbolSize <= SSBG1_LENGTH_FIELD_BYTES) {\n throw new Error(`ssbg_mode1 symbol size must exceed ${SSBG1_LENGTH_FIELD_BYTES} (got ${symbolSize})`)\n }\n if (!ssbg1FitsSymbol(packet.byteLength, symbolSize)) {\n throw new Error(\n `ssbg_mode1 source symbol needs ${packet.byteLength} + ${SSBG1_LENGTH_FIELD_BYTES} bytes but T is ${symbolSize}`,\n )\n }\n const symbol = new Uint8Array(symbolSize)\n symbol[0] = (packet.byteLength >>> 8) & 0xff\n symbol[1] = packet.byteLength & 0xff\n symbol.set(packet, SSBG1_LENGTH_FIELD_BYTES)\n return symbol\n}\n\n/**\n * Recover the protected packet from one ssbg_mode1 source symbol: the bytes\n * the length field names. Returns null, never a guess, when the length field\n * points past the symbol or any padding octet is non-zero (C.3.3 padding is\n * all 00h), since either means the recovered symbol is not what was sent.\n */\nexport function trimSsbg1SourceSymbol(symbol: Uint8Array): Uint8Array | null {\n if (symbol.byteLength < SSBG1_LENGTH_FIELD_BYTES) return null\n const length = (symbol[0]! << 8) | symbol[1]!\n const end = SSBG1_LENGTH_FIELD_BYTES + length\n if (end > symbol.byteLength) return null\n for (let i = end; i < symbol.byteLength; i++) {\n if (symbol[i] !== 0) return null\n }\n return symbol.subarray(SSBG1_LENGTH_FIELD_BYTES, end)\n}\n\n/** The SS_ID after the last source symbol of `[ssStart, ssStart + ssbLength)`. */\nexport function isoBlockEnd(ssStart: number, ssbLength: number): number {\n return serial32Add(ssStart, ssbLength)\n}\n\n/**\n * Range membership (C.5.2, C.5.3): whether `ssId` is one of the\n * `ssbLength` consecutive SS_IDs starting at `ssStart`, modulo 2^32.\n */\nexport function isoBlockContains(ssStart: number, ssbLength: number, ssId: number): boolean {\n return serial32Distance(ssStart, ssId) < ssbLength\n}\n\n/**\n * A lower bound on how many blocks the block starting at `a` lies behind the\n * one starting at `b` (0 when `a` does not precede `b`). An iso-ssbg1-v1\n * block holds at most `kMax` source symbols (the catalog `sourceSymbols`, the\n * AL-FEC maximum_k_for_repair_flow), so `n * kMax` SS_IDs always span at least\n * `n` blocks. Every interleave horizon in the receiver (decoder retention,\n * worker retirement, ordered-relay settlement) uses this one bound, so the\n * manager's horizon can never fall inside the decoder's.\n */\nexport function isoBlocksBehindLowerBound(a: number, b: number, kMax: number): number {\n if (!Number.isSafeInteger(kMax) || kMax <= 0) {\n throw new Error(`iso-ssbg1-v1 block horizon needs a positive maximum K (got ${kMax})`)\n }\n return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0\n}\n", "/**\n * @blockcast/transport - DRIAD Discovery (RFC 8777)\n *\n * DRIAD (DNS Reverse IP AMT Discovery) discovers AMT relays based on the\n * **source address**, NOT the multicast group. The source network operator\n * configures DNS records for their source IPs to advertise which AMT relay(s)\n * can tunnel their traffic.\n *\n * Example: For source 69.25.95.10 sending to group 232.0.0.1:\n * Query: 10.95.25.69.amt.in-addr.arpa (source-based, NOT group-based)\n */\n\nimport { DEFAULT_AMT_RELAY_PORT } from \"@blockcast/mmt-base\";\nimport type { DRIADRelay } from \"./types.js\";\n\n/**\n * DRIAD (DNS Reverse IP AMT Discovery) - RFC 8777\n * Discovers AMT relays based on source address via DNS lookups\n */\nexport class DRIADDiscovery {\n\tprivate cache = new Map<string, DRIADRelay[]>();\n\tprivate cacheExpiry = 300_000; // 5 minutes\n\n\t/**\n\t * Discover AMT relays for a multicast source address (RFC 8777)\n\t *\n\t * @param source - The multicast source IP address (NOT the group!)\n\t * @returns Array of discovered AMT relays that can tunnel traffic from this source\n\t */\n\tasync discoverRelays(source: string): Promise<DRIADRelay[]> {\n\t\t// Check cache first\n\t\tconst cached = this.cache.get(source);\n\t\tif (cached) {\n\t\t\treturn cached;\n\t\t}\n\n\t\ttry {\n\t\t\t// Convert source to DRIAD format (reversed octets)\n\t\t\tconst reversedSource = source.split(\".\").reverse().join(\".\");\n\t\t\tconst queryDomain = `${reversedSource}.amt.in-addr.arpa`;\n\n\t\t\tconsole.log(`[DRIAD] Discovering relays for source ${source} via ${queryDomain}`);\n\n\t\t\t// In browser, we use a DRIAD proxy service or DNS-over-HTTPS\n\t\t\tconst relays = await this.queryDRIADProxy(source);\n\n\t\t\tif (relays.length > 0) {\n\t\t\t\tthis.cache.set(source, relays);\n\t\t\t\tsetTimeout(() => this.cache.delete(source), this.cacheExpiry);\n\t\t\t}\n\n\t\t\treturn relays;\n\t\t} catch (error) {\n\t\t\tconsole.warn(`[DRIAD] Discovery failed for source ${source}:`, error);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Query DRIAD proxy service (browser-compatible)\n\t */\n\tprivate async queryDRIADProxy(source: string): Promise<DRIADRelay[]> {\n\t\t// List of known DRIAD proxy endpoints\n\t\tconst proxyEndpoints = [\n\t\t\t\"https://driad.amt.net/api/v1/relays\",\n\t\t\t\"https://multicast-gateway.cloudflare.com/driad\",\n\t\t];\n\n\t\tfor (const endpoint of proxyEndpoints) {\n\t\t\ttry {\n\t\t\t\t// Query by source address per RFC 8777\n\t\t\t\tconst response = await fetch(`${endpoint}?source=${encodeURIComponent(source)}`, {\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\theaders: { Accept: \"application/json\" },\n\t\t\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t\t\t});\n\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = (await response.json()) as { relays: DRIADRelay[] };\n\t\t\t\t\treturn data.relays || [];\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Try next proxy endpoint\n\t\t\t}\n\t\t}\n\n\t\t// Fallback: Try DNS-over-HTTPS query\n\t\tconst dohRelay = await this.queryDNSOverHTTPS(source);\n\t\tif (dohRelay) {\n\t\t\treturn [dohRelay];\n\t\t}\n\n\t\treturn [];\n\t}\n\n\t/**\n\t * Query DNS-over-HTTPS for DRIAD relay based on source address (RFC 8777)\n\t */\n\tprivate async queryDNSOverHTTPS(source: string): Promise<DRIADRelay | null> {\n\t\ttry {\n\t\t\t// Build DRIAD query name from source address (reversed octets)\n\t\t\tconst reversedSource = source.split(\".\").reverse().join(\".\");\n\t\t\tconst queryName = `${reversedSource}.amt.in-addr.arpa`;\n\n\t\t\tconst dohUrl = `https://cloudflare-dns.com/dns-query?name=${queryName}&type=A`;\n\t\t\tconst response = await fetch(dohUrl, {\n\t\t\t\theaders: { Accept: \"application/dns-json\" },\n\t\t\t\tsignal: AbortSignal.timeout(3000),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst result = (await response.json()) as { Answer?: Array<{ data: string }> };\n\n\t\t\t// Extract first A/AAAA record\n\t\t\tif (result.Answer && result.Answer.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\thost: result.Answer[0].data,\n\t\t\t\t\tport: DEFAULT_AMT_RELAY_PORT,\n\t\t\t\t\tpriority: 10,\n\t\t\t\t\tweight: 100,\n\t\t\t\t\tasn: 0,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[DRIAD] DNS-over-HTTPS query failed:\", error);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Select best relay based on RTT measurements\n\t */\n\tasync selectBestRelay(relays: DRIADRelay[]): Promise<DRIADRelay | null> {\n\t\tif (relays.length === 0) return null;\n\n\t\t// Measure RTT to each relay\n\t\tconst measured = await Promise.all(\n\t\t\trelays.map(async (relay) => {\n\t\t\t\tconst rtt = await this.measureRTT(relay.host, relay.port);\n\t\t\t\treturn { ...relay, rtt };\n\t\t\t}),\n\t\t);\n\n\t\t// Sort by priority, then by RTT\n\t\tmeasured.sort((a, b) => {\n\t\t\tif (a.priority !== b.priority) return a.priority - b.priority;\n\t\t\treturn (a.rtt || Infinity) - (b.rtt || Infinity);\n\t\t});\n\n\t\tconsole.log(`[DRIAD] Selected relay: ${measured[0].host} (RTT: ${measured[0].rtt}ms)`);\n\t\treturn measured[0];\n\t}\n\n\tprivate async measureRTT(host: string, _port: number): Promise<number> {\n\t\tconst start = performance.now();\n\t\ttry {\n\t\t\t// Use HTTP/HTTPS probe since we can't do UDP from browser\n\t\t\tawait fetch(`https://${host}/health`, {\n\t\t\t\tmethod: \"HEAD\",\n\t\t\t\tsignal: AbortSignal.timeout(2000),\n\t\t\t\tmode: \"no-cors\",\n\t\t\t});\n\t\t\treturn performance.now() - start;\n\t\t} catch {\n\t\t\treturn Infinity;\n\t\t}\n\t}\n\n\t/**\n\t * Clear the relay cache\n\t */\n\tclearCache(): void {\n\t\tthis.cache.clear();\n\t}\n\n\t/**\n\t * Get cached relays for a source address\n\t */\n\tgetCached(source: string): DRIADRelay[] | undefined {\n\t\treturn this.cache.get(source);\n\t}\n}\n\n// Export singleton instance\nexport const driadDiscovery = new DRIADDiscovery();\n", "export interface MulticastTierEndpoint {\n\tsourceAddress?: string;\n\tgroupAddress: string;\n\tport: number;\n\ttracks: { name: string; packetId?: number }[];\n\tbandwidth?: number;\n\tprotocol?: string;\n\t/** False when every track on this endpoint is a `fec-repair` track, so the\n\t * endpoint carries repair symbols only and no media. A repair-layer\n\t * endpoint is joined by the loss-driven layer policy, never by the\n\t * bandwidth ladder: selecting one as a tier would leave the receiver with\n\t * no media at all. Set by the catalog policy, which resolves repair-track\n\t * membership from `depends`/`fec.repairTrack` rather than from names.\n\t * Undefined means \"not classified\" and is treated as carrying media, so a\n\t * caller that builds endpoints without the policy is unaffected. */\n\tcarriesMedia?: boolean;\n}\n\nexport function endpointKey(endpoint: MulticastTierEndpoint): string {\n\treturn `${endpoint.sourceAddress ?? \"\"}|${endpoint.groupAddress}|${endpoint.port}`;\n}\n\n/**\n * Whether this endpoint may be selected as a media tier.\n *\n * Every path that picks a tier must apply this, not only `selectMulticastTier`:\n * the orchestrator's headroom up-selection ranks endpoints itself, because it\n * advances one adjacent rung rather than fitting a bandwidth budget. Exported\n * so both rankings share one definition of \"carries media\" instead of\n * repeating the comparison and drifting apart.\n */\nexport function carriesMedia(endpoint: MulticastTierEndpoint): boolean {\n\treturn endpoint.carriesMedia !== false;\n}\n\nexport function selectMulticastTier(\n\tendpoints: readonly MulticastTierEndpoint[],\n\testimate: number | undefined,\n): MulticastTierEndpoint | undefined {\n\t// Repair-only endpoints are never a media tier. Filtering once here covers\n\t// every return path below, including the two early returns and the fallback:\n\t// a ladder that has only repair-layer endpoints returns undefined rather\n\t// than joining one and receiving no media.\n\tconst media = endpoints.filter(carriesMedia);\n\tif (media.length <= 1) return media[0];\n\n\tconst ranked = media\n\t\t.filter((endpoint) => typeof endpoint.bandwidth === \"number\" && endpoint.bandwidth > 0)\n\t\t.sort((a, b) => a.bandwidth! - b.bandwidth!);\n\tif (ranked.length < 2) return media[0];\n\tif (estimate === undefined) return ranked[0];\n\n\tconst budget = estimate * 0.8;\n\tlet selected: MulticastTierEndpoint | undefined;\n\tfor (const endpoint of ranked) {\n\t\tif (endpoint.bandwidth! <= budget) selected = endpoint;\n\t}\n\treturn selected ?? ranked[0];\n}\n", "/**\n * @blockcast/transport - Adaptive Bitrate Controller\n *\n * Progressive adaptive bitrate increase based on network conditions.\n * Monitors received bandwidth and packet loss to adjust quality levels.\n *\n * Per draft-ramadan-moq-fec-00, ABR decisions should use the **effective loss rate**\n * after FEC recovery, not the raw transport-level loss rate. This allows FEC to\n * mask burst losses without unnecessarily downgrading quality.\n */\n\nimport type { QualityLevel } from \"./types.js\";\nimport { selectMulticastTier, type MulticastTierEndpoint } from \"./multicast-tier.js\";\n\n/**\n * FEC statistics for ABR decisions\n */\nexport interface FecStatsForAbr {\n\t/** Raw loss rate before FEC recovery (0.0-1.0) */\n\trawLossRate: number;\n\t/** Effective loss rate after FEC recovery (0.0-1.0) */\n\teffectiveLossRate: number;\n\t/** FEC recovery rate (percentage of lost packets recovered) */\n\trecoveryRate: number;\n\t/** Whether FEC is enabled */\n\tfecEnabled: boolean;\n}\n\n/**\n * A generic 240p..4K ladder, kept as public API only.\n *\n * @deprecated Not a stream's ladder and never a default: the controller\n * adapts over the ladder its caller supplies (`AdaptiveBitrateControllerOptions.ladder`,\n * e.g. the catalog's video renditions). Walking this list is how a player on a\n * 360p/480p/720p catalog logged upgrades to 1080p and 1440p it could never\n * switch to (BLO-35597).\n */\nexport const QUALITY_LEVELS: readonly QualityLevel[] = Object.freeze([\n\t{ name: \"240p\", bitrate: 300_000, width: 426, height: 240, fps: 15 },\n\t{ name: \"360p\", bitrate: 500_000, width: 640, height: 360, fps: 24 },\n\t{ name: \"480p\", bitrate: 1_000_000, width: 854, height: 480, fps: 30 },\n\t{ name: \"720p\", bitrate: 2_500_000, width: 1280, height: 720, fps: 30 },\n\t{ name: \"1080p\", bitrate: 5_000_000, width: 1920, height: 1080, fps: 30 },\n\t{ name: \"1440p\", bitrate: 8_000_000, width: 2560, height: 1440, fps: 30 },\n\t{ name: \"4K\", bitrate: 15_000_000, width: 3840, height: 2160, fps: 30 },\n].map((level) => Object.freeze(level)));\n\n/** What the controller adapts over and where it starts; both from the caller. */\nexport interface AdaptiveBitrateControllerOptions {\n\t/**\n\t * The renditions this stream carries (e.g. the catalog's video tracks), in\n\t * any order: a unique name and a bitrate (bps) each, plus dimensions and\n\t * fps when the catalog states them. Renditions may share a bitrate.\n\t */\n\tladder: readonly QualityLevel[];\n\t/** Name of the ladder rendition presented at construction; the lowest\n\t * rung when omitted. A name that matches no rung is refused. */\n\tinitialQuality?: string;\n}\n\nfunction positiveFinite(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n\n/**\n * Validate a caller ladder and return it frozen, ascending by bitrate. Throws\n * on an empty ladder, a rung without a name or a positive finite bitrate, a\n * width, height or fps that is present but not positive finite, or a name that\n * appears twice: a name is how `setQuality` and the initial rendition find a\n * rung. A shared bitrate is legal (e.g. 720p30 and 720p60, or two codecs);\n * `Array.prototype.sort` is stable, so equal bitrates keep the caller's order.\n */\nexport function validateQualityLadder(ladder: readonly QualityLevel[]): readonly QualityLevel[] {\n\tif (!Array.isArray(ladder) || ladder.length === 0) {\n\t\tthrow new TypeError(\"[ABR] ladder must list at least one rendition (e.g. the catalog's video tracks)\");\n\t}\n\tconst names = new Set<string>();\n\tfor (const level of ladder) {\n\t\tif (typeof level?.name !== \"string\" || level.name.length === 0) {\n\t\t\tthrow new TypeError(\"[ABR] every ladder rendition needs a name\");\n\t\t}\n\t\tif (!positiveFinite(level.bitrate)) {\n\t\t\tthrow new RangeError(`[ABR] rendition ${level.name}: bitrate must be a positive finite number, got ${String(level.bitrate)}`);\n\t\t}\n\t\tfor (const field of [\"width\", \"height\", \"fps\"] as const) {\n\t\t\tif (level[field] !== undefined && !positiveFinite(level[field])) {\n\t\t\t\tthrow new RangeError(`[ABR] rendition ${level.name}: ${field} must be a positive finite number when stated, got ${String(level[field])}`);\n\t\t\t}\n\t\t}\n\t\tif (names.has(level.name)) throw new RangeError(`[ABR] rendition ${level.name} appears twice in the ladder`);\n\t\tnames.add(level.name);\n\t}\n\treturn Object.freeze(\n\t\t[...ladder].sort((a, b) => a.bitrate - b.bitrate).map((level) => Object.freeze({ ...level })),\n\t);\n}\n\n/**\n * Progressive Adaptive Bitrate Controller\n *\n * Features:\n * - Monitors bandwidth via received bytes over time window\n * - Tracks packet loss rate (raw and effective after FEC)\n * - Uses effective loss rate for ABR decisions per draft-ramadan-moq-fec-00\n * - Progressive quality upgrades (waits for stability)\n * - Fast quality downgrades on congestion\n */\nexport class AdaptiveBitrateController {\n\t/** The caller's renditions, ascending by bitrate (`validateQualityLadder`). */\n\treadonly ladder: readonly QualityLevel[];\n\tprivate currentQualityIndex: number;\n\tprivate history: { timestamp: number; bytesReceived: number; packetsLost: number }[] = [];\n\tprivate readonly historyWindow = 10_000; // 10 second window\n\tprivate readonly upgradeThreshold = 0.8; // 80% of target bitrate sustained\n\tprivate readonly downgradeThreshold = 0.5; // 50% of target bitrate triggers downgrade\n\tprivate readonly stabilityPeriod = 5_000; // Wait 5s before upgrading\n\n\tprivate lastQualityChange = 0;\n\tprivate onQualityChange?: (quality: QualityLevel) => void;\n\n\t// FEC stats for effective loss rate calculation\n\tprivate fecStats: FecStatsForAbr = {\n\t\trawLossRate: 0,\n\t\teffectiveLossRate: 0,\n\t\trecoveryRate: 1,\n\t\tfecEnabled: false,\n\t};\n\n\tconstructor(options: AdaptiveBitrateControllerOptions) {\n\t\tif (!options || typeof options !== \"object\") {\n\t\t\tthrow new TypeError(\"[ABR] AdaptiveBitrateController needs { ladder, initialQuality }\");\n\t\t}\n\t\tthis.ladder = validateQualityLadder(options.ladder);\n\t\tthis.currentQualityIndex =\n\t\t\toptions.initialQuality === undefined ? 0 : this.ladder.findIndex((q) => q.name === options.initialQuality);\n\t\tif (this.currentQualityIndex < 0) {\n\t\t\tthrow new RangeError(`[ABR] initial rendition ${String(options.initialQuality)} is not in the ladder`);\n\t\t}\n\t}\n\n\tsetCallback(callback: (quality: QualityLevel) => void) {\n\t\tthis.onQualityChange = callback;\n\t}\n\n\tgetCurrentQuality(): QualityLevel {\n\t\treturn this.ladder[this.currentQualityIndex];\n\t}\n\n\t/** Selects the multicast (S,G) tier without replacing a player's unicast ABR manager. */\n\tselectMulticastTier(\n\t\tendpoints: readonly MulticastTierEndpoint[],\n\t\testimate: number | undefined = this.getMetrics().measuredBitrate || undefined,\n\t): MulticastTierEndpoint | undefined {\n\t\treturn selectMulticastTier(endpoints, estimate);\n\t}\n\n\t/**\n\t * Record network sample for bitrate adaptation\n\t *\n\t * @param bytesReceived - Bytes received in this sample\n\t * @param packetsLost - Packets lost (raw, before FEC recovery)\n\t */\n\trecordSample(bytesReceived: number, packetsLost: number) {\n\t\tconst now = Date.now();\n\n\t\tthis.history.push({ timestamp: now, bytesReceived, packetsLost });\n\n\t\t// Prune old samples\n\t\tthis.history = this.history.filter((s) => now - s.timestamp < this.historyWindow);\n\n\t\t// Calculate metrics\n\t\tconst { measuredBitrate, lossRate: rawLossRate } = this.calculateMetrics();\n\t\tconst currentQuality = this.getCurrentQuality();\n\n\t\t// Per draft-ramadan-moq-fec-00: Use effective loss rate after FEC for ABR decisions\n\t\t// This allows FEC to mask burst losses without triggering unnecessary downgrades\n\t\tconst effectiveLossRate = this.fecStats.fecEnabled\n\t\t\t? this.fecStats.effectiveLossRate\n\t\t\t: rawLossRate;\n\n\t\t// Decision logic\n\t\tif (now - this.lastQualityChange < this.stabilityPeriod) {\n\t\t\treturn; // Wait for stability\n\t\t}\n\n\t\t// Check for downgrade - use effective loss rate (post-FEC)\n\t\t// Only downgrade if FEC cannot recover the losses\n\t\tif (effectiveLossRate > 0.05 || measuredBitrate < currentQuality.bitrate * this.downgradeThreshold) {\n\t\t\tthis.downgrade();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for upgrade (progressive increase) - use effective loss rate\n\t\tif (\n\t\t\tmeasuredBitrate > currentQuality.bitrate * this.upgradeThreshold &&\n\t\t\teffectiveLossRate < 0.01 &&\n\t\t\tthis.currentQualityIndex < this.ladder.length - 1\n\t\t) {\n\t\t\tthis.upgrade();\n\t\t}\n\t}\n\n\t/**\n\t * Update FEC statistics for ABR decisions\n\t *\n\t * Per draft-ramadan-moq-fec-00, ABR should use the effective loss rate\n\t * (after FEC recovery) rather than raw transport loss rate.\n\t *\n\t * @param stats - FEC statistics from the FEC decoder\n\t */\n\tsetFecStats(stats: FecStatsForAbr) {\n\t\tthis.fecStats = stats;\n\t}\n\n\t/**\n\t * Get current FEC statistics\n\t */\n\tgetFecStats(): FecStatsForAbr {\n\t\treturn { ...this.fecStats };\n\t}\n\n\tprivate calculateMetrics() {\n\t\tif (this.history.length < 2) {\n\t\t\treturn { measuredBitrate: 0, lossRate: 0 };\n\t\t}\n\n\t\tconst totalBytes = this.history.reduce((sum, s) => sum + s.bytesReceived, 0);\n\t\tconst totalLost = this.history.reduce((sum, s) => sum + s.packetsLost, 0);\n\t\tconst duration = (this.history[this.history.length - 1].timestamp - this.history[0].timestamp) / 1000;\n\n\t\tconst measuredBitrate = duration > 0 ? (totalBytes * 8) / duration : 0;\n\t\tconst lossRate = totalLost / (this.history.length + totalLost);\n\n\t\treturn { measuredBitrate, lossRate };\n\t}\n\n\tprivate upgrade() {\n\t\tif (this.currentQualityIndex >= this.ladder.length - 1) return;\n\n\t\tthis.currentQualityIndex++;\n\t\tthis.lastQualityChange = Date.now();\n\n\t\tconst newQuality = this.getCurrentQuality();\n\t\tconsole.log(`[ABR] Upgrading to ${newQuality.name} (${newQuality.bitrate / 1_000_000}Mbps)`);\n\n\t\tthis.onQualityChange?.(newQuality);\n\t}\n\n\tprivate downgrade() {\n\t\tif (this.currentQualityIndex <= 0) return;\n\n\t\tthis.currentQualityIndex--;\n\t\tthis.lastQualityChange = Date.now();\n\n\t\tconst newQuality = this.getCurrentQuality();\n\t\tconsole.log(`[ABR] Downgrading to ${newQuality.name} (${newQuality.bitrate / 1_000_000}Mbps)`);\n\n\t\tthis.onQualityChange?.(newQuality);\n\t}\n\n\t/**\n\t * Select a ladder rendition: the caller switched to it (a manual override,\n\t * or a switch the caller made). Throws for a name the ladder does not carry.\n\t */\n\tsetQuality(name: string) {\n\t\tconst index = this.ladder.findIndex((q) => q.name === name);\n\t\tif (index < 0) throw new RangeError(`[ABR] rendition ${name} is not in the ladder`);\n\t\tthis.currentQualityIndex = index;\n\t\tthis.lastQualityChange = Date.now();\n\t\tthis.onQualityChange?.(this.getCurrentQuality());\n\t}\n\n\t/**\n\t * Get current metrics including both raw and effective loss rates\n\t */\n\tgetMetrics(): {\n\t\tmeasuredBitrate: number;\n\t\trawLossRate: number;\n\t\teffectiveLossRate: number;\n\t\tfecEnabled: boolean;\n\t\tsampleCount: number;\n\t} {\n\t\tconst { measuredBitrate, lossRate: rawLossRate } = this.calculateMetrics();\n\t\treturn {\n\t\t\tmeasuredBitrate,\n\t\t\trawLossRate,\n\t\t\teffectiveLossRate: this.fecStats.fecEnabled ? this.fecStats.effectiveLossRate : rawLossRate,\n\t\t\tfecEnabled: this.fecStats.fecEnabled,\n\t\t\tsampleCount: this.history.length,\n\t\t};\n\t}\n\n\t/**\n\t * Reset history and start fresh\n\t */\n\treset() {\n\t\tthis.history = [];\n\t\tthis.lastQualityChange = 0;\n\t}\n}\n", "import { compareNtpShort, isNtpShort } from \"@blockcast/mmt-base\";\nimport { carriesMedia, endpointKey, selectMulticastTier, type MulticastTierEndpoint } from \"./multicast-tier.js\";\n\nexport type OrchestratorState = \"stable\" | \"joining\";\nexport type OrchestratorCommand =\n\t| { kind: \"join-target\"; current: MulticastTierEndpoint; target: MulticastTierEndpoint }\n\t| { kind: \"commit-swap\"; target: MulticastTierEndpoint; old: MulticastTierEndpoint }\n\t| { kind: \"abandon-join\"; current: MulticastTierEndpoint; target: MulticastTierEndpoint };\n\nexport interface MulticastTierPacket {\n\tendpointKey: string;\n\tkeyframe: boolean;\n\t/**\n\t * The packet's MMTP header `timestamp`: its UTC sending instant in NTP short\n\t * format (ISO/IEC 23008-1 \u00A79.2.2), which wraps every 2^16 s (~18.2 h). The\n\t * orchestrator orders these only with RFC 1982 serial arithmetic\n\t * (`compareNtpShort`), never with `<` or `Math.max`.\n\t *\n\t * A non-finite value means the host could not read a header; that packet\n\t * neither raises the high-water mark nor commits. Any other value that is\n\t * not NTP short is a host bug and throws a RangeError.\n\t */\n\ttimestamp: number;\n\thasValidCodecConfig: boolean;\n}\n\nexport interface OrchestratorOptions {\n\tendpoints: readonly MulticastTierEndpoint[];\n\tinitialEndpoint?: MulticastTierEndpoint;\n\tadaptive?: boolean;\n\tnow: () => number;\n\tdwellMs?: number;\n\tjoinDeadlineMs?: number;\n}\n\nexport interface GoodputSample { bitrate: number | undefined; }\nexport interface HeadroomSample { healthy: boolean; }\n\nexport const DEFAULT_DWELL_MS = 3_000;\nexport const DEFAULT_JOIN_DEADLINE_MS = 5_000;\n\nexport function goodputFromStats(\n\tprevious: { bytesReceived: number; atMs: number } | undefined,\n\tcurrent: { bytesReceived: number; atMs: number },\n\tminSampleMs = 500,\n): number | undefined {\n\tif (!previous) return undefined;\n\tconst durationMs = current.atMs - previous.atMs;\n\tconst bytes = current.bytesReceived - previous.bytesReceived;\n\tif (durationMs < minSampleMs || bytes <= 0) return undefined;\n\treturn (bytes * 8 * 1000) / durationMs;\n}\n\nexport class MulticastOrchestrator {\n\t#state: OrchestratorState = \"stable\";\n\t#current: MulticastTierEndpoint | undefined;\n\t#target: MulticastTierEndpoint | undefined;\n\t#estimate: number | undefined;\n\t#favored: { key: string; since: number; source: \"goodput\" | \"headroom\" } | undefined;\n\t#joinStartedAt = 0;\n\t/**\n\t * Latest active-tier send time (NTP short), or undefined before the first.\n\t * \"Latest\" is serial order, not the numerically largest value: after the\n\t * field wraps, the largest value is the stale one.\n\t */\n\t#highWater: number | undefined;\n\treadonly #dwellMs: number;\n\treadonly #joinDeadlineMs: number;\n\n\tconstructor(private readonly options: OrchestratorOptions) {\n\t\tthis.#dwellMs = options.dwellMs ?? DEFAULT_DWELL_MS;\n\t\tthis.#joinDeadlineMs = options.joinDeadlineMs ?? DEFAULT_JOIN_DEADLINE_MS;\n\t\tif (!Number.isFinite(this.#dwellMs) || !Number.isFinite(this.#joinDeadlineMs) ||\n\t\t\tthis.#dwellMs <= 0 || this.#joinDeadlineMs <= 0) {\n\t\t\tthrow new Error(\"Multicast dwell and join deadline must be positive\");\n\t\t}\n\t}\n\n\tget state(): OrchestratorState { return this.#state; }\n\tget current(): MulticastTierEndpoint | undefined { return this.#current; }\n\tget target(): MulticastTierEndpoint | undefined { return this.#target; }\n\tget estimate(): number | undefined { return this.#estimate; }\n\n\tstart(): MulticastTierEndpoint | undefined {\n\t\tthis.#current ??= this.options.initialEndpoint ?? selectMulticastTier(this.options.endpoints, undefined);\n\t\treturn this.#current;\n\t}\n\n\tonGoodput(sample: GoodputSample): OrchestratorCommand | undefined {\n\t\tconst bitrate = sample.bitrate;\n\t\tif (!this.#current || this.options.adaptive === false) return undefined;\n\t\tif (this.#state === \"joining\") return this.poll();\n\t\tif (bitrate === undefined || !Number.isFinite(bitrate)) return undefined;\n\t\tthis.#estimate = bitrate;\n\t\tconst desired = selectMulticastTier(this.options.endpoints, bitrate);\n\t\tif (!desired) return undefined;\n\t\tconst desiredKey = endpointKey(desired);\n\t\tif (desiredKey === endpointKey(this.#current)) {\n\t\t\tif (this.#favored?.source === \"goodput\") this.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\t\tif (typeof desired.bandwidth !== \"number\" || typeof this.#current.bandwidth !== \"number\" ||\n\t\t\tdesired.bandwidth >= this.#current.bandwidth) {\n\t\t\tif (this.#favored?.source === \"goodput\") this.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst now = this.#now();\n\t\tif (!this.#favored || this.#favored.key !== desiredKey || this.#favored.source !== \"goodput\") {\n\t\t\tthis.#favored = { key: desiredKey, since: now, source: \"goodput\" };\n\t\t\treturn undefined;\n\t\t}\n\t\tif (now - this.#favored.since < this.#dwellMs) return undefined;\n\n\t\tthis.#state = \"joining\";\n\t\tthis.#target = desired;\n\t\tthis.#joinStartedAt = now;\n\t\tthis.#favored = undefined;\n\t\treturn { kind: \"join-target\", current: this.#current, target: desired };\n\t}\n\n\tonHeadroom(sample: HeadroomSample): OrchestratorCommand | undefined {\n\t\tif (!this.#current || this.options.adaptive === false || this.#state === \"joining\") return undefined;\n\t\tif (!sample.healthy) {\n\t\t\tif (this.#favored?.source === \"headroom\") this.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\t\tif (this.#favored?.source === \"goodput\") return undefined;\n\n\t\tconst currentBandwidth = this.#current.bandwidth;\n\t\tif (typeof currentBandwidth !== \"number\") return undefined;\n\t\t// Ranked here rather than through selectMulticastTier: up-selection\n\t\t// advances exactly one adjacent rung, where the selector fits a budget to\n\t\t// an estimate. The media filter is shared so a repair-only endpoint above\n\t\t// the current rung is not joined as an upgrade, which would swap the\n\t\t// receiver onto repair symbols and no media.\n\t\tconst desired = this.options.endpoints\n\t\t\t.filter((endpoint) =>\n\t\t\t\tcarriesMedia(endpoint) && typeof endpoint.bandwidth === \"number\" && endpoint.bandwidth > currentBandwidth,\n\t\t\t)\n\t\t\t.sort((a, b) => a.bandwidth! - b.bandwidth!)[0];\n\t\tif (!desired) {\n\t\t\tthis.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst desiredKey = endpointKey(desired);\n\t\tif (desiredKey === endpointKey(this.#current)) {\n\t\t\tthis.#favored = undefined;\n\t\t\treturn undefined;\n\t\t}\n\t\tconst now = this.#now();\n\t\tif (!this.#favored || this.#favored.key !== desiredKey) {\n\t\t\tthis.#favored = { key: desiredKey, since: now, source: \"headroom\" };\n\t\t\treturn undefined;\n\t\t}\n\t\tif (now - this.#favored.since < this.#dwellMs) return undefined;\n\n\t\tthis.#state = \"joining\";\n\t\tthis.#target = desired;\n\t\tthis.#joinStartedAt = now;\n\t\tthis.#favored = undefined;\n\t\treturn { kind: \"join-target\", current: this.#current, target: desired };\n\t}\n\n\t/**\n\t * Feed a packet from the active tier (to advance the high-water mark) or\n\t * from the in-flight target (to find a committable RAP).\n\t *\n\t * Serial order is only the true order while the compared instants are less\n\t * than `NTP_SHORT_HALF_RANGE` (2^15 s, ~9.1 h) apart. Feeding every\n\t * active-tier packet keeps the mark that close to the live stream. A mark\n\t * refreshed only by commits can fall further behind than that, and a\n\t * target RAP may then compare as older, so the join is abandoned.\n\t */\n\tonTierPacket(packet: MulticastTierPacket): OrchestratorCommand | undefined {\n\t\tif (!this.#current) return undefined;\n\t\tconst sentAt = tierPacketSendTime(packet);\n\t\t// An instant exactly half the space from the mark (RFC 1982's undefined\n\t\t// case) is neither later nor earlier, so it leaves the mark alone, as a\n\t\t// reordered earlier packet does.\n\t\tif (packet.endpointKey === endpointKey(this.#current) && sentAt !== undefined &&\n\t\t\t(this.#highWater === undefined || compareNtpShort(sentAt, this.#highWater) === \"after\")) {\n\t\t\tthis.#highWater = sentAt;\n\t\t}\n\t\tif (this.#state !== \"joining\" || !this.#target) return undefined;\n\t\tconst expired = this.poll();\n\t\tif (expired) return expired;\n\t\tif (\n\t\t\tpacket.endpointKey !== endpointKey(this.#target) ||\n\t\t\t!packet.keyframe ||\n\t\t\t!packet.hasValidCodecConfig ||\n\t\t\tsentAt === undefined ||\n\t\t\t!this.#reachesHighWater(sentAt)\n\t\t) return undefined;\n\n\t\tconst command: OrchestratorCommand = {\n\t\t\tkind: \"commit-swap\",\n\t\t\ttarget: this.#target,\n\t\t\told: this.#current,\n\t\t};\n\t\tthis.#highWater = sentAt;\n\t\tthis.#current = this.#target;\n\t\tthis.#target = undefined;\n\t\tthis.#state = \"stable\";\n\t\treturn command;\n\t}\n\n\tpoll(): OrchestratorCommand | undefined {\n\t\tif (this.#state !== \"joining\" || !this.#target || !this.#current) return undefined;\n\t\tif (this.#now() - this.#joinStartedAt < this.#joinDeadlineMs) return undefined;\n\t\treturn this.cancel();\n\t}\n\n\tcancel(): OrchestratorCommand | undefined {\n\t\tif (this.#state !== \"joining\" || !this.#target || !this.#current) return undefined;\n\t\tconst command: OrchestratorCommand = {\n\t\t\tkind: \"abandon-join\",\n\t\t\tcurrent: this.#current,\n\t\t\ttarget: this.#target,\n\t\t};\n\t\tthis.#target = undefined;\n\t\tthis.#state = \"stable\";\n\t\treturn command;\n\t}\n\n\t/**\n\t * Whether a target RAP sent at `sentAt` is at or beyond the active tier's\n\t * high-water mark. A pair exactly half the NTP-short space apart has no\n\t * serial order, so it cannot show that and does not commit.\n\t */\n\t#reachesHighWater(sentAt: number): boolean {\n\t\tif (this.#highWater === undefined) return true;\n\t\tconst order = compareNtpShort(sentAt, this.#highWater);\n\t\treturn order === \"after\" || order === \"equal\";\n\t}\n\n\t#now(): number {\n\t\tconst now = this.options.now();\n\t\tif (!Number.isFinite(now)) throw new Error(\"Multicast orchestrator clock must be finite\");\n\t\treturn now;\n\t}\n}\n\n/** A tier packet's NTP-short send time, or undefined when the host had no header to read. */\nfunction tierPacketSendTime(packet: MulticastTierPacket): number | undefined {\n\tif (!Number.isFinite(packet.timestamp)) return undefined;\n\tif (!isNtpShort(packet.timestamp)) {\n\t\tthrow new RangeError(\n\t\t\t`Multicast tier packet timestamp must be an MMTP NTP-short send time, got ${packet.timestamp}`,\n\t\t);\n\t}\n\treturn packet.timestamp;\n}\n", "import { MMTP_PACKET_ID_MAX, MMTP_SIGNALING_PACKET_ID } from \"@blockcast/mmt-base\";\nimport type {\n\tWindowMulticastSubscribeConfig,\n\tWindowMulticastSubscribeConfigBytes,\n} from \"./types.js\";\n\nexport type MulticastTransportPreference = \"auto\" | \"native\" | \"tunnel\";\n\nexport { DEFAULT_AMT_RELAY_PORT } from \"@blockcast/mmt-base\";\n\nexport interface MulticastEndpointConfig {\n\tgroupAddress: string;\n\tsourceAddress?: string;\n\tport: number;\n\tmode?: MulticastTransportPreference;\n\tamtRelay?: string;\n\tamtRelayPort?: number;\n}\n\n/**\n * Input accepted by the canonical window.multicast request converter.\n *\n * `mode` and the `amtRelay*` fields are internal endpoint-selection state;\n * they are deliberately not emitted on the page-facing request. The\n * resulting `origin` is the only acquisition selector understood by the\n * gateway.\n */\nexport interface WindowMulticastEndpointInput {\n\tgroup: string;\n\tport: number;\n\tsource?: string;\n\tmode?: MulticastTransportPreference;\n\tamtRelay?: string;\n\tamtRelayPort?: number;\n\tpacketIds?: readonly number[];\n\ttracks?: ReadonlyArray<{ name: string; packetId: number }>;\n}\n\n/**\n * Convert internal endpoint-selection state to the canonical page contract.\n *\n * Native is the default and an explicit `native` selection always wins over\n * stale relay metadata. Tunnel/AMT requests must carry the resolved relay\n * and port; there is no implicit relay-port default at this boundary.\n *\n * That rule is deliberate and is NOT the place to fix a relay that names no\n * port: this is the last hop before the page-facing request, so a default\n * applied here would be invisible to every caller and to the join it produces.\n * A catalog relay with no port gets `DEFAULT_AMT_RELAY_PORT` upstream, in the\n * readers that resolve it, and arrives here explicit like any other\n * (BLO-38176).\n */\nexport function toWindowMulticastSubscribeConfig(\n\tinput: WindowMulticastEndpointInput,\n): WindowMulticastSubscribeConfig {\n\tif (input.mode !== undefined && ![\"auto\", \"native\", \"tunnel\"].includes(input.mode)) {\n\t\tthrow new TypeError(`Unsupported multicast mode: ${String(input.mode)}`);\n\t}\n\t// An explicit non-array (the null \"endpoint unusable\" sentinel from\n\t// selectEndpointPacketIdsForTracks) must not degrade into \"no filter\".\n\tif (input.packetIds !== undefined && !Array.isArray(input.packetIds)) {\n\t\tthrow new TypeError(\n\t\t\t`multicast packetIds must be an array, got ${input.packetIds === null ? \"null\" : typeof input.packetIds}`,\n\t\t);\n\t}\n\t// Every join request passes through here, so the track packet_id range is\n\t// enforced here rather than trusted from each caller.\n\tif (input.packetIds?.some(id => !isMulticastTrackPacketId(id)) ||\n\t\tinput.tracks?.some(track => !isMulticastTrackPacketId(track.packetId))) {\n\t\tthrow new RangeError(\n\t\t\t`multicast packetId must be an integer from 1 to ${MMTP_PACKET_ID_MAX}; ` +\n\t\t\t\"packetId 0 is the MMTP signaling flow and is never a track's\",\n\t\t);\n\t}\n\n\tconst base = {\n\t\tgroup: input.group,\n\t\tport: input.port,\n\t\t...(input.source?.trim() ? { source: input.source.trim() } : {}),\n\t\tconsume: \"bytes\" as const,\n\t\t...(input.packetIds?.length ? { packetIds: [...input.packetIds] } : {}),\n\t};\n\n\tconst relay = input.amtRelay?.trim();\n\tconst wantsAmt = input.mode === \"tunnel\" ||\n\t\t(input.mode !== \"native\" && relay !== undefined && relay !== \"\");\n\tif (!wantsAmt) {\n\t\tconst native: WindowMulticastSubscribeConfigBytes = {\n\t\t\t...base,\n\t\t\torigin: \"ip-multicast\",\n\t\t\t...(input.tracks?.length ? { tracks: input.tracks.map(({ name, packetId }) => ({ name, packetId })) } : {}),\n\t\t};\n\t\treturn native;\n\t}\n\n\tconst relayPort = input.amtRelayPort;\n\tif (!relay || relayPort === undefined || !isPositivePort(relayPort)) {\n\t\tthrow new TypeError(\"AMT requires a resolved relay and a valid relay port\");\n\t}\n\treturn {\n\t\t...base,\n\t\torigin: \"amt\",\n\t\trelay,\n\t\trelayPort,\n\t};\n}\n\nexport function isValidMulticastGroupAddress(address: string | undefined): boolean {\n\tconst value = address?.trim().toLowerCase();\n\tif (!value) return false;\n\tif (value.startsWith(\"ff\")) return isValidIpv6MulticastAddress(value);\n\n\tconst parts = value.split(\".\");\n\tif (parts.length !== 4) return false;\n\tconst octets = parts.map((part) => {\n\t\tif (!/^\\d+$/.test(part)) return Number.NaN;\n\t\tconst octet = Number(part);\n\t\treturn Number.isInteger(octet) && octet >= 0 && octet <= 255 ? octet : Number.NaN;\n\t});\n\treturn octets.every(Number.isInteger) && octets[0] >= 224 && octets[0] <= 239;\n}\n\nexport function normalizeMulticastEndpoint(\n\tendpoint: MulticastEndpointConfig,\n): MulticastEndpointConfig | undefined {\n\tconst groupAddress = endpoint.groupAddress.trim().toLowerCase();\n\tconst sourceAddress = endpoint.sourceAddress?.trim().toLowerCase() || undefined;\n\tconst amtRelay = endpoint.amtRelay?.trim().toLowerCase() || undefined;\n\tif (!isValidMulticastGroupAddress(groupAddress) || !isPositivePort(endpoint.port)) return undefined;\n\tif (endpoint.mode && ![\"auto\", \"native\", \"tunnel\"].includes(endpoint.mode)) return undefined;\n\tif (endpoint.amtRelayPort !== undefined && !isPositivePort(endpoint.amtRelayPort)) return undefined;\n\n\treturn {\n\t\tgroupAddress,\n\t\t...(sourceAddress ? { sourceAddress } : {}),\n\t\tport: endpoint.port,\n\t\t...(endpoint.mode ? { mode: endpoint.mode } : {}),\n\t\t...(amtRelay ? { amtRelay } : {}),\n\t\t...(endpoint.amtRelayPort ? { amtRelayPort: endpoint.amtRelayPort } : {}),\n\t};\n}\n\n/** Stable ownership key for one IWA multicast/AMT subscription. */\nexport function multicastEndpointKey(endpoint: MulticastEndpointConfig): string {\n\tconst normalized = normalizeMulticastEndpoint(endpoint);\n\tif (!normalized) throw new Error(\"Invalid multicast endpoint\");\n\treturn JSON.stringify([\n\t\tnormalized.sourceAddress ?? \"\",\n\t\tnormalized.groupAddress,\n\t\tnormalized.port,\n\t\tnormalized.mode ?? \"auto\",\n\t\tnormalized.amtRelay ?? \"\",\n\t\tnormalized.amtRelayPort ?? \"\",\n\t]);\n}\n\n/**\n * True when `value` may be advertised as a multicast track's MMTP `packet_id`:\n * an integer in 1..MMTP_PACKET_ID_MAX. packet_id 0 (MMTP_SIGNALING_PACKET_ID)\n * is the MMTP signaling flow -- the FFmpeg `moq_mmt` muxer sends the AL-FEC\n * message (0x0203) on it into the same group as the media -- so it is never a\n * track's. Only a number qualifies: a string, boolean or null is not coerced.\n */\nexport function isMulticastTrackPacketId(value: unknown): value is number {\n\treturn Number.isInteger(value) &&\n\t\t(value as number) > MMTP_SIGNALING_PACKET_ID &&\n\t\t(value as number) <= MMTP_PACKET_ID_MAX;\n}\n\nexport function isUsableMulticastEndpoint(\n\tendpoint: { groupAddress?: string; port?: unknown } | undefined,\n): boolean {\n\tconst port = Number(endpoint?.port);\n\treturn isValidMulticastGroupAddress(endpoint?.groupAddress) && isPositivePort(port);\n}\n\n/**\n * Unprefixed aliases of the `mc_` URL override keys, retired in BLO-37507, each\n * paired with the key that replaces it.\n */\nconst RETIRED_MULTICAST_URL_PARAMS = [\n\t[\"amt_relay\", \"mc_amt_relay\"],\n\t[\"amt_relay_port\", \"mc_amt_relay_port\"],\n] as const;\n\n/**\n * Reject a URL multicast override that still carries a retired unprefixed alias.\n *\n * Both URL readers call this only once a valid override is established (a valid\n * multicast `mc_group` and a valid `mc_port`; `readUrlMulticastConfig` also\n * requires the whole endpoint to normalize), which is where they used to read\n * the aliases: a link that\n * relied on one fails here with its replacement named, instead of silently\n * losing its AMT relay (and, in `readUrlMulticastConfig`, its tunnel mode).\n * Presence is the error, whatever the value and whether or not the `mc_` form\n * is also present: quietly preferring one of two keys is the tolerant read being\n * retired. Outside an override the readers read no override key at all, the\n * `mc_` forms included, so the bare names are left to the page; they are live\n * page parameters elsewhere, and keeping clear of those is what the `mc_`\n * namespace is for.\n */\nexport function assertNoRetiredMulticastUrlParams(params: URLSearchParams): void {\n\tfor (const [retired, replacement] of RETIRED_MULTICAST_URL_PARAMS) {\n\t\tif (params.has(retired)) {\n\t\t\tthrow new Error(\n\t\t\t\t`multicast URL override: query parameter \"${retired}\" is retired; use \"${replacement}\"`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Read an explicit multicast endpoint override from `mc_*` URL parameters.\n * Throws when an override carries a retired unprefixed alias\n * (`assertNoRetiredMulticastUrlParams`).\n */\nexport function readUrlMulticastConfig(\n\tsearch: string | URLSearchParams = typeof location === \"undefined\" ? \"\" : location.search,\n): MulticastEndpointConfig | undefined {\n\tconst params = typeof search === \"string\" ? new URLSearchParams(search) : search;\n\tconst groupAddress = params.get(\"mc_group\") || \"\";\n\tconst port = positivePort(params.get(\"mc_port\"));\n\tif (!groupAddress || !port) return undefined;\n\n\tconst amtRelay = params.get(\"mc_amt_relay\") || undefined;\n\tconst amtRelayPort = positivePort(params.get(\"mc_amt_relay_port\"));\n\tconst requestedMode = params.get(\"mc_mode\");\n\tconst mode = amtRelay ? \"tunnel\" : multicastMode(requestedMode);\n\n\tconst endpoint = normalizeMulticastEndpoint({\n\t\tgroupAddress,\n\t\tsourceAddress: params.get(\"mc_source\") || undefined,\n\t\tport,\n\t\t...(mode ? { mode } : {}),\n\t\t...(amtRelay ? { amtRelay } : {}),\n\t\t...(amtRelayPort ? { amtRelayPort } : {}),\n\t});\n\t// An endpoint that does not normalize (e.g. a non-multicast mc_group) is no\n\t// override, so the page keeps its own bare names; the same scope\n\t// readPlayerMulticastConfig applies by validating the group first.\n\tif (endpoint) assertNoRetiredMulticastUrlParams(params);\n\treturn endpoint;\n}\n\nexport function isMulticastExplicitlyDisabled(\n\tsearch: string | URLSearchParams = typeof location === \"undefined\" ? \"\" : location.search,\n): boolean {\n\tconst params = typeof search === \"string\" ? new URLSearchParams(search) : search;\n\tconst multicast = params.get(\"multicast\")?.trim().toLowerCase();\n\tconst transport = params.get(\"transport\")?.trim().toLowerCase();\n\treturn transport === \"moq-only\" || [\"0\", \"false\", \"no\", \"off\"].includes(multicast || \"\");\n}\n\nfunction isValidIpv6MulticastAddress(value: string): boolean {\n\tif (!/^[0-9a-f:]+$/.test(value)) return false;\n\tconst compressed = value.includes(\"::\");\n\tif (value.indexOf(\"::\") !== value.lastIndexOf(\"::\")) return false;\n\tconst groups = value.split(\":\").filter(Boolean);\n\tif (groups.length === 0 || groups.length > 8) return false;\n\tif (!compressed && groups.length !== 8) return false;\n\treturn groups.every((group) => /^[0-9a-f]{1,4}$/.test(group));\n}\n\nfunction multicastMode(value: string | null): MulticastTransportPreference | undefined {\n\treturn value === \"native\" || value === \"tunnel\" || value === \"auto\" ? value : undefined;\n}\n\nfunction positivePort(value: string | null): number | undefined {\n\tconst port = Number(value);\n\treturn isPositivePort(port) ? port : undefined;\n}\n\nfunction isPositivePort(port: number): boolean {\n\treturn Number.isInteger(port) && port > 0 && port <= 65535;\n}\n", "import type { WindowMulticastSubscription } from \"./types.js\";\n\n/**\n * True when `subscription` delivers packets in batches through the `packets`\n * provider extension (BLO-36599).\n *\n * The pim window-multicast-bridge (0.2.0 and later) advertises the extension\n * in `eventTypes`. It builds only the events some listener asked for, so a\n * consumer that listens to `packets` alone gets one event per reader batch\n * instead of one DOM event per packet. A provider without the field, or one\n * that does not list `packets`, is a v1 provider: listen to `bytes`.\n */\nexport function subscriptionDeliversPacketBatches(\n\tsubscription: Pick<WindowMulticastSubscription, \"eventTypes\">,\n): boolean {\n\treturn subscription.eventTypes?.includes(\"packets\") === true;\n}\n", "import {\n\tmulticastEndpointKey,\n\tnormalizeMulticastEndpoint,\n\ttoWindowMulticastSubscribeConfig,\n} from \"./endpoint-config.js\";\nimport { subscriptionDeliversPacketBatches } from \"./packet-batch.js\";\nimport type {\n\tTransportConfig,\n\tWindowMulticastGateway,\n\tWindowMulticastSubscription,\n\tWindowMulticastSubscriptionState,\n} from \"./types.js\";\n\nexport interface MulticastSubscriptionConsumer {\n\tonBytes(data: ArrayBuffer): void;\n\tonState?(state: WindowMulticastSubscriptionState): void;\n\tonError?(error: Error): void;\n}\n\ninterface PoolEntry {\n\tsubscription: WindowMulticastSubscription;\n\tconsumers: Map<symbol, MulticastSubscriptionConsumer>;\n\t/**\n\t * Exactly one packet surface is listened to: `packets` when the provider\n\t * advertises it, `bytes` otherwise (BLO-36599).\n\t */\n\tpacketListener:\n\t\t| { type: \"bytes\"; listener: (event: CustomEvent<ArrayBuffer>) => void }\n\t\t| { type: \"packets\"; listener: (event: CustomEvent<readonly ArrayBuffer[]>) => void };\n\tstateListener: (event: CustomEvent<WindowMulticastSubscriptionState>) => void;\n\terrorListener: (event: CustomEvent<unknown>) => void;\n\tclosed: boolean;\n}\n\ninterface PoolRecord { claims: number; entry: Promise<PoolEntry>; }\n\nexport interface MulticastSubscriptionLease {\n\treadonly key: string;\n\trelease(): Promise<void>;\n}\n\n/** Shares only IWA multicast/AMT subscriptions. Unicast sessions remain per manager. */\nexport class MulticastSubscriptionPool {\n\treadonly #entries = new Map<string, PoolRecord>();\n\treadonly #gatewayIds = new WeakMap<object, number>();\n\t#nextGatewayId = 1;\n\n\tasync acquire(\n\t\tgateway: WindowMulticastGateway,\n\t\tconfig: TransportConfig,\n\t\tconsumer: MulticastSubscriptionConsumer,\n\t): Promise<MulticastSubscriptionLease> {\n\t\tconst endpoint = normalizeMulticastEndpoint({\n\t\t\tgroupAddress: config.group,\n\t\t\tsourceAddress: config.source,\n\t\t\tport: config.port,\n\t\t\tmode: config.mode,\n\t\t\tamtRelay: config.amtRelay,\n\t\t\tamtRelayPort: config.amtRelayPort,\n\t\t});\n\t\tif (!endpoint) throw new Error(\"Invalid multicast endpoint\");\n\t\tif (typeof gateway.subscribe !== \"function\") throw new Error(\"window.multicast.subscribe unavailable\");\n\n\t\tconst key = multicastEndpointKey(endpoint);\n\t\tconst internalKey = `${this.#gatewayId(gateway)}|${key}`;\n\t\tlet record = this.#entries.get(internalKey);\n\t\tif (!record) {\n\t\t\trecord = { claims: 0, entry: Promise.resolve(null as unknown as PoolEntry) };\n\t\t\tconst createdRecord = record;\n\t\t\trecord.entry = this.#createEntry(gateway, endpoint, (entry) => {\n\t\t\t\tif (this.#entries.get(internalKey) === createdRecord) this.#entries.delete(internalKey);\n\t\t\t\tthis.#detach(entry);\n\t\t\t}).catch((error) => {\n\t\t\t\tif (this.#entries.get(internalKey) === createdRecord) this.#entries.delete(internalKey);\n\t\t\t\tthrow error;\n\t\t\t});\n\t\t\tthis.#entries.set(internalKey, record);\n\t\t}\n\n\t\trecord.claims++;\n\t\tlet entry: PoolEntry;\n\t\ttry {\n\t\t\tentry = await record.entry;\n\t\t} catch (error) {\n\t\t\trecord.claims--;\n\t\t\tthrow error;\n\t\t}\n\t\tif (entry.closed) {\n\t\t\trecord.claims--;\n\t\t\treturn this.acquire(gateway, config, consumer);\n\t\t}\n\t\tconst token = Symbol(key);\n\t\tentry.consumers.set(token, consumer);\n\t\tlet released = false;\n\t\treturn {\n\t\t\tkey,\n\t\t\trelease: async () => {\n\t\t\t\tif (released) return;\n\t\t\t\treleased = true;\n\t\t\t\tentry.consumers.delete(token);\n\t\t\t\trecord.claims--;\n\t\t\t\tif (record.claims > 0) return;\n\t\t\t\tif (this.#entries.get(internalKey) === record) this.#entries.delete(internalKey);\n\t\t\t\tthis.#detach(entry);\n\t\t\t\tif (!entry.closed) await entry.subscription.unsubscribe();\n\t\t\t},\n\t\t};\n\t}\n\n\tget size(): number { return this.#entries.size; }\n\n\tasync #createEntry(\n\t\tgateway: WindowMulticastGateway,\n\t\tendpoint: NonNullable<ReturnType<typeof normalizeMulticastEndpoint>>,\n\t\tonClosed: (entry: PoolEntry) => void,\n\t): Promise<PoolEntry> {\n\t\tconst subscription = await gateway.subscribe!(toWindowMulticastSubscribeConfig({\n\t\t\tgroup: endpoint.groupAddress,\n\t\t\t...(endpoint.sourceAddress ? { source: endpoint.sourceAddress } : {}),\n\t\t\tport: endpoint.port,\n\t\t\t...(endpoint.mode ? { mode: endpoint.mode } : {}),\n\t\t\t...(endpoint.amtRelay ? { amtRelay: endpoint.amtRelay } : {}),\n\t\t\t...(endpoint.amtRelayPort ? { amtRelayPort: endpoint.amtRelayPort } : {}),\n\t\t}));\n\t\tconst consumers = new Map<symbol, MulticastSubscriptionConsumer>();\n\t\tlet entry: PoolEntry;\n\t\t// A batch is delivered as the same per-packet onBytes calls the bytes\n\t\t// surface makes, in arrival order and with each call isolated, so a\n\t\t// consumer cannot tell the two apart; only the DOM event per packet is\n\t\t// gone. The batch array and its elements are the provider's own, uncopied.\n\t\tconst packetListener: PoolEntry[\"packetListener\"] = subscriptionDeliversPacketBatches(subscription)\n\t\t\t? {\n\t\t\t\t\ttype: \"packets\",\n\t\t\t\t\tlistener: (event) => {\n\t\t\t\t\t\tfor (const data of event.detail) {\n\t\t\t\t\t\t\tthis.#dispatch(consumers, (consumer) => consumer.onBytes(data));\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\ttype: \"bytes\",\n\t\t\t\t\tlistener: (event) => {\n\t\t\t\t\t\tthis.#dispatch(consumers, (consumer) => consumer.onBytes(event.detail));\n\t\t\t\t\t},\n\t\t\t\t};\n\t\tconst stateListener = (event: CustomEvent<WindowMulticastSubscriptionState>) => {\n\t\t\tthis.#dispatch(consumers, (consumer) => consumer.onState?.(event.detail));\n\t\t\tif (event.detail === \"closed\" && !entry.closed) {\n\t\t\t\tentry.closed = true;\n\t\t\t\tonClosed(entry);\n\t\t\t}\n\t\t};\n\t\tconst errorListener = (event: CustomEvent<unknown>) => {\n\t\t\tconst error = event.detail instanceof Error ? event.detail : new Error(String(event.detail));\n\t\t\tthis.#dispatch(consumers, (consumer) => consumer.onError?.(error));\n\t\t};\n\t\tentry = { subscription, consumers, packetListener, stateListener, errorListener, closed: false };\n\t\tif (packetListener.type === \"packets\") {\n\t\t\tsubscription.addEventListener(\"packets\", packetListener.listener);\n\t\t} else {\n\t\t\tsubscription.addEventListener(\"bytes\", packetListener.listener);\n\t\t}\n\t\tsubscription.addEventListener(\"state\", stateListener);\n\t\tsubscription.addEventListener(\"error\", errorListener);\n\t\treturn entry;\n\t}\n\n\t#gatewayId(gateway: WindowMulticastGateway): number {\n\t\tconst object = gateway as object;\n\t\tlet id = this.#gatewayIds.get(object);\n\t\tif (!id) {\n\t\t\tid = this.#nextGatewayId++;\n\t\t\tthis.#gatewayIds.set(object, id);\n\t\t}\n\t\treturn id;\n\t}\n\n\t#detach(entry: PoolEntry): void {\n\t\tconst { packetListener } = entry;\n\t\tif (packetListener.type === \"packets\") {\n\t\t\tentry.subscription.removeEventListener(\"packets\", packetListener.listener);\n\t\t} else {\n\t\t\tentry.subscription.removeEventListener(\"bytes\", packetListener.listener);\n\t\t}\n\t\tentry.subscription.removeEventListener(\"state\", entry.stateListener);\n\t\tentry.subscription.removeEventListener(\"error\", entry.errorListener);\n\t}\n\n\t#dispatch(\n\t\tconsumers: Map<symbol, MulticastSubscriptionConsumer>,\n\t\tcallback: (consumer: MulticastSubscriptionConsumer) => void,\n\t): void {\n\t\tfor (const consumer of consumers.values()) {\n\t\t\ttry { callback(consumer); } catch (error) { console.error(\"[Transport] multicast consumer callback failed\", error); }\n\t\t}\n\t}\n}\n\nexport const multicastSubscriptionPool = new MulticastSubscriptionPool();\n", "/**\n * @blockcast/transport - Transport Manager\n *\n * Unified transport abstraction for MoQ players supporting:\n * - window.multicast gateway subscriptions\n * - AMT proxy tunneling (RFC 7450) with optional DRIAD relay discovery\n * - WebTransport/QUIC (MoQ-lite protocol)\n * - WebSocket fallback\n *\n * Transport promotion hierarchy (WebTransport-first for fast startup):\n * 1. WebTransport - immediate connection for fast channel select\n * 2. Background discovery - check if window.multicast is available\n * 3. Promote to gateway multicast if available\n * 4. WebSocket fallback if WebTransport unavailable\n *\n * Features:\n * - Progressive adaptive bitrate based on network conditions\n * - FEC (Forward Error Correction) for burst loss recovery\n * - Seamless transport switching with session continuity\n * - Unified FEC/ABR pipeline across all transport modes\n */\n\nimport type {\n\tTransportConfig,\n\tTransportConnectConfig,\n\tTransportSubscription,\n\tTransportCapabilities,\n\tTransportState,\n\tQualityLevel,\n\tWindowMulticastCapabilities,\n\tWindowMulticastGateway,\n\tWindowMulticastSubscriptionState,\n} from \"./types.js\";\nimport { DRIADDiscovery } from \"./driad-discovery.js\";\nimport { AdaptiveBitrateController } from \"./abr-controller.js\";\nimport {\n\tMulticastOrchestrator,\n\ttype MulticastTierPacket,\n\ttype OrchestratorCommand,\n\ttype OrchestratorOptions,\n} from \"./multicast-orchestrator.js\";\nimport type { MulticastTierEndpoint } from \"./multicast-tier.js\";\nimport { multicastSubscriptionPool } from \"./multicast-subscription-pool.js\";\nimport { normalizeMulticastEndpoint } from \"./endpoint-config.js\";\n\n/**\n * Transport Manager\n *\n * Manages transport connections with automatic mode selection:\n * WebTransport -> window.multicast -> AMT proxy -> WebSocket\n */\nexport class TransportManager {\n\tprivate state: TransportState = {\n\t\tmode: \"disconnected\",\n\t\tconnected: false,\n\t\tpacketsReceived: 0,\n\t\tbytesReceived: 0,\n\t\tlastPacketTime: 0,\n\t};\n\n\tprivate capabilities: TransportCapabilities | null = null;\n\tprivate subscription: TransportSubscription | null = null;\n\tprivate driad = new DRIADDiscovery();\n\t/** Built by `connect()` from the caller's ladder; there is no built-in one. */\n\tprivate abr: AdaptiveBitrateController | null = null;\n\tprivate multicastOrchestrator: MulticastOrchestrator | null = null;\n\tprivate onOrchestratorCommand?: (command: OrchestratorCommand) => void;\n\tprivate connectionGeneration = 0;\n\n\tprivate onPacket?: (data: ArrayBuffer) => void;\n\tprivate onError?: (error: Error) => void;\n\tprivate onStateChange?: (state: TransportState) => void;\n\n\tprivate getWindow(): Window | undefined {\n\t\treturn typeof window === \"undefined\" ? undefined : window;\n\t}\n\n\tprivate getWindowMulticastGateway(): WindowMulticastGateway | undefined {\n\t\treturn this.getWindow()?.multicast;\n\t}\n\n\tprivate async getGatewayCapabilities(\n\t\tgateway: WindowMulticastGateway,\n\t): Promise<WindowMulticastCapabilities | undefined> {\n\t\tif (!gateway?.capabilities) return undefined;\n\t\ttry {\n\t\t\treturn typeof gateway.capabilities === \"function\" ?\n\t\t\t\tawait gateway.capabilities() :\n\t\t\t\tgateway.capabilities;\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[Transport] window.multicast capabilities failed:\", error);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate async gatewaySupportsBytes(gateway: WindowMulticastGateway | undefined): Promise<boolean> {\n\t\tif (typeof gateway?.subscribe !== \"function\") return false;\n\t\tif (!gateway.capabilities) return true;\n\n\t\tconst capabilities = await this.getGatewayCapabilities(gateway);\n\t\tif (!capabilities) return false;\n\t\tconst consumeModes = capabilities.consumeModes;\n\t\treturn !consumeModes || consumeModes.includes(\"bytes\");\n\t}\n\n\t/**\n\t * Detect available transport capabilities\n\t */\n\tasync detectCapabilities(): Promise<TransportCapabilities> {\n\t\tif (this.capabilities) return this.capabilities;\n\n\t\tconst gateway = this.getWindowMulticastGateway();\n\t\tconst hasGatewayBytes = await this.gatewaySupportsBytes(gateway);\n\t\tconst win = this.getWindow();\n\n\t\tthis.capabilities = {\n\t\t\tnativeSSM: hasGatewayBytes,\n\t\t\tnativeIGMP: hasGatewayBytes,\n\t\t\tdirectSockets: typeof win?.UDPSocket !== \"undefined\",\n\t\t\tamt: false,\n\t\t\tdriad: true, // DRIAD uses HTTP proxy, always available\n\t\t\tfecDecode: false,\n\t\t};\n\n\t\tconsole.log(\"[Transport] Capabilities:\", this.capabilities);\n\t\treturn this.capabilities;\n\t}\n\n\t/**\n\t * Connect using best available transport\n\t *\n\t * Promotion hierarchy:\n\t * 1. window.multicast gateway bytes\n\t * 2. AMT proxy (specified or discovered via DRIAD)\n\t * 3. MOQ WebTransport (final fallback)\n\t *\n\t * The ABR ladder (`abrLadder`) and initial rendition (`initialQuality`)\n\t * are checked first, before anything is torn down. A config that fails is\n\t * refused with the same shape as an invalid endpoint, the error to the\n\t * error callback and `false` resolved; unlike one, it is caught before the\n\t * live subscription closes, so a refused reconnect leaves that subscription,\n\t * its state and any connect already in flight untouched.\n\t */\n\tasync connect(config: TransportConnectConfig): Promise<boolean> {\n\t\tlet abr: AdaptiveBitrateController;\n\t\ttry {\n\t\t\tabr = new AdaptiveBitrateController({\n\t\t\t\tladder: config.abrLadder,\n\t\t\t\tinitialQuality: config.initialQuality?.name,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\t// A refused config is a caller bug. Without an error callback, say so\n\t\t\t// on the console, since `false` alone also means \"no multicast path\";\n\t\t\t// with one, the callback is the report.\n\t\t\tconst refused = error instanceof Error ? error : new Error(String(error));\n\t\t\tif (this.onError) this.onError(refused);\n\t\t\telse console.error(\"[Transport] ABR config refused, not connecting:\", refused);\n\t\t\treturn false;\n\t\t}\n\t\tconst generation = ++this.connectionGeneration;\n\t\tthis.subscription?.close();\n\t\tthis.subscription = null;\n\t\t// The previous connection's ladder ends with it, as in disconnect(): a\n\t\t// reconnect that bails below (invalid endpoint, or superseded while\n\t\t// detecting capabilities) leaves no ladder, and only the success path\n\t\t// installs `abr`.\n\t\tthis.abr = null;\n\t\tthis.state = {\n\t\t\tmode: \"disconnected\",\n\t\t\tconnected: false,\n\t\t\tpacketsReceived: 0,\n\t\t\tbytesReceived: 0,\n\t\t\tlastPacketTime: 0,\n\t\t};\n\t\tthis.onStateChange?.(this.state);\n\t\tconst endpoint = normalizeMulticastEndpoint({\n\t\t\tgroupAddress: config.group,\n\t\t\tsourceAddress: config.source,\n\t\t\tport: config.port,\n\t\t\tmode: config.mode,\n\t\t\tamtRelay: config.amtRelay,\n\t\t\tamtRelayPort: config.amtRelayPort,\n\t\t});\n\t\tif (!endpoint) {\n\t\t\tconst error = new Error(\"Invalid multicast endpoint configuration\");\n\t\t\tthis.onError?.(error);\n\t\t\treturn false;\n\t\t}\n\t\tconfig = {\n\t\t\t...config,\n\t\t\tgroup: endpoint.groupAddress,\n\t\t\tsource: endpoint.sourceAddress ?? \"\",\n\t\t\tmode: endpoint.mode,\n\t\t\tamtRelay: endpoint.amtRelay,\n\t\t\tamtRelayPort: endpoint.amtRelayPort,\n\t\t};\n\t\tconst caps = await this.detectCapabilities();\n\t\tif (generation !== this.connectionGeneration) return false;\n\n\t\t// ABR adapts over the caller's ladder (the catalog's renditions),\n\t\t// validated above before anything was torn down.\n\t\tthis.abr = abr;\n\n\t\tabr.setCallback((quality) => {\n\t\t\tthis.state.quality = quality;\n\t\t\tthis.subscription?.onqualitychange?.(quality);\n\t\t\tthis.onStateChange?.(this.state);\n\t\t});\n\n\t\t// Try transport hierarchy\n\t\tlet connected = false;\n\n\t\t// 1. Try the canonical browser multicast gateway.\n\t\tif (caps.nativeSSM || caps.nativeIGMP) {\n\t\t\tconsole.log(\"[Transport] Attempting window.multicast...\");\n\t\t\tconnected = await this.connectWindowMulticast(config, generation);\n\t\t\tif (connected) {\n\t\t\t\tif (generation !== this.connectionGeneration) return false;\n\t\t\t\tthis.state.mode = \"multicast\";\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t// 2. Try AMT proxy (with optional DRIAD discovery)\n\t\tif (!connected) {\n\t\t\tconsole.log(\"[Transport] Attempting AMT tunnel...\");\n\t\t\tconnected = await this.connectAmt(config, generation);\n\t\t\tif (connected) {\n\t\t\t\tif (generation !== this.connectionGeneration) return false;\n\t\t\t\tthis.state.mode = \"amt\";\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t// 3. Fallback to MOQ\n\t\tconsole.log(\"[Transport] All multicast transports failed, falling back to MOQ\");\n\t\treturn false;\n\t}\n\n\t/**\n\t * Connect via the lowercase window.multicast bytes gateway.\n\t */\n\tprivate async connectWindowMulticast(config: TransportConfig, generation: number): Promise<boolean> {\n\t\ttry {\n\t\t\tconst gateway = this.getWindowMulticastGateway();\n\t\t\tif (typeof gateway?.subscribe !== \"function\") return false;\n\n\t\t\tconst lease = await multicastSubscriptionPool.acquire(gateway, config, {\n\t\t\t\tonBytes: (data) => {\n\t\t\t\t\tif (generation === this.connectionGeneration) this.handlePacket(data);\n\t\t\t\t},\n\t\t\t\tonState: (state: WindowMulticastSubscriptionState) => {\n\t\t\t\t\tif (generation !== this.connectionGeneration) return;\n\t\t\t\t\tthis.state.connected = state !== \"closed\";\n\t\t\t\t\tthis.onStateChange?.(this.state);\n\t\t\t\t},\n\t\t\t\tonError: (error) => {\n\t\t\t\t\tif (generation !== this.connectionGeneration) return;\n\t\t\t\t\tconsole.error(\"[Transport] window.multicast error:\", error);\n\t\t\t\t\tthis.onError?.(error);\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (generation !== this.connectionGeneration) {\n\t\t\t\tawait lease.release();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis.subscription = {\n\t\t\t\tid: lease.key,\n\t\t\t\tconfig,\n\t\t\t\tclose: () => { void lease.release(); },\n\t\t\t};\n\n\t\t\tthis.state = {\n\t\t\t\tmode: \"multicast\",\n\t\t\t\tconnected: true,\n\t\t\t\tgroup: config.group,\n\t\t\t\tsource: config.source,\n\t\t\t\tquality: this.requireAbr().getCurrentQuality(),\n\t\t\t\tpacketsReceived: 0,\n\t\t\t\tbytesReceived: 0,\n\t\t\t\tlastPacketTime: 0,\n\t\t\t};\n\n\t\t\tconsole.log(`[Transport] Connected via window.multicast: (${config.source}, ${config.group})`);\n\t\t\tthis.onStateChange?.(this.state);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[Transport] window.multicast failed:\", error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Connect via AMT proxy (with optional DRIAD discovery)\n\t */\n\tprivate async connectAmt(config: TransportConfig, generation: number): Promise<boolean> {\n\t\ttry {\n\t\t\tlet amtRelay = config.amtRelay;\n\n\t\t\t// Use DRIAD to discover relay if not specified (RFC 8777)\n\t\t\t// DRIAD discovers relays based on SOURCE address, not group\n\t\t\tif (!amtRelay && config.driadEnabled !== false && config.source) {\n\t\t\t\tconsole.log(`[Transport] Discovering AMT relay via DRIAD for source ${config.source}...`);\n\t\t\t\tconst relays = await this.driad.discoverRelays(config.source);\n\t\t\t\tif (generation !== this.connectionGeneration) return false;\n\t\t\t\tconst bestRelay = await this.driad.selectBestRelay(relays);\n\t\t\t\tif (generation !== this.connectionGeneration) return false;\n\n\t\t\t\tif (bestRelay) {\n\t\t\t\t\tamtRelay = bestRelay.host;\n\t\t\t\t\tthis.state.amtRelay = amtRelay;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!amtRelay) {\n\t\t\t\tconsole.warn(\"[Transport] No AMT relay available (DRIAD requires source address for SSM)\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Use the WebSocket-based AMT proxy when a relay is known.\n\t\t\treturn await this.connectAmtProxy(config, amtRelay, generation);\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[Transport] AMT connection failed:\", error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Connect via WebSocket-based AMT proxy (browser fallback)\n\t */\n\tprivate async connectAmtProxy(config: TransportConfig, amtRelay: string, generation: number): Promise<boolean> {\n\t\ttry {\n\t\t\tconst relayHost = amtRelay.includes(\":\") && !amtRelay.startsWith(\"[\") ? `[${amtRelay}]` : amtRelay;\n\t\t\t// amtRelayPort is the relay's RFC 7450 UDP port, not the proxy's HTTPS port.\n\t\t\tconst wsUrl = new URL(`wss://${relayHost}/amt-proxy`);\n\t\t\twsUrl.searchParams.set(\"group\", config.group);\n\t\t\twsUrl.searchParams.set(\"source\", config.source || \"\");\n\t\t\twsUrl.searchParams.set(\"port\", String(config.port));\n\n\t\t\tconst ws = new WebSocket(wsUrl);\n\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tws.binaryType = \"arraybuffer\";\n\t\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\t\tws.close();\n\t\t\t\t\treject(new Error(\"AMT proxy connection timeout\"));\n\t\t\t\t}, 10000);\n\n\t\t\t\tws.onopen = () => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (generation !== this.connectionGeneration) {\n\t\t\t\t\t\tws.close();\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconsole.log(`[Transport] Connected to AMT proxy: ${amtRelay}`);\n\n\t\t\t\t\tthis.subscription = {\n\t\t\t\t\t\tid: `amt-proxy-${Date.now()}`,\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\tclose: () => ws.close(),\n\t\t\t\t\t};\n\n\t\t\t\t\tthis.state = {\n\t\t\t\t\t\tmode: \"amt\",\n\t\t\t\t\t\tconnected: true,\n\t\t\t\t\t\tgroup: config.group,\n\t\t\t\t\t\tsource: config.source,\n\t\t\t\t\t\tamtRelay,\n\t\t\t\t\t\tquality: this.requireAbr().getCurrentQuality(),\n\t\t\t\t\t\tpacketsReceived: 0,\n\t\t\t\t\t\tbytesReceived: 0,\n\t\t\t\t\t\tlastPacketTime: 0,\n\t\t\t\t\t};\n\n\t\t\t\t\tthis.onStateChange?.(this.state);\n\t\t\t\t\tresolve(true);\n\t\t\t\t};\n\n\t\t\t\tws.onmessage = (event) => {\n\t\t\t\t\tif (generation === this.connectionGeneration && event.data instanceof ArrayBuffer) {\n\t\t\t\t\t\tthis.handlePacket(event.data);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tws.onerror = (error) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (generation !== this.connectionGeneration) {\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconsole.error(\"[Transport] AMT proxy error:\", error);\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\n\t\t\t\tws.onclose = () => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (generation !== this.connectionGeneration) {\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.state.connected = false;\n\t\t\t\t\tthis.onStateChange?.(this.state);\n\t\t\t\t\tresolve(false);\n\t\t\t\t};\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[Transport] AMT proxy failed:\", error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Handle received packet\n\t */\n\tprivate handlePacket(data: ArrayBuffer) {\n\t\tthis.state.packetsReceived++;\n\t\tthis.state.bytesReceived += data.byteLength;\n\t\tthis.state.lastPacketTime = Date.now();\n\n\t\t// Update ABR with sample\n\t\t// Packets reach here only through a connect()ed join, which built the ABR.\n\t\tthis.abr?.recordSample(data.byteLength, 0);\n\n\t\t// Forward to callback\n\t\tthis.onPacket?.(data);\n\t}\n\n\t/**\n\t * Get current transport state\n\t */\n\tgetState(): TransportState {\n\t\treturn { ...this.state };\n\t}\n\n\t/**\n\t * The rung of the live connection's `abrLadder` ABR is on; `null` when\n\t * there is no ladder (before `connect(config)` succeeds, after\n\t * `disconnect()`, or after a reconnect that was torn down and bailed).\n\t */\n\tgetQuality(): QualityLevel | null {\n\t\treturn this.abr?.getCurrentQuality() ?? null;\n\t}\n\n\t/**\n\t * Force the rung named `name` of the live connection's `abrLadder`.\n\t * Throws `Error` when there is no ladder (see `getQuality()`), and\n\t * `RangeError` when the ladder has no rung of that name; it never falls\n\t * back to another rung.\n\t */\n\tsetQuality(name: string) {\n\t\tthis.requireAbr().setQuality(name);\n\t}\n\n\t/** The controller `connect()` built; before it there is no ladder to adapt over. */\n\tprivate requireAbr(): AdaptiveBitrateController {\n\t\tif (!this.abr) throw new Error(\"[Transport] ABR has no ladder before connect(config)\");\n\t\treturn this.abr;\n\t}\n\n\t/** Configures multicast-only ABR while leaving the caller's MoQ ABR untouched. */\n\tconfigureMulticastTiers(\n\t\toptions: OrchestratorOptions,\n\t\tonCommand?: (command: OrchestratorCommand) => void,\n\t): MulticastTierEndpoint | undefined {\n\t\tthis.dispatchOrchestratorCommand(this.multicastOrchestrator?.cancel());\n\t\tthis.multicastOrchestrator = new MulticastOrchestrator(options);\n\t\tthis.onOrchestratorCommand = onCommand;\n\t\treturn this.multicastOrchestrator.start();\n\t}\n\n\trecordMulticastGoodput(bitrate: number | undefined): OrchestratorCommand | undefined {\n\t\treturn this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onGoodput({ bitrate }));\n\t}\n\n\trecordMulticastHeadroom(healthy: boolean): OrchestratorCommand | undefined {\n\t\treturn this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onHeadroom({ healthy }));\n\t}\n\n\trecordMulticastTierPacket(packet: MulticastTierPacket): OrchestratorCommand | undefined {\n\t\treturn this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onTierPacket(packet));\n\t}\n\n\tpollMulticastTierSwitch(): OrchestratorCommand | undefined {\n\t\treturn this.dispatchOrchestratorCommand(this.multicastOrchestrator?.poll());\n\t}\n\n\t/**\n\t * Set packet callback\n\t */\n\tsetPacketCallback(callback: (data: ArrayBuffer) => void) {\n\t\tthis.onPacket = callback;\n\t}\n\n\t/**\n\t * Set error callback\n\t */\n\tsetErrorCallback(callback: (error: Error) => void) {\n\t\tthis.onError = callback;\n\t}\n\n\t/**\n\t * Set state change callback\n\t */\n\tsetStateCallback(callback: (state: TransportState) => void) {\n\t\tthis.onStateChange = callback;\n\t}\n\n\t/**\n\t * Disconnect\n\t */\n\tdisconnect() {\n\t\tthis.connectionGeneration++;\n\t\tthis.subscription?.close();\n\t\tthis.subscription = null;\n\t\t// No ladder after a disconnect, as before the first connect():\n\t\t// getQuality() is null and setQuality() throws until connect(config).\n\t\tthis.abr = null;\n\t\tthis.dispatchOrchestratorCommand(this.multicastOrchestrator?.cancel());\n\t\tthis.multicastOrchestrator = null;\n\t\tthis.onOrchestratorCommand = undefined;\n\t\tthis.state = {\n\t\t\tmode: \"disconnected\",\n\t\t\tconnected: false,\n\t\t\tpacketsReceived: 0,\n\t\t\tbytesReceived: 0,\n\t\t\tlastPacketTime: 0,\n\t\t};\n\t\tthis.onStateChange?.(this.state);\n\t}\n\n\tprivate dispatchOrchestratorCommand(command: OrchestratorCommand | undefined): OrchestratorCommand | undefined {\n\t\tif (command) this.onOrchestratorCommand?.(command);\n\t\treturn command;\n\t}\n}\n\n// Export singleton instance\nexport const transportManager = new TransportManager();\n", "const MAX_MOQT_PRIORITY = 0xff;\n\ndeclare const subscriberPriorityBrand: unique symbol;\ndeclare const publisherPriorityBrand: unique symbol;\n\nexport type MoqtSubscriberPriority = number & { readonly [subscriberPriorityBrand]: true };\nexport type MoqtPublisherPriority = number & { readonly [publisherPriorityBrand]: true };\n\nfunction moqtPriority(value: number, field: string): number {\n\tif (!Number.isInteger(value) || value < 0 || value > MAX_MOQT_PRIORITY) {\n\t\tthrow new RangeError(`${field} must be an integer in [0, ${MAX_MOQT_PRIORITY}], got ${value}`);\n\t}\n\treturn value;\n}\n\n/** Validate a protocol-native MOQT subscriber priority without transforming it. */\nexport function moqtSubscriberPriority(value: number): MoqtSubscriberPriority {\n\treturn moqtPriority(value, \"MOQT subscriber priority\") as MoqtSubscriberPriority;\n}\n\n/** Validate a protocol-native MOQT publisher priority without transforming it. */\nexport function moqtPublisherPriority(value: number): MoqtPublisherPriority {\n\treturn moqtPriority(value, \"MOQT publisher priority\") as MoqtPublisherPriority;\n}\n\n/** Shared media subscription policy on MOQT's native lower-number-wins scale. */\nexport const MOQT_SUBSCRIBER_PRIORITY = Object.freeze({\n\tcatalog: moqtSubscriberPriority(0),\n\t/**\n\t * @deprecated A repair FETCH's SUBSCRIBER_PRIORITY is derived per request\n\t * from the catalog source and repair priorities (draft-ramadan-moq-fec\n\t * \u00A711.4.5), not a player constant. Use the `subscriberPriority` that\n\t * `@blockcast/mmt-container`'s `wireRepairFetch` puts on each\n\t * `RepairFetchRequest` (`repairFetchSubscriberPriority`).\n\t */\n\tdeadlineRepair: moqtSubscriberPriority(0),\n\taudio: moqtSubscriberPriority(1),\n\tvideo: moqtSubscriberPriority(2),\n\t/**\n\t * @deprecated See `deadlineRepair`: use the request's catalog-derived\n\t * `RepairFetchRequest.subscriberPriority` (draft-ramadan-moq-fec \u00A711.4.5).\n\t */\n\tbackgroundRepair: moqtSubscriberPriority(3),\n});\n\n/** Publisher priority bands from draft-ramadan-moq-fec section 10. */\nexport const MOQT_PUBLISHER_PRIORITY = Object.freeze({\n\tcatalog: moqtPublisherPriority(32),\n\tsource: moqtPublisherPriority(128),\n\trepair: moqtPublisherPriority(240),\n});\n", "/**\n * @blockcast/transport - AMT Gateway\n *\n * Consolidated AMT (Automatic Multicast Tunneling) implementation\n * supporting both Service Worker and Direct Sockets approaches.\n *\n * RFC 7450: Automatic Multicast Tunneling\n */\n\nimport type { GroupKey, PacketMetadata, AmtRelayInfo } from \"./types.js\";\nimport { DRIADDiscovery } from \"./driad-discovery.js\";\nimport { DEFAULT_AMT_RELAY_PORT } from \"@blockcast/mmt-base\";\n\n/** AMT message types */\nconst AMT_MESSAGE_TYPES = {\n\tRELAY_DISCOVERY: 1,\n\tRELAY_ADVERTISEMENT: 2,\n\tREQUEST: 3,\n\tMEMBERSHIP_QUERY: 4,\n\tMEMBERSHIP_UPDATE: 5,\n\tMULTICAST_DATA: 6,\n\tTEARDOWN: 7,\n} as const;\n\n/** AMT gateway states */\nconst GATEWAY_STATES = {\n\tIDLE: \"idle\",\n\tDISCOVERING: \"discovering\",\n\tREQUESTING: \"requesting\",\n\tQUERYING: \"querying\",\n\tACTIVE: \"active\",\n\tLEAVING: \"leaving\",\n\tCLOSED: \"closed\",\n} as const;\n\ntype GatewayState = (typeof GATEWAY_STATES)[keyof typeof GATEWAY_STATES];\n\n/** AMT subscription configuration */\nexport interface AMTSubscriptionConfig {\n\tkey: GroupKey;\n\trelayAddress?: string;\n\trelayPort?: number;\n\tonPacket: (data: ArrayBuffer, metadata: PacketMetadata) => void;\n\tonError: (error: Error) => void;\n}\n\n/** AMT packet info */\nexport interface AMTPacketInfo {\n\tdata: Uint8Array;\n\tsrcAddress: string;\n\tdstAddress: string;\n\tsrcPort: number;\n\tdstPort: number;\n\tgroupKey: string;\n\ttimestamp: number;\n}\n\n/** AMT Gateway options */\nexport interface AMTGatewayOptions {\n\trelayAddress: string;\n\trelayPort?: number;\n\tonPacket?: (packet: AMTPacketInfo) => void;\n\tonStateChange?: (newState: GatewayState, oldState: GatewayState) => void;\n\tonError?: (error: Error) => void;\n\tonLog?: (message: string) => void;\n}\n\n/** Active AMT subscription */\ninterface AMTSubscription {\n\tkey: GroupKey;\n\tsubscriptionId: string;\n\tconfig: AMTSubscriptionConfig;\n\trelay?: AmtRelayInfo;\n\thandshakeComplete: boolean;\n\tkeepAliveTimer?: ReturnType<typeof setInterval>;\n\tlastKeepAlive?: number;\n}\n\n/** Check if address is IPv4 */\nfunction isIPv4(address: string): boolean {\n\treturn /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(address);\n}\n\n/**\n * AMT Gateway Manager (Service Worker based)\n *\n * Manages AMT tunnel subscriptions using Service Worker for UDP communication.\n * Handles DRIAD discovery, AMT handshake, and packet decapsulation.\n */\nexport class AMTGatewayManager {\n\tprivate subscriptions = new Map<string, AMTSubscription>();\n\tprivate messageHandler?: (event: MessageEvent) => void;\n\tprivate pendingPackets = new Map<string, Array<{ data: ArrayBuffer; remoteAddress: string; remotePort: number }>>();\n\tprivate driad = new DRIADDiscovery();\n\n\tconstructor() {\n\t\t// Set up message handler for packets from service worker\n\t\tthis.messageHandler = (event: MessageEvent) => {\n\t\t\tconst data = event.data as { type: string; subscriptionId?: string; data?: unknown; error?: string; message?: string };\n\t\t\tconsole.log(`[AMT] SW message received:`, data.type);\n\t\t\tif (data.type === \"UDP_PACKET\") {\n\t\t\t\tthis.handlePacket(data);\n\t\t\t} else if (data.type === \"SOCKET_ERROR\") {\n\t\t\t\tthis.handleSocketError(data);\n\t\t\t} else if (data.type === \"DEBUG_LOG\") {\n\t\t\t\tconsole.log(`[AMT SW Debug] ${data.message}`);\n\t\t\t}\n\t\t};\n\n\t\t// Register message handler\n\t\tif (typeof navigator !== \"undefined\" && navigator.serviceWorker) {\n\t\t\tnavigator.serviceWorker.addEventListener(\"message\", this.messageHandler);\n\t\t\tconsole.log(\"[AMT] Registered service worker message handler\");\n\t\t} else {\n\t\t\tconsole.warn(\"[AMT] Service worker not available\");\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe via AMT tunnel\n\t */\n\tasync subscribe(config: AMTSubscriptionConfig): Promise<string> {\n\t\tconst { key } = config;\n\t\tconst keyStr = this.getKeyString(key);\n\n\t\tif (this.subscriptions.has(keyStr)) {\n\t\t\tconsole.log(`[AMT] Reusing existing subscription to ${keyStr}`);\n\t\t\treturn keyStr;\n\t\t}\n\n\t\ttry {\n\t\t\tlet relayAddress: string;\n\t\t\tlet discoveryMethod: \"driad\" | \"manual\";\n\t\t\tconst relayPort = config.relayPort || DEFAULT_AMT_RELAY_PORT;\n\n\t\t\tif (config.relayAddress) {\n\t\t\t\trelayAddress = config.relayAddress;\n\t\t\t\tdiscoveryMethod = \"manual\";\n\t\t\t\tconsole.log(`[AMT] Using configured relay ${relayAddress}:${relayPort}`);\n\t\t\t} else if (key.source) {\n\t\t\t\t// DRIAD (RFC 8777) discovers relays based on SOURCE address, not group\n\t\t\t\tconsole.log(`[AMT] No relay configured, discovering via DRIAD for source ${key.source}...`);\n\t\t\t\tconst relays = await this.driad.discoverRelays(key.source);\n\t\t\t\tconst bestRelay = await this.driad.selectBestRelay(relays);\n\n\t\t\t\tif (!bestRelay) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`AMT relay discovery failed for source ${key.source}. ` +\n\t\t\t\t\t\t\t`Please specify a relay address in the subscription configuration.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\trelayAddress = bestRelay.host;\n\t\t\t\tdiscoveryMethod = \"driad\";\n\t\t\t\tconsole.log(`[AMT] Discovered relay via DRIAD: ${relayAddress}:${relayPort}`);\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`AMT relay discovery requires a source address for SSM (RFC 8777). ` +\n\t\t\t\t\t\t`Please specify either a relay address or source address.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst subscriptionId = keyStr;\n\n\t\t\t// Initialize packet queue for handshake\n\t\t\tthis.pendingPackets.set(subscriptionId, []);\n\n\t\t\t// Create subscription\n\t\t\tconst subscription: AMTSubscription = {\n\t\t\t\tkey,\n\t\t\t\tsubscriptionId,\n\t\t\t\tconfig,\n\t\t\t\thandshakeComplete: false,\n\t\t\t\trelay: {\n\t\t\t\t\taddress: relayAddress,\n\t\t\t\t\tport: relayPort,\n\t\t\t\t\tdiscoveryMethod,\n\t\t\t\t\tdiscoveredAt: Date.now(),\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tthis.subscriptions.set(keyStr, subscription);\n\n\t\t\t// Note: Actual UDP socket creation and handshake would require\n\t\t\t// Service Worker communication - this is a simplified version\n\t\t\tconsole.log(`[AMT] Subscription created for ${keyStr} via ${relayAddress}:${relayPort}`);\n\n\t\t\treturn keyStr;\n\t\t} catch (error) {\n\t\t\tconst subscription = this.subscriptions.get(keyStr);\n\t\t\tif (subscription) {\n\t\t\t\tthis.subscriptions.delete(keyStr);\n\t\t\t\tthis.pendingPackets.delete(subscription.subscriptionId);\n\t\t\t}\n\n\t\t\tthrow new Error(`Failed to subscribe via AMT: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Unsubscribe from AMT tunnel\n\t */\n\tasync unsubscribe(handle: string): Promise<void> {\n\t\tconst subscription = this.subscriptions.get(handle);\n\t\tif (!subscription) {\n\t\t\tthrow new Error(`Subscription ${handle} not found`);\n\t\t}\n\n\t\ttry {\n\t\t\t// Stop keep-alive timer\n\t\t\tif (subscription.keepAliveTimer) {\n\t\t\t\tclearInterval(subscription.keepAliveTimer);\n\t\t\t}\n\n\t\t\tconsole.log(`[AMT] Unsubscribed from ${handle}`);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[AMT] Error during unsubscribe:\", error);\n\t\t} finally {\n\t\t\tthis.subscriptions.delete(handle);\n\t\t\tthis.pendingPackets.delete(subscription.subscriptionId);\n\t\t}\n\t}\n\n\t/**\n\t * Get list of active subscriptions\n\t */\n\tgetSubscriptions(): string[] {\n\t\treturn Array.from(this.subscriptions.keys());\n\t}\n\n\t/**\n\t * Handle packet from service worker\n\t */\n\tprivate handlePacket(message: { subscriptionId?: string; data?: unknown }): void {\n\t\tconst { subscriptionId, data } = message;\n\t\tif (!subscriptionId) return;\n\n\t\tconst subscription = this.subscriptions.get(subscriptionId);\n\t\tif (!subscription) {\n\t\t\tconsole.warn(`[AMT] Received packet for unknown subscription: ${subscriptionId}`);\n\t\t\treturn;\n\t\t}\n\n\t\t// During handshake, queue packets\n\t\tif (!subscription.handshakeComplete) {\n\t\t\tconst queue = this.pendingPackets.get(subscriptionId);\n\t\t\tconst packetData = data as { data: ArrayBuffer; remoteAddress: string; remotePort: number };\n\t\t\tif (queue) {\n\t\t\t\tqueue.push({\n\t\t\t\t\tdata: packetData.data,\n\t\t\t\t\tremoteAddress: packetData.remoteAddress,\n\t\t\t\t\tremotePort: packetData.remotePort,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// After handshake, forward to callback\n\t\tconst packetData = data as { data: ArrayBuffer };\n\t\tconst metadata: PacketMetadata = {\n\t\t\tsourceAddress: subscription.relay?.address || \"unknown\",\n\t\t\tsourcePort: subscription.relay?.port || 0,\n\t\t\ttransport: \"tunnel\",\n\t\t\ttimestamp: performance.now(),\n\t\t};\n\n\t\tsubscription.config.onPacket(packetData.data, metadata);\n\t}\n\n\t/**\n\t * Handle socket error from service worker\n\t */\n\tprivate handleSocketError(message: { subscriptionId?: string; error?: string }): void {\n\t\tconst { subscriptionId, error } = message;\n\t\tif (!subscriptionId) return;\n\n\t\tconst subscription = this.subscriptions.get(subscriptionId);\n\t\tif (!subscription) {\n\t\t\tconsole.warn(`[AMT] Received error for unknown subscription: ${subscriptionId}`);\n\t\t\treturn;\n\t\t}\n\n\t\tsubscription.config.onError(new Error(error || \"Unknown error\"));\n\t}\n\n\t/**\n\t * Cleanup on destroy\n\t */\n\tdestroy(): void {\n\t\tif (this.messageHandler && typeof navigator !== \"undefined\" && navigator.serviceWorker) {\n\t\t\tnavigator.serviceWorker.removeEventListener(\"message\", this.messageHandler);\n\t\t}\n\n\t\tconst handles = Array.from(this.subscriptions.keys());\n\t\tfor (const handle of handles) {\n\t\t\tthis.unsubscribe(handle).catch((err) => {\n\t\t\t\tconsole.error(`[AMT] Error unsubscribing ${handle}:`, err);\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Get key string for HashMap\n\t */\n\tprivate getKeyString(key: GroupKey): string {\n\t\tconst source = key.source ? `@${key.source}` : \"\";\n\t\treturn `${key.group}:${key.port}${source}`;\n\t}\n}\n\n/**\n * AMT Gateway Client (Direct Sockets based)\n *\n * Connects to an AMT relay to receive multicast data via unicast tunnel.\n * Implements the gateway side of RFC 7450.\n * Supports multiple (S,G) groups over a single AMT tunnel.\n */\nexport class AMTGateway {\n\tprivate relayAddress: string;\n\tprivate relayPort: number;\n\tprivate groups = new Map<\n\t\tstring,\n\t\t{\n\t\t\tgroupPort: number;\n\t\t\tonPacket: ((packet: AMTPacketInfo) => void) | null;\n\t\t\tjoinedAt: number;\n\t\t}\n\t>();\n\n\t// Callbacks\n\tprivate onPacket: (packet: AMTPacketInfo) => void;\n\tprivate onStateChange: (newState: GatewayState, oldState: GatewayState) => void;\n\tprivate onError: (error: Error) => void;\n\tprivate onLog: (message: string) => void;\n\n\t// State\n\tprivate state: GatewayState = GATEWAY_STATES.IDLE;\n\tprivate nonce: Uint8Array | null = null;\n\tprivate responseMAC: Uint8Array | null = null;\n\tprivate relayConfirmedAddress: string | null = null;\n\tprivate lastDataTime = 0;\n\n\t// Stats\n\tprivate stats = {\n\t\tpacketsReceived: 0,\n\t\tbytesReceived: 0,\n\t\tqueriesReceived: 0,\n\t\tupdatesSet: 0,\n\t};\n\n\tconstructor(options: AMTGatewayOptions) {\n\t\tthis.relayAddress = options.relayAddress;\n\t\tthis.relayPort = options.relayPort || DEFAULT_AMT_RELAY_PORT;\n\n\t\tthis.onPacket = options.onPacket || (() => {});\n\t\tthis.onStateChange = options.onStateChange || (() => {});\n\t\tthis.onError = options.onError || ((err) => console.error(\"AMT Error:\", err));\n\t\tthis.onLog = options.onLog || ((msg) => console.log(\"AMT:\", msg));\n\t}\n\n\t/**\n\t * Generate random nonce\n\t */\n\tprivate generateNonce(): Uint8Array {\n\t\tconst nonce = new Uint8Array(4);\n\t\tcrypto.getRandomValues(nonce);\n\t\treturn nonce;\n\t}\n\n\t/**\n\t * Set gateway state\n\t */\n\tprivate setState(newState: GatewayState): void {\n\t\tconst oldState = this.state;\n\t\tthis.state = newState;\n\t\tthis.onLog(`State: ${oldState} -> ${newState}`);\n\t\tthis.onStateChange(newState, oldState);\n\t}\n\n\t/**\n\t * Join a multicast group\n\t */\n\tasync joinGroup(\n\t\tgroupAddress: string,\n\t\tgroupPort: number,\n\t\tsourceAddress: string | null = null,\n\t\tonPacket: ((packet: AMTPacketInfo) => void) | null = null,\n\t): Promise<string> {\n\t\tconst groupKey = `${sourceAddress || \"*\"},${groupAddress}`;\n\n\t\tif (this.groups.has(groupKey)) {\n\t\t\tthis.onLog(`Group already joined: ${groupKey}`);\n\t\t\treturn groupKey;\n\t\t}\n\n\t\tthis.groups.set(groupKey, {\n\t\t\tgroupPort,\n\t\t\tonPacket,\n\t\t\tjoinedAt: Date.now(),\n\t\t});\n\n\t\tthis.onLog(`Joined group: ${groupKey} (port ${groupPort})`);\n\t\treturn groupKey;\n\t}\n\n\t/**\n\t * Leave a multicast group\n\t */\n\tasync leaveGroup(groupAddress: string, sourceAddress: string | null = null): Promise<boolean> {\n\t\tconst groupKey = `${sourceAddress || \"*\"},${groupAddress}`;\n\n\t\tif (!this.groups.has(groupKey)) {\n\t\t\tthis.onLog(`Group not found: ${groupKey}`);\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.groups.delete(groupKey);\n\t\tthis.onLog(`Left group: ${groupKey} (${this.groups.size} remaining)`);\n\n\t\tif (this.groups.size === 0) {\n\t\t\tthis.onLog(\"No groups remaining - closing AMT tunnel\");\n\t\t\tawait this.close();\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Get list of active groups\n\t */\n\tgetGroups(): Array<{\n\t\tgroupKey: string;\n\t\tgroupAddress: string;\n\t\tsourceAddress: string | null;\n\t\tgroupPort: number;\n\t\tjoinedAt: number;\n\t}> {\n\t\treturn Array.from(this.groups.entries()).map(([key, info]) => {\n\t\t\tconst [source, group] = key.split(\",\");\n\t\t\treturn {\n\t\t\t\tgroupKey: key,\n\t\t\t\tgroupAddress: group,\n\t\t\t\tsourceAddress: source !== \"*\" ? source : null,\n\t\t\t\tgroupPort: info.groupPort,\n\t\t\t\tjoinedAt: info.joinedAt,\n\t\t\t};\n\t\t});\n\t}\n\n\t/**\n\t * Open connection to AMT relay\n\t */\n\tasync open(): Promise<void> {\n\t\tif (this.state !== GATEWAY_STATES.IDLE) {\n\t\t\tthrow new Error(`Cannot open in state: ${this.state}`);\n\t\t}\n\n\t\tthis.setState(GATEWAY_STATES.DISCOVERING);\n\t\tthis.onLog(`Connecting to AMT relay ${this.relayAddress}:${this.relayPort}`);\n\t\tthis.onLog(`Active groups: ${this.groups.size}`);\n\n\t\t// Generate session nonce\n\t\tthis.nonce = this.generateNonce();\n\t\tthis.onLog(`Generated nonce: ${Array.from(this.nonce).map((b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`);\n\n\t\t// Note: Actual UDPSocket connection would go here\n\t\t// This is a simplified version showing the state machine\n\t}\n\n\t/**\n\t * Close connection\n\t */\n\tasync close(): Promise<void> {\n\t\tif (this.state === GATEWAY_STATES.CLOSED) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setState(GATEWAY_STATES.LEAVING);\n\n\t\t// Clear all groups\n\t\tthis.groups.clear();\n\n\t\tthis.setState(GATEWAY_STATES.CLOSED);\n\t\tthis.onLog(\"AMT Gateway closed\");\n\t}\n\n\t/**\n\t * Get current statistics\n\t */\n\tgetStats(): typeof this.stats {\n\t\treturn { ...this.stats };\n\t}\n\n\t/**\n\t * Get current state\n\t */\n\tgetState(): GatewayState {\n\t\treturn this.state;\n\t}\n\n\t/**\n\t * Check if gateway is active and receiving data\n\t */\n\tisActive(): boolean {\n\t\treturn this.state === GATEWAY_STATES.ACTIVE;\n\t}\n}\n\n// Export singleton manager instance\nexport const amtGatewayManager = new AMTGatewayManager();\n", "/**\n * Thin transport wrapper for SSM multicast \u2014 join, socket, watchdog,\n * takeover/failback. Delegates raw packets to a callback (MmtpRouter\n * or any direct consumer).\n */\n\nimport {\n isMulticastTrackPacketId,\n subscriptionDeliversPacketBatches,\n toWindowMulticastSubscribeConfig,\n type WindowMulticastDelivery,\n type WindowMulticastGateway,\n type WindowMulticastSubscription,\n} from '@blockcast/mmt-transport'\nimport {\n MMTP_PACKET_ID_MAX,\n MMTP_SIGNALING_PACKET_ID,\n readMMTPPacketId,\n} from '@blockcast/mmt-base'\n\nexport interface MulticastTransportConfig {\n sourceAddress: string\n groupAddress: string\n port: number\n mode?: 'auto' | 'native' | 'tunnel'\n amtRelay?: string\n amtRelayPort?: number\n packetIds?: number[]\n tracks?: { name: string; packetId: number }[]\n}\n\nexport type ResolvedMulticastTransport = 'native' | 'amt'\n\n/**\n * Why a joined transport handed control back to MoQ.\n *\n * - `silence`: the data plane was live (at least one accepted packet since the\n * join) and then stayed silent for the catalog-derived failback window.\n * - `subscription-error`: the provider reported the subscription failed. This\n * is the provider's own explicit, typed outcome (a `MulticastError`, branch\n * on its `code`), e.g. a bridge whose AMT tunnel it declared dead.\n */\nexport type MulticastFailbackReason =\n | { readonly kind: 'silence'; readonly windowMs: number }\n | { readonly kind: 'subscription-error'; readonly error: unknown }\n\n/**\n * Why a join() resolved false, named by the step at which the join stopped,\n * never by what an error message says.\n *\n * - `disposed`: the transport was disposed before the join started, or while\n * the provider's subscribe() was pending.\n * - `unavailable`: there is no window.multicast gateway with a subscribe().\n * - `superseded`: a newer join() started while this join's subscribe() was\n * pending; the newer join owns the transport.\n * - `invalid-request`: the join config does not make a valid window.multicast\n * request (toWindowMulticastSubscribeConfig refused it), so the provider was\n * never asked.\n * - `provider-rejected`: the provider's subscribe() rejected or threw. The only\n * cause that carries the provider's own verdict (`providerError`).\n * - `invalid-subscription`: subscribe() resolved something that is not a usable\n * subscription: no event surface, a malformed `eventTypes`, an unknown\n * `delivery`, or an `addEventListener` that threw.\n * - `delivery-hook-failed`: the consumer's onDelivery hook threw for the\n * delivery the subscription declared, e.g. 'ordered-relay' to FEC tracks\n * configured without ordered geometry. A local configuration failure, not\n * the provider's.\n */\nexport type MulticastJoinDeclineReason =\n | 'disposed'\n | 'unavailable'\n | 'superseded'\n | 'invalid-request'\n | 'provider-rejected'\n | 'invalid-subscription'\n | 'delivery-hook-failed'\n\n/**\n * The typed part of a provider's subscribe() rejection. `name` and `code` are\n * the provider's own typed fields; the frozen multicast contract makes `code`\n * the one to branch on (e.g. a bridge's `amt_relay_refused`). `message` is\n * free text carried for humans only: never branch on it.\n */\nexport interface MulticastJoinProviderError {\n readonly name: string\n readonly message: string\n /** Present only when the provider's error carries a string `code`. */\n readonly code?: string\n}\n\n/**\n * How a join() resolved: joined, or the typed reason it resolved false.\n * `providerError` is present only for `provider-rejected`, and only when the\n * provider's rejection value carries a string `name` and `message`.\n */\nexport type MulticastJoinOutcome =\n | { readonly ok: true }\n | {\n readonly ok: false\n readonly reason: MulticastJoinDeclineReason\n readonly providerError?: MulticastJoinProviderError\n }\n\n/**\n * Receive counters plus the ingress-filter drop counters.\n *\n * `packetsReceived` / `bytesReceived` count only packets that passed the\n * allow-list and were handed to the page \u2014 a rejected packet must never\n * inflate them. The `dropped*` counters are the other half of that story:\n * without them a filtered PID is indistinguishable from a PID that was never\n * transmitted, which is exactly the ambiguity that makes an all-zero FEC\n * repair count unreadable (a repair leg dropped here and a repair leg the\n * publisher never sent produce byte-identical downstream telemetry).\n */\nexport interface MulticastTransportStats {\n packetsReceived: number\n bytesReceived: number\n /** Parsed cleanly, but the packet ID is absent from the join's allow-list. */\n packetsDroppedUnregisteredPid: number\n /** MMTP header did not parse; the packet ID is unknown, so it cannot be attributed. */\n packetsDroppedUnparseable: number\n /**\n * packet_id 0, the MMTP signaling flow. Expected traffic on a protected\n * session (moq_mmt sends the AL-FEC message on it), so it is counted apart\n * from the unregistered-PID fault counter and never takes a\n * `droppedPacketIds` slot.\n */\n packetsDroppedSignaling: number\n}\n\n/**\n * join() calls by how they resolved, cumulative for the life of the transport:\n * across rejoins, failed joins and dispose. `succeeded` plus every `declined`\n * count is the number of join() calls that resolved, superseded ones\n * included; a join() that rejects counts in none (see\n * MulticastTransport.lastJoinOutcome).\n */\nexport interface MulticastJoinStats {\n /** join() calls that resolved true. */\n readonly succeeded: number\n /** join() calls that resolved false, by the reason each one did. */\n readonly declined: Readonly<Record<MulticastJoinDeclineReason, number>>\n}\n\nconst JOIN_SUCCEEDED: MulticastJoinOutcome = Object.freeze({ ok: true })\n\n/**\n * The typed part of a provider's subscribe() rejection, or undefined when the\n * rejection value is not an error with a string `name` and `message`. Nothing\n * is filled in for a field the provider did not type, and the message is\n * carried, never read.\n */\nfunction providerErrorOf(error: unknown): MulticastJoinProviderError | undefined {\n if (typeof error !== 'object' || error === null) return undefined\n const fields = error as { readonly name?: unknown; readonly message?: unknown; readonly code?: unknown }\n let name: unknown\n let message: unknown\n let code: unknown\n try {\n name = fields.name\n message = fields.message\n code = fields.code\n } catch {\n // A rejection value whose fields throw on read (a revoked Proxy) types\n // nothing. Reading it must not turn a declined join into a rejected one.\n return undefined\n }\n if (typeof name !== 'string' || typeof message !== 'string') return undefined\n return Object.freeze(typeof code === 'string' ? { name, message, code } : { name, message })\n}\n\n/**\n * Distinct unregistered PIDs retained for diagnostics. A conformant catalog\n * advertises a handful of PIDs per endpoint, so this only has to be large\n * enough to name every leg of a mis-provisioned one; the cap exists so a\n * malformed or hostile stream cycling the 16-bit PID space cannot grow the map\n * without bound. Drops keep counting in aggregate after the cap.\n */\nconst MAX_TRACKED_DROPPED_PIDS = 16\n\ntype MulticastGlobals = typeof globalThis & {\n multicast?: WindowMulticastGateway\n}\n\nfunction getMulticastGlobals(): MulticastGlobals {\n return globalThis as MulticastGlobals\n}\n\nfunction hasMulticastGateway(): boolean {\n return typeof getMulticastGlobals().multicast?.subscribe === 'function'\n}\n\n/**\n * The packet one `bytes` event (or one element of a `packets` batch) carries,\n * as a view of the provider's own bytes. A Uint8Array is used as it is; the\n * bridge hands every packet over as one, so the common path builds nothing.\n */\nfunction normalizeBytesEventDetail(detail: unknown): Uint8Array | null {\n const value =\n detail && typeof detail === 'object' && 'data' in detail\n ? (detail as { data?: unknown }).data\n : detail\n if (value instanceof Uint8Array) return value\n if (value instanceof ArrayBuffer) return new Uint8Array(value)\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength)\n }\n return null\n}\n\n/**\n * A provider that can replace a live subscription's MMTP packet-ID filter\n * without rejoining its socket: the frozen multicast contract's\n * `Subscription.setPacketIds` (pim `packages/multicast-contract`). It is not\n * declared on every provider this transport meets, so it is detected, never\n * assumed.\n */\ninterface PacketFilterUpdater {\n setPacketIds(packetIds: readonly number[]): Promise<void> | void\n}\n\nfunction packetFilterUpdater(\n subscription: WindowMulticastSubscription,\n): PacketFilterUpdater['setPacketIds'] | undefined {\n const update = (subscription as Partial<PacketFilterUpdater>).setPacketIds\n return typeof update === 'function' ? update.bind(subscription) : undefined\n}\n\nfunction sameIds(a: ReadonlySet<number>, b: ReadonlySet<number>): boolean {\n if (a.size !== b.size) return false\n for (const id of a) if (!b.has(id)) return false\n return true\n}\n\n/**\n * The provider-side packet filter of one live join, and the in-place updates\n * sent to it. It lives exactly as long as the join: a later join or release\n * replaces it, and an acknowledgement for a replaced one is ignored.\n */\ninterface ProviderFilter {\n /**\n * The filter as the newest acknowledged update left it: the packet IDs the\n * provider forwards, or null when it forwards every ID. Until an update is\n * acknowledged it is the join's own filter.\n */\n acknowledged: ReadonlySet<number> | null\n /** Updates sent to the provider, numbered from 1 in send order. */\n sent: number\n /** The number of the newest acknowledged update; 0 for the join's own filter. */\n acknowledgedUpdate: number\n /** Updates sent and not yet settled. */\n inFlight: number\n}\n\n/** Which packet surface a join listens to, and its listener. */\ntype PacketListener =\n | { readonly type: 'bytes'; readonly listener: (event: CustomEvent<unknown>) => void }\n | { readonly type: 'packets'; readonly listener: (event: CustomEvent<readonly unknown[]>) => void }\n\nexport class MulticastTransport<TPacketContext = never> {\n #onPacket: (packet: Uint8Array, context?: TPacketContext) => void\n #subscription: WindowMulticastSubscription | null = null\n #packetListener: PacketListener | null = null\n #resolvedTransport: ResolvedMulticastTransport | undefined\n #videoConfirmed = false\n #audioConfirmed = false\n // One armed no-packets timer per silence window (BLO-36599). Packets only\n // record #lastPacketAtMs; the timer re-arms for the remainder when it wakes\n // inside the window, instead of a clear+set pair on every packet.\n #watchdog: ReturnType<typeof setTimeout> | null = null\n #lastPacketAtMs = 0\n #failbackWindowMs: number | null = null\n #onTakeover: (() => void) | null = null\n #onFailback: ((reason: MulticastFailbackReason) => void) | null = null\n // Data-plane-live edge: the first packet this join accepted. Everything\n // before it is join latency, not silence (see #resetWatchdog).\n #dataPlaneLive = false\n #onDataPlaneLive: (() => void) | null = null\n #errorListener: ((event: CustomEvent<unknown>) => void) | null = null\n #onDelivery: ((delivery: WindowMulticastDelivery) => void) | null = null\n #delivery: WindowMulticastDelivery | undefined\n #disposed = false\n #stats: MulticastTransportStats = {\n packetsReceived: 0,\n bytesReceived: 0,\n packetsDroppedUnregisteredPid: 0,\n packetsDroppedUnparseable: 0,\n packetsDroppedSignaling: 0,\n }\n // join() calls by how they resolved; see joinStats.\n #joinsSucceeded = 0\n readonly #joinsDeclined: Record<MulticastJoinDeclineReason, number> = {\n disposed: 0,\n unavailable: 0,\n superseded: 0,\n 'invalid-request': 0,\n 'provider-rejected': 0,\n 'invalid-subscription': 0,\n 'delivery-hook-failed': 0,\n }\n // The newest resolved join's outcome (see lastJoinOutcome) and the join\n // generation it belongs to. Generations are issued in start order, so a\n // join that resolves after a newer join has recorded leaves it standing.\n #lastJoinOutcome: MulticastJoinOutcome | undefined = undefined\n #lastJoinOutcomeGeneration = 0\n #droppedPidCounts = new Map<number, number>()\n // Accepted bytes per MMTP packet_id; see bytesReceivedForPacketIds. Each\n // value is a mutable cell, so the per-packet update is one lookup and an\n // in-place add: no insertion once the key exists, and no re-boxed number\n // per packet once a count outgrows the small-integer range.\n readonly #acceptedBytesByPacketId = new Map<number, { bytes: number }>()\n #packetIds: ReadonlySet<number> | null = null\n // What the live join delivers with each accepted packet. A field, not a\n // listener closure, so an in-place admission change (setContext) applies\n // from the next packet on without rejoining.\n #context: TPacketContext | undefined = undefined\n // The live join's provider-side filter. An in-place widening must reach it,\n // or the provider never delivers the new ID; null without a live join.\n #providerFilter: ProviderFilter | null = null\n // IDs an in-place update withdrew while the provider may still be\n // delivering them. Their late packets are still counted as filtered, but\n // they are the expected tail of a deliberate change, not a provisioning\n // fault, so they are counted apart and do not raise the unadvertised-PID\n // warning.\n readonly #withdrawnPacketIds = new Set<number>()\n #withdrawnPacketIdDrops = 0\n // Distinct packet IDs dropped as provisioning faults: on the wire but in\n // no allow-list the join or an in-place update declared. Capped with\n // droppedPacketIds.\n readonly #unadvertisedPacketIds = new Set<number>()\n #joinGeneration = 0\n\n constructor(onPacket: (packet: Uint8Array, context?: TPacketContext) => void) {\n this.#onPacket = onPacket\n }\n\n static isAvailable(): boolean {\n return hasMulticastGateway()\n }\n\n async join(config: MulticastTransportConfig, context?: TPacketContext): Promise<boolean> {\n const generation = ++this.#joinGeneration\n this.#releaseSubscription()\n this.#dataPlaneLive = false\n\n if (this.#disposed) return this.#declined(generation, 'disposed')\n if (!MulticastTransport.isAvailable()) {\n console.warn('[MulticastTransport] Multicast API not available')\n return this.#declined(generation, 'unavailable')\n }\n\n // What a throw from the step in progress means. Each step names its\n // failure before it runs, so the catch classifies a decline by where the\n // join stopped, never by what the error says.\n let failure: MulticastJoinDeclineReason = 'invalid-request'\n try {\n const packetIds = config.packetIds?.length ? new Set(config.packetIds) : null\n console.log(\n `[MulticastTransport] window.multicast.subscribe(${config.sourceAddress}, ${config.groupAddress}, ${config.port})`,\n )\n const subscribeConfig = toWindowMulticastSubscribeConfig({\n source: config.sourceAddress,\n group: config.groupAddress,\n port: config.port,\n mode: config.mode,\n amtRelay: config.amtRelay,\n amtRelayPort: config.amtRelayPort,\n packetIds: config.packetIds,\n tracks: config.tracks,\n })\n failure = 'provider-rejected'\n const subscription = await getMulticastGlobals().multicast!.subscribe!(subscribeConfig)\n failure = 'invalid-subscription'\n console.log(`[MulticastTransport] window.multicast.subscribe returned:`, !!subscription)\n\n if (this.#disposed || generation !== this.#joinGeneration) {\n void subscription.unsubscribe()\n return this.#declined(generation, this.#displacedReason())\n }\n if (typeof subscription.addEventListener !== 'function') {\n if (typeof subscription.unsubscribe === 'function') {\n void subscription.unsubscribe()\n }\n throw new Error('window.multicast subscription does not support bytes events')\n }\n\n // Own the subscription before anything below can throw, so a failed\n // join's release reaches unsubscribe() instead of leaking it.\n this.#subscription = subscription\n const live = () => !this.#disposed && generation === this.#joinGeneration\n // Exactly one packet surface. A provider that advertises the `packets`\n // extension delivers each reader batch as one event, so the page pays\n // one DOM event and one liveness read per batch instead of per packet\n // (BLO-36599). Every packet still goes through the same filter, counters\n // and callback, in arrival order, as a view of the provider's bytes.\n const packetListener: PacketListener = subscriptionDeliversPacketBatches(subscription)\n ? {\n type: 'packets',\n listener: (event) => {\n if (!live()) return\n this.#deliverBatch(event.detail, live)\n },\n }\n : {\n type: 'bytes',\n listener: (event) => {\n if (!live()) return\n if (this.#deliver(event.detail)) this.#resetWatchdog()\n },\n }\n this.#packetListener = packetListener\n this.#packetIds = packetIds\n this.#providerFilter = {\n acknowledged: packetIds,\n sent: 0,\n acknowledgedUpdate: 0,\n inFlight: 0,\n }\n this.#context = context\n // Seed the allow-list's counters so accepted packets never insert. An\n // id already counted by an earlier join keeps its count: the counters\n // are cumulative across rejoins, as bytesReceived is.\n if (packetIds) {\n for (const packetId of packetIds) {\n if (!this.#acceptedBytesByPacketId.has(packetId)) {\n this.#acceptedBytesByPacketId.set(packetId, { bytes: 0 })\n }\n }\n }\n // The request origin is the accepted acquisition contract. The\n // gateway no longer exposes a mutable transport/transportchange\n // surface, so assign this only after subscribe() has resolved and the\n // returned subscription has passed the event-surface check above.\n this.#resolvedTransport = subscribeConfig.origin === 'amt' ? 'amt' : 'native'\n // The provider alone knows whether its packets are independent\n // datagrams or an ordered relay of one socket; undeclared promises no\n // ordering. Announce it before the listener can deliver a packet. A\n // rejected declaration fails the join and releases the subscription.\n const delivery = subscription.delivery ?? 'datagram'\n if (delivery !== 'datagram' && delivery !== 'ordered-relay') {\n throw new Error(\n `window.multicast subscription declared unknown delivery ${String(delivery)}`,\n )\n }\n this.#delivery = delivery\n failure = 'delivery-hook-failed'\n this.#onDelivery?.(delivery)\n failure = 'invalid-subscription'\n // The provider's explicit, terminal subscription failure. Before the\n // first packet this is the only signal that bounds a join, so it must\n // reach the failback path rather than be dropped.\n const onError = (event: CustomEvent<unknown>) => {\n if (this.#disposed || generation !== this.#joinGeneration) return\n this.#failBackOnSubscriptionError(event.detail)\n }\n this.#errorListener = onError\n if (packetListener.type === 'packets') {\n subscription.addEventListener('packets', packetListener.listener)\n } else {\n subscription.addEventListener('bytes', packetListener.listener)\n }\n subscription.addEventListener('error', onError)\n // No watchdog here. A resolved subscribe() is the provider's join\n // outcome, not proof that the data plane is live: over AMT it resolves\n // once the gateway has sent its Membership Update, and the relay's\n // upstream join plus one path RTT still precede the first packet. The\n // silence window is a packet-gap clock for a stream that has flowed;\n // charging join latency to it failed back joins that went on to play.\n // The join phase is bounded by the provider's typed error or the host's\n // takeover deadline (see SessionManager.joinActive). The window starts\n // at the first accepted packet (#resetWatchdog).\n console.log(\n `[MulticastTransport] Joined ${config.groupAddress}:${config.port}`,\n )\n return this.#joined(generation)\n } catch (err) {\n if (this.#disposed || generation !== this.#joinGeneration) {\n return this.#declined(generation, this.#displacedReason())\n }\n this.#releaseSubscription()\n console.error('[MulticastTransport] Failed to join SSM group:', err)\n return this.#declined(\n generation,\n failure,\n failure === 'provider-rejected' ? providerErrorOf(err) : undefined,\n )\n }\n }\n\n /** Record a join that resolved true; returns its result. */\n #joined(generation: number): true {\n this.#joinsSucceeded++\n this.#recordJoinOutcome(generation, JOIN_SUCCEEDED)\n return true\n }\n\n /** Record a join that resolved false, and why; returns its result. */\n #declined(\n generation: number,\n reason: MulticastJoinDeclineReason,\n providerError?: MulticastJoinProviderError,\n ): false {\n this.#joinsDeclined[reason]++\n this.#recordJoinOutcome(\n generation,\n Object.freeze(\n providerError === undefined ? { ok: false, reason } : { ok: false, reason, providerError },\n ),\n )\n return false\n }\n\n /**\n * Why a join that no longer owns the transport resolved false. dispose()\n * advances the join generation too, so disposal is checked first.\n */\n #displacedReason(): 'disposed' | 'superseded' {\n return this.#disposed ? 'disposed' : 'superseded'\n }\n\n #recordJoinOutcome(generation: number, outcome: MulticastJoinOutcome): void {\n if (generation <= this.#lastJoinOutcomeGeneration) return\n this.#lastJoinOutcomeGeneration = generation\n this.#lastJoinOutcome = outcome\n }\n\n /**\n * One packet from the subscription: filter, count, and hand it to the\n * callback. Returns whether it was delivered; the caller notes liveness.\n */\n #deliver(detail: unknown): boolean {\n const packet = normalizeBytesEventDetail(detail)\n if (!packet) {\n this.#stats.packetsDroppedUnparseable++\n return false\n }\n if (!this.#acceptPacket(packet)) return false\n // Read per packet: a callback that changes the admission (an in-place\n // rendition switch committing on this very packet) takes effect from the\n // next packet, including the rest of a batch.\n this.#onPacket(packet, this.#context)\n return true\n }\n\n /**\n * One `packets` event. Each packet is delivered as a `bytes` event would\n * have delivered it, including isolation: a packet whose callback throws\n * does not cost the rest of its batch, as it would not have cost the next\n * `bytes` event. The first failure is rethrown once the batch is done, so\n * it reaches the event-listener error reporting a `bytes` listener's throw\n * reached; any further one is rethrown from its own microtask, which the\n * host reports the same way, so no failure is swallowed. A callback that\n * disposes the transport or starts another join ends the batch there, as\n * the per-packet guard would have ignored the remaining events.\n */\n #deliverBatch(\n batch: readonly unknown[],\n live: () => boolean,\n ): void {\n if (!Array.isArray(batch)) {\n throw new TypeError('[MulticastTransport] packets event detail must be an array of packets')\n }\n let delivered = false\n let failures: unknown[] | null = null\n for (let i = 0; i < batch.length; i++) {\n try {\n if (this.#deliver(batch[i])) delivered = true\n } catch (error) {\n if (failures === null) failures = []\n failures.push(error)\n }\n if (!live()) break\n }\n // One liveness read covers the batch: every packet in it arrived in the\n // same provider task. A batch that delivered nothing, like a rejected\n // bytes event, is not liveness.\n if (delivered && live()) this.#resetWatchdog()\n if (failures !== null) {\n for (let i = 1; i < failures.length; i++) {\n const failure = failures[i]\n queueMicrotask(() => {\n throw failure\n })\n }\n throw failures[0]\n }\n }\n\n /** Feed a raw packet from an external source (e.g., AMT tunnel or ASM subscription). */\n feedPacket(data: ArrayBuffer | Uint8Array, context?: TPacketContext): void {\n if (this.#disposed) return\n const packet = data instanceof Uint8Array ? data : new Uint8Array(data)\n if (!this.#acceptPacket(packet)) return\n this.#onPacket(packet, context)\n this.#resetWatchdog()\n }\n\n confirmVideo(): void {\n this.#videoConfirmed = true\n this.#checkTakeover()\n }\n\n confirmAudio(): void {\n this.#audioConfirmed = true\n this.#checkTakeover()\n }\n\n onTakeover(cb: () => void): void {\n this.#onTakeover = cb\n }\n\n onFailback(cb: (reason: MulticastFailbackReason) => void): void {\n this.#onFailback = cb\n }\n\n /**\n * Called on every successful join, before the joined subscription can\n * deliver a packet, with how that subscription delivers packets.\n */\n onDelivery(cb: (delivery: WindowMulticastDelivery) => void): void {\n this.#onDelivery = cb\n }\n\n /** How the current subscription delivers packets, once joined. */\n get delivery(): WindowMulticastDelivery | undefined {\n return this.#delivery\n }\n\n /**\n * Fires once per join, at the first packet the join accepts (after the PID\n * filter; a rejected packet is not liveness). This is the data-plane-live\n * edge, where this transport's silence window starts. A join whose source\n * never sends never reaches it, so a host may start a media-arrival budget\n * here only if the provider bounds the join phase with a typed subscription\n * error (see SessionManager.joinActive); otherwise the host starts it when\n * the subscribe settles.\n */\n onDataPlaneLive(cb: () => void): void {\n this.#onDataPlaneLive = cb\n }\n\n /** Whether the current join has accepted a packet (see onDataPlaneLive). */\n get dataPlaneLive(): boolean {\n return this.#dataPlaneLive\n }\n\n setFailbackWindowMs(windowMs: number): void {\n if (!Number.isFinite(windowMs) || windowMs <= 0) {\n throw new Error(\n `[MulticastTransport] failbackWindowMs=${windowMs}. ` +\n `Derive a positive failback window from the stream catalog.`,\n )\n }\n this.#failbackWindowMs = windowMs\n // An armed timer was scheduled against the previous window; re-derive its\n // wake-up from the last packet so a narrower window is not enforced late.\n if (this.#watchdog !== null) {\n clearTimeout(this.#watchdog)\n const silentMs = performance.now() - this.#lastPacketAtMs\n this.#armWatchdog(Math.max(0, Math.ceil(windowMs - silentMs)))\n }\n }\n\n get stats(): MulticastTransportStats {\n return this.#stats\n }\n\n /**\n * How the newest join() to resolve did: `{ ok: true }`, or `{ ok: false }`\n * with the typed reason it resolved false (and, for `provider-rejected`, the\n * provider's typed error fields). Undefined until a join resolves. Branch on\n * `reason` and `providerError.code`, never on log text or\n * `providerError.message`.\n *\n * Newest is by start order: a join that resolves after a newer join has\n * recorded its outcome does not replace it, so once `await join()` resolves\n * this names that join's outcome unless a newer join has already resolved.\n * It records how a join resolved, not whether it is still joined (see\n * `joined`); a join() that rejects instead of resolving records nothing,\n * the rejection being its report. Frozen: a later outcome replaces the\n * object, never changes it.\n */\n get lastJoinOutcome(): MulticastJoinOutcome | undefined {\n return this.#lastJoinOutcome\n }\n\n /**\n * join() calls by how they resolved (see MulticastJoinStats). A fresh frozen\n * snapshot on every read, so a later join never changes one already read.\n * Kept apart from `stats`, which is the live counter object.\n */\n get joinStats(): MulticastJoinStats {\n return Object.freeze({\n succeeded: this.#joinsSucceeded,\n declined: Object.freeze({ ...this.#joinsDeclined }),\n })\n }\n\n /**\n * Accepted bytes summed over `packetIds`: the `bytesReceived` share of those\n * MMTP packet_ids. Counted by the same rule, on every path that counts\n * `bytesReceived` (only packets that pass the ingress filter; packet_id 0\n * signaling never), and cumulative for the life of the transport, across\n * rejoins, as `bytesReceived` is. An id never accepted contributes 0. An\n * unparseable packet admitted by a join without an allow-list has no\n * packet_id, so it counts toward `bytesReceived` only.\n *\n * Use it to scope an ABR throughput sample to the active tier's source and\n * repair packet_ids when the join admits more than the active tier (a\n * shared group joined across rendition switches); `bytesReceived` is then\n * the whole allow-list's arrival, not the active tier's. A Set, so an id\n * shared by two tracks of the tier is counted once.\n *\n * Monotonic only for a fixed set: each id has counted since it was first\n * admitted, so after a tier switch the new set's sum is not a continuation\n * of the old one. A cumulative consumer takes a fresh baseline for the new\n * set and never differences across sets.\n */\n bytesReceivedForPacketIds(packetIds: ReadonlySet<number>): number {\n let bytes = 0\n for (const packetId of packetIds) {\n bytes += this.#acceptedBytesByPacketId.get(packetId)?.bytes ?? 0\n }\n return bytes\n }\n\n /**\n * Per-PID drop breakdown, newest-capped at MAX_TRACKED_DROPPED_PIDS distinct\n * PIDs. This is what names the offender: `packetsDroppedUnregisteredPid`\n * says the ingress filter fired, this says it fired on 0x8001.\n */\n get droppedPacketIds(): ReadonlyMap<number, number> {\n return this.#droppedPidCounts\n }\n\n /**\n * Distinct packet IDs dropped as provisioning faults: arriving on the wire\n * but in no allow-list the join or an in-place update (setPacketIds)\n * declared, the drops that raise the unadvertised-PID warning. A packet ID\n * an in-place update withdrew is not one (see withdrawnPacketIdDrops).\n * Cumulative across rejoins and capped with droppedPacketIds.\n */\n get unadvertisedPacketIds(): ReadonlySet<number> {\n return this.#unadvertisedPacketIds\n }\n\n /**\n * Packets dropped because an in-place update (setPacketIds) withdrew their\n * packet ID while the provider may still deliver it: the expected tail of\n * a deliberate change. A subset of packetsDroppedUnregisteredPid,\n * cumulative across rejoins.\n */\n get withdrawnPacketIdDrops(): number {\n return this.#withdrawnPacketIdDrops\n }\n\n /** Whether a subscription is live: joined, and not yet released or replaced. */\n get joined(): boolean {\n return this.#subscription !== null\n }\n\n /**\n * What the live join delivers with every accepted packet (the join's\n * `context`, or the last setContext), or undefined without a live join.\n */\n get context(): TPacketContext | undefined {\n return this.#context\n }\n\n /**\n * The live join's page-side packet-ID allow-list, or null when it admits\n * every packet ID (a join that carried none, or no live join).\n */\n get packetIds(): ReadonlySet<number> | null {\n return this.#packetIds\n }\n\n /**\n * Replace what the live join delivers with each accepted packet, from the\n * next packet on. The subscription (socket, group membership, AMT tunnel)\n * is untouched: this is how an in-place rendition switch moves the router's\n * ingress identities without leaving and re-joining the group.\n */\n setContext(context: TPacketContext): void {\n if (this.#subscription === null) {\n throw new Error(\n '[MulticastTransport] setContext needs a live join; join() carries the first context',\n )\n }\n this.#context = context\n }\n\n /**\n * Replace the live join's packet-ID allow-list in place, without rejoining.\n *\n * The page-side filter changes synchronously, before this returns: the\n * next packet is admitted or dropped by the new list. The provider's own\n * filter is updated through the subscription's `setPacketIds` when it has\n * one; the returned promise settles with the provider's acknowledgement of\n * this update. It resolves at once, telling the provider nothing, only\n * when the provider cannot be told, or when no earlier update is still in\n * flight and the provider already forwards exactly this list.\n *\n * While an update is in flight the provider's filter is not known, so every\n * later update is sent: one skipped because it matched the last\n * acknowledged filter would let the in-flight update land after it, and\n * leave the provider forwarding a list nobody asked for last. Updates are\n * numbered in send order and the newest acknowledged one describes the\n * provider, so an acknowledgement that overtakes an older one is not undone\n * by it.\n *\n * A provider without `setPacketIds` can still be narrowed: its wider filter\n * keeps delivering the dropped IDs and the page-side filter discards them.\n * It cannot be widened, since it would never deliver the added IDs; that is\n * refused with a throw before anything changes. An ID this update withdraws\n * may keep arriving until the provider applies the narrower list; those\n * packets are counted as filtered like any other and in\n * withdrawnPacketIdDrops, but are not unadvertised packet IDs: their\n * arrival is the expected tail of this change rather than a provisioning\n * fault.\n */\n setPacketIds(packetIds: readonly number[]): Promise<void> {\n const subscription = this.#subscription\n const filter = this.#providerFilter\n if (subscription === null || filter === null) {\n throw new Error(\n '[MulticastTransport] setPacketIds needs a live join; join() carries the first allow-list',\n )\n }\n if (!Array.isArray(packetIds) || packetIds.length === 0) {\n throw new RangeError(\n '[MulticastTransport] setPacketIds needs a non-empty packet-ID list: ' +\n 'an in-place update names the packet IDs it admits',\n )\n }\n if (!packetIds.every(isMulticastTrackPacketId)) {\n throw new RangeError(\n `[MulticastTransport] packetId must be an integer from 1 to ${MMTP_PACKET_ID_MAX}; ` +\n \"packetId 0 is the MMTP signaling flow and is never a track's\",\n )\n }\n const next: ReadonlySet<number> = new Set(packetIds)\n const provider = filter.acknowledged\n const update = packetFilterUpdater(subscription)\n // Without an updater nothing is ever sent, so the join's filter is exact.\n if (update === undefined && provider !== null) {\n const missing = [...next].filter((id) => !provider.has(id))\n if (missing.length > 0) {\n throw new Error(\n '[MulticastTransport] this subscription cannot widen its packet filter in place ' +\n `(the provider exposes no setPacketIds); rejoin to admit ${missing.join(', ')}`,\n )\n }\n }\n const previous = this.#packetIds\n this.#packetIds = next\n for (const id of next) {\n this.#withdrawnPacketIds.delete(id)\n if (!this.#acceptedBytesByPacketId.has(id)) {\n this.#acceptedBytesByPacketId.set(id, { bytes: 0 })\n }\n }\n if (previous !== null) {\n for (const id of previous) if (!next.has(id)) this.#withdrawnPacketIds.add(id)\n }\n if (update === undefined) return Promise.resolve()\n if (filter.inFlight === 0 && provider !== null && sameIds(provider, next)) {\n return Promise.resolve()\n }\n const number = ++filter.sent\n filter.inFlight++\n let applied: Promise<void>\n try {\n applied = Promise.resolve(update([...next]))\n } catch (error) {\n applied = Promise.reject(error)\n }\n return applied.then(\n () => {\n if (this.#providerFilter !== filter) return\n filter.inFlight--\n if (number > filter.acknowledgedUpdate) {\n filter.acknowledgedUpdate = number\n filter.acknowledged = next\n }\n },\n (error: unknown) => {\n // A refused update leaves the provider on the filter it had.\n if (this.#providerFilter === filter) filter.inFlight--\n throw error\n },\n )\n }\n\n get resolvedTransport(): ResolvedMulticastTransport | undefined {\n return this.#resolvedTransport\n }\n\n dispose(): void {\n ++this.#joinGeneration\n this.#disposed = true\n this.#releaseSubscription()\n }\n\n #releaseSubscription(): void {\n if (this.#watchdog) clearTimeout(this.#watchdog)\n this.#watchdog = null\n if (this.#subscription) {\n const packetListener = this.#packetListener\n if (packetListener?.type === 'packets') {\n this.#subscription.removeEventListener('packets', packetListener.listener)\n } else if (packetListener) {\n this.#subscription.removeEventListener('bytes', packetListener.listener)\n }\n if (this.#errorListener) {\n this.#subscription.removeEventListener('error', this.#errorListener)\n }\n void this.#subscription.unsubscribe()\n }\n this.#subscription = null\n this.#packetListener = null\n this.#errorListener = null\n this.#resolvedTransport = undefined\n this.#delivery = undefined\n this.#packetIds = null\n this.#providerFilter = null\n this.#context = undefined\n this.#withdrawnPacketIds.clear()\n }\n\n /** The ingress filter; an accepted packet is counted here, once. */\n #acceptPacket(packet: Uint8Array): boolean {\n // This early read is intentional: rejected PIDs must not affect page\n // delivery, receive statistics, or the failback liveness watchdog. It\n // refuses exactly what parseMMTPHeader refuses but builds no header\n // object; the router parses the header once, later (BLO-36599).\n const packetId = readMMTPPacketId(packet)\n if (packetId === null) {\n // Without an allow-list there is no filter to enforce, so an unparseable\n // packet keeps flowing to the router as before.\n if (!this.#packetIds) {\n this.#countAccepted(packet, null)\n return true\n }\n this.#stats.packetsDroppedUnparseable++\n return false\n }\n // packet_id 0 is the MMTP signaling flow and never a track's. Drop it even\n // when the join carried no allow-list (a legacy catalog whose tracks omit\n // packetId): the discard rule must not depend on how the catalog was written.\n if (packetId === MMTP_SIGNALING_PACKET_ID) {\n this.#stats.packetsDroppedSignaling++\n return false\n }\n if (!this.#packetIds || this.#packetIds.has(packetId)) {\n this.#countAccepted(packet, packetId)\n return true\n }\n this.#recordUnregisteredPidDrop(packetId)\n return false\n }\n\n /** Receive counters for one accepted packet; `packetId` is null when it has none. */\n #countAccepted(packet: Uint8Array, packetId: number | null): void {\n this.#stats.packetsReceived++\n this.#stats.bytesReceived += packet.byteLength\n if (packetId === null) return\n const counter = this.#acceptedBytesByPacketId.get(packetId)\n if (counter !== undefined) {\n counter.bytes += packet.byteLength\n } else {\n // First sight of this id on a join with no allow-list (an allow-list's\n // ids are seeded at join). Bounded by the 16-bit packet_id space.\n this.#acceptedBytesByPacketId.set(packetId, { bytes: packet.byteLength })\n }\n }\n\n #recordUnregisteredPidDrop(packetId: number): void {\n this.#stats.packetsDroppedUnregisteredPid++\n const withdrawn = this.#withdrawnPacketIds.has(packetId)\n if (withdrawn) this.#withdrawnPacketIdDrops++\n const seen = this.#droppedPidCounts.get(packetId)\n if (seen !== undefined) {\n this.#droppedPidCounts.set(packetId, seen + 1)\n return\n }\n if (this.#droppedPidCounts.size >= MAX_TRACKED_DROPPED_PIDS) return\n this.#droppedPidCounts.set(packetId, 1)\n if (withdrawn) return\n this.#unadvertisedPacketIds.add(packetId)\n // Warn once per distinct PID. A PID arriving on the wire but missing from\n // the catalog's advertised set is a provisioning fault, not stream noise,\n // and it is silent in every downstream counter.\n console.warn(\n `[MulticastTransport] Dropping packetId=${packetId} (0x${packetId.toString(16)}): ` +\n `not in the advertised set [${[...(this.#packetIds ?? [])].join(', ')}]. ` +\n `If this is an FEC repair leg, the catalog is not advertising its packetId.`,\n )\n }\n\n #checkTakeover(): void {\n if (this.#videoConfirmed && this.#audioConfirmed && this.#onTakeover) {\n const cb = this.#onTakeover\n this.#onTakeover = null // fire exactly once\n cb()\n }\n }\n\n /**\n * Note liveness now; arm the no-packets timer if none is armed.\n *\n * Called only for accepted packets, so the first call of a join is the\n * data-plane-live edge and is what first arms the silence window: the window\n * measures silence of a flowing stream, which is undefined before the stream\n * has flowed.\n */\n #resetWatchdog(): void {\n this.#lastPacketAtMs = performance.now()\n if (!this.#dataPlaneLive) {\n this.#dataPlaneLive = true\n this.#onDataPlaneLive?.()\n }\n if (this.#watchdog !== null) return\n if (!this.#onFailback) return\n if (this.#failbackWindowMs === null) {\n throw new Error(\n '[MulticastTransport] Failback watchdog requires a catalog-derived window',\n )\n }\n this.#armWatchdog(this.#failbackWindowMs)\n }\n\n #armWatchdog(delayMs: number): void {\n this.#watchdog = setTimeout(() => {\n this.#watchdog = null\n if (this.#disposed || !this.#onFailback || this.#failbackWindowMs === null) return\n const silentMs = performance.now() - this.#lastPacketAtMs\n if (silentMs < this.#failbackWindowMs) {\n this.#armWatchdog(Math.ceil(this.#failbackWindowMs - silentMs))\n return\n }\n console.warn(\n `[MulticastTransport] No packets for ${this.#failbackWindowMs}ms; failing back to MoQ`,\n )\n this.#onFailback({ kind: 'silence', windowMs: this.#failbackWindowMs })\n }, delayMs)\n }\n\n /** Hand control back to MoQ for a provider-reported subscription failure. */\n #failBackOnSubscriptionError(error: unknown): void {\n // The subscription is dead; a silence timer armed against it would only\n // report the same failure a second time.\n if (this.#watchdog !== null) clearTimeout(this.#watchdog)\n this.#watchdog = null\n console.warn('[MulticastTransport] Subscription failed; failing back to MoQ:', error)\n this.#onFailback?.({ kind: 'subscription-error', error })\n }\n}\n", "/**\n * Facade composing FECManager + MmtpRouter + MulticastTransport.\n *\n * Public methods delegate to the decomposed components. Applications that\n * need individual layers can use them directly:\n * - FECManager \u2014 codec-agnostic FEC decode\n * - MmtpRouter \u2014 MMTP packet parsing + frame routing\n * - MulticastTransport \u2014 SSM join/socket/watchdog\n */\n\nimport { parseFecBlockProfile, type FecBlockProfile } from \"@blockcast/mmt-base\";\nimport {\n FECManager,\n MIN_INTERLEAVE_DEPTH_MS,\n type FECManagerConfig,\n type WasmFecDecoder,\n type FecDecoderUnavailableReason,\n type FecFallbackDecoder,\n type MulticastSymbolDelivery,\n type RepairVia,\n} from \"./fec-manager.js\";\nimport {\n deriveBlockClassifierGeometry,\n repairPriorityForClass,\n type BlockClass,\n type ClassifierMode,\n type ClassifierReason,\n type FrameGapDecision,\n type ReclassificationEvidence,\n type RepairPriority,\n type TrackKind,\n} from \"./block-policy.js\";\nimport type {\n RepairFetchHost,\n RepairPriorityReason,\n RepairSkipReason,\n RepairSubscribeCallback,\n} from \"./repair-fetch.js\";\nimport {\n MmtpRouter,\n type FrameCallback,\n type ManifestIdentityResolver,\n type MmtpIngressContext,\n type MmtpStreamIdentity,\n type StagedStreamSwitch,\n type StreamSwitchCommit,\n type TrackPacketCallback,\n} from \"./mmtp-router.js\";\nimport { isMulticastTrackPacketId } from \"@blockcast/mmt-transport\";\nimport {\n MulticastTransport,\n type MulticastFailbackReason,\n type MulticastJoinOutcome,\n type MulticastJoinStats,\n} from \"./multicast-transport.js\";\nimport { computeFecDeliveryWindowMs } from \"./track-session.js\";\nimport { DEFAULT_AMT_RELAY_PORT } from \"./catalog-network-source.js\";\nimport { deriveFecInterleaveDepth, FecBlockMapper } from \"@blockcast/mmt-fec\";\nimport type {\n FecBlockSnapshot,\n FecTrackConfig,\n FecTrackStats,\n FecWorkerClient,\n} from \"@blockcast/fec-worker\";\nimport type { MfuReassemblerBackend } from \"./mfu-reassembler.js\";\nimport type { MfuReassemblyPolicy } from \"./mfu-reassembly-policy.js\";\nimport type {\n ManifestObjectIdentity,\n ManifestVerifier,\n} from \"@blockcast/mmt-manifest-verify\";\nimport type {\n BlockAssemblerConfig,\n CmafBlockSample,\n} from \"./cmaf-block-assembler.js\";\nimport {\n hasSwitchingSetRole,\n parseTrackRole,\n validateSwitchingSet,\n isFecCatalogConfig,\n isFecCatalogGeometry,\n type FecCatalogConfig,\n type TrackRole,\n} from \"./carp-catalog.js\";\nimport { catalogFecUnavailable, type TrackFecUnavailable } from \"./catalog-fec-params.js\";\n\nexport interface MulticastEndpointConfig {\n sourceAddress: string;\n groupAddress: string;\n port: number;\n /** Explicit gateway transport policy. Omitted means gateway-selected auto mode. */\n mode?: 'auto' | 'native' | 'tunnel';\n /** AMT relay hostname/IP from catalog endpoint. */\n relayAddress?: string;\n /** AMT relay port from catalog endpoint. */\n relayPort?: number;\n /** AMT relay hostname/IP from player/UI overrides. Wins over `relayAddress`. */\n amtRelay?: string;\n /** AMT relay port from player/UI overrides. Wins over `relayPort`. */\n amtRelayPort?: number;\n /** MMTP packet IDs to forward before data enters the page transport queue. */\n packetIds?: number[];\n /** Exact catalog tracks carried by this physical multicast endpoint. */\n tracks?: MmtpIngressContext[\"tracks\"];\n /**\n * The catalog `fec.blockProfile` (BLO-35597): `iso-ssbg1-v1`, the only\n * profile (the retired `k-padded-v1` token is refused). Required for\n * per-track FEC configuration; never defaulted.\n */\n fecBlockProfile?: FecBlockProfile;\n fecK?: number;\n /** Number of repair symbols per FEC block (P). Used to size recovery reorder latency. */\n fecRepairSymbols?: number;\n /** Symbol size in bytes (T). NOT repair count. */\n fecSymbolSize?: number;\n /** Catalog fec.reorderToleranceMs \u2014 signaled jitter/reorder allowance, ADDED\n * to the derived delivery window. Never a multiplier on the span, so one\n * network yields the same slack on a wide video span and a narrow audio one.\n *\n * Absence widens the window by zero rather than throwing, unlike the datagram\n * MFU deadline in hang-mmt-fec. The distinction is which way absence fails:\n * there the tolerance IS the deadline's slack term and guessing one invents\n * a reorder budget, so it must fail loud; here the window is already fully\n * derived from other catalog values and the tolerance only widens it, so\n * adding nothing is the identity and errs toward earlier failback. Do not\n * \"harmonize\" this into a throw, and do not turn the zero into a default. */\n fecReorderToleranceMs?: number;\n fecInterleaveDepthMs?: number;\n /** Aggregate endpoint bandwidth in bits per second from the multicast catalog. */\n bandwidth?: number;\n fps?: number;\n /** Track media kind (repair class `audio` vs video block classification). */\n trackKind?: TrackKind;\n /** Catalog codec string (video conceal mode, codec-aware RAP checks). */\n codec?: string;\n /** Catalog track timescale for `keyframeIntervalTicks`. */\n timescale?: number;\n /** Raw catalog `keyframeIntervalTicks` (never a resolved cadence). */\n keyframeIntervalTicks?: number;\n /** Raw catalog `keyframeIntervalMs` (never a resolved cadence). */\n keyframeIntervalMs?: number;\n /** Catalog-derived lower bound for packet-silence failback (for example, keyframe repair cadence). */\n minimumFailbackWindowMs?: number;\n}\nexport interface MulticastManagerOptions {\n /**\n * MFU reassembly timeout (ms). Threaded to the inner MmtpRouter (and its\n * MmtpContainerParser). Caller derives from catalog `fec.interleaveDepthMs`,\n * which is already in milliseconds \u2014 `moqenc_mmt.c` converts\n * `depth_ms = fec_depth * (1000/fps)` before populating the catalog.\n * Defaults to 1000ms (with parser warning) when omitted.\n */\n mfuTimeoutMs?: number;\n reassemblyPolicy?: MfuReassemblyPolicy;\n reassembler?: MfuReassemblerBackend;\n maxBufferedMfus?: number;\n manifestVerifier?: ManifestVerifier;\n manifestVerifierOwnership?: \"caller\" | \"router\";\n resolveManifestIdentity?: (\n packetId: number,\n sbn: number,\n esi: number,\n catalogTrackName?: string,\n ) => ManifestObjectIdentity | null;\n resolveManifestIdentityForRoute?: ManifestIdentityResolver;\n /**\n * Also write every repair lifecycle event to the console as one\n * `REPAIR_LIFECYCLE_LOG_PREFIX` JSON line. Off unless asked for: the lines\n * are serialized on the page thread for every failed block and repair step\n * (and again for an attached inspector), and addRepairLifecycleObserver is\n * the lifecycle API. Enable only for interactive debugging (BLO-36599).\n */\n logRepairLifecycle?: boolean;\n}\n\n/**\n * An in-place video rendition switch on the live join: MulticastManager\n * .switchRendition. Every rendition of a ladder can share one SSM group and\n * differ only by MMTP packet ID, so the switch remaps packet IDs at a RAP\n * instead of leaving and re-joining the group.\n */\nexport interface RenditionSwitchRequest {\n /** The rendition playing now: an ingress track of the live join. */\n readonly from: MmtpStreamIdentity;\n /**\n * The rendition replacing it, already registered as video with everything\n * it plays through (setPacketIdType, setTrackCodec, configureTrackFec, its\n * FEC decoder, frame and init callbacks, MSE assembler, repair owner).\n */\n readonly to: MmtpStreamIdentity;\n /**\n * The live join's complete ingress once the switch commits: every track\n * that stays admitted (audio, repair legs) plus `to` and its repair legs.\n * It must carry `to`, nothing on `from`'s packet ID (the commit retires\n * it), and no live packet ID under another name.\n */\n readonly tracks: MmtpIngressContext[\"tracks\"];\n /**\n * The allow-list once the switch commits, when it changes: an allow-list\n * scoped to the active rendition. Omit it for a rendition-stable allow-list\n * that already admits `to`; the live allow-list is then kept unchanged.\n */\n readonly packetIds?: readonly number[];\n /**\n * Called synchronously at the commit, inside the frame path, after `from`\n * is retired and the admission narrowed, and before `to`'s newest init and\n * its RAP are delivered: reconfigure the decoder here. Throwing fails the\n * switch (`commit-failed`): `from` cannot be restored, so `to` is retired\n * as well and none of it is delivered.\n */\n readonly onCommit: (commit: StreamSwitchCommit) => void;\n /** Called once when the switch ends without a completed commit. */\n readonly onCancel: (cancel: RenditionSwitchCancel) => void;\n}\n\n/**\n * Why a staged rendition switch ended without a completed commit.\n *\n * - `cancelled`: RenditionSwitch.cancel().\n * - `superseded`: join() or dispose() replaced the admission it was staged on.\n * - `provider-refused`: the subscription refused to widen its packet filter to\n * `to`, so `to` can never arrive.\n * - `commit-failed`: `onCommit` threw. `from` was already retired, so `to` is\n * retired as well and the subscription now delivers neither rendition;\n * recover with join().\n */\nexport type RenditionSwitchCancel =\n | { readonly reason: \"cancelled\" }\n | { readonly reason: \"superseded\" }\n | { readonly reason: \"provider-refused\"; readonly error: unknown }\n | { readonly reason: \"commit-failed\"; readonly error: unknown };\n\nexport type RenditionSwitchState = \"staged\" | \"committed\" | \"cancelled\" | \"failed\";\n\n/** A rendition switch MulticastManager.switchRendition staged. */\nexport interface RenditionSwitch {\n readonly state: RenditionSwitchState;\n /**\n * Abandon the switch before it commits: the pre-switch admission is\n * restored and `to` is retired (its router registrations and FEC state\n * released). Returns false once the switch committed or ended.\n */\n cancel(): boolean;\n}\n\ninterface ActiveRenditionSwitch {\n readonly from: MmtpStreamIdentity;\n readonly to: MmtpStreamIdentity;\n readonly onCommit: (commit: StreamSwitchCommit) => void;\n readonly onCancel: (cancel: RenditionSwitchCancel) => void;\n /** The admission the switch was staged on, restored by cancel(). */\n readonly liveIngress: MmtpIngressContext;\n readonly livePacketIds: ReadonlySet<number> | null;\n /** True when staging widened the allow-list (so cancel narrows it back). */\n readonly widened: boolean;\n /** The admission once committed. */\n readonly committedIngress: MmtpIngressContext;\n readonly committedPacketIds: readonly number[] | undefined;\n router: StagedStreamSwitch | null;\n state: RenditionSwitchState;\n}\n\nfunction freezeIngress(tracks: Iterable<Readonly<{ name: string; packetId: number }>>): MmtpIngressContext {\n return Object.freeze({\n tracks: Object.freeze(\n [...tracks].map(({ name, packetId }) => Object.freeze({ name, packetId })),\n ),\n });\n}\n\nfunction sameIdSet(a: ReadonlySet<number>, b: ReadonlySet<number>): boolean {\n if (a.size !== b.size) return false;\n for (const id of a) if (!b.has(id)) return false;\n return true;\n}\n\nexport interface RepairStreamIdentity {\n readonly packetId: number;\n /** Non-empty catalog identity, or null when the producer has no track name. */\n readonly catalogTrackName: string | null;\n}\n\nexport interface RepairBlockIdentity extends RepairStreamIdentity {\n readonly sbn: number;\n}\n\ninterface RepairSymbolCounts {\n readonly multicast: number;\n readonly moq: number;\n}\n\n/**\n * Typed repair lifecycle, version 2 (keyframe-protected FEC policy). One\n * clean cut from version 1: there is no v1 emit and no alias.\n *\n * - `requested`: a block short of its source at the repair deadline; its\n * class sets the QUIC repair priority (P0 keyframe/unknown, P1 audio,\n * P2 delta).\n * - `reclassified`: new evidence (or a disabled classifier) changed a block's\n * class.\n * - `recovered` / `failed`: every block that could not complete from its\n * first-arrival source symbols resolves with exactly one of these.\n * - `repair-preempted` / `repair-deferred`: the scheduler aborted a delta\n * repair for keyframe work, or never ran it before its block resolved.\n * - `repair-priority`: the catalog could not supply the SUBSCRIBER_PRIORITY a\n * repair FETCH must carry (draft-ramadan-moq-fec \u00A711.4.5), so the FETCH was\n * sent without the parameter (`omitted`, keyframe/unknown blocks) or not\n * sent at all (`declined`, delta/audio blocks).\n * - `classifier`: a video stream's classifier mode and why.\n * - `gap`: the stall/conceal decision attached to the first frame released\n * after a lossy gap \u2014 the very object carried as `gapBefore`.\n * - `decoder-unavailable`: a stream's Worker decoder left routing.\n */\nexport type RepairLifecycleEvent =\n | {\n readonly version: 2;\n readonly type: \"requested\";\n readonly block: RepairBlockIdentity;\n readonly blockClass: BlockClass;\n readonly priority: RepairPriority;\n }\n | {\n readonly version: 2;\n readonly type: \"reclassified\";\n readonly block: RepairBlockIdentity;\n readonly from: BlockClass;\n readonly to: BlockClass;\n readonly evidence: ReclassificationEvidence;\n }\n | {\n readonly version: 2;\n readonly type: \"recovered\";\n readonly block: RepairBlockIdentity;\n readonly blockClass: BlockClass;\n readonly via: RepairVia;\n readonly symbols: RepairSymbolCounts;\n }\n | {\n readonly version: 2;\n readonly type: \"failed\";\n readonly block: RepairBlockIdentity;\n readonly blockClass: BlockClass;\n readonly symbols: RepairSymbolCounts;\n }\n | {\n readonly version: 2;\n readonly type: \"repair-preempted\" | \"repair-deferred\";\n readonly block: RepairBlockIdentity;\n }\n | {\n readonly version: 2;\n /**\n * The scheduler sent no FETCH for a requested block (BLO-35597): the\n * measured reliable-path RTT could not come back before the block's\n * presentation deadline, the path offered no RTT, or the player gave\n * no presentation delay. `rttMs` is null when unmeasured; `budgetMs` is\n * the time that was left to the deadline.\n */\n readonly type: \"repair-skipped\";\n readonly block: RepairBlockIdentity;\n readonly reason: RepairSkipReason;\n readonly rttMs: number | null;\n readonly budgetMs: number;\n }\n | {\n readonly version: 2;\n readonly type: \"repair-priority\";\n readonly block: RepairBlockIdentity;\n readonly blockClass: BlockClass;\n readonly action: \"omitted\" | \"declined\";\n readonly reason: RepairPriorityReason;\n }\n | {\n readonly version: 2;\n readonly type: \"classifier\";\n readonly stream: RepairStreamIdentity;\n readonly mode: ClassifierMode;\n readonly reason: ClassifierReason | null;\n }\n | FrameGapDecision\n | {\n readonly version: 2;\n /**\n * The stream's Worker decoder left routing (FECManager.clearFecWorker,\n * or a disposed client evicted on its next symbol). `fallbackDecoder`\n * says what still decodes the stream: 'sync' when a synchronous WASM\n * decoder registered for the same stream keeps serving it, 'none' when\n * nothing does. Only 'none' means the stream's symbols reach no decoder\n * until a host registers one again; consumers fail closed on that\n * instead of reading the following silence as a clean stream.\n */\n readonly type: \"decoder-unavailable\";\n readonly stream: RepairStreamIdentity;\n readonly reason: FecDecoderUnavailableReason;\n readonly fallbackDecoder: FecFallbackDecoder;\n };\n\n/**\n * Keyframe-protected repair policy counters for one catalog track. Reset\n * whenever the track's epoch changes (catalog geometry change or reset).\n *\n * \"Protected\" means the block was short of its source at the repair deadline\n * with class `keyframe` or `unknown`. The `keyframe*` recovered/failed\n * counters cover every P0 block (class `keyframe` or `unknown`).\n */\nexport interface BlockPolicyStats {\n readonly packetId: number;\n readonly catalogTrackName: string | null;\n readonly epoch: number;\n classifierMode: ClassifierMode | null;\n classifierReason: ClassifierReason | null;\n keyframeBlocksProtected: number;\n unknownBlocksProtected: number;\n keyframeBlocksRecoveredInband: number;\n keyframeBlocksRecoveredQuic: number;\n keyframeBlocksFailed: number;\n deltaBlocksShort: number;\n deltaBlocksRecoveredInband: number;\n deltaBlocksRecoveredQuic: number;\n deltaRepairsPreempted: number;\n deltaRepairsDeferred: number;\n /** Repair requests sent no FETCH (see the `repair-skipped` event). */\n repairsSkipped: number;\n deltaBlocksFailed: number;\n audioBlocksRecoveredInband: number;\n audioBlocksRecoveredQuic: number;\n audioBlocksFailed: number;\n stalls: number;\n concealedBlocks: number;\n concealedGaps: number;\n concealedFrames: number;\n concealedAudioMs: number;\n vanishedBlocks: number;\n}\n\nfunction newBlockPolicyStats(\n packetId: number,\n catalogTrackName: string | null,\n epoch: number,\n): BlockPolicyStats {\n return {\n packetId,\n catalogTrackName,\n epoch,\n classifierMode: null,\n classifierReason: null,\n keyframeBlocksProtected: 0,\n unknownBlocksProtected: 0,\n keyframeBlocksRecoveredInband: 0,\n keyframeBlocksRecoveredQuic: 0,\n keyframeBlocksFailed: 0,\n deltaBlocksShort: 0,\n deltaBlocksRecoveredInband: 0,\n deltaBlocksRecoveredQuic: 0,\n deltaRepairsPreempted: 0,\n deltaRepairsDeferred: 0,\n repairsSkipped: 0,\n deltaBlocksFailed: 0,\n audioBlocksRecoveredInband: 0,\n audioBlocksRecoveredQuic: 0,\n audioBlocksFailed: 0,\n stalls: 0,\n concealedBlocks: 0,\n concealedGaps: 0,\n concealedFrames: 0,\n concealedAudioMs: 0,\n vanishedBlocks: 0,\n };\n}\n\nexport type RepairLifecycleObserver = (event: RepairLifecycleEvent) => void;\n\nexport interface RepairLifecycleSource {\n addRepairLifecycleObserver(observer: RepairLifecycleObserver): () => void;\n}\n\n/** Minimal event shape accepted by application-owned event targets. */\nexport interface RepairLifecycleApplicationEvent {\n readonly type: string;\n readonly detail: RepairLifecycleEvent;\n}\n\n/** DOM EventTarget event shape, including the Node 18 Event fallback. */\nexport interface RepairLifecycleDomEvent extends Event {\n readonly detail: RepairLifecycleEvent;\n}\n\n/** Structural target contract shared by DOM and Closure-style event targets. */\nexport interface RepairLifecycleEventTarget<\n TEvent extends RepairLifecycleApplicationEvent,\n> {\n dispatchEvent(event: TEvent): unknown;\n}\n\nexport interface RepairLifecycleEventOptions<\n TEvent extends RepairLifecycleApplicationEvent = RepairLifecycleDomEvent,\n> {\n bubbles?: boolean;\n composed?: boolean;\n createEvent?: RepairLifecycleEventFactory<TEvent>;\n}\n\nexport type RepairLifecycleEventFactory<\n TEvent extends RepairLifecycleApplicationEvent = RepairLifecycleDomEvent,\n> = (\n type: typeof REPAIR_LIFECYCLE_EVENT_TYPE,\n detail: RepairLifecycleEvent,\n options: Readonly<{ bubbles: boolean; composed: boolean }>,\n) => TEvent;\n\n/** Public EventTarget event carrying an immutable RepairLifecycleEvent detail. */\nexport const REPAIR_LIFECYCLE_EVENT_TYPE = \"repairlifecycle\";\n\n/** Diagnostic JSON-lines prefix. Never consume logs as a lifecycle API. */\nexport const REPAIR_LIFECYCLE_LOG_PREFIX =\n \"[MulticastManager] repair-lifecycle \";\n\nfunction createDefaultRepairLifecycleEvent(\n detail: RepairLifecycleEvent,\n options: Readonly<{ bubbles: boolean; composed: boolean }>,\n): RepairLifecycleDomEvent {\n if (typeof globalThis.CustomEvent === \"function\") {\n return new CustomEvent<RepairLifecycleEvent>(REPAIR_LIFECYCLE_EVENT_TYPE, {\n detail,\n ...options,\n });\n }\n\n const event = new Event(REPAIR_LIFECYCLE_EVENT_TYPE, options);\n Object.defineProperty(event, \"detail\", {\n value: detail,\n enumerable: true,\n });\n return event as RepairLifecycleDomEvent;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isRepairStreamIdentity(value: unknown): value is RepairStreamIdentity {\n if (!isRecord(value)) return false;\n const packetId = value[\"packetId\"];\n const catalogTrackName = value[\"catalogTrackName\"];\n return (\n Number.isSafeInteger(packetId) &&\n (packetId as number) >= 0 &&\n (catalogTrackName === null ||\n (typeof catalogTrackName === \"string\" && catalogTrackName.length > 0))\n );\n}\n\nfunction isRepairBlockIdentity(value: unknown): value is RepairBlockIdentity {\n if (!isRecord(value)) return false;\n const sbn = value[\"sbn\"];\n return (\n isRepairStreamIdentity(value) &&\n Number.isSafeInteger(sbn) &&\n (sbn as number) >= 0\n );\n}\n\nfunction isDecoderUnavailableReason(\n value: unknown,\n): value is FecDecoderUnavailableReason {\n return value === \"cleared\" || value === \"disposed\";\n}\n\nfunction isFecFallbackDecoder(value: unknown): value is FecFallbackDecoder {\n return value === \"sync\" || value === \"none\";\n}\n\nconst BLOCK_CLASSES: ReadonlySet<unknown> = new Set([\"keyframe\", \"delta\", \"unknown\", \"audio\"]);\nconst RECLASSIFICATION_EVIDENCE: ReadonlySet<unknown> = new Set([\n \"rap\",\n \"mpu-span\",\n \"window\",\n \"classifier-disabled\",\n]);\nconst CLASSIFIER_MODES: ReadonlySet<unknown> = new Set([\"classifying\", \"all-protected\"]);\nconst CLASSIFIER_REASONS: ReadonlySet<unknown> = new Set([\n \"framerate-absent\",\n \"keyframe-cadence-absent\",\n \"keyframe-interval-not-integral\",\n \"block-spans-gop\",\n \"keyframe-interval-not-media-integral\",\n \"rap-not-mpu-start\",\n \"stream-geometry-absent\",\n \"mpu-longer-than-gop\",\n \"block-not-contiguous\",\n \"rap-spacing-mismatch\",\n \"mpu-multiple-fragments\",\n \"block-wider-than-interleave\",\n]);\nconst REPAIR_PRIORITY_REASONS: ReadonlySet<unknown> = new Set([\n \"source-priority-absent\",\n \"source-priority-invalid\",\n \"repair-priority-absent\",\n \"repair-priority-invalid\",\n]);\nconst GAP_REASONS: Readonly<Record<string, \"stall\" | \"conceal\">> = {\n \"rap-lost\": \"stall\",\n \"rap-unknown\": \"stall\",\n discontinuity: \"stall\",\n \"delta-lost\": \"conceal\",\n \"rap-next\": \"conceal\",\n \"audio-gap\": \"conceal\",\n \"audio-discontinuity\": \"conceal\",\n};\n\nfunction hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {\n const present = Object.keys(value);\n return present.length === keys.length && keys.every((key) => key in value);\n}\n\nfunction isNonNegativeSafeInteger(value: unknown): boolean {\n return Number.isSafeInteger(value) && (value as number) >= 0;\n}\n\nfunction isSymbolCounts(value: unknown): boolean {\n return (\n isRecord(value) &&\n hasExactKeys(value, [\"multicast\", \"moq\"]) &&\n isNonNegativeSafeInteger(value[\"multicast\"]) &&\n isNonNegativeSafeInteger(value[\"moq\"])\n );\n}\n\nfunction isNullableNonNegative(value: unknown): boolean {\n return value === null || (typeof value === \"number\" && Number.isFinite(value) && value >= 0);\n}\n\n/** Runtime guard for observer, DOM-event, and cross-realm consumers. */\nexport function isRepairLifecycleEvent(\n value: unknown,\n): value is RepairLifecycleEvent {\n if (!isRecord(value) || value[\"version\"] !== 2) return false;\n const block = value[\"block\"];\n switch (value[\"type\"]) {\n case \"requested\": {\n const blockClass = value[\"blockClass\"];\n return (\n hasExactKeys(value, [\"version\", \"type\", \"block\", \"blockClass\", \"priority\"]) &&\n isRepairBlockIdentity(block) &&\n BLOCK_CLASSES.has(blockClass) &&\n value[\"priority\"] === repairPriorityForClass(blockClass as BlockClass)\n );\n }\n case \"reclassified\":\n return (\n hasExactKeys(value, [\"version\", \"type\", \"block\", \"from\", \"to\", \"evidence\"]) &&\n isRepairBlockIdentity(block) &&\n BLOCK_CLASSES.has(value[\"from\"]) &&\n BLOCK_CLASSES.has(value[\"to\"]) &&\n RECLASSIFICATION_EVIDENCE.has(value[\"evidence\"])\n );\n case \"recovered\":\n return (\n hasExactKeys(value, [\"version\", \"type\", \"block\", \"blockClass\", \"via\", \"symbols\"]) &&\n isRepairBlockIdentity(block) &&\n BLOCK_CLASSES.has(value[\"blockClass\"]) &&\n (value[\"via\"] === \"inband\" || value[\"via\"] === \"quic\") &&\n isSymbolCounts(value[\"symbols\"])\n );\n case \"failed\":\n return (\n hasExactKeys(value, [\"version\", \"type\", \"block\", \"blockClass\", \"symbols\"]) &&\n isRepairBlockIdentity(block) &&\n BLOCK_CLASSES.has(value[\"blockClass\"]) &&\n isSymbolCounts(value[\"symbols\"])\n );\n case \"repair-preempted\":\n case \"repair-deferred\":\n return hasExactKeys(value, [\"version\", \"type\", \"block\"]) && isRepairBlockIdentity(block);\n case \"repair-priority\": {\n const blockClass = value[\"blockClass\"];\n const action = value[\"action\"];\n const reason = value[\"reason\"];\n // The action follows from the class: a keyframe/unknown request is\n // sent without the parameter, a delta/audio one is not sent.\n const expected =\n BLOCK_CLASSES.has(blockClass) &&\n repairPriorityForClass(blockClass as BlockClass) === 0\n ? \"omitted\"\n : \"declined\";\n return (\n hasExactKeys(value, [\"version\", \"type\", \"block\", \"blockClass\", \"action\", \"reason\"]) &&\n isRepairBlockIdentity(block) &&\n BLOCK_CLASSES.has(blockClass) &&\n action === expected &&\n REPAIR_PRIORITY_REASONS.has(reason) &&\n (action === \"omitted\") === (reason === \"source-priority-absent\" || reason === \"source-priority-invalid\")\n );\n }\n case \"classifier\": {\n const reason = value[\"reason\"];\n return (\n hasExactKeys(value, [\"version\", \"type\", \"stream\", \"mode\", \"reason\"]) &&\n isRepairStreamIdentity(value[\"stream\"]) &&\n CLASSIFIER_MODES.has(value[\"mode\"]) &&\n (reason === null || CLASSIFIER_REASONS.has(reason))\n );\n }\n case \"gap\": {\n const reason = value[\"reason\"];\n const blocks = value[\"blocks\"];\n return (\n hasExactKeys(value, [\n \"version\",\n \"type\",\n \"stream\",\n \"trackKind\",\n \"action\",\n \"reason\",\n \"blocks\",\n \"missingFrames\",\n \"missingMs\",\n \"epoch\",\n ]) &&\n isRepairStreamIdentity(value[\"stream\"]) &&\n (value[\"trackKind\"] === \"video\" || value[\"trackKind\"] === \"audio\") &&\n typeof reason === \"string\" &&\n GAP_REASONS[reason] !== undefined &&\n GAP_REASONS[reason] === value[\"action\"] &&\n Array.isArray(blocks) &&\n blocks.every(isNonNegativeSafeInteger) &&\n (value[\"missingFrames\"] === null || isNonNegativeSafeInteger(value[\"missingFrames\"])) &&\n isNullableNonNegative(value[\"missingMs\"]) &&\n isNonNegativeSafeInteger(value[\"epoch\"])\n );\n }\n case \"decoder-unavailable\":\n return (\n hasExactKeys(value, [\"version\", \"type\", \"stream\", \"reason\", \"fallbackDecoder\"]) &&\n isRepairStreamIdentity(value[\"stream\"]) &&\n isDecoderUnavailableReason(value[\"reason\"]) &&\n isFecFallbackDecoder(value[\"fallbackDecoder\"])\n );\n default:\n return false;\n }\n}\n\n/**\n * Forward typed repair lifecycle observations through an application-owned\n * EventTarget. The adapter retains no history; the returned function detaches\n * the observer.\n */\nexport function forwardRepairLifecycleEvents<\n TEvent extends RepairLifecycleApplicationEvent,\n>(\n source: RepairLifecycleSource,\n target: RepairLifecycleEventTarget<TEvent>,\n options: RepairLifecycleEventOptions<TEvent> & {\n createEvent: RepairLifecycleEventFactory<TEvent>;\n },\n): () => void;\nexport function forwardRepairLifecycleEvents(\n source: RepairLifecycleSource,\n target: EventTarget,\n options?: RepairLifecycleEventOptions,\n): () => void;\nexport function forwardRepairLifecycleEvents(\n source: RepairLifecycleSource,\n target:\n | EventTarget\n | RepairLifecycleEventTarget<RepairLifecycleApplicationEvent>,\n options: RepairLifecycleEventOptions<RepairLifecycleApplicationEvent> = {},\n): () => void {\n return source.addRepairLifecycleObserver((event) => {\n const eventOptions = {\n bubbles: options.bubbles ?? false,\n composed: options.composed ?? false,\n };\n const forwardedEvent = options.createEvent\n ? options.createEvent(REPAIR_LIFECYCLE_EVENT_TYPE, event, eventOptions)\n : createDefaultRepairLifecycleEvent(event, eventOptions);\n (\n target as RepairLifecycleEventTarget<RepairLifecycleApplicationEvent>\n ).dispatchEvent(forwardedEvent);\n });\n}\n\nfunction repairStreamIdentity(\n packetId: number,\n catalogTrackName?: string,\n): RepairStreamIdentity {\n if (!Number.isSafeInteger(packetId) || packetId < 0) {\n throw new TypeError(\"packetId must be a non-negative safe integer\");\n }\n if (\n catalogTrackName !== undefined &&\n (typeof catalogTrackName !== \"string\" || catalogTrackName.length === 0)\n ) {\n throw new TypeError(\"catalogTrackName must be a non-empty string when provided\");\n }\n return {\n packetId,\n catalogTrackName: catalogTrackName ?? null,\n };\n}\n\nfunction repairBlockIdentity(\n packetId: number,\n sbn: number,\n catalogTrackName?: string,\n): RepairBlockIdentity {\n const stream = repairStreamIdentity(packetId, catalogTrackName);\n if (!Number.isSafeInteger(sbn) || sbn < 0) {\n throw new TypeError(\"sbn must be a non-negative safe integer\");\n }\n return { ...stream, sbn };\n}\n\n/**\n * Reject a catalog geometry value that is present but not usable. Absent stays\n * absent \u2014 an omitted field is resolved from the previously configured track or\n * left unset \u2014 but a supplied non-finite/non-positive value is a catalog defect\n * and must fail at configuration time rather than collapse a derived deadline\n * to ~0 packets later.\n */\nfunction assertPositiveGeometry(\n label: string,\n value: number | undefined,\n): void {\n if (value === undefined) return;\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(\n `[MulticastManager] ${label}=${value}. ` +\n `Derive a positive ${label} from the stream catalog.`,\n );\n }\n}\n\n/** Repair-symbol count (P) may legitimately be 0; only reject malformed values. */\nfunction assertNonNegativeGeometry(\n label: string,\n value: number | undefined,\n): void {\n if (value === undefined) return;\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(\n `[MulticastManager] ${label}=${value}. ` +\n `Derive a non-negative ${label} from the stream catalog.`,\n );\n }\n}\n\n/**\n * One contributor to the stream-global FEC timing reduction.\n *\n * FEC geometry is per-track, but a MulticastManager owns exactly one transport\n * and one router, so the failback watchdog and the router's MFU/reorder timing\n * are stream-global. They are reduced with `max` across every contributor:\n * failback must not fire, and the router must not evict, while ANY configured\n * track's FEC block could still complete.\n */\ninterface FecTimingContribution {\n deliveryWindowMs?: number;\n interleaveDepthMs?: number;\n fps?: number;\n k?: number;\n repairSymbols?: number;\n minimumFailbackWindowMs?: number;\n}\n\nfunction fecReorderWindowFrames(\n fecInterleaveDepthMs?: number,\n fps?: number,\n _k?: number,\n _repairSymbols?: number,\n): number {\n if (!fecInterleaveDepthMs || fecInterleaveDepthMs <= 0 || !fps || fps <= 0) return 0;\n const frameMs = 1000 / fps;\n // Reorder across the FEC block span: fecInterleaveDepthMs IS the full block\n // span (\u00A75.1), so this window admits any FEC-recovered frame from the block\n // without holding frames longer than one block.\n return deriveFecInterleaveDepth(fecInterleaveDepthMs, frameMs);\n}\n\n/** Catalog track descriptor for configureMultiTrack(). */\nexport interface MultiTrackCatalogTrack {\n name: string;\n altGroup?: number;\n framerate?: number;\n fec?: unknown;\n /** PacketId for testability \u2014 in production, learned at subscribe time (Plan 03). */\n packetId?: number;\n /** `\"fec-repair\"` marks a self-describing repair track (draft-ramadan-moq-fec\n * \u00A76.3). Absent on media tracks and on naming-era catalogs. */\n packaging?: string;\n /** The one source track this repair track carries repair symbols for. Layers\n * 1..n and a keyframe overlay are reachable only through `depends`; layer 0\n * is additionally named by the source's `fec.repairTrack`. */\n depends?: string[];\n /** Base layer index. Absent on a keyframe overlay, which is a separate FEC\n * instance rather than a member of the layer chain. */\n repairLayer?: number;\n /** `\"keyframe\"` on an overlay track. */\n scope?: string;\n}\n\n/** Multi-track catalog shape accepted by configureMultiTrack(). */\nexport interface MultiTrackCatalog {\n tracks: MultiTrackCatalogTrack[];\n}\n\n/** Creates one stateful synchronous FEC decoder per physical catalog stream. */\nexport type WasmFecDecoderFactory = (\n track: Readonly<MultiTrackCatalogTrack>,\n) => WasmFecDecoder;\n\nconst DEFAULT_ALT_GROUP = \"__default__\";\n\ntype FecDiagnosticsGateway = {\n reportFecDiagnostics?: (snapshot: unknown) => void;\n};\n\n/** Active-block tail forwarded to the bridge; mirrors the IWA's bound. */\nconst FEC_DIAGNOSTIC_BLOCK_TAIL = 8;\n\n/**\n * Project a Worker block snapshot onto the bridge/IWA `subscription_diagnostics`\n * contract. The contract names the per-block repair symbol count `t`; the\n * Worker's `repairCount` is the same quantity (P) under its unambiguous name.\n */\nfunction projectFecDiagnosticBlock(block: FecBlockSnapshot) {\n return {\n sbn: block.sbn,\n state: block.state,\n sourceReceived: block.sourceReceived,\n repairReceived: block.repairReceived,\n k: block.k,\n t: block.repairCount,\n firstSymbolTs: block.firstSymbolTs,\n lastSymbolTs: block.lastSymbolTs,\n ...(block.completedTs === undefined ? {} : { completedTs: block.completedTs }),\n deadlineTs: block.deadlineTs,\n altaVerified: block.altaVerified,\n relayBlockSigValid: block.relayBlockSigValid,\n };\n}\n\n/**\n * Project the Worker track config onto the bridge/IWA contract. Every key\n * below is a named field of that contract (see pim-multicast-gateway\n * `normalizeSubscriptionDiagnostics`); `k`/`t`/`symbolSize` are three\n * independent axes there, so `repairCount` is emitted as `t` and never\n * confused with the symbol size. Worker-internal fields such as\n * `deliveryWindowMs` are not part of the contract and are not forwarded.\n */\nfunction projectFecDiagnosticConfig(config: FecTrackConfig) {\n return {\n codec: config.codec,\n trackName: config.trackName,\n trackType: config.trackType,\n ...(config.resolution === undefined\n ? {}\n : { resolution: { w: config.resolution.w, h: config.resolution.h } }),\n ...(config.framerate === undefined ? {} : { framerate: config.framerate }),\n ...(config.sampleRate === undefined ? {} : { sampleRate: config.sampleRate }),\n ...(config.algorithm === undefined ? {} : { algorithm: config.algorithm }),\n k: config.k,\n t: config.repairCount,\n symbolSize: config.symbolSize,\n interleaveDepth: config.interleaveDepth,\n interleaveMs: config.interleaveMs,\n fecMode: config.fecMode,\n ...(config.repairTrack === undefined ? {} : { repairTrack: config.repairTrack }),\n altaEnabled: config.altaEnabled,\n relayBlockSigEnabled: config.relayBlockSigEnabled,\n };\n}\n\nfunction publishFecDiagnostics(packetId: number, stats: FecTrackStats): boolean {\n const gateway = (globalThis as typeof globalThis & {\n multicast?: FecDiagnosticsGateway;\n }).multicast;\n if (typeof gateway?.reportFecDiagnostics !== \"function\") return false;\n\n // Keep the control-plane payload bounded and JSON-safe. The Worker snapshot\n // already bounds active blocks; the bridge needs only the current block tail\n // and stable counters, not decoder internals or authentication failure text.\n // The shape is the IWA contract, not the Worker's internal types: spreading\n // FecTrackConfig raw once shipped `repairCount` with no `t`, and the IWA\n // discarded every report (\"fec_track.stats.config.t must be a finite\n // non-negative number\").\n const snapshot = {\n version: 1,\n packetId,\n observedAt: Date.now(),\n stats: {\n config: projectFecDiagnosticConfig(stats.config),\n sourceSymbols: stats.sourceSymbols,\n repairSymbols: stats.repairSymbols,\n blocksComplete: stats.blocksComplete,\n blocksRecovered: stats.blocksRecovered,\n blocksFailed: stats.blocksFailed,\n // Required by the IWA subscription_diagnostics contract (pim\n // normalizeSubscriptionDiagnostics rejects a report without it). No\n // fill frame is ever synthesised any more \u2014 concealment is the sink's\n // hold-last-good-frame \u2014 so the value is 0 until that contract drops\n // the field.\n greenFillFrames: 0,\n recoveryRate: stats.recoveryRate,\n liveSbn: stats.liveSbn,\n blocks: stats.blocks.slice(-FEC_DIAGNOSTIC_BLOCK_TAIL).map(projectFecDiagnosticBlock),\n interleaveWindowBlocks: stats.interleaveWindowBlocks,\n avgRecoveryMs: stats.avgRecoveryMs,\n maxRecoveryMs: stats.maxRecoveryMs,\n },\n };\n\n gateway.reportFecDiagnostics(snapshot);\n return true;\n}\n\nfunction altGroupKey(track: MultiTrackCatalogTrack): string {\n return track.altGroup === undefined ? DEFAULT_ALT_GROUP : String(track.altGroup);\n}\n\n/**\n * Every repair track of each source: layer 0 named by the source's\n * `fec.repairTrack`, plus every self-describing `packaging: \"fec-repair\"` track\n * whose `depends` names that source (layers 1..n and a keyframe overlay), per\n * draft-ramadan-moq-fec \u00A76.3.\n *\n * Membership comes from the explicit pointer and from `depends`, never from a\n * name suffix, so a publisher-chosen name like \"parity-alpha\" resolves.\n *\n * This deliberately does not call `resolveRepairLayers`: that validates the\n * full layer contract and requires `packaging: \"mmtp\"` on the source plus a\n * declared layer-0 repair track, neither of which a `MultiTrackCatalog`\n * carries. Running it here would throw on every naming-era catalog. Ownership\n * registration needs membership only; RS_ID offsets and layer ordering are the\n * decoder's concern, not the router's.\n */\nfunction repairTrackNamesBySource(\n tracks: readonly MultiTrackCatalogTrack[],\n): Map<string, Set<string>> {\n const bySource = new Map<string, Set<string>>();\n const add = (source: string, repair: string): void => {\n if (!source || !repair) return;\n const names = bySource.get(source) ?? new Set<string>();\n names.add(repair);\n bySource.set(source, names);\n };\n for (const track of tracks) {\n // The explicit pointer counts whether or not the source's FEC config is\n // usable (an unimplemented block profile leaves the track without FEC,\n // not without its repair track): ownership is membership, not\n // decodability, so no isFecCatalogConfig gate here.\n const repairTrack = typeof track.fec === \"object\" && track.fec !== null\n ? (track.fec as { readonly repairTrack?: unknown }).repairTrack\n : undefined;\n if (typeof repairTrack === \"string\") add(track.name, repairTrack);\n if (track.packaging === \"fec-repair\" && Array.isArray(track.depends) && track.depends.length === 1) {\n add(track.depends[0]!, track.name);\n }\n }\n return bySource;\n}\n\nexport class MulticastManager {\n readonly #fec: FECManager;\n readonly #router: MmtpRouter;\n readonly #transport: MulticastTransport<MmtpIngressContext>;\n #reportedFecDiagnosticsFailure = false;\n\n // \u2500\u2500 Stream-global FEC timing reduction \u2500\u2500\n //\n // FEC geometry is per-track; the transport failback watchdog and the router's\n // MFU/reorder timing are stream-global (one transport, one router). Both\n // configuration entry points feed the same reduction so neither can silently\n // clip the other: configureFec() supplies the baseline, configureTrackFec()\n // supplies one entry per track, and #applyStreamGlobalFecTiming() takes the\n // max across all of them.\n #streamFecBaseline?: FecTimingContribution;\n readonly #trackFecTiming = new Map<number, FecTimingContribution>();\n\n // \u2500\u2500 Phase 14: multi-track state (D-06, D-07, D-17, D-26, D-28) \u2500\u2500\n\n /** Maps altGroup \u2192 track names sharing that altGroup. */\n #switchingSets: Map<number, string[]> = new Map();\n\n /** Repair packetIds that should not get their own FEC decoder (they ARE the repair source). */\n #repairPacketIds: Set<number> = new Set();\n\n /** Tracks physical-stream FEC blocks with an outstanding repair fetch. */\n #repairInFlight: Set<string> = new Set();\n\n /** Causal repair events; observers own any retention they require. */\n #repairLifecycleObservers = new Set<RepairLifecycleObserver>();\n readonly #logRepairLifecycle: boolean;\n\n #trackResetObservers = new Set<\n (packetId: number, catalogTrackName: string) => void\n >();\n\n /** Callback fired when a repair fetch should be issued for a block. */\n #onRepairSubscribeCb: RepairSubscribeCallback | null = null;\n\n /** Keyframe-protected repair policy counters per catalog track. */\n #blockPolicyStats = new Map<string, BlockPolicyStats>();\n\n /** The staged in-place rendition switch, if any. */\n #renditionSwitch: ActiveRenditionSwitch | null = null;\n\n constructor(options?: MulticastManagerOptions) {\n this.#logRepairLifecycle = options?.logRepairLifecycle === true;\n this.#fec = new FECManager();\n this.#fec.addFecStatsObserver((packetId, stats) => {\n try {\n if (publishFecDiagnostics(packetId, stats)) {\n this.#reportedFecDiagnosticsFailure = false;\n }\n } catch (error) {\n if (!this.#reportedFecDiagnosticsFailure) {\n this.#reportedFecDiagnosticsFailure = true;\n console.warn('[MulticastManager] Failed to publish FEC diagnostics:', error);\n }\n }\n });\n this.#router = new MmtpRouter({\n fecManager: this.#fec,\n mfuTimeoutMs: options?.mfuTimeoutMs,\n reassemblyPolicy: options?.reassemblyPolicy,\n reassembler: options?.reassembler,\n maxBufferedMfus: options?.maxBufferedMfus,\n manifestVerifier: options?.manifestVerifier,\n manifestVerifierOwnership: options?.manifestVerifierOwnership,\n resolveManifestIdentity: options?.resolveManifestIdentity,\n resolveManifestIdentityForRoute: options?.resolveManifestIdentityForRoute,\n // The router has reset its own state for the stream; reset the rest.\n onPublisherRestart: (packetId, catalogTrackName) => {\n this.#resetStreamOutsideRouter(packetId, catalogTrackName);\n },\n onBlockFailed: (sbn, packetId, catalogTrackName, blockClass) => {\n const pathCount = this.#fec.blockPathCount(\n sbn,\n packetId,\n catalogTrackName,\n );\n const stats = this.#statsFor(packetId, catalogTrackName);\n if (blockClass === \"delta\") stats.deltaBlocksFailed++;\n else if (blockClass === \"audio\") stats.audioBlocksFailed++;\n else stats.keyframeBlocksFailed++;\n this.#emitRepairLifecycle({\n version: 2,\n type: \"failed\",\n block: repairBlockIdentity(packetId, sbn, catalogTrackName),\n blockClass,\n symbols: {\n multicast: pathCount?.multicastSymbols ?? 0,\n moq: pathCount?.moqSymbols ?? 0,\n },\n });\n },\n onFrameGap: (decision) => {\n const stats = this.#statsFor(\n decision.stream.packetId,\n decision.stream.catalogTrackName ?? undefined,\n );\n if (decision.action === \"stall\") {\n stats.stalls++;\n } else {\n stats.concealedGaps++;\n stats.concealedBlocks += decision.blocks.length;\n if (decision.trackKind === \"audio\") {\n stats.concealedAudioMs += decision.missingMs ?? 0;\n } else {\n stats.concealedFrames += decision.missingFrames ?? 0;\n }\n }\n this.#emitRepairLifecycle(decision);\n },\n });\n this.#transport = new MulticastTransport<MmtpIngressContext>((packet, ingress) => {\n this.#router.feedPacket(packet, \"multicast\", undefined, ingress);\n });\n // Each joined subscription declares how it delivers packets. An ordered\n // relay decides FEC block fate by stream order instead of the datagram\n // inactivity deadline (see MulticastSymbolDelivery in fec-manager).\n this.#transport.onDelivery((delivery) => {\n this.#fec.setMulticastSymbolDelivery(delivery);\n });\n\n // Worker-path FEC recovery: route recovered source-symbol bytes through\n // MmtpRouter so MFU reassembly + dedup runs. Without this wiring the\n // FecWorkerClient's recovered frames fire FECManager.onBlockRecovered\n // into a null callback and VideoDecoder never sees a real frame.\n this.#fec.onBlockRecovered = (sbn, packetId, data, meta, recoveredTrackName) => {\n const recovered = new Uint8Array(data);\n const catalogTrackName = recoveredTrackName\n ?? this.#fec.catalogTrackNameForBlock(sbn, packetId);\n if (catalogTrackName === undefined) {\n this.#router.routeRecoveredSymbol(sbn, recovered, packetId, meta.esi);\n } else {\n this.#router.routeRecoveredSymbol(\n sbn,\n recovered,\n packetId,\n meta.esi,\n catalogTrackName,\n );\n }\n };\n\n // D-28: release the in-flight repair slot when a repair-requested block\n // resolves (completes, recovers, or fails), and emit the gate log line.\n // The request-side bridge (FECManager.onRepairNeeded) is wired in\n // onRepairSubscribe() so repair deadlines only arm once a player has\n // registered a fetcher \u2014 consumers without D-28 pay no timer cost.\n this.#fec.onRepairResolved = (sbn, packetId, _outcome, catalogTrackName) => {\n this.clearRepairForBlock(sbn, packetId, catalogTrackName);\n };\n // Every block that could not complete from its first-arrival source\n // symbols and completed anyway reports `recovered` exactly once.\n this.#fec.onBlockRepaired = (sbn, packetId, catalogTrackName, blockClass, via) => {\n const pathCount = this.#fec.blockPathCount(sbn, packetId, catalogTrackName);\n const stats = this.#statsFor(packetId, catalogTrackName);\n if (blockClass === \"delta\") {\n if (via === \"quic\") stats.deltaBlocksRecoveredQuic++;\n else stats.deltaBlocksRecoveredInband++;\n } else if (blockClass === \"audio\") {\n if (via === \"quic\") stats.audioBlocksRecoveredQuic++;\n else stats.audioBlocksRecoveredInband++;\n } else if (via === \"quic\") {\n stats.keyframeBlocksRecoveredQuic++;\n } else {\n stats.keyframeBlocksRecoveredInband++;\n }\n this.#emitRepairLifecycle({\n version: 2,\n type: \"recovered\",\n block: repairBlockIdentity(packetId, sbn, catalogTrackName),\n blockClass,\n via,\n symbols: {\n multicast: pathCount?.multicastSymbols ?? 0,\n moq: pathCount?.moqSymbols ?? 0,\n },\n });\n };\n this.#fec.onBlockReclassified = (sbn, packetId, catalogTrackName, from, to, evidence) => {\n this.#emitRepairLifecycle({\n version: 2,\n type: \"reclassified\",\n block: repairBlockIdentity(packetId, sbn, catalogTrackName),\n from,\n to,\n evidence,\n });\n };\n this.#fec.onClassifierMode = (packetId, catalogTrackName, mode, reason) => {\n const stats = this.#statsFor(packetId, catalogTrackName);\n stats.classifierMode = mode;\n stats.classifierReason = reason;\n this.#emitRepairLifecycle({\n version: 2,\n type: \"classifier\",\n stream: repairStreamIdentity(packetId, catalogTrackName),\n mode,\n reason,\n });\n };\n this.#fec.onBlockVanished = (_sbn, packetId, catalogTrackName) => {\n this.#statsFor(packetId, catalogTrackName).vanishedBlocks++;\n };\n this.#fec.onDecoderUnavailable = (event) => {\n this.#emitRepairLifecycle({\n version: 2,\n type: \"decoder-unavailable\",\n stream: repairStreamIdentity(event.packetId, event.catalogTrackName),\n reason: event.reason,\n fallbackDecoder: event.fallbackDecoder,\n });\n };\n }\n\n // ---------------------------------------------------------------------------\n // Decomposed component accessors (for new code)\n // ---------------------------------------------------------------------------\n\n get fecManager(): FECManager {\n return this.#fec;\n }\n get router(): MmtpRouter {\n return this.#router;\n }\n get transport(): MulticastTransport<MmtpIngressContext> {\n return this.#transport;\n }\n\n // ---------------------------------------------------------------------------\n // Stats\n // ---------------------------------------------------------------------------\n\n get reassembler() {\n return this.#router.reassembler;\n }\n get diagnostics() {\n return {\n ...this.#router.getDiagnostics(),\n _fec: this.#fec.diagnostics,\n };\n }\n get stats() {\n return this.#transport.stats;\n }\n\n /**\n * See MulticastTransport.lastJoinOutcome: how the newest join() to resolve\n * did, and the typed reason it resolved false. join() here adds no false\n * path of its own, so every false it returns is named there; a join() that\n * rejects (catalog FEC configuration that configureFec refuses, for one)\n * records nothing.\n */\n get lastJoinOutcome(): MulticastJoinOutcome | undefined {\n return this.#transport.lastJoinOutcome;\n }\n /** See MulticastTransport.joinStats: join() calls by how they resolved. */\n get joinStats(): MulticastJoinStats {\n return this.#transport.joinStats;\n }\n\n /**\n * Accepted bytes for these MMTP packet_ids, cumulative like\n * `stats.bytesReceived`; see MulticastTransport.bytesReceivedForPacketIds.\n * Scopes an ABR throughput sample to the active tier when the join admits\n * more packet_ids than that tier's. Monotonic only while the set is fixed:\n * re-baseline when the queried set changes (e.g. on a tier switch).\n */\n bytesReceivedForPacketIds(packetIds: ReadonlySet<number>): number {\n return this.#transport.bytesReceivedForPacketIds(packetIds);\n }\n\n get fecStats() {\n return this.#fec.fecStats;\n }\n\n /** The raw WASM decoder for stats access */\n get wasmDecoder() {\n return this.#fec.wasmDecoder;\n }\n\n // ---------------------------------------------------------------------------\n // FEC configuration (delegates to FECManager)\n // ---------------------------------------------------------------------------\n\n setFecDecoder(\n packetId: number,\n client: WasmFecDecoder,\n k: number,\n trackType: \"video\" | \"audio\" = \"video\",\n config?: FECManagerConfig,\n catalogTrackName?: string,\n ) {\n const fecConfig = { ...config, k, trackKind: config?.trackKind ?? trackType };\n this.#fec.setFecDecoder(packetId, client, k, fecConfig, catalogTrackName);\n this.#configureTrackMapper(packetId, fecConfig);\n this.#router.setPacketIdType(packetId, trackType);\n }\n\n /**\n * Register a Worker-backed FEC decoder for a specific packetId (track).\n * Worker-backed tracks decode asynchronously \u2014 recovered frames arrive via\n * FECManager.onBlockRecovered callback instead of feedSourceSymbol return value.\n */\n setFecWorker(\n packetId: number,\n client: FecWorkerClient,\n trackType: \"video\" | \"audio\",\n config: FECManagerConfig,\n catalogTrackName?: string,\n ): void {\n this.#fec.setFecWorker(\n packetId,\n client,\n { ...config, trackKind: config.trackKind ?? trackType },\n catalogTrackName,\n );\n this.#configureTrackMapper(packetId, config);\n this.#router.setPacketIdType(packetId, trackType);\n }\n\n /**\n * Stop routing a stream's symbols to its Worker decoder. Returns false when\n * none is registered; on success one `decoder-unavailable` repair lifecycle\n * event with reason 'cleared' reaches every observer, carrying whether a\n * synchronous WASM decoder still serves the stream. The packet mapping and\n * track type are untouched: the track still exists, it just has no Worker\n * decoder. Call this before disposing the client.\n */\n clearFecWorker(packetId: number, catalogTrackName?: string): boolean {\n return this.#fec.clearFecWorker(packetId, catalogTrackName);\n }\n\n /** Register callback for Worker-level FecTrackStats updates. */\n setOnFecStatsUpdate(\n cb: (packetId: number, stats: FecTrackStats) => void,\n ): void {\n this.#fec.onFecStatsUpdate = cb;\n }\n\n setFramerate(fps: number) {\n this.#router.setFramerate(fps);\n }\n\n #configureTrackMapper(packetId: number, config: FECManagerConfig): void {\n const k = config.k;\n const interleaveMs = config.interleaveDepthMs;\n const fps = config.fps;\n const blockProfile = config.blockProfile;\n if (blockProfile !== undefined && k && k > 0 && interleaveMs && interleaveMs > 0 && fps && fps > 0) {\n this.#router.setFecMapper(\n new FecBlockMapper({\n blockProfile,\n k,\n interleaveDepthMs: interleaveMs,\n gopDurationMs: 1000 / fps,\n }),\n packetId,\n );\n }\n }\n\n /**\n * Reduce every registered FEC timing contributor to the stream-global\n * deadlines and apply them to the single transport and single router.\n *\n * `max`, not `min` or last-write-wins: a narrower track must never pull the\n * failback watchdog or the router's reorder window in under a wider track's\n * still-completable FEC block. Recomputed on every registration so a track\n * that arrives late widens the stream rather than being clipped by it.\n */\n #applyStreamGlobalFecTiming(): void {\n const contributions: FecTimingContribution[] = [\n ...(this.#streamFecBaseline ? [this.#streamFecBaseline] : []),\n ...this.#trackFecTiming.values(),\n ];\n if (contributions.length === 0) return;\n\n let deliveryWindowMs: number | undefined;\n let minimumFailbackWindowMs: number | undefined;\n let mfuTimeoutMs: number | undefined;\n let reorderWindowFrames: number | undefined;\n\n for (const c of contributions) {\n if (c.deliveryWindowMs !== undefined) {\n deliveryWindowMs = Math.max(deliveryWindowMs ?? 0, c.deliveryWindowMs);\n }\n if (c.minimumFailbackWindowMs !== undefined) {\n minimumFailbackWindowMs = Math.max(\n minimumFailbackWindowMs ?? 0,\n c.minimumFailbackWindowMs,\n );\n }\n // Only a valid \u00A75.1 ms interleave may drive router timing. A sub-floor\n // value is a raw frame count (no fps to convert with) and would collapse\n // the MFU timeout to ~0 and the reorder window to a single frame; skip\n // the derivation rather than clamp it, leaving previously-configured\n // router timing untouched.\n const interleaveOk =\n c.interleaveDepthMs !== undefined &&\n c.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS;\n if (interleaveOk && c.deliveryWindowMs) {\n mfuTimeoutMs = Math.max(mfuTimeoutMs ?? 0, c.deliveryWindowMs);\n }\n if (interleaveOk && c.fps && c.fps > 0) {\n reorderWindowFrames = Math.max(\n reorderWindowFrames ?? 0,\n fecReorderWindowFrames(\n c.interleaveDepthMs,\n c.fps,\n c.k,\n c.repairSymbols,\n ),\n );\n }\n }\n\n const failbackWindowMs =\n deliveryWindowMs === undefined\n ? minimumFailbackWindowMs\n : Math.max(deliveryWindowMs, minimumFailbackWindowMs ?? 0);\n if (failbackWindowMs !== undefined) {\n this.#transport.setFailbackWindowMs(failbackWindowMs);\n }\n if (mfuTimeoutMs !== undefined) {\n this.#router.setMfuTimeoutMs(mfuTimeoutMs);\n }\n if (reorderWindowFrames !== undefined) {\n this.#router.setReorderWindow(reorderWindowFrames);\n }\n }\n\n /**\n * Configure one track's FEC geometry, block mapper, and block-inactivity\n * deadline from that track's own catalog values, then re-reduce the\n * stream-global deadlines across every registered track.\n *\n * This is the packet-specific replacement for the global `configureFec`\n * side effects: per-track concerns stay per-track, and the genuinely\n * stream-global ones (transport failback, router MFU timeout, router reorder\n * window) become an explicit max reduction instead of a last-caller-wins race\n * in which whichever track configured last silently set the whole stream's\n * timing.\n *\n * `catalogTrackName` scopes the block-classifier geometry (kind, fps,\n * interleave, cadence) to that one stream. Pass it whenever ABR rungs can\n * share this packetId: each rung then classifies with its own framerate\n * and GOP, and a rung on the packetId that was never configured this way\n * runs all-protected (`stream-geometry-absent`) instead of borrowing a\n * sibling's geometry. K, T and the delivery timing stay per packetId.\n */\n configureTrackFec(\n packetId: number,\n config: Pick<\n MulticastEndpointConfig,\n | \"fecBlockProfile\"\n | \"fecK\"\n | \"fecRepairSymbols\"\n | \"fecSymbolSize\"\n | \"fecInterleaveDepthMs\"\n | \"fecReorderToleranceMs\"\n | \"fps\"\n | \"bandwidth\"\n | \"minimumFailbackWindowMs\"\n | \"trackKind\"\n | \"codec\"\n | \"timescale\"\n | \"keyframeIntervalTicks\"\n | \"keyframeIntervalMs\"\n >,\n catalogTrackName?: string,\n ): void {\n if (catalogTrackName !== undefined && catalogTrackName.length === 0) {\n throw new Error(\n `[MulticastManager] configureTrackFec(${packetId}) catalogTrackName must be non-empty when given`,\n );\n }\n if (config.fecK === undefined || config.fecInterleaveDepthMs === undefined) {\n throw new Error(\n `[MulticastManager] configureTrackFec(${packetId}) requires fecK and ` +\n \"fecInterleaveDepthMs. Derive the per-track FEC geometry from the \" +\n \"stream catalog.\",\n );\n }\n const blockProfile = parseFecBlockProfile(\n config.fecBlockProfile,\n `[MulticastManager] configureTrackFec(${packetId}) fecBlockProfile`,\n );\n assertPositiveGeometry(\"fecK\", config.fecK);\n assertPositiveGeometry(\"fecInterleaveDepthMs\", config.fecInterleaveDepthMs);\n assertPositiveGeometry(\"fecSymbolSize\", config.fecSymbolSize);\n assertPositiveGeometry(\"fps\", config.fps);\n assertPositiveGeometry(\"bandwidth\", config.bandwidth);\n assertPositiveGeometry(\n \"minimumFailbackWindowMs\",\n config.minimumFailbackWindowMs,\n );\n assertPositiveGeometry(\"timescale\", config.timescale);\n assertPositiveGeometry(\"keyframeIntervalTicks\", config.keyframeIntervalTicks);\n assertPositiveGeometry(\"keyframeIntervalMs\", config.keyframeIntervalMs);\n assertNonNegativeGeometry(\"fecRepairSymbols\", config.fecRepairSymbols);\n // A tolerance is the network path's allowance, added to the encoder's\n // span. These are public entry points: the catalog parser validates what it\n // parses, but a direct caller reaches this arithmetic without passing\n // through it. A negative value silently *shortens* the delivery window\n // below the signaled span, and Infinity/NaN yields a window no block can\n // ever miss -- the eviction bound stops bounding. Reuse the shared\n // non-negative guard rather than an inline check: undefined must stay\n // undefined (absent means the encoder never signaled one, and must not be\n // defaulted), while a declared 0 is a real value that has to survive.\n assertNonNegativeGeometry(\n \"fecReorderToleranceMs\",\n config.fecReorderToleranceMs,\n );\n\n // Same derivation as the stream-global path, but from THIS track's\n // geometry. FECManager already stores a per-track timeoutMs\n // (configureTrack/timeoutMsForPacket); it just never had the repair-symbol\n // and bandwidth inputs needed to compute one.\n const deliveryWindowMs =\n config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS\n ? computeFecDeliveryWindowMs({\n interleaveDepthMs: config.fecInterleaveDepthMs,\n sourceSymbols: config.fecK,\n repairSymbols: config.fecRepairSymbols,\n symbolSize: config.fecSymbolSize,\n mediaUnitsPerSecond: config.fps,\n bandwidthBps: config.bandwidth,\n jitterBufferMs: config.fecReorderToleranceMs,\n })\n : undefined;\n\n // The block classifier's geometry comes from the raw catalog cadence\n // fields; a track whose media kind is not known yet classifies fail-closed.\n const trackKind = config.trackKind ?? this.#router.trackKindFor(packetId);\n const geometry = trackKind === undefined\n ? undefined\n : deriveBlockClassifierGeometry({\n trackKind,\n framerate: config.fps,\n interleaveDepthMs: config.fecInterleaveDepthMs,\n timescale: config.timescale,\n keyframeIntervalTicks: config.keyframeIntervalTicks,\n keyframeIntervalMs: config.keyframeIntervalMs,\n });\n const fecConfig: FECManagerConfig = {\n blockProfile,\n k: config.fecK,\n symbolSize: config.fecSymbolSize,\n interleaveDepthMs: config.fecInterleaveDepthMs,\n fps: config.fps,\n timeoutMs: deliveryWindowMs,\n ...(trackKind === undefined ? {} : { trackKind }),\n ...(config.codec === undefined ? {} : { codec: config.codec }),\n ...(geometry === undefined ? {} : { geometry }),\n };\n this.#fec.configureTrack(packetId, fecConfig, catalogTrackName);\n this.#configureTrackMapper(packetId, fecConfig);\n\n this.#trackFecTiming.set(packetId, {\n deliveryWindowMs,\n interleaveDepthMs: config.fecInterleaveDepthMs,\n fps: config.fps,\n k: config.fecK,\n repairSymbols: config.fecRepairSymbols,\n minimumFailbackWindowMs: config.minimumFailbackWindowMs,\n });\n this.#applyStreamGlobalFecTiming();\n }\n\n configureFec(\n config: Pick<\n MulticastEndpointConfig,\n | \"fecBlockProfile\"\n | \"fecK\"\n | \"fecRepairSymbols\"\n | \"fecSymbolSize\"\n | \"fecInterleaveDepthMs\"\n | \"fecReorderToleranceMs\"\n | \"fps\"\n | \"bandwidth\"\n | \"minimumFailbackWindowMs\"\n >,\n ) {\n const minimumFailbackWindowMs = config.minimumFailbackWindowMs;\n if (\n minimumFailbackWindowMs !== undefined &&\n (!Number.isFinite(minimumFailbackWindowMs) || minimumFailbackWindowMs <= 0)\n ) {\n throw new Error(\n `[MulticastManager] minimumFailbackWindowMs=${minimumFailbackWindowMs}. ` +\n \"Derive a positive failback floor from the stream catalog.\",\n );\n }\n // A tolerance is the network path's allowance, added to the encoder's\n // span. These are public entry points: the catalog parser validates what it\n // parses, but a direct caller reaches this arithmetic without passing\n // through it. A negative value silently *shortens* the delivery window\n // below the signaled span, and Infinity/NaN yields a window no block can\n // ever miss -- the eviction bound stops bounding. Reuse the shared\n // non-negative guard rather than an inline check: undefined must stay\n // undefined (absent means the encoder never signaled one, and must not be\n // defaulted), while a declared 0 is a real value that has to survive.\n assertNonNegativeGeometry(\n \"fecReorderToleranceMs\",\n config.fecReorderToleranceMs,\n );\n const deliveryWindowMs =\n config.fecK &&\n config.fecK > 0 &&\n config.fecInterleaveDepthMs &&\n config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS\n ? computeFecDeliveryWindowMs({\n interleaveDepthMs: config.fecInterleaveDepthMs,\n sourceSymbols: config.fecK,\n repairSymbols: config.fecRepairSymbols,\n symbolSize: config.fecSymbolSize,\n mediaUnitsPerSecond: config.fps,\n bandwidthBps: config.bandwidth,\n jitterBufferMs: config.fecReorderToleranceMs,\n })\n : undefined;\n this.#fec.configure({\n k: config.fecK,\n symbolSize: config.fecSymbolSize,\n interleaveDepthMs: config.fecInterleaveDepthMs,\n fps: config.fps,\n timeoutMs: deliveryWindowMs,\n });\n // External-feed consumers own the multicast socket and call feedPacket()\n // directly, so they never enter join(). Configure the transport watchdog\n // here where both transport-owned and external-feed paths share the same\n // catalog-derived FEC timing.\n //\n // This call supplies the stream-global BASELINE contribution. Any track\n // registered via configureTrackFec() participates in the same max\n // reduction, so a per-track window can only widen these deadlines, never\n // narrow them below what this baseline already guarantees.\n this.#streamFecBaseline = {\n deliveryWindowMs,\n interleaveDepthMs: config.fecInterleaveDepthMs,\n fps: config.fps,\n k: config.fecK,\n repairSymbols: config.fecRepairSymbols,\n minimumFailbackWindowMs,\n };\n this.#applyStreamGlobalFecTiming();\n // Wire the stream-global FecBlockMapper to MmtpRouter. Under iso-ssbg1-v1\n // (the only profile) the mapper locates no source symbol: a source is\n // placed only once a repair states its block (SS_Start, SSB_length), and\n // FecBlockMapper.fromSsId refuses every FEC stream. The router reads the\n // mapper only for the stream's block profile and K_max when no FEC\n // manager answers for the packet.\n //\n // gopDurationMs is the per-frame interval (1000/fps), never the\n // interleaveDepthMs: the mapper derives its interleave depth D from their\n // ratio, and passing interleaveDepthMs collapses D to 1 (BLO-3323 L14).\n const k = config.fecK;\n const interleaveMs = config.fecInterleaveDepthMs;\n const fps = config.fps;\n // The stream-global mapper exists only for a stream whose catalog names a\n // block profile; parseFecBlockProfile refuses the retired k-padded-v1.\n const blockProfile = config.fecBlockProfile === undefined\n ? undefined\n : parseFecBlockProfile(config.fecBlockProfile, \"[MulticastManager] configureFec fecBlockProfile\");\n if (blockProfile !== undefined && k && k > 0 && interleaveMs && interleaveMs > 0 && fps && fps > 0) {\n const mapper = new FecBlockMapper({\n blockProfile,\n k,\n interleaveDepthMs: interleaveMs,\n gopDurationMs: 1000 / fps,\n });\n this.#router.setFecMapper(mapper);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Track configuration (delegates to MmtpRouter)\n // ---------------------------------------------------------------------------\n\n // Track type discovery callback \u2014 fires when setPacketIdType is called.\n // Players wire this to MediaRouter.registerTrackTypeFromInit() for\n // init data authority (authoritative until next catalog update).\n #onTrackTypeDiscovered:\n | ((packetId: number, trackType: \"video\" | \"audio\") => void)\n | null = null;\n\n /** Register callback for track type discovery (from init data / inline signaling). */\n onTrackTypeDiscovered(\n cb: (packetId: number, trackType: \"video\" | \"audio\") => void,\n ): void {\n this.#onTrackTypeDiscovered = cb;\n }\n\n setPacketIdType(packetId: number, trackType: \"video\" | \"audio\") {\n this.#router.setPacketIdType(packetId, trackType);\n this.#onTrackTypeDiscovered?.(packetId, trackType);\n }\n\n /**\n * Set the keyframe loss timeout on the inner MmtpRouter (Phase 13).\n * Applies only to tracks WITHOUT FEC: an FEC track never arms the timer,\n * because its vanished blocks resolve through catalog-derived block\n * deadlines and the release gate's stall decision.\n * Must be called BEFORE setPacketIdType('video'). A reader is passed\n * through; see MmtpRouter.setKeyframeLostTimeoutMs.\n */\n setKeyframeLostTimeoutMs(ms: number | (() => number)): void {\n this.#router.setKeyframeLostTimeoutMs(ms);\n }\n\n setMfuTimeoutMs(ms: number): void {\n this.#router.setMfuTimeoutMs(ms);\n }\n\n setTrackCodec(packetId: number, codec: string) {\n this.#router.setTrackCodec(packetId, codec);\n // Never configure an FEC track by side effect: only an already\n // configured FEC track learns its codec here.\n if (this.#fec.isTrackConfigured(packetId)) {\n this.#fec.configureTrack(packetId, { codec });\n }\n }\n\n // ---------------------------------------------------------------------------\n // Callbacks (delegates to MmtpRouter)\n // ---------------------------------------------------------------------------\n\n onVideoFrame(cb: FrameCallback) {\n this.#router.onVideoFrame(cb);\n }\n onAudioFrame(cb: FrameCallback) {\n this.#router.onAudioFrame(cb);\n }\n onTrackFrame(packetId: number, cb: FrameCallback) {\n this.#router.onTrackFrame(packetId, cb);\n }\n onTrackPacket(packetId: number, cb: TrackPacketCallback) {\n this.#router.onTrackPacket(packetId, cb);\n }\n /** @deprecated Use onTrackInit(packetId, cb); see MmtpRouter.onInitSegment. */\n onInitSegment(cb: (data: Uint8Array) => void) {\n this.#router.onInitSegment(cb);\n }\n onTrackInit(packetId: number, cb: (data: Uint8Array) => void) {\n this.#router.onTrackInit(packetId, cb);\n }\n onFecBlockStarted(cb: (sbn: number, packetId: number) => void) {\n this.#router.onFecBlockStarted(cb);\n }\n /** Unrecoverable-FEC-block signal. Players drive decoder recovery from this\n * (a starved decoder cannot resync on its own once the block is abandoned). */\n onFecBlockFailed(\n cb: (sbn: number, packetId: number, catalogTrackName?: string) => void,\n ) {\n this.#router.onFecBlockFailed(cb);\n }\n\n // ---------------------------------------------------------------------------\n // MSE integration (delegates to MmtpRouter)\n // ---------------------------------------------------------------------------\n\n enableMseAssembler(\n packetId: number,\n config: Omit<BlockAssemblerConfig, \"k\">,\n catalogTrackName?: string,\n ) {\n return this.#router.enableMseAssembler(packetId, config, catalogTrackName);\n }\n\n onMseSegment(packetId: number, cb: (segment: Uint8Array) => void, catalogTrackName?: string) {\n this.#router.onMseSegment(packetId, cb, catalogTrackName);\n }\n\n onMseSample(packetId: number, cb: (sample: CmafBlockSample) => void, catalogTrackName?: string) {\n this.#router.onMseSample(packetId, cb, catalogTrackName);\n }\n\n onMseInitSegment(packetId: number, cb: (init: Uint8Array) => void, catalogTrackName?: string) {\n this.#router.onMseInitSegment(packetId, cb, catalogTrackName);\n }\n\n emitMseInit(packetId: number, catalogTrackName?: string) {\n this.#router.emitMseInit(packetId, catalogTrackName);\n }\n\n markExternalVideoSync(packetId?: number, catalogTrackName?: string) {\n this.#router.markExternalVideoSync(packetId, catalogTrackName);\n }\n\n // ---------------------------------------------------------------------------\n // Transport (delegates to MulticastTransport)\n // ---------------------------------------------------------------------------\n\n onTakeover(cb: () => void) {\n this.#transport.onTakeover(cb);\n }\n onFailback(cb: (reason: MulticastFailbackReason) => void) {\n this.#transport.onFailback(cb);\n }\n /** See MulticastTransport.onDataPlaneLive: the join's first accepted packet. */\n onDataPlaneLive(cb: () => void) {\n this.#transport.onDataPlaneLive(cb);\n }\n confirmVideo() {\n this.#transport.confirmVideo();\n }\n confirmAudio() {\n this.#transport.confirmAudio();\n }\n\n static isAvailable(): boolean {\n return MulticastTransport.isAvailable();\n }\n\n async join(config: MulticastEndpointConfig): Promise<boolean> {\n // A join replaces the admission a staged switch was built on. The new\n // join's config decides what is admitted, so nothing is restored or\n // retired here: the switch just ends.\n const staged = this.#renditionSwitch;\n if (staged !== null) this.#endRenditionSwitch(staged, { reason: \"superseded\" }, false);\n\n // Configure every FEC/MFU deadline before subscribing so direct-feed and\n // transport-owned consumers use the same catalog-derived delivery window.\n this.configureFec(config);\n\n // Every transport join starts at an arbitrary multicast symbol. Reset only\n // join-boundary classification; retain configured tracks, decoders, and\n // cumulative diagnostics across failback/rejoin cycles.\n this.#fec.beginMulticastJoin();\n const selectedPacketIds = config.packetIds?.length\n ? new Set(config.packetIds)\n : null;\n const catalogTracks = config.tracks?.filter(({ packetId }) =>\n selectedPacketIds === null || selectedPacketIds.has(packetId));\n const ingressTracks = catalogTracks\n ? catalogTracks\n : selectedPacketIds === null\n ? undefined\n : [...selectedPacketIds].map((packetId) => ({ name: \"\", packetId }));\n const ingress = ingressTracks === undefined\n ? undefined\n : {\n tracks: Object.freeze(\n ingressTracks.map(({ name, packetId }) =>\n Object.freeze({ name, packetId }),\n ),\n ),\n };\n // Precedence between two sources, not a tolerant alias. BOTH operands can\n // carry a catalog-declared relay, so neither one is \"the\" inlet:\n // `amtRelay` \u2014 the player/UI override, AND the post-migration catalog\n // path: `endpointToMulticastConfig` (packages/transport\n // multicast-catalog-policy.ts) resolves\n // `multicast.networkSource[{type:\"amt\"}]` into it\n // through `findAmtRelay`.\n // `relayAddress` \u2014 the pre-migration per-endpoint relay, retained so a\n // catalog published before BLO-37834 still joins.\n // The override/networkSource value wins, else the legacy endpoint value.\n // `??`, not `||`, so precedence is deliberately NOT trim-aware: a player\n // clearing its override field means \"no AMT\", not \"fall back to the legacy\n // relay\". Blank is absent for the port decision below but present here.\n // Retiring `relayAddress` is the legacy cut (BLO-38068), not this branch.\n const amtRelay = config.amtRelay ?? config.relayAddress;\n const transportConfig = {\n sourceAddress: config.sourceAddress,\n groupAddress: config.groupAddress,\n port: config.port,\n mode: config.mode,\n amtRelay,\n // A relay here is a commitment to that relay, not a hint: a relay with no\n // port does NOT fall back to DRIAD, it joins on RFC 7450's 2268.\n // `toWindowMulticastSubscribeConfig` (packages/transport\n // endpoint-config.ts) deliberately has no implicit relay port \u2014 it throws\n // and `MulticastTransport.join` resolves false \u2014 so the default is\n // resolved HERE and arrives there explicit, exactly as the two catalog\n // readers do it. This is the last of the three readers: the `relayAddress`\n // half cannot supply a port at all (the pre-migration wire shape has no\n // port field), and `resolveNetworkSource` emits the same port-less shape.\n // Keyed on the resolved relay, so a DRIAD entry \u2014 which pins no relay \u2014\n // never has a port manufactured for it. Blank counts as no relay, the\n // same test `catalogAmtNetworkSource` applies (it trims, then skips) and\n // the same one `toWindowMulticastSubscribeConfig` re-derives downstream\n // (`input.amtRelay?.trim()`): a port paired with a relay that consumer\n // reads as absent is a shape no reader here should manufacture. Only the\n // default is gated \u2014 an explicit `amtRelayPort` is caller data and passes\n // through whatever the relay looks like, since dropping it would be the\n // worse behaviour.\n amtRelayPort:\n config.amtRelayPort ??\n config.relayPort ??\n (amtRelay?.trim() ? DEFAULT_AMT_RELAY_PORT : undefined),\n packetIds: config.packetIds,\n tracks: catalogTracks?.map(({ name, packetId }) => ({ name, packetId })),\n };\n return ingress === undefined\n ? this.#transport.join(transportConfig)\n : this.#transport.join(transportConfig, ingress);\n }\n\n // ---------------------------------------------------------------------------\n // In-place rendition switch\n // ---------------------------------------------------------------------------\n\n /**\n * Switch the live join from one video rendition to another in place: the\n * subscription (socket, group membership, AMT tunnel) is kept, and the\n * packet-ID remap commits atomically at a RAP of the incoming rendition.\n *\n * Staging widens the admission to carry both renditions: the ingress\n * tracks become the live ones plus `tracks`, and, when `packetIds` is\n * given, the allow-list becomes the live one plus `packetIds` (a provider\n * that cannot widen in place is refused here, before anything changes).\n * The incoming rendition's packets then feed its FEC and parser while the\n * outgoing rendition keeps playing, and MmtpRouter.stageStreamSwitch\n * withholds every incoming frame and its init until the commit.\n *\n * The commit is one synchronous step inside the frame path, at the\n * incoming RAP (see MmtpRouter.stageStreamSwitch for where exactly): the\n * outgoing packet ID is retired from the router, the admission becomes\n * exactly `tracks` (and `packetIds`), the outgoing stream's FEC and repair\n * state is released (FECManager.releasePacket; repair fetches in flight are\n * aborted through the track-reset observers), `onCommit` runs, and then the\n * incoming init and RAP are delivered. No outgoing frame is delivered after\n * it and no incoming frame before it. No timer is armed: the switch waits\n * on RAPs and on the outgoing stream's own frames.\n *\n * Exactly one of `onCommit` and `onCancel` is called per switch. One switch\n * may be staged at a time.\n */\n switchRendition(request: RenditionSwitchRequest): RenditionSwitch {\n if (this.#renditionSwitch !== null) {\n throw new Error(\n \"[MulticastManager] a rendition switch is already staged; cancel it before staging another\",\n );\n }\n if (typeof request?.onCommit !== \"function\" || typeof request.onCancel !== \"function\") {\n throw new Error(\"[MulticastManager] switchRendition requires onCommit and onCancel callbacks\");\n }\n const live = this.#transport.context;\n if (!this.#transport.joined || live === undefined) {\n throw new Error(\n \"[MulticastManager] switchRendition needs a live join with catalog ingress tracks: \" +\n \"join() with packetIds or tracks first\",\n );\n }\n const { from, to } = request;\n if (!Array.isArray(request.tracks)) {\n throw new TypeError(\"[MulticastManager] switchRendition tracks must be an array\");\n }\n for (const track of request.tracks) {\n if (\n typeof track?.name !== \"string\" ||\n track.name.length === 0 ||\n !isMulticastTrackPacketId(track.packetId)\n ) {\n throw new RangeError(\n \"[MulticastManager] switchRendition tracks need a non-empty catalog name and a track packet ID each\",\n );\n }\n }\n const holds = (\n tracks: MmtpIngressContext[\"tracks\"],\n stream: MmtpStreamIdentity | undefined,\n ): boolean =>\n stream !== undefined &&\n tracks.some((track) => track.name === stream.catalogTrackName && track.packetId === stream.packetId);\n if (!holds(live.tracks, from)) {\n throw new Error(\n `[MulticastManager] switchRendition: from (${from?.catalogTrackName} on ${from?.packetId}) ` +\n \"is not an ingress track of the live join\",\n );\n }\n if (to === undefined || live.tracks.some((track) => track.packetId === to.packetId)) {\n throw new Error(\n `[MulticastManager] switchRendition: to's packet ID ${to?.packetId} is already admitted ` +\n \"by the live join's ingress; an in-place switch admits a rendition that is not playing\",\n );\n }\n if (!holds(request.tracks, to)) {\n throw new Error(\"[MulticastManager] switchRendition: tracks must carry to\");\n }\n if (request.tracks.some((track) => track.packetId === from.packetId)) {\n throw new Error(\n `[MulticastManager] switchRendition: tracks must not carry from's packet ID ${from.packetId}; ` +\n \"the commit retires it\",\n );\n }\n // While staged, the ingress is the live tracks plus these. One packet ID\n // under two names is ambiguous to the router, which drops it, so a track\n // must keep the name the live join gave its packet ID.\n const renamed = request.tracks.find((track) =>\n live.tracks.some((liveTrack) => liveTrack.packetId === track.packetId && liveTrack.name !== track.name));\n if (renamed !== undefined) {\n throw new Error(\n `[MulticastManager] switchRendition: tracks name packet ID ${renamed.packetId} ` +\n `\"${renamed.name}\", but the live join admits it under another name`,\n );\n }\n const livePacketIds = this.#transport.packetIds;\n let committedPacketIds: readonly number[] | undefined;\n if (request.packetIds !== undefined) {\n if (\n !Array.isArray(request.packetIds) ||\n request.packetIds.length === 0 ||\n !request.packetIds.every(isMulticastTrackPacketId)\n ) {\n throw new RangeError(\n \"[MulticastManager] switchRendition packetIds must be a non-empty list of track packet IDs\",\n );\n }\n if (!request.packetIds.includes(to.packetId)) {\n throw new Error(\"[MulticastManager] switchRendition: packetIds must admit to\");\n }\n committedPacketIds = Object.freeze([...new Set(request.packetIds)]);\n } else if (livePacketIds !== null && !livePacketIds.has(to.packetId)) {\n throw new Error(\n `[MulticastManager] switchRendition: the live allow-list does not admit to (${to.packetId}); ` +\n \"pass the post-switch packetIds\",\n );\n }\n\n const stagedTracks = new Map<string, Readonly<{ name: string; packetId: number }>>();\n for (const track of [...live.tracks, ...request.tracks]) {\n stagedTracks.set(`${track.packetId}\\0${track.name}`, track);\n }\n const stagedIngress = freezeIngress(stagedTracks.values());\n const stagedPacketIds =\n committedPacketIds === undefined || livePacketIds === null\n ? null\n : new Set([...livePacketIds, ...committedPacketIds]);\n const widened = stagedPacketIds !== null && !sameIdSet(stagedPacketIds, livePacketIds!);\n\n const active: ActiveRenditionSwitch = {\n from: Object.freeze({ packetId: from.packetId, catalogTrackName: from.catalogTrackName }),\n to: Object.freeze({ packetId: to.packetId, catalogTrackName: to.catalogTrackName }),\n onCommit: request.onCommit,\n onCancel: request.onCancel,\n liveIngress: live,\n livePacketIds,\n widened,\n committedIngress: freezeIngress(request.tracks),\n committedPacketIds,\n router: null,\n state: \"staged\",\n };\n // Throws on an unregistered or non-video stream before any admission changes.\n active.router = this.#router.stageStreamSwitch({\n from: active.from,\n to: active.to,\n onCommit: (commit) => this.#commitRenditionSwitch(active, commit),\n });\n let widening: Promise<void> | undefined;\n try {\n if (widened) widening = this.#transport.setPacketIds([...stagedPacketIds!]);\n this.#transport.setContext(stagedIngress);\n } catch (error) {\n active.router.cancel();\n throw error;\n }\n this.#renditionSwitch = active;\n widening?.catch((error: unknown) => {\n if (this.#renditionSwitch !== active) return;\n this.#endRenditionSwitch(active, { reason: \"provider-refused\", error }, true);\n });\n return {\n get state() {\n return active.state;\n },\n cancel: () => {\n if (this.#renditionSwitch !== active) return false;\n this.#endRenditionSwitch(active, { reason: \"cancelled\" }, true);\n return true;\n },\n };\n }\n\n /** The router committed the staged switch (it already retired `from`). */\n #commitRenditionSwitch(active: ActiveRenditionSwitch, commit: StreamSwitchCommit): void {\n if (this.#renditionSwitch !== active) return;\n this.#renditionSwitch = null;\n active.state = \"committed\";\n active.router = null;\n if (this.#transport.joined) {\n this.#transport.setContext(active.committedIngress);\n if (active.committedPacketIds !== undefined) {\n // The page-side filter narrows now; the provider may keep delivering\n // the retired IDs until it applies the narrower list, and the page\n // drops them meanwhile.\n this.#transport.setPacketIds(active.committedPacketIds).catch((error: unknown) => {\n console.warn(\n \"[MulticastManager] the provider did not narrow its packet filter after a rendition switch; \" +\n \"the page-side filter drops the retired packet IDs\",\n error,\n );\n });\n }\n }\n this.#releaseRetiredStream(commit.from);\n try {\n active.onCommit(commit);\n } catch (error) {\n // The caller could not take `to`. Report it, release what the manager\n // holds for `to`, and rethrow so the router retires `to` instead of\n // feeding it to a decoder still configured for `from`.\n active.state = \"failed\";\n this.#releaseRetiredStream(active.to);\n try {\n active.onCancel({ reason: \"commit-failed\", error });\n } catch (cancelError) {\n console.warn(\"[MulticastManager] rendition switch onCancel failed\", cancelError);\n }\n throw error;\n }\n }\n\n /**\n * End a staged switch without committing. `restore` puts the pre-switch\n * admission back and retires `to`; a join or dispose supersedes both.\n */\n #endRenditionSwitch(\n active: ActiveRenditionSwitch,\n cancel: RenditionSwitchCancel,\n restore: boolean,\n ): void {\n if (this.#renditionSwitch !== active) return;\n this.#renditionSwitch = null;\n active.state = \"cancelled\";\n active.router?.cancel();\n active.router = null;\n if (restore) {\n if (this.#transport.joined) {\n this.#transport.setContext(active.liveIngress);\n if (active.widened && active.livePacketIds !== null) {\n this.#transport.setPacketIds([...active.livePacketIds]).catch((error: unknown) => {\n console.warn(\n \"[MulticastManager] the provider did not narrow its packet filter after a cancelled \" +\n \"rendition switch; the page-side filter drops the incoming packet IDs\",\n error,\n );\n });\n }\n }\n this.#router.retirePacketId(active.to.packetId);\n this.#releaseRetiredStream(active.to);\n }\n try {\n active.onCancel(cancel);\n } catch (error) {\n console.warn(\"[MulticastManager] rendition switch onCancel failed\", error);\n }\n }\n\n /**\n * Release what the manager and FEC hold for a stream whose packet ID the\n * router retired, or whose FEC a catalog reconfiguration no longer configures.\n */\n #releaseRetiredStream(stream: MmtpStreamIdentity): void {\n // Repair transports abort I/O for a stream on a reset notice\n // (wireRepairFetch); a retired stream's repair fetches end with it.\n for (const observer of this.#trackResetObservers) {\n try {\n observer(stream.packetId, stream.catalogTrackName);\n } catch (error) {\n console.warn(\"[MulticastManager] track reset observer failed\", error);\n }\n }\n for (const key of [...this.#repairInFlight]) {\n try {\n const [keyPacketId] = JSON.parse(key);\n if (keyPacketId === stream.packetId) this.#repairInFlight.delete(key);\n } catch {\n // Internal keys are JSON tuples; preserve unknown legacy entries.\n }\n }\n for (const [key, stats] of [...this.#blockPolicyStats]) {\n if (stats.packetId === stream.packetId) this.#blockPolicyStats.delete(key);\n }\n // The retired track stops contributing to the stream-global FEC timing;\n // the deadlines already applied stay until the next configuration\n // re-reduces them, which only ever errs toward the wider window.\n this.#trackFecTiming.delete(stream.packetId);\n this.#multiTrackFecTracks.delete(stream.packetId);\n this.#fec.releasePacket(stream.packetId);\n }\n\n /** Feed a raw packet from an external source (AMT tunnel, ASM subscription).\n *\n * For MoQ-unicast-fallback mode (no SSM bridge), call\n * `manager.router.feedPacket(packet, \"moq\")` directly to bypass\n * MulticastTransport's takeover/watchdog/failback gates while preserving\n * transport attribution in FEC path counters.\n * The `router` accessor above is already public.\n */\n feedPacket(data: ArrayBuffer | Uint8Array, ingress?: MmtpIngressContext) {\n this.#transport.feedPacket(data, ingress);\n }\n\n /**\n * Declare how packets given to feedPacket() reach this manager. A consumer\n * that owns the multicast socket itself declares its own leg; join()\n * applies what the window.multicast subscription declares instead.\n */\n setMulticastSymbolDelivery(delivery: MulticastSymbolDelivery): void {\n this.#fec.setMulticastSymbolDelivery(delivery);\n }\n\n /** Process a repair packet from MoQ (reliable QUIC). */\n processRepairFromMoQ(\n packet: Uint8Array,\n ownerPacketId?: number,\n catalogTrackName?: string,\n ) {\n this.#router.feedRepairPacket(packet, ownerPacketId, catalogTrackName);\n }\n\n /** Route a Worker-recovered FEC block through the MMTP parser + dedup pipeline. */\n routeRecoveredBlock(\n blockId: number,\n data: Uint8Array,\n packetId: number,\n catalogTrackName?: string,\n ) {\n this.#router.routeRecoveredBlock(blockId, data, packetId, catalogTrackName);\n }\n\n /** Route one Worker-recovered source symbol through the MMTP parser + dedup pipeline. */\n routeRecoveredSymbol(\n blockId: number,\n data: Uint8Array,\n packetId: number,\n esi = 0,\n catalogTrackName?: string,\n ) {\n this.#router.routeRecoveredSymbol(blockId, data, packetId, esi, catalogTrackName);\n }\n\n // ---------------------------------------------------------------------------\n // Phase 14: multi-track catalog ingest (D-04, D-06, D-07, D-17, D-26, D-28)\n // ---------------------------------------------------------------------------\n\n /**\n * Configure the manager for a multi-track (base/delta/repair) CMAF switching set.\n *\n * D-04: Validates completeness via validateSwitchingSet \u2014 throws if incomplete.\n * D-07: Applies per-track FEC configs (base+delta get decoders; repair does not).\n * D-17: Builds altGroup \u2192 track-name index for getSwitchingSet().\n * D-26: Registers packetId \u2192 TrackRole on MmtpRouter for block-complete fanout.\n *\n * FEC availability is per track (draft-ramadan-moq-fec \u00A75.1). A media track\n * whose `fec` names no implemented `blockProfile` (only `iso-ssbg1-v1` is\n * implemented; the retired `k-padded-v1` is not), or an `iso-ssbg1-v1` span\n * it cannot use, is consumed without FEC: it gets no decoder, and none of\n * its repair tracks (layer 0 by `fec.repairTrack`, further layers and the\n * overlay by `depends`) is given an owner, so the router drops their packets\n * before any decoder. Those legs keep the `repair` role and their owner's\n * switching set. Neither the call nor any other track's FEC fails on that\n * account; `trackFecUnavailable(name)` reports the reason. FEC a previous\n * call configured on a packetId this call does not is released\n * (FECManager.releasePacket); frames its release gate held are delivered in\n * order, each gap stalled. FEC wired by hand (setFecDecoder) is left alone.\n * Geometry a decoder could not configure (K, P, T, `repairTrack`,\n * algorithm) still throws.\n *\n * @param catalog - Multi-track catalog (tracks array with name, altGroup, fec, packetId).\n * @param wasmDecoderSource - One WASM FEC decoder for a single FEC stream, or\n * a factory returning a distinct decoder per stream.\n */\n configureMultiTrack(\n catalog: MultiTrackCatalog,\n wasmDecoderSource?: WasmFecDecoder | WasmFecDecoderFactory,\n ): void {\n // D-06: Resolve explicit fec.repairTrack pointers FIRST. This allows\n // publisher-chosen repair track names (e.g. \"video/repair-alt\") that don't\n // match the standard \"/repair\" suffix convention. The explicit pointer wins\n // over the naming convention: a track pointed to by repairTrack is always\n // 'repair'. The pointer lives on the owner, and a repair leg is a raw\n // application/raptorq track the publisher never tags with an altGroup, so\n // the leg belongs to its owner's group regardless of what the leg itself\n // declares.\n //\n // Layers 1..n and a keyframe overlay are named only by their own `depends`\n // (draft-ramadan-moq-fec \u00A76.3), so they are resolved here too. A layer that\n // did not reach this map would fall through to parseTrackRole and be\n // mis-roled or rejected on its name, and would land in its own altGroup\n // rather than its owner's.\n const repairNamesBySource = repairTrackNamesBySource(catalog.tracks);\n // \u00A75.1: classify each media track's FEC before roles are resolved. A track\n // whose FEC is unavailable still names its layer 0 by `fec.repairTrack`;\n // that pointer is a catalog fact about the leg, not about the profile, so\n // the leg stays roled `repair` in its owner's group rather than falling\n // through to its name. It is never given an owner below.\n const declaredRepairNames = new Set<string>();\n for (const names of repairNamesBySource.values()) {\n for (const name of names) declaredRepairNames.add(name);\n }\n const fecUnavailableByTrack = new Map<string, TrackFecUnavailable>();\n for (let i = 0; i < catalog.tracks.length; i++) {\n const track = catalog.tracks[i]!;\n if (\n track.fec === undefined ||\n track.packaging === \"fec-repair\" ||\n declaredRepairNames.has(track.name)\n ) {\n continue;\n }\n const fecUnavailable = catalogFecUnavailable(track.fec, `$.tracks[${i}].fec`);\n if (fecUnavailable === undefined) continue;\n fecUnavailableByTrack.set(track.name, fecUnavailable);\n const layerZero = (track.fec as { repairTrack?: unknown }).repairTrack;\n if (typeof layerZero === \"string\" && layerZero.length > 0) {\n const names = repairNamesBySource.get(track.name) ?? new Set<string>();\n names.add(layerZero);\n repairNamesBySource.set(track.name, names);\n }\n }\n // Every repair leg of a source without FEC goes unowned, including one a\n // source with FEC also names: the router could not tell the two sources'\n // symbols apart on it, and the unavailable source's must reach no decoder.\n const unownedRepairNames = new Set<string>();\n for (const sourceName of fecUnavailableByTrack.keys()) {\n for (const repairName of repairNamesBySource.get(sourceName) ?? []) {\n unownedRepairNames.add(repairName);\n }\n }\n const repairOwnerGroup = new Map<string, string>();\n const repairOwnerAltGroup = new Map<string, number | undefined>();\n for (const track of catalog.tracks) {\n for (const repairName of repairNamesBySource.get(track.name) ?? []) {\n repairOwnerGroup.set(repairName, altGroupKey(track));\n repairOwnerAltGroup.set(repairName, track.altGroup);\n }\n }\n const groupKeyFor = (track: MultiTrackCatalogTrack): string =>\n repairOwnerGroup.get(track.name) ?? altGroupKey(track);\n\n const trackNamesByGroup = new Map<string, string[]>();\n for (const track of catalog.tracks) {\n const groupKey = groupKeyFor(track);\n const groupNames = trackNamesByGroup.get(groupKey) ?? [];\n groupNames.push(track.name);\n trackNamesByGroup.set(groupKey, groupNames);\n }\n\n const hasSwitchingSetByGroup = new Map<string, boolean>();\n for (const [groupKey, trackNames] of trackNamesByGroup) {\n hasSwitchingSetByGroup.set(groupKey, hasSwitchingSetRole(trackNames));\n }\n\n // Resolve role for each track \u2014 explicit pointer wins over naming convention.\n // Normalize non-standard repair names to 'repair' before calling validateSwitchingSet,\n // which relies on parseTrackRole and would throw on custom repair-track names.\n const resolvedRoles: TrackRole[] = catalog.tracks.map((track) => {\n if (repairOwnerGroup.has(track.name)) return \"repair\";\n const hasSwitchingSet = hasSwitchingSetByGroup.get(altGroupKey(track)) ?? false;\n if (!hasSwitchingSet) return \"single\";\n return parseTrackRole(track.name);\n });\n\n // D-04: Validate completeness per altGroup using resolved roles.\n // We replicate validateSwitchingSet logic here to avoid re-calling parseTrackRole\n // (which would throw on explicit-pointer repair track names like \"video/repair-alt\").\n const resolvedRolesByGroup = new Map<string, TrackRole[]>();\n for (let i = 0; i < catalog.tracks.length; i++) {\n const groupKey = groupKeyFor(catalog.tracks[i]!);\n const roles = resolvedRolesByGroup.get(groupKey) ?? [];\n roles.push(resolvedRoles[i]!);\n resolvedRolesByGroup.set(groupKey, roles);\n }\n for (const [groupKey, roles] of resolvedRolesByGroup) {\n if (!(hasSwitchingSetByGroup.get(groupKey) ?? false)) continue;\n\n const presentRoles = new Set(roles.filter((r) => r !== \"single\"));\n const required: Array<\"base\" | \"delta\" | \"repair\"> = [\n \"base\",\n \"delta\",\n \"repair\",\n ];\n const missing = required.filter((r) => !presentRoles.has(r));\n if (missing.length > 0) {\n throw new Error(\n `[MulticastManager] incomplete switching set: missing ${missing.join(\", \")}`,\n );\n }\n }\n\n const fecTrackIndexes: number[] = [];\n for (let i = 0; i < catalog.tracks.length; i++) {\n const track = catalog.tracks[i]!;\n if (resolvedRoles[i] === \"repair\" || track.fec === undefined) continue;\n // Malformed geometry fails the call whatever the profile says; an\n // unavailable profile or span only costs this track its FEC (\u00A75.1).\n const unavailable = fecUnavailableByTrack.has(track.name);\n if (!(unavailable ? isFecCatalogGeometry(track.fec) : isFecCatalogConfig(track.fec))) {\n throw new Error(\n `[MulticastManager] invalid fec config for track '${track.name}'`,\n );\n }\n if (unavailable) continue;\n if (track.packetId !== undefined) fecTrackIndexes.push(i);\n }\n\n // Stateful decoders cannot span physical streams: resetting one stream\n // would otherwise clear or disable every peer sharing that instance.\n const fecDecoders = new Map<number, WasmFecDecoder>();\n if (wasmDecoderSource !== undefined && fecTrackIndexes.length > 0) {\n if (typeof wasmDecoderSource !== \"function\") {\n if (fecTrackIndexes.length > 1) {\n throw new Error(\n \"[MulticastManager] multiple FEC streams require a decoder factory\",\n );\n }\n fecDecoders.set(fecTrackIndexes[0]!, wasmDecoderSource);\n } else {\n const clients = new Set<WasmFecDecoder>();\n for (const index of fecTrackIndexes) {\n const client = wasmDecoderSource(catalog.tracks[index]!);\n if (clients.has(client)) {\n throw new Error(\n \"[MulticastManager] decoder factory must return one client per physical stream\",\n );\n }\n clients.add(client);\n fecDecoders.set(index, client);\n }\n }\n }\n\n // Reset switching-set index, repair packetId set and FEC availability.\n this.#switchingSets.clear();\n this.#repairPacketIds.clear();\n this.#router.clearRepairPacketIdOwners();\n // FEC a previous call configured that this one does not (the source is\n // gone, lost its `fec`, or its FEC is now unavailable) must not stay\n // installed: its symbols would still be read as block coordinates and\n // held for repair legs that no longer have an owner. FEC a caller wired\n // by hand is not this call's to release.\n const fecPacketIds = new Set([...fecDecoders.keys()].map((i) => catalog.tracks[i]!.packetId));\n // The record survives a throw below: a packet this call configures again\n // is overwritten by its set(), and a released one is dropped by\n // #releaseRetiredStream.\n for (const [packetId, catalogTrackName] of this.#multiTrackFecTracks) {\n if (fecPacketIds.has(packetId)) continue;\n if (!this.#fec.isTrackConfigured(packetId)) {\n this.#multiTrackFecTracks.delete(packetId);\n continue;\n }\n this.#router.releaseFecPacket(packetId);\n this.#releaseRetiredStream({ packetId, catalogTrackName });\n }\n this.#fecUnavailableByTrack.clear();\n for (const [name, reason] of fecUnavailableByTrack) {\n this.#fecUnavailableByTrack.set(name, reason);\n }\n const catalogTracksByName = new Map(\n catalog.tracks.map((track) => [track.name, track] as const),\n );\n\n for (let i = 0; i < catalog.tracks.length; i++) {\n const track = catalog.tracks[i]!;\n const role = resolvedRoles[i]!;\n\n // D-17: Build altGroup \u2192 track-name index. A repair leg belongs to the\n // switching set of the owner that points at it.\n const altGroup = repairOwnerAltGroup.has(track.name)\n ? repairOwnerAltGroup.get(track.name)\n : track.altGroup;\n if (altGroup !== undefined) {\n const existing = this.#switchingSets.get(altGroup) ?? [];\n existing.push(track.name);\n this.#switchingSets.set(altGroup, existing);\n }\n\n // D-26: Register packetId \u2192 role on MmtpRouter (if packetId provided at ingest time).\n if (track.packetId !== undefined) {\n this.#router.setPacketIdRole(track.packetId, role);\n }\n\n if (role === \"repair\") {\n // D-06: Repair track is the repair source \u2014 no FEC decoder registered on it.\n if (track.packetId !== undefined) {\n this.#repairPacketIds.add(track.packetId);\n }\n continue;\n }\n\n // D-07: Apply per-track FEC config for non-repair tracks. A track whose\n // FEC is unavailable (\u00A75.1) is consumed like a track without `fec`.\n if (track.fec !== undefined && !fecUnavailableByTrack.has(track.name)) {\n const fecCfg = track.fec as FecCatalogConfig;\n // Every repair layer and the keyframe overlay route to this source's\n // decoder, not only layer 0. A layer whose packetId had no registered\n // owner would have its repair symbols dropped as an unknown owner, and\n // two sources whose RS_ID ranges overlap would be indistinguishable.\n if (track.packetId !== undefined) {\n for (const repairName of repairNamesBySource.get(track.name) ?? []) {\n if (unownedRepairNames.has(repairName)) continue;\n const repairTrack = catalogTracksByName.get(repairName);\n if (repairTrack?.packetId !== undefined) {\n this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);\n }\n }\n }\n const wasmDecoder = fecDecoders.get(i);\n if (track.packetId !== undefined && wasmDecoder !== undefined) {\n // trackType: base/delta/single video tracks are 'video'; audio would be 'audio'.\n // At this layer we treat all content tracks as 'video' \u2014 audio tracks typically\n // have no FEC in Phase 14 (D-26 covers video base+delta only). Callers that need\n // audio FEC can call setFecDecoder() directly after configureMultiTrack().\n const blockProfile = parseFecBlockProfile(\n (fecCfg as { blockProfile?: unknown }).blockProfile,\n `tracks[${JSON.stringify(track.name)}].fec.blockProfile`,\n );\n this.#fec.setFecDecoder(\n track.packetId,\n wasmDecoder,\n fecCfg.sourceSymbols,\n {\n blockProfile,\n k: fecCfg.sourceSymbols,\n symbolSize: fecCfg.symbolSize,\n interleaveDepthMs: fecCfg.interleaveDepthMs,\n fps: track.framerate,\n trackKind: \"video\",\n },\n track.name,\n );\n this.#multiTrackFecTracks.set(track.packetId, track.name);\n this.#configureTrackMapper(track.packetId, {\n blockProfile,\n k: fecCfg.sourceSymbols,\n symbolSize: fecCfg.symbolSize,\n interleaveDepthMs: fecCfg.interleaveDepthMs,\n fps: track.framerate,\n });\n this.#router.setPacketIdType(track.packetId, \"video\");\n }\n }\n }\n }\n\n /**\n * Why `configureMultiTrack` consumes the named catalog track without FEC\n * although its `fec` block declares one (draft-ramadan-moq-fec \u00A75.1): no\n * implemented `blockProfile` (`track_fec_block_profile_unimplemented`, e.g.\n * the retired `k-padded-v1`; `track_fec_block_profile` when absent), or an\n * `iso-ssbg1-v1` span it cannot use (`track_fec_block_span` when absent,\n * `track_fec` when declared but unusable). The same reason the hang\n * resolver reports (`catalogFecUnavailable`). Undefined for every other\n * track, and for every track before the first call; each call replaces the\n * previous call's answers.\n */\n trackFecUnavailable(trackName: string): TrackFecUnavailable | undefined {\n return this.#fecUnavailableByTrack.get(trackName);\n }\n\n /** Per-track FEC availability from the last `configureMultiTrack` call. */\n readonly #fecUnavailableByTrack = new Map<string, TrackFecUnavailable>();\n\n /** packetId \u2192 catalog track name of the FEC the last `configureMultiTrack` installed. */\n readonly #multiTrackFecTracks = new Map<number, string>();\n\n /**\n * Return the track names belonging to the given altGroup switching set.\n * D-17: altGroup groups base+delta+repair into one switching set.\n *\n * @param altGroup - The altGroup number to look up.\n * @returns Array of track names in that switching set (empty if none).\n */\n getSwitchingSet(altGroup: number): string[] {\n return this.#switchingSets.get(altGroup) ?? [];\n }\n\n /**\n * Register callback fired when a repair fetch should be issued for an FEC block.\n *\n * D-28: At most one outstanding repair fetch per (packetId, sbn) block.\n * clearRepairForBlock fires automatically when the block resolves (via\n * FECManager.onRepairResolved); call it manually only on fetch failure\n * paths the FEC layer cannot observe (fetch error, range not retained,\n * transport timeout) so a stuck fetch cannot wedge the block's slot.\n *\n * Registering also arms the FEC repair deadline: each block gets a one-shot\n * timer for the derived K-symbol source-production horizon; a block still\n * short of K when it fires requests repair here. Failure accounting timing\n * is unchanged.\n */\n onRepairSubscribe(cb: RepairSubscribeCallback): void {\n this.#onRepairSubscribeCb = cb;\n this.#fec.onRepairNeeded = (sbn, packetId, catalogTrackName, blockClass) => {\n this.requestRepairForBlock(sbn, packetId, catalogTrackName, blockClass);\n };\n }\n\n /**\n * Record a repair request whose SUBSCRIBER_PRIORITY the catalog could not\n * supply (draft-ramadan-moq-fec \u00A711.4.5): sent without the parameter, or\n * not sent. Emits one `repair-priority` lifecycle event.\n */\n noteRepairPriority(\n action: \"omitted\" | \"declined\",\n reason: RepairPriorityReason,\n blockClass: BlockClass,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n this.#emitRepairLifecycle({\n version: 2,\n type: \"repair-priority\",\n block: repairBlockIdentity(packetId, sbn, catalogTrackName),\n blockClass,\n action,\n reason,\n });\n }\n\n /** Record a repair request the scheduler sent no FETCH for. */\n noteRepairSkipped(\n reason: RepairSkipReason,\n measurement: { readonly rttMs: number | null; readonly budgetMs: number },\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n this.#statsFor(packetId, catalogTrackName).repairsSkipped++;\n this.#emitRepairLifecycle({\n version: 2,\n type: \"repair-skipped\",\n block: repairBlockIdentity(packetId, sbn, catalogTrackName),\n reason,\n rttMs: measurement.rttMs,\n budgetMs: measurement.budgetMs,\n });\n }\n\n /**\n * Record a repair-scheduler decision about a delta block: preempted for\n * keyframe/audio work, or never dispatched before its block resolved.\n */\n noteRepairScheduling(\n type: \"repair-preempted\" | \"repair-deferred\",\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n const stats = this.#statsFor(packetId, catalogTrackName);\n if (type === \"repair-preempted\") stats.deltaRepairsPreempted++;\n else stats.deltaRepairsDeferred++;\n this.#emitRepairLifecycle({\n version: 2,\n type,\n block: repairBlockIdentity(packetId, sbn, catalogTrackName),\n });\n }\n\n /** Catalog/reset epoch of a packet's streams; gap decisions carry it. */\n epochFor(packetId: number): number {\n return this.#fec.epochFor(packetId);\n }\n\n /**\n * Keyframe-protected repair policy counters, keyed by catalog track name\n * (`packetId:<n>` for a stream with no catalog name). Each track's\n * counters reset with its epoch.\n */\n getBlockPolicyStats(): Record<string, BlockPolicyStats> {\n const out: Record<string, BlockPolicyStats> = {};\n for (const [key, stats] of this.#blockPolicyStats) {\n if (stats.epoch !== this.#fec.epochFor(stats.packetId)) continue;\n out[key] = { ...stats };\n }\n return out;\n }\n\n #statsFor(packetId: number, catalogTrackName?: string): BlockPolicyStats {\n const key = catalogTrackName ?? `packetId:${packetId}`;\n const epoch = this.#fec.epochFor(packetId);\n let stats = this.#blockPolicyStats.get(key);\n if (!stats || stats.epoch !== epoch || stats.packetId !== packetId) {\n stats = newBlockPolicyStats(packetId, catalogTrackName ?? null, epoch);\n this.#blockPolicyStats.set(key, stats);\n }\n return stats;\n }\n\n /**\n * Observe causal repair lifecycle events without retaining snapshots in the\n * transport. Returns an unsubscribe function.\n */\n addRepairLifecycleObserver(observer: RepairLifecycleObserver): () => void {\n this.#repairLifecycleObservers.add(observer);\n return () => this.#repairLifecycleObservers.delete(observer);\n }\n\n /** Observe per-stream epoch resets (used by repair transports to abort I/O). */\n addTrackResetObserver(\n observer: (packetId: number, catalogTrackName: string) => void,\n ): () => void {\n this.#trackResetObservers.add(observer);\n return () => this.#trackResetObservers.delete(observer);\n }\n\n /** Begin a fresh retained-replay epoch for one physical media stream. */\n resetTrack(packetId: number, catalogTrackName: string): void {\n if (!catalogTrackName) {\n throw new Error('[MulticastManager] catalogTrackName is required for track reset');\n }\n this.#resetStreamOutsideRouter(packetId, catalogTrackName);\n this.#router.resetTrack(packetId, catalogTrackName);\n }\n\n /** resetTrack() for everything but the router's own state. */\n #resetStreamOutsideRouter(packetId: number, catalogTrackName: string): void {\n for (const observer of this.#trackResetObservers) {\n try {\n observer(packetId, catalogTrackName);\n } catch (error) {\n console.warn('[MulticastManager] track reset observer failed', error);\n }\n }\n for (const key of this.#repairInFlight) {\n try {\n const [keyPacketId, keyTrackName] = JSON.parse(key);\n if (keyPacketId === packetId && keyTrackName === catalogTrackName) {\n this.#repairInFlight.delete(key);\n }\n } catch {\n // Internal keys are JSON tuples; preserve unknown legacy entries.\n }\n }\n this.#fec.resetTrack(packetId, catalogTrackName);\n }\n\n /**\n * Request a repair-track fetch for the given FEC block.\n *\n * D-28: duplicate requests for the same (packetId, sbn) are suppressed\n * until clearRepairForBlock is called.\n *\n * @param sbn - Source Block Number identifying the FEC block needing repair.\n * @param packetId - MMTP packet_id owning the block (video/audio tracks have\n * independent SBN spaces, so the in-flight key must be composite).\n */\n requestRepairForBlock(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ): void {\n // D-28: at most one outstanding fetch per FEC block (per packetId:sbn).\n // Different blocks are independent and are NOT blocked by each other.\n const block = repairBlockIdentity(packetId, sbn, catalogTrackName);\n const key = JSON.stringify([packetId, block.catalogTrackName, sbn]);\n if (this.#repairInFlight.has(key)) return;\n this.#repairInFlight.add(key);\n const stats = this.#statsFor(packetId, catalogTrackName);\n if (blockClass === \"keyframe\") stats.keyframeBlocksProtected++;\n else if (blockClass === \"unknown\") stats.unknownBlocksProtected++;\n else if (blockClass === \"delta\") stats.deltaBlocksShort++;\n this.#emitRepairLifecycle({\n version: 2,\n type: \"requested\",\n block,\n blockClass,\n priority: repairPriorityForClass(blockClass),\n });\n this.#onRepairSubscribeCb?.(sbn, packetId, catalogTrackName, blockClass);\n }\n\n /**\n * Clear in-flight repair state for the given block.\n * Fired automatically when the block resolves; safe to call redundantly.\n */\n clearRepairForBlock(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n this.#repairInFlight.delete(\n JSON.stringify([packetId, catalogTrackName ?? null, sbn]),\n );\n }\n\n #emitRepairLifecycle(event: RepairLifecycleEvent): void {\n // A gap decision is already frozen and is the same object the frame\n // carries as gapBefore: forward it as is, never a copy.\n const immutableEvent = event.type === \"gap\"\n ? event\n : (Object.freeze(\n \"stream\" in event\n ? { ...event, stream: Object.freeze({ ...event.stream }) }\n : {\n ...event,\n block: Object.freeze({ ...event.block }),\n ...(\"symbols\" in event\n ? { symbols: Object.freeze({ ...event.symbols }) }\n : {}),\n },\n ) as RepairLifecycleEvent);\n // Serialize before invoking observers so a re-entrant observer cannot log\n // a resolution before the request that caused it.\n if (this.#logRepairLifecycle) {\n console.info(\n `${REPAIR_LIFECYCLE_LOG_PREFIX}${JSON.stringify(immutableEvent)}`,\n );\n }\n for (const observer of this.#repairLifecycleObservers) {\n try {\n observer(immutableEvent);\n } catch (error) {\n console.warn(\"[MulticastManager] repair lifecycle observer failed\", error);\n }\n }\n }\n\n dispose() {\n const staged = this.#renditionSwitch;\n if (staged !== null) this.#endRenditionSwitch(staged, { reason: \"superseded\" }, false);\n this.#trackResetObservers.clear();\n this.#repairLifecycleObservers.clear();\n this.#transport.dispose();\n this.#router.dispose();\n this.#fec.dispose();\n }\n}\n\n// Compile-time contract: the manager is the host the D-28 repair scheduler\n// (wireRepairFetch) drives.\nconst repairFetchHost = (manager: MulticastManager): RepairFetchHost => manager;\nvoid repairFetchHost;\n", "/**\n * `multicast.auth` v2 \u2014 catalog representation for per-track source-auth\n * scopes and manifest tracks (BLO-16700 decision).\n *\n * Reconciles two previously-accepted, mutually incompatible contracts:\n * - BLO-16481 (S0): every publisher session/track/source-symbol-format\n * needs its own fresh, unpredictable 32-byte `auth_scope`, bound into\n * the signed per-group Merkle root (see `sourceAuthSignaturePreimage`\n * in ./source-auth.ts).\n * - BLO-16485 (S3 draft): a singular `multicast.auth` block with one\n * `manifestTrack` string and no `authScope` field at all \u2014 unable to\n * describe two protected tracks (e.g. audio + video) in one session.\n *\n * `multicast.auth` v2 keeps one shared signing identity (scheme, hash,\n * sig, publicKey, keyId, previousKey, compaction, enforcement) but moves\n * `manifestTrack` into a `tracks[]` array, one entry per protected media\n * track, each carrying its own `manifestTrack` name, `authScope`, and\n * `sourceSymbolFormat`. This is additive over the S3 draft shape (every\n * top-level field the draft specified still exists at the top level) \u2014\n * only `manifestTrack` moves from top-level into each track entry.\n *\n * `manifestTrack` is carried as an opaque string field, deliberately NOT\n * run through `parseTrackRole`/`VALID_ROLE_SUFFIXES` in ./carp-catalog.ts.\n * `carp-catalog.ts` only recognises `base`/`delta`/`repair` role suffixes\n * and throws on anything else (including a hypothetical `manifest` role);\n * resolving BLO-16485's \"manifest-track name collision\" MANDATORY item by\n * adding a role would force every catalog consumer to learn a fourth role\n * and would break `validateSwitchingSet`'s base/delta/repair completeness\n * check. Carrying it as a typed field here avoids both.\n */\n\nexport type MulticastAuthScheme = \"bc-provenance\";\nexport type MulticastAuthHash = \"blake3\";\nexport type MulticastAuthSig = \"ed25519\";\nexport type MulticastAuthCompaction = \"merkle\" | \"none\";\nexport type MulticastAuthEnforcement = \"shadow\" | \"enforce\";\nexport type MulticastAuthMode = \"off\" | MulticastAuthEnforcement;\n\nconst RAW_ED25519_KEY_LENGTH = 32;\nconst SOURCE_AUTH_SCOPE_LENGTH = 32;\n\nconst BASE64URL_ALPHABET =\n\t\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n\n/** Encode raw bytes as unpadded base64url \u2014 the one accepted encoding for\n * `authScope`/`publicKey`/`previousKey` (BLO-16483's operator quickstart). */\nexport function encodeBase64Url(bytes: Uint8Array): string {\n\tlet output = \"\";\n\tfor (let i = 0; i < bytes.length; i += 3) {\n\t\tconst b0 = bytes[i]!;\n\t\tconst b1 = bytes[i + 1];\n\t\tconst b2 = bytes[i + 2];\n\t\toutput += BASE64URL_ALPHABET[b0 >> 2];\n\t\toutput += BASE64URL_ALPHABET[((b0 & 0x03) << 4) | (b1 === undefined ? 0 : b1 >> 4)];\n\t\tif (b1 === undefined) break;\n\t\toutput += BASE64URL_ALPHABET[((b1 & 0x0f) << 2) | (b2 === undefined ? 0 : b2 >> 6)];\n\t\tif (b2 === undefined) break;\n\t\toutput += BASE64URL_ALPHABET[b2 & 0x3f];\n\t}\n\treturn output;\n}\n\nconst BASE64URL_DECODE_TABLE = (() => {\n\tconst table = new Int8Array(128).fill(-1);\n\tfor (let i = 0; i < BASE64URL_ALPHABET.length; i++) {\n\t\ttable[BASE64URL_ALPHABET.charCodeAt(i)] = i;\n\t}\n\treturn table;\n})();\n\n/** Decode unpadded base64url text to raw bytes. Throws on any padding\n * (`=`), standard-alphabet (`+`/`/`), or otherwise invalid character. */\nexport function decodeBase64Url(text: string): Uint8Array {\n\tif (text.length % 4 === 1) {\n\t\tthrow new Error(`[multicast-auth-catalog] invalid unpadded base64url length for \"${text}\"`);\n\t}\n\tconst bytes: number[] = [];\n\tlet buffer = 0;\n\tlet bits = 0;\n\tfor (let i = 0; i < text.length; i++) {\n\t\tconst code = text.charCodeAt(i);\n\t\tconst value = code < 128 ? BASE64URL_DECODE_TABLE[code] : -1;\n\t\tif (value === undefined || value < 0) {\n\t\t\tthrow new Error(`[multicast-auth-catalog] invalid base64url character in \"${text}\"`);\n\t\t}\n\t\tbuffer = (buffer << 6) | value;\n\t\tbits += 6;\n\t\tif (bits >= 8) {\n\t\t\tbits -= 8;\n\t\t\tbytes.push((buffer >> bits) & 0xff);\n\t\t}\n\t}\n\tconst decoded = Uint8Array.from(bytes);\n\tif (encodeBase64Url(decoded) !== text) {\n\t\tthrow new Error(`[multicast-auth-catalog] non-canonical base64url encoding \"${text}\"`);\n\t}\n\treturn decoded;\n}\n\n/** Decode + length-validate an `authScope` field. Throws unless it decodes\n * to exactly `SOURCE_AUTH_SCOPE_LENGTH` (32) raw bytes. */\nexport function decodeAuthScope(authScope: string): Uint8Array {\n\tconst bytes = decodeBase64Url(authScope);\n\tif (bytes.byteLength !== SOURCE_AUTH_SCOPE_LENGTH) {\n\t\tthrow new Error(\n\t\t\t`[multicast-auth-catalog] authScope must decode to ${SOURCE_AUTH_SCOPE_LENGTH} raw bytes, got ${bytes.byteLength}`,\n\t\t);\n\t}\n\treturn bytes;\n}\n\n/** Decode a catalog key in the only accepted representation: exactly 32 raw\n * ed25519 bytes encoded as canonical, unpadded base64url. */\nexport function decodeEd25519PublicKey(publicKey: string): Uint8Array {\n\tconst bytes = decodeBase64Url(publicKey);\n\tif (bytes.byteLength !== RAW_ED25519_KEY_LENGTH) {\n\t\tthrow new Error(\n\t\t\t`[multicast-auth-catalog] public key must decode to ${RAW_ED25519_KEY_LENGTH} raw bytes, got ${bytes.byteLength}`,\n\t\t);\n\t}\n\treturn bytes;\n}\n\n/**\n * One protected media track's manifest + scope binding.\n *\n * - `mediaTrack`: the actual media track name (e.g. `\"video/base\"`).\n * - `manifestTrack`: the sibling track carrying this track's signed\n * manifest objects (see ./source-auth.ts `SourceAuthObjectEntry`/\n * `SourceAuthGroupClose`). Opaque string \u2014 not role-parsed.\n * - `authScope`: this track's unique 32-byte scope, raw bytes as unpadded\n * base64url. BLO-16481 requires a scope fresh per (publisher session,\n * track, source-symbol format) \u2014 a track publishing under two source-\n * symbol formats in the same session MUST appear as two entries here,\n * each with its own scope.\n * - `sourceSymbolFormat`: names the deployed source-symbol profile this\n * track's authenticated bytes are derived from (e.g.\n * `\"full-mmtp-packet\"` or `\"fec-padded-recovery\"`, the two formats\n * BLO-16481's S0 fixture covers).\n */\nexport interface MulticastAuthTrackConfig {\n\tmediaTrack: string;\n\tmanifestTrack: string;\n\tauthScope: string;\n\tsourceSymbolFormat: string;\n}\n\n/**\n * `multicast.auth` v2 \u2014 supersedes the BLO-16485 draft's singular\n * `manifestTrack`/no-`authScope` shape (BLO-16700 decision).\n */\nexport interface MulticastAuthConfig {\n\tscheme: MulticastAuthScheme;\n\thash: MulticastAuthHash;\n\tsig: MulticastAuthSig;\n\t/** Raw 32-byte ed25519 public key, unpadded base64url. */\n\tpublicKey: string;\n\tkeyId: string;\n\t/** Raw 32-byte ed25519 public key, unpadded base64url. Accepted\n\t * alongside `publicKey` during a key-rotation overlap window. */\n\tpreviousKey?: string;\n\tcompaction: MulticastAuthCompaction;\n\t/** Player-config-wins default; catalog value only selects the default\n\t * when the player hasn't pinned one (S3 enforcement precedence). */\n\tenforcement?: MulticastAuthEnforcement;\n\ttracks: MulticastAuthTrackConfig[];\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.length > 0;\n}\n\nfunction isValidAuthScopeField(value: unknown): value is string {\n\tif (!isNonEmptyString(value)) return false;\n\ttry {\n\t\tdecodeAuthScope(value);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isValidPublicKeyField(value: unknown): value is string {\n\tif (!isNonEmptyString(value)) return false;\n\ttry {\n\t\tdecodeEd25519PublicKey(value);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction hasOnlyKeys(object: Record<string, unknown>, allowed: readonly string[]): boolean {\n\tconst allowedKeys = new Set(allowed);\n\treturn Object.keys(object).every((key) => allowedKeys.has(key));\n}\n\nexport function isMulticastAuthTrackConfig(obj: unknown): obj is MulticastAuthTrackConfig {\n\tif (typeof obj !== \"object\" || obj === null) return false;\n\tconst o = obj as Record<string, unknown>;\n\treturn (\n\t\thasOnlyKeys(o, [\"mediaTrack\", \"manifestTrack\", \"authScope\", \"sourceSymbolFormat\"]) &&\n\t\tisNonEmptyString(o[\"mediaTrack\"]) &&\n\t\tisNonEmptyString(o[\"manifestTrack\"]) &&\n\t\to[\"mediaTrack\"] !== o[\"manifestTrack\"] &&\n\t\tisValidAuthScopeField(o[\"authScope\"]) &&\n\t\tisNonEmptyString(o[\"sourceSymbolFormat\"])\n\t);\n}\n\nconst VALID_COMPACTION = new Set<string>([\"merkle\", \"none\"]);\nconst VALID_ENFORCEMENT = new Set<string>([\"shadow\", \"enforce\"]);\n\n/** Semantic uniqueness rule shared by runtime parsing and the published\n * catalog schema's `uniqueMulticastAuthTrackBindings` keyword. */\nexport function hasUniqueMulticastAuthTrackBindings(tracks: readonly unknown[]): boolean {\n\tconst bindings = tracks.flatMap((track) => {\n\t\tif (typeof track !== \"object\" || track === null) return [];\n\t\tconst value = track as Record<string, unknown>;\n\t\tif (typeof value[\"mediaTrack\"] !== \"string\" || typeof value[\"sourceSymbolFormat\"] !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\t\treturn [JSON.stringify([value[\"mediaTrack\"], value[\"sourceSymbolFormat\"]])];\n\t});\n\treturn new Set(bindings).size === bindings.length;\n}\n\nexport function isMulticastAuthConfig(obj: unknown): obj is MulticastAuthConfig {\n\tif (typeof obj !== \"object\" || obj === null) return false;\n\tconst o = obj as Record<string, unknown>;\n\n\tif (o[\"scheme\"] !== \"bc-provenance\") return false;\n\tif (o[\"hash\"] !== \"blake3\") return false;\n\tif (o[\"sig\"] !== \"ed25519\") return false;\n\tif (!hasOnlyKeys(o, [\"scheme\", \"hash\", \"sig\", \"publicKey\", \"keyId\", \"previousKey\", \"compaction\", \"enforcement\", \"tracks\"])) return false;\n\tif (!isValidPublicKeyField(o[\"publicKey\"])) return false;\n\tif (!isNonEmptyString(o[\"keyId\"])) return false;\n\tif (o[\"previousKey\"] !== undefined && !isValidPublicKeyField(o[\"previousKey\"])) return false;\n\tif (o[\"previousKey\"] === o[\"publicKey\"]) return false;\n\tif (typeof o[\"compaction\"] !== \"string\" || !VALID_COMPACTION.has(o[\"compaction\"])) return false;\n\tif (o[\"enforcement\"] !== undefined) {\n\t\tif (typeof o[\"enforcement\"] !== \"string\" || !VALID_ENFORCEMENT.has(o[\"enforcement\"])) return false;\n\t}\n\n\tif (!Array.isArray(o[\"tracks\"]) || o[\"tracks\"].length === 0) return false;\n\tconst tracks = o[\"tracks\"] as unknown[];\n\tif (!tracks.every((t) => isMulticastAuthTrackConfig(t))) return false;\n\tconst typedTracks = tracks as MulticastAuthTrackConfig[];\n\n\t// BLO-16481: a fresh scope per track \u2014 catalog-declared scopes must be\n\t// pairwise distinct within one auth block.\n\tconst scopes = typedTracks.map((t) => t.authScope);\n\tif (new Set(scopes).size !== scopes.length) return false;\n\tif (!hasUniqueMulticastAuthTrackBindings(typedTracks)) return false;\n\n\t// Each entry must point at its own manifest track.\n\tconst manifestTracks = typedTracks.map((t) => t.manifestTrack);\n\tif (new Set(manifestTracks).size !== manifestTracks.length) return false;\n\n\treturn true;\n}\n\nexport interface ResolvedMulticastAuthConfig {\n\tkeyId: string;\n\t/** Current key first, followed by the overlap key when advertised. */\n\tacceptedPublicKeys: Uint8Array[];\n\tmode: MulticastAuthMode;\n\tcompaction: MulticastAuthCompaction;\n\ttracks: Array<Omit<MulticastAuthTrackConfig, \"authScope\"> & { authScope: Uint8Array }>;\n}\n\n/** Player configuration is authoritative, including an explicit `off`.\n * Catalog enforcement is only a default; an auth block without it defaults\n * to shadow, while a catalog without auth defaults to off. */\nexport function resolveMulticastAuthMode(\n\tplayerMode: MulticastAuthMode | undefined,\n\tauth: MulticastAuthConfig | undefined,\n): MulticastAuthMode {\n\tif (playerMode !== undefined) return playerMode;\n\tif (auth === undefined) return \"off\";\n\treturn auth.enforcement ?? \"shadow\";\n}\n\n/** Parse and resolve the catalog auth block into verifier-ready raw keys and\n * scopes. Invalid blocks and dangling media/manifest track references fail\n * closed instead of silently falling back to unauthenticated playback. */\nexport function resolveMulticastAuthCatalog(\n\tcatalog: unknown,\n\tplayerMode?: MulticastAuthMode,\n): ResolvedMulticastAuthConfig | undefined {\n\tif (typeof catalog !== \"object\" || catalog === null || Array.isArray(catalog)) {\n\t\tthrow new Error(\"[multicast-auth-catalog] catalog must be an object\");\n\t}\n\tconst root = catalog as Record<string, unknown>;\n\tconst rawAuth = root[\"multicast.auth\"];\n\tif (rawAuth === undefined) {\n\t\tif (playerMode === \"enforce\") {\n\t\t\tthrow new Error(\"[multicast-auth-catalog] player enforce mode requires multicast.auth\");\n\t\t}\n\t\treturn undefined;\n\t}\n\tif (!isMulticastAuthConfig(rawAuth)) {\n\t\tthrow new Error(\"[multicast-auth-catalog] invalid multicast.auth v2 block\");\n\t}\n\n\tconst rawTracks = root[\"tracks\"];\n\tif (!Array.isArray(rawTracks)) {\n\t\tthrow new Error(\"[multicast-auth-catalog] catalog tracks must be an array\");\n\t}\n\tconst catalogTrackNames = new Set(\n\t\trawTracks.flatMap((track) =>\n\t\t\ttypeof track === \"object\" && track !== null &&\n\t\t\ttypeof (track as Record<string, unknown>)[\"name\"] === \"string\"\n\t\t\t\t? [(track as Record<string, unknown>)[\"name\"] as string]\n\t\t\t\t: []\n\t\t),\n\t);\n\tfor (const track of rawAuth.tracks) {\n\t\tif (!catalogTrackNames.has(track.mediaTrack)) {\n\t\t\tthrow new Error(`[multicast-auth-catalog] unknown mediaTrack \"${track.mediaTrack}\"`);\n\t\t}\n\t\tif (!catalogTrackNames.has(track.manifestTrack)) {\n\t\t\tthrow new Error(`[multicast-auth-catalog] unknown manifestTrack \"${track.manifestTrack}\"`);\n\t\t}\n\t}\n\n\treturn {\n\t\tkeyId: rawAuth.keyId,\n\t\tacceptedPublicKeys: [\n\t\t\tdecodeEd25519PublicKey(rawAuth.publicKey),\n\t\t\t...(rawAuth.previousKey === undefined ? [] : [decodeEd25519PublicKey(rawAuth.previousKey)]),\n\t\t],\n\t\tmode: resolveMulticastAuthMode(playerMode, rawAuth),\n\t\tcompaction: rawAuth.compaction,\n\t\ttracks: rawAuth.tracks.map((track) => ({\n\t\t\t...track,\n\t\t\tauthScope: decodeAuthScope(track.authScope),\n\t\t})),\n\t};\n}\n", "// Layered and overlay FEC repair contract (draft-ramadan-moq-fec \u00A76.3).\n//\n// One source track may carry several repair tracks. Every base layer is\n// generated from the same source partition (RaptorQ, K, T, interleaveDepthMs,\n// reorderToleranceMs) and differs from its siblings only in its repair symbol\n// count P_i. Layer i owns the Repair Symbol IDs [O_i, O_i + P_i) with\n// O_i = sum(P_j, j < i), and ESI = K + RS_ID, so any subset of layers combines\n// into one decode. A keyframe overlay is a second, independent FEC instance\n// covering only random access point fragments, with its own K and P and its\n// own RS_ID space starting at 0.\n//\n// Repair tracks describe themselves (`depends`, `repairLayer`, `scope`); names\n// carry no meaning. This module holds the one rule set both catalog dialects\n// (MSF `tracks[]` and hang `repair.renditions`) resolve through, so the CMSF\n// validator and the player resolver cannot drift apart.\n//\n// The one dialect difference is where layer 0 lives. In MSF, `fec.repairTrack`\n// must name a `fec-repair` track in `tracks[]`, so a dangling pointer fails\n// closed (`layerZeroMustBeDeclared`). In hang, layer 0 is named only by the\n// rendition's `fec.repairTrack` and needs no `repair.renditions` entry; an\n// entry there is optional and, when present, must agree with the source.\n\nimport { isFecBlockProfile, parseFecBlockProfile, type FecBlockProfile } from \"@blockcast/mmt-base\";\n\n/** Lowest MoQT-scale priority a repair track may declare. */\nexport const REPAIR_PRIORITY_MIN = 192;\n/** Highest MoQT-scale priority a repair track may declare. */\nexport const REPAIR_PRIORITY_MAX = 255;\n/** Priority of a base layer 0 track that declares none. */\nexport const DEFAULT_REPAIR_PRIORITY = 240;\n/** RFC 6330 \u00A74.4.1.2: the largest number of source symbols in one RaptorQ source block. */\nexport const MAX_RAPTORQ_SOURCE_SYMBOLS = 56403;\n/** The 24-bit RS_ID field (\u00A77.1) bounds the repair total of one FEC instance. */\nexport const MAX_REPAIR_SYMBOLS = 0xffffff;\n/** The 24-bit ESI bounds K + totalP - 1 for one FEC instance. */\nexport const MAX_ENCODING_SYMBOL_ID = 0xffffff;\n\nexport type RepairContractCode =\n\t| \"track_fec\"\n\t| \"track_repair_track\"\n\t| \"track_repair_depends\"\n\t| \"track_repair_layer\"\n\t| \"track_repair_priority\"\n\t| \"track_repair_overlay\"\n\t| \"track_repair_geometry\"\n\t| \"track_fec_bounds\"\n\t| \"track_fec_block_profile\"\n\t| \"track_fec_block_profile_unimplemented\";\n\n/** Reports one contract violation and never returns. */\nexport type RepairContractFail = (code: RepairContractCode, path: string, message: string) => never;\n\nexport class RepairContractError extends Error {\n\tconstructor(\n\t\tpublic readonly code: RepairContractCode,\n\t\tpublic readonly path: string,\n\t\tmessage: string,\n\t) {\n\t\tsuper(`[repair-layers:${code}] ${path}: ${message}`);\n\t\tthis.name = \"RepairContractError\";\n\t}\n}\n\nexport const throwRepairContractError: RepairContractFail = (code, path, message) => {\n\tthrow new RepairContractError(code, path, message);\n};\n\n/**\n * Read one catalog `fec.blockProfile` (BLO-35597, ISO/IEC 23008-1 Annex C):\n * the one rule both catalog dialects, the CMSF validator and the per-track\n * resolver apply.\n *\n * draft-ramadan-moq-fec \u00A75.1 defines exactly one profile, `iso-ssbg1-v1`, and\n * it is the only one this library implements. A `fec` block that declares an\n * FEC instance (any `algorithm` other than `\"none\"`) MUST name it; a block\n * declaring no FEC instance may omit the token (returns undefined), but a token\n * it does carry must be implemented. Failures are reported at `path`:\n *\n * - `track_fec_block_profile_unimplemented`: a non-empty string that names no\n * implemented profile. The retired Blockcast-private `k-padded-v1` is one\n * such token; a near-miss such as `\"iso-ssbg1\"` is another, never\n * normalized.\n * - `track_fec_block_profile`: no token on a declared FEC instance, or a value\n * that is not a non-empty string.\n *\n * The token is never defaulted and never inferred from the wire. A receiver\n * treats either failure as \"FEC unavailable\" for that track alone (it consumes\n * the source without FEC and joins none of its repair tracks); a conformance\n * validator rejects the catalog.\n *\n * @param path JSON path of the `blockProfile` field, for the error.\n */\nexport function readFecBlockProfile(\n\tvalue: unknown,\n\tpath: string,\n\tdeclaresFecInstance: true,\n\tfail: RepairContractFail,\n): FecBlockProfile;\nexport function readFecBlockProfile(\n\tvalue: unknown,\n\tpath: string,\n\tdeclaresFecInstance: boolean,\n\tfail: RepairContractFail,\n): FecBlockProfile | undefined;\nexport function readFecBlockProfile(\n\tvalue: unknown,\n\tpath: string,\n\tdeclaresFecInstance: boolean,\n\tfail: RepairContractFail,\n): FecBlockProfile | undefined {\n\tif (value === undefined && !declaresFecInstance) return undefined;\n\tif (isFecBlockProfile(value)) return value;\n\tif (typeof value === \"string\" && value.length > 0) {\n\t\treturn fail(\n\t\t\t\"track_fec_block_profile_unimplemented\",\n\t\t\tpath,\n\t\t\t`${JSON.stringify(value)} is not an implemented FEC block profile; only \"iso-ssbg1-v1\" is implemented`,\n\t\t);\n\t}\n\ttry {\n\t\treturn parseFecBlockProfile(value, \"blockProfile\");\n\t} catch (error) {\n\t\treturn fail(\"track_fec_block_profile\", path, (error as Error).message);\n\t}\n}\n\n/** The self-describing fields of one repair track entry, in either dialect. */\nexport type RepairTrackDescriptor = {\n\t/** Track (MSF) or rendition (hang) name. */\n\tname: string;\n\t/** JSON path of the entry, for error reporting. */\n\tpath: string;\n\tdepends?: unknown;\n\trepairLayer?: unknown;\n\trepairSymbols?: unknown;\n\tpriority?: unknown;\n\tscope?: unknown;\n\tsourceSymbols?: unknown;\n};\n\n/** The source track's `fec` object as the layer contract reads it. */\nexport type RepairSourceGeometry = {\n\t/** Source track or rendition name. */\n\tname: string;\n\t/** JSON path of the `fec` object, for error reporting. */\n\tpath: string;\n\talgorithm?: unknown;\n\t/** K. */\n\tsourceSymbols?: unknown;\n\t/** P_0. */\n\trepairSymbols?: unknown;\n\t/** Layer 0 track name. */\n\trepairTrack?: unknown;\n};\n\n/** A resolved base repair layer with its derived RS_ID range. */\nexport type RepairLayerParams = {\n\t/** Layer index; 0 is the track named by the source's `fec.repairTrack`. */\n\tlayer: number;\n\trepairTrackName: string;\n\t/** P_i: repair symbols per block carried by this layer. */\n\trepairSymbols: number;\n\t/** O_i = sum(P_j for j < i): the first RS_ID this layer carries. ESI = K + RS_ID. */\n\trsIdOffset: number;\n\t/** MoQT-scale priority (lower first); 240 when layer 0 declares none. */\n\tpriority: number;\n};\n\n/** A resolved keyframe overlay: a separate FEC instance over RAP fragments only. */\nexport type RepairOverlayParams = {\n\trepairTrackName: string;\n\tscope: \"keyframe\";\n\t/** K_overlay: source symbols of the overlay instance. */\n\tsourceSymbols: number;\n\t/** P_overlay: repair symbols per overlay block; also the overlay's own RSB_length. */\n\trepairSymbols: number;\n\t/** MoQT-scale priority, independent of the base layer chain. */\n\tpriority: number;\n};\n\nexport type RepairLayersResolution = {\n\tsourceTrackName: string;\n\t/** Base layers in layer order, layer 0 first. Never empty. */\n\tbaseLayers: readonly RepairLayerParams[];\n\t/** Keyframe overlay instances, in catalog order. Empty when none is declared. */\n\t/** At most one entry: the wire carries one overlay trailer per RAP packet. */\n\toverlays: readonly RepairOverlayParams[];\n\t/** Sum of P over base layers: the wire RSB_length on every base layer packet. */\n\ttotalBaseRepairSymbols: number;\n};\n\nconst REPAIR_FIELDS = [\"depends\", \"repairLayer\", \"repairSymbols\", \"priority\", \"scope\", \"sourceSymbols\"] as const;\n\n/** Track-level block geometry keys a repair track must not carry (\u00A76.3 rule 8): geometry is the source's. */\nexport const REPAIR_TRACK_GEOMETRY_KEYS = [\n\t\"mmtpMode\",\n\t\"timescale\",\n\t\"groupDurationMs\",\n\t\"groupDurationTicks\",\n\t\"keyframeIntervalMs\",\n\t\"keyframeIntervalTicks\",\n\t\"fec\",\n\t\"symbolSize\",\n\t\"interleaveDepthMs\",\n] as const;\n\n/** Self-describing repair fields that only a repair track or repair rendition may carry. */\nexport const REPAIR_TRACK_ONLY_KEYS = [\"repairLayer\", \"repairSymbols\", \"sourceSymbols\", \"scope\"] as const;\n\n/** Rule 8: a repair track (MSF) or repair rendition (hang) carries no block geometry. */\nexport function rejectRepairTrackGeometry(path: string, fields: Record<string, unknown>, fail: RepairContractFail): void {\n\tfor (const key of REPAIR_TRACK_GEOMETRY_KEYS) {\n\t\tif (fields[key] !== undefined) {\n\t\t\tfail(\n\t\t\t\t\"track_repair_geometry\",\n\t\t\t\t`${path}.${key}`,\n\t\t\t\t\"a repair track carries no block geometry; it is shared with the source track's fec object\",\n\t\t\t);\n\t\t}\n\t}\n}\n\n/** Which repair tracks a catalog can reach, for rule 9 (no orphan repair track). */\nexport type RepairReachability = {\n\t/** True once any track or rendition carries `fec` with an algorithm other than `none`. */\n\tdeclaresFecSource: boolean;\n\t/** Every non-empty `fec.repairTrack` a FEC source names: the layer-0 tracks. */\n\tlayerZeroNames: ReadonlySet<string>;\n};\n\n/**\n * Derive rule 9's inputs from the raw `fec` objects of every media track (MSF,\n * packaging other than `fec-repair`) or media rendition (hang) in a catalog.\n * Callers must not pass repair tracks: rule 8 forbids `fec` on them, and a\n * repair track that carried one would otherwise name itself as its own\n * layer 0 and slip past rule 9. One derivation for the validator and both\n * resolver dialects, so an empty-string pointer or an `algorithm: \"none\"`\n * source is read the same way everywhere.\n */\nexport function repairReachability(fecObjects: Iterable<unknown>): RepairReachability {\n\tlet declaresFecSource = false;\n\tconst layerZeroNames = new Set<string>();\n\tfor (const candidate of fecObjects) {\n\t\tif (typeof candidate !== \"object\" || candidate === null || Array.isArray(candidate)) continue;\n\t\tconst fec = candidate as Record<string, unknown>;\n\t\tif (fec[\"algorithm\"] === \"none\") continue;\n\t\tdeclaresFecSource = true;\n\t\tconst repairTrack = fec[\"repairTrack\"];\n\t\tif (typeof repairTrack === \"string\" && repairTrack.length > 0) layerZeroNames.add(repairTrack);\n\t}\n\treturn { declaresFecSource, layerZeroNames };\n}\n\n/**\n * Rule 9: once a catalog declares a FEC source, a repair track that declares no\n * `depends` must be some source's layer 0. A catalog with no FEC source may\n * still carry a naming-era repair track that nothing joins.\n */\nexport function assertRepairTrackReachable(\n\tdescriptor: RepairTrackDescriptor,\n\treachability: RepairReachability,\n\tfail: RepairContractFail,\n): void {\n\tif (dependsTarget(descriptor) !== undefined) return;\n\tif (reachability.declaresFecSource && !reachability.layerZeroNames.has(descriptor.name)) {\n\t\tfail(\n\t\t\t\"track_repair_depends\",\n\t\t\t`${descriptor.path}.depends`,\n\t\t\t\"no source names this repair track in fec.repairTrack and it declares no depends\",\n\t\t);\n\t}\n}\n\n/** A media track (MSF) or media rendition (hang) carries no repair-track fields. */\nexport function rejectRepairFieldsOnMediaTrack(path: string, fields: Record<string, unknown>, fail: RepairContractFail): void {\n\tfor (const key of REPAIR_TRACK_ONLY_KEYS) {\n\t\tif (fields[key] !== undefined) {\n\t\t\tfail(\"track_repair_layer\", `${path}.${key}`, `${key} is a repair track field`);\n\t\t}\n\t}\n}\n\n/** Pick the self-describing repair fields out of a raw track or rendition record. */\nexport function describeRepairTrack(name: string, path: string, fields: Record<string, unknown>): RepairTrackDescriptor {\n\tconst descriptor: RepairTrackDescriptor = { name, path };\n\tfor (const field of REPAIR_FIELDS) {\n\t\tif (field in fields) descriptor[field] = fields[field];\n\t}\n\treturn descriptor;\n}\n\nfunction isSafeInteger(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value);\n}\n\nfunction positiveInteger(value: unknown): value is number {\n\treturn isSafeInteger(value) && value > 0;\n}\n\n/** The single source a repair track names in `depends`, once its shape has been validated. */\nexport function dependsTarget(descriptor: RepairTrackDescriptor): string | undefined {\n\tconst depends = descriptor.depends;\n\treturn Array.isArray(depends) && depends.length === 1 && typeof depends[0] === \"string\" && depends[0].length > 0\n\t\t? depends[0]\n\t\t: undefined;\n}\n\n/**\n * Validate the shape of one repair track's self-describing fields, independent\n * of any source. Cross-track rules (layer 0 agreement, contiguity, priority\n * order, bounds) live in `assembleRepairLayers`.\n */\nexport function validateRepairTrackFields(descriptor: RepairTrackDescriptor, fail: RepairContractFail): void {\n\tconst { path } = descriptor;\n\tif (descriptor.depends !== undefined && dependsTarget(descriptor) === undefined) {\n\t\tfail(\"track_repair_depends\", `${path}.depends`, \"must name exactly one source track\");\n\t}\n\tif (descriptor.repairLayer !== undefined && (!isSafeInteger(descriptor.repairLayer) || descriptor.repairLayer < 0)) {\n\t\tfail(\"track_repair_layer\", `${path}.repairLayer`, \"must be a non-negative integer\");\n\t}\n\tif (\n\t\tdescriptor.repairSymbols !== undefined &&\n\t\t(!positiveInteger(descriptor.repairSymbols) || descriptor.repairSymbols > MAX_REPAIR_SYMBOLS)\n\t) {\n\t\tfail(\"track_repair_layer\", `${path}.repairSymbols`, `must be a positive integer at most ${MAX_REPAIR_SYMBOLS}`);\n\t}\n\tif (\n\t\tdescriptor.priority !== undefined &&\n\t\t(!isSafeInteger(descriptor.priority) ||\n\t\t\tdescriptor.priority < REPAIR_PRIORITY_MIN ||\n\t\t\tdescriptor.priority > REPAIR_PRIORITY_MAX)\n\t) {\n\t\tfail(\n\t\t\t\"track_repair_priority\",\n\t\t\t`${path}.priority`,\n\t\t\t`must be an integer from ${REPAIR_PRIORITY_MIN} to ${REPAIR_PRIORITY_MAX} (MoQT scale, lower first)`,\n\t\t);\n\t}\n\tif (descriptor.scope !== undefined) {\n\t\tif (descriptor.scope !== \"keyframe\") {\n\t\t\tfail(\"track_repair_overlay\", `${path}.scope`, 'must be \"keyframe\"');\n\t\t}\n\t\tif (descriptor.repairLayer !== undefined) {\n\t\t\tfail(\"track_repair_overlay\", `${path}.repairLayer`, \"an overlay is a separate FEC instance and carries no repairLayer\");\n\t\t}\n\t\tfor (const field of [\"depends\", \"sourceSymbols\", \"repairSymbols\", \"priority\"] as const) {\n\t\t\tif (descriptor[field] === undefined) {\n\t\t\t\tfail(\"track_repair_overlay\", `${path}.${field}`, `an overlay must declare ${field}`);\n\t\t\t}\n\t\t}\n\t}\n\tif (descriptor.sourceSymbols !== undefined) {\n\t\tif (descriptor.scope === undefined) {\n\t\t\tfail(\"track_repair_overlay\", `${path}.sourceSymbols`, \"only an overlay (scope: keyframe) declares its own sourceSymbols\");\n\t\t}\n\t\tif (!positiveInteger(descriptor.sourceSymbols) || descriptor.sourceSymbols > MAX_RAPTORQ_SOURCE_SYMBOLS) {\n\t\t\tfail(\n\t\t\t\t\"track_fec_bounds\",\n\t\t\t\t`${path}.sourceSymbols`,\n\t\t\t\t`must be a positive integer at most ${MAX_RAPTORQ_SOURCE_SYMBOLS} (RFC 6330 source block limit)`,\n\t\t\t);\n\t\t}\n\t}\n\tif (isSafeInteger(descriptor.repairLayer) && descriptor.repairLayer >= 1) {\n\t\tfor (const field of [\"depends\", \"repairSymbols\", \"priority\"] as const) {\n\t\t\tif (descriptor[field] === undefined) {\n\t\t\t\tfail(\"track_repair_layer\", `${path}.${field}`, `repair layer ${descriptor.repairLayer} must declare ${field}`);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Resolve the base layers and overlays of one source track and derive each\n * base layer's RS_ID offset. Fails closed on every \u00A76.3 rule violation through\n * `fail`, which receives the code and the JSON path of the offending field.\n *\n * `descriptors` are every repair track (or repair rendition) in the catalog,\n * already shape-checked by `validateRepairTrackFields`. Returns undefined when\n * the source names no layer 0 and nothing depends on it.\n */\nexport type AssembleRepairLayersOptions = {\n\t/**\n\t * MSF: `fec.repairTrack` must name one of `descriptors` (a `fec-repair`\n\t * track), so a dangling pointer fails closed with `track_repair_track`.\n\t * hang: layer 0 lives outside `repair.renditions`, so leave this unset.\n\t */\n\tlayerZeroMustBeDeclared?: boolean;\n};\n\nexport function assembleRepairLayers(\n\tsource: RepairSourceGeometry,\n\tdescriptors: readonly RepairTrackDescriptor[],\n\tfail: RepairContractFail = throwRepairContractError,\n\toptions: AssembleRepairLayersOptions = {},\n): RepairLayersResolution | undefined {\n\tconst layerZeroName =\n\t\ttypeof source.repairTrack === \"string\" && source.repairTrack.length > 0 ? source.repairTrack : undefined;\n\tconst layerZero = layerZeroName === undefined ? undefined : descriptors.find((d) => d.name === layerZeroName);\n\tif (layerZeroName !== undefined && layerZero === undefined && options.layerZeroMustBeDeclared) {\n\t\tfail(\"track_repair_track\", `${source.path}.repairTrack`, `unknown FEC repair track ${JSON.stringify(layerZeroName)}`);\n\t}\n\tconst dependents = descriptors.filter((d) => d.name !== layerZeroName && dependsTarget(d) === source.name);\n\tconst k = positiveInteger(source.sourceSymbols) ? source.sourceSymbols : 0;\n\t// RFC 6330 bounds every RaptorQ source block, with or without a repair track.\n\tif (source.algorithm === \"raptorq\" && k > MAX_RAPTORQ_SOURCE_SYMBOLS) {\n\t\tfail(\n\t\t\t\"track_fec_bounds\",\n\t\t\t`${source.path}.sourceSymbols`,\n\t\t\t`must be at most ${MAX_RAPTORQ_SOURCE_SYMBOLS} (RFC 6330 source block limit)`,\n\t\t);\n\t}\n\tif (layerZeroName === undefined) {\n\t\tif (dependents.length === 0) return undefined;\n\t\tfail(\n\t\t\t\"track_repair_layer\",\n\t\t\t`${dependents[0]!.path}.depends`,\n\t\t\t`repair layers of ${JSON.stringify(source.name)} require a layer 0 (fec.repairTrack) on the source`,\n\t\t);\n\t}\n\n\tconst layered = dependents.length > 0;\n\tlet layerZeroSymbols = 0;\n\tif (positiveInteger(source.repairSymbols)) {\n\t\tlayerZeroSymbols = source.repairSymbols;\n\t} else if (layered) {\n\t\tfail(\"track_fec\", `${source.path}.repairSymbols`, \"layered or overlay repair needs a positive integer repairSymbols on layer 0\");\n\t}\n\tif (layered && !positiveInteger(source.sourceSymbols)) {\n\t\tfail(\"track_fec\", `${source.path}.sourceSymbols`, \"layered or overlay repair needs a positive integer sourceSymbols on the source\");\n\t}\n\tif (layered && source.algorithm !== \"raptorq\") {\n\t\tfail(\n\t\t\t\"track_fec\",\n\t\t\t`${source.path}.algorithm`,\n\t\t\t\"repair layers and overlays are defined only for raptorq: a block code fixes its codeword length, so symbols of a larger P are not symbols of the code a receiver holding fewer layers decodes\",\n\t\t);\n\t}\n\n\tlet layerZeroPriority = DEFAULT_REPAIR_PRIORITY;\n\tif (layerZero !== undefined) {\n\t\tif (layerZero.depends !== undefined && dependsTarget(layerZero) !== source.name) {\n\t\t\tfail(\n\t\t\t\t\"track_repair_depends\",\n\t\t\t\t`${layerZero.path}.depends`,\n\t\t\t\t`layer 0 of ${JSON.stringify(source.name)} must depend on that source`,\n\t\t\t);\n\t\t}\n\t\tif (layerZero.repairLayer !== undefined && layerZero.repairLayer !== 0) {\n\t\t\tfail(\"track_repair_layer\", `${layerZero.path}.repairLayer`, \"the track named by fec.repairTrack is layer 0\");\n\t\t}\n\t\tif (layerZero.scope !== undefined) {\n\t\t\tfail(\"track_repair_overlay\", `${layerZero.path}.scope`, \"the track named by fec.repairTrack is base layer 0, not an overlay\");\n\t\t}\n\t\tif (layerZero.repairSymbols !== undefined && layerZero.repairSymbols !== layerZeroSymbols) {\n\t\t\tfail(\n\t\t\t\t\"track_repair_layer\",\n\t\t\t\t`${layerZero.path}.repairSymbols`,\n\t\t\t\t`must agree with the source's fec.repairSymbols (${layerZeroSymbols})`,\n\t\t\t);\n\t\t}\n\t\tif (typeof layerZero.priority === \"number\") layerZeroPriority = layerZero.priority;\n\t}\n\n\tconst baseLayers: RepairLayerParams[] = [\n\t\t{ layer: 0, repairTrackName: layerZeroName!, repairSymbols: layerZeroSymbols, rsIdOffset: 0, priority: layerZeroPriority },\n\t];\n\t// Clarification 7 fixes the wire: a RAP packet carries exactly two Source\n\t// FEC Payload IDs, base first and overlay last, and the receiver strips a\n\t// fixed trailer length. A second overlay would need a third trailer that\n\t// the draft does not define, so one overlay per source is the contract.\n\tconst overlays: RepairOverlayParams[] = [];\n\tfor (const d of dependents) {\n\t\tif (d.scope === undefined && d.repairLayer === undefined) {\n\t\t\tfail(\n\t\t\t\t\"track_repair_layer\",\n\t\t\t\t`${d.path}.repairLayer`,\n\t\t\t\t`a repair track that depends on ${JSON.stringify(source.name)} must declare repairLayer (1..n) or scope: \"keyframe\"`,\n\t\t\t);\n\t\t}\n\t}\n\tconst layerTracks = dependents\n\t\t.filter((d) => d.scope === undefined)\n\t\t.sort((a, b) => (a.repairLayer as number) - (b.repairLayer as number));\n\tfor (const d of dependents) {\n\t\tif (d.scope !== \"keyframe\") continue;\n\t\tif (overlays.length === 1) {\n\t\t\tfail(\n\t\t\t\t\"track_repair_overlay\",\n\t\t\t\t`${d.path}.scope`,\n\t\t\t\t`${JSON.stringify(source.name)} already has keyframe overlay ${JSON.stringify(overlays[0]!.repairTrackName)}: a RAP packet carries exactly two Source FEC Payload IDs (base, then overlay), so one overlay per source`,\n\t\t\t);\n\t\t}\n\t\tconst sourceSymbols = d.sourceSymbols as number;\n\t\tconst repairSymbols = d.repairSymbols as number;\n\t\tif (sourceSymbols + repairSymbols - 1 > MAX_ENCODING_SYMBOL_ID) {\n\t\t\tfail(\n\t\t\t\t\"track_fec_bounds\",\n\t\t\t\t`${d.path}.repairSymbols`,\n\t\t\t\t`overlay sourceSymbols + repairSymbols - 1 exceeds the 24-bit ESI space (${MAX_ENCODING_SYMBOL_ID})`,\n\t\t\t);\n\t\t}\n\t\toverlays.push({ repairTrackName: d.name, scope: \"keyframe\", sourceSymbols, repairSymbols, priority: d.priority as number });\n\t}\n\n\tlet offset = layerZeroSymbols;\n\tlet previousPriority = layerZeroPriority;\n\tlayerTracks.forEach((d, i) => {\n\t\tconst expected = i + 1;\n\t\tif (d.repairLayer === 0) {\n\t\t\tfail(\"track_repair_layer\", `${d.path}.repairLayer`, `layer 0 of ${JSON.stringify(source.name)} is ${JSON.stringify(layerZeroName)}, named by fec.repairTrack`);\n\t\t}\n\t\tif (d.repairLayer !== expected) {\n\t\t\tfail(\n\t\t\t\t\"track_repair_layer\",\n\t\t\t\t`${d.path}.repairLayer`,\n\t\t\t\ti > 0 && d.repairLayer === layerTracks[i - 1]!.repairLayer\n\t\t\t\t\t? `repair layer ${d.repairLayer} is declared twice for ${JSON.stringify(source.name)}`\n\t\t\t\t\t: `repair layers must be contiguous from 1: expected ${expected}, found ${d.repairLayer}`,\n\t\t\t);\n\t\t}\n\t\tconst priority = d.priority as number;\n\t\tif (priority < previousPriority) {\n\t\t\tfail(\n\t\t\t\t\"track_repair_priority\",\n\t\t\t\t`${d.path}.priority`,\n\t\t\t\t`must be at least ${previousPriority}, the priority of layer ${expected - 1}: priority is monotonic non-decreasing with layer index`,\n\t\t\t);\n\t\t}\n\t\tpreviousPriority = priority;\n\t\tconst repairSymbols = d.repairSymbols as number;\n\t\tbaseLayers.push({ layer: expected, repairTrackName: d.name, repairSymbols, rsIdOffset: offset, priority });\n\t\toffset += repairSymbols;\n\t});\n\n\tconst totalBaseRepairSymbols = offset;\n\tif (totalBaseRepairSymbols > MAX_REPAIR_SYMBOLS) {\n\t\tfail(\n\t\t\t\"track_fec_bounds\",\n\t\t\t`${layerTracks.length === 0 ? source.path : layerTracks[layerTracks.length - 1]!.path}.repairSymbols`,\n\t\t\t`total repair symbols across base layers exceeds the 24-bit RS_ID space (${MAX_REPAIR_SYMBOLS})`,\n\t\t);\n\t}\n\tif (k + totalBaseRepairSymbols - 1 > MAX_ENCODING_SYMBOL_ID) {\n\t\tfail(\n\t\t\t\"track_fec_bounds\",\n\t\t\t`${source.path}.sourceSymbols`,\n\t\t\t`sourceSymbols + total base repair symbols - 1 exceeds the 24-bit ESI space (${MAX_ENCODING_SYMBOL_ID})`,\n\t\t);\n\t}\n\n\treturn { sourceTrackName: source.name, baseLayers, overlays, totalBaseRepairSymbols };\n}\n", "/**\n * CARP Catalog Types \u2014 FEC and Multicast catalog extensions.\n *\n * Per draft-law-ramadan-moq-carp-reliability \u00A73 (FEC) and \u00A74 (Multicast).\n * Plain TypeScript interfaces + type guards \u2014 no Zod dependency.\n *\n * These are the canonical type definitions. Upstream Zod schemas\n * should derive from these via z.infer<> to avoid duplication.\n */\n\nimport {\n\tMMTP_PACKET_ID_MAX,\n\tMMTP_SIGNALING_PACKET_ID,\n\tisFecBlockProfile,\n\ttype FecBlockProfile,\n} from \"@blockcast/mmt-base\";\nimport { resolveMulticastAuthCatalog, type MulticastAuthConfig } from \"./multicast-auth-catalog.js\";\nimport {\n\tassembleRepairLayers,\n\tassertRepairTrackReachable,\n\tdescribeRepairTrack,\n\trejectRepairFieldsOnMediaTrack,\n\trejectRepairTrackGeometry,\n\trepairReachability,\n\tdependsTarget,\n\treadFecBlockProfile,\n\tvalidateRepairTrackFields,\n} from \"./repair-layers.js\";\n\n// \u2500\u2500 Track Role (Phase 14, D-02..D-05) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Track role derived from the last slash-segment of the track name.\n *\n * Convention: `{content}/base`, `{content}/delta`, `{content}/repair`.\n * Single-track (no slash) legacy streams use `\"single\"`.\n *\n * D-01: No `trackRole` / `role` / `kind` field in the catalog.\n * D-02: Role is expressed via track name suffix only.\n * D-03: Unknown suffixes throw at parse time (error on bad init).\n * D-04: Legacy single-track streams (no slash suffix) return `\"single\"`.\n * D-05: Single role per track \u2014 no multi-role arrays.\n */\nexport type TrackRole = \"base\" | \"delta\" | \"repair\" | \"single\";\n\nconst VALID_ROLE_SUFFIXES = new Set<string>([\"base\", \"delta\", \"repair\"]);\n\n/**\n * Derive the role of a track from its name.\n *\n * - Names with no `/` \u2192 `\"single\"` (legacy single-track streams, D-04).\n * - Names ending with `/base`, `/delta`, or `/repair` \u2192 that role (D-02).\n * - Empty name \u2192 throws (error on bad init).\n * - Trailing slash (empty last segment) \u2192 throws.\n * - Non-ASCII or control characters anywhere in the name \u2192 throws (T-14-01 fuzz hardening).\n * - Unknown last segment \u2192 throws.\n *\n * @throws {Error} On empty name, non-ASCII/control chars, empty suffix, or unknown suffix.\n */\nexport function parseTrackRole(trackName: string): TrackRole {\n\tif (trackName === \"\") {\n\t\tthrow new Error(\"[carp-catalog] empty track name\");\n\t}\n\n\t// T-14-01 fuzz hardening: reject non-ASCII and control characters.\n\t// This prevents catalog injection attacks where a publisher sends a crafted\n\t// track name that bypasses role checks by embedding unusual code points.\n\tfor (let i = 0; i < trackName.length; i++) {\n\t\tconst cp = trackName.charCodeAt(i);\n\t\t// Reject control characters (0x00-0x1F, 0x7F) and non-ASCII (>0x7E)\n\t\tif (cp <= 0x1f || cp === 0x7f || cp > 0x7e) {\n\t\t\tthrow new Error(\n\t\t\t\t`[carp-catalog] track name contains non-ASCII or control character (code point 0x${cp.toString(16)}) in \"${trackName}\"`,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst lastSlash = trackName.lastIndexOf(\"/\");\n\tif (lastSlash === -1) {\n\t\t// No slash \u2014 legacy single-track stream (D-04)\n\t\treturn \"single\";\n\t}\n\n\tconst suffix = trackName.slice(lastSlash + 1);\n\tif (suffix === \"\") {\n\t\tthrow new Error(\n\t\t\t`[carp-catalog] empty role suffix in track name \"${trackName}\"; track names must not end with \"/\"`,\n\t\t);\n\t}\n\n\tif (!VALID_ROLE_SUFFIXES.has(suffix)) {\n\t\tthrow new Error(\n\t\t\t`[carp-catalog] unknown role suffix \"${suffix}\" in track name \"${trackName}\"; expected one of: base, delta, repair`,\n\t\t);\n\t}\n\n\treturn suffix as TrackRole;\n}\n\n/**\n * Non-throwing switching-set detector (BLO-8158).\n *\n * Returns `true` iff at least one track name carries a media role suffix\n * (`base` or `delta`) \u2014 i.e. the set looks like a base/delta/repair switching\n * set. A `/repair` track alone is ordinary FEC topology and is not sufficient\n * evidence of a switching set. Unlike `parseTrackRole`, this NEVER throws:\n * names without a `/`, and names with an unrecognised suffix (e.g. an ABR\n * resolution ladder like `video/1080p`, `video/720p`, `video/8k`), simply do\n * not count as a role.\n *\n * Use this for DETECTION (\"is this a switching set?\"). Once a switching set is\n * confirmed, `parseTrackRole` / `validateSwitchingSet` still strictly gate the\n * actual wiring. ABR ladders yield `false` here and fall through to the\n * ABR/single path instead of throwing.\n */\nexport function hasSwitchingSetRole(names: string[]): boolean {\n\treturn names.some((name) => {\n\t\tconst lastSlash = name.lastIndexOf(\"/\");\n\t\tif (lastSlash === -1) return false;\n\t\tconst suffix = name.slice(lastSlash + 1);\n\t\treturn suffix === \"base\" || suffix === \"delta\";\n\t});\n}\n\n/**\n * Validate that a set of track names forms a complete switching set.\n *\n * - Empty or single-track sets (all parsing to `\"single\"`) \u2192 `{ ok: true }`.\n * - Multi-track sets (any name has a role suffix) MUST include all three\n * roles (`base`, `delta`, `repair`). Missing roles are listed.\n *\n * Propagates throws from `parseTrackRole` (non-ASCII, unknown suffix, etc.).\n *\n * D-04: Legacy single-track streams pass without enforcement.\n * D-04: Once any suffix appears, the set is multi-track and must be complete.\n */\nexport function validateSwitchingSet(\n\ttrackNames: string[],\n): { ok: true } | { ok: false; missing: TrackRole[] } {\n\tif (trackNames.length === 0) return { ok: true };\n\n\tconst roles = trackNames.map(parseTrackRole);\n\n\t// If every role is \"single\", this is a legacy single-track stream \u2014 pass.\n\tif (roles.every((r) => r === \"single\")) {\n\t\treturn { ok: true };\n\t}\n\n\t// Multi-track: at least one name has a role suffix.\n\t// The set MUST contain all three required roles.\n\t// Explicit Set<TrackRole> so the TrackRole-typed `required.filter`\n\t// callback can call .has(r) without a narrow-mismatch complaint.\n\tconst presentRoles: Set<TrackRole> = new Set(roles.filter((r) => r !== \"single\"));\n\tconst required: TrackRole[] = [\"base\", \"delta\", \"repair\"];\n\tconst missing = required.filter((r) => !presentRoles.has(r));\n\n\tif (missing.length > 0) {\n\t\treturn { ok: false, missing };\n\t}\n\treturn { ok: true };\n}\n\n// \u2500\u2500 FEC Catalog Extension (CARP \u00A73.2) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type FecAlgorithm = \"none\" | \"raptorq\" | \"reed-solomon\";\n\nexport interface FecCatalogConfig {\n\t/** FEC algorithm identifier (REQUIRED per \u00A74.1, values per \u00A73.1). */\n\talgorithm: FecAlgorithm;\n\t/** FEC block profile (BLO-35597, ISO/IEC 23008-1 Annex C). REQUIRED when\n\t * `algorithm` declares an FEC instance (anything but `\"none\"`); optional\n\t * on `\"none\"`, where a present token must still be implemented. The only\n\t * profile is `iso-ssbg1-v1` (ssbg_mode1, K' = SSB_length, consecutive\n\t * SS_IDs; draft-ramadan-moq-fec \u00A75.1). Never defaulted and never inferred\n\t * from the wire: a track naming any other token (the retired `k-padded-v1`\n\t * included) has no FEC (`TrackFecUnavailable`). */\n\tblockProfile?: FecBlockProfile;\n\t/** K \u2014 source symbols per block (REQUIRED). */\n\tsourceSymbols: number;\n\t/** P \u2014 repair symbols per block (REQUIRED). */\n\trepairSymbols: number;\n\t/** T \u2014 bytes per symbol, 8-byte aligned (REQUIRED). */\n\tsymbolSize: number;\n\t/** FEC block span in MILLISECONDS (OPTIONAL, default 0 = single-group blocks).\n\t * D is the positive round-half-up value of interleaveDepthMs / GOP_duration_ms.\n\t * BLO-14526: renamed from the legacy `interleaveDepth` count-era key \u2014 no fallback read. */\n\tinterleaveDepthMs?: number;\n\t/** Declared jitter/reorder allowance in MILLISECONDS.\n\t * Added to interleaveDepthMs to form a block's deadline; never multiplied\n\t * by it. Jitter is a property of the network path and the block span is a\n\t * property of the encoder, so a multiplicative \"safety factor\" couples two\n\t * independent quantities and grants different slack per track for the same\n\t * network. Consumed identically by FECManager, FEC workers and player\n\t * policies so one signaled value governs end to end. */\n\treorderToleranceMs?: number;\n\t/** Track name for repair symbols (REQUIRED per \u00A76.1). Base layer 0 when the\n\t * source carries further repair layers or a keyframe overlay: those are\n\t * self-describing `fec-repair` tracks (`depends`, `repairLayer`, `scope`)\n\t * per draft-ramadan-moq-fec \u00A76.3, never listed here. */\n\trepairTrack: string;\n\t/** FEC block mode: \"interleaved\" (default) or \"object\" (per-frame). */\n\t// extensions feature \u2014 see moqcast-draft docs/fec-extensions-material.md, not in public -00\n\tmode?: \"interleaved\" | \"object\";\n\t/** UEP (Unequal Error Protection) \u2014 extensions feature, see moqcast-draft docs/fec-extensions-material.md, not in public -00. When present, repair count\n\t * varies per frame type. repairSymbols becomes the base (P-frame) count.\n\t * MUST be omitted on multi-track base/delta streams (D-08). */\n\tuep?: {\n\t\t/** Keyframe repair ratio: P = ceil(K * keyRatio). Default 0.5. */\n\t\tkeyRatio: number;\n\t\t/** Delta frame repair ratio: P = ceil(K * deltaRatio). Default 0.25. */\n\t\tdeltaRatio: number;\n\t};\n\t/**\n\t * Base-only fallback window in FEC blocks (D-11, Phase 14).\n\t *\n\t * When the delta track's FEC block failure rate exceeds the derived threshold\n\t * over this many consecutive blocks, MediaRouter falls back to base-only mode.\n\t * Derivation: framerate * fallback_window_seconds. If absent, construction\n\t * of the multi-track subscriber MUST throw (error on bad init).\n\t *\n\t * Present only on the base track's fec config (not the delta or repair track).\n\t */\n\tdeltaFailWindowBlocks?: number;\n\t/**\n\t * Delta re-subscribe probe interval in milliseconds (D-13, Phase 14).\n\t *\n\t * After a base-only fallback, wait this long before re-subscribing to the\n\t * delta track to check if conditions have improved.\n\t * Derivation: computeFecDeliveryWindowMs(...) \u2014 the single FEC recovery-window\n\t * source of truth (draft-ramadan-moq-fec \u00A79). Never a fixed interleave multiple.\n\t *\n\t * Present only on the base track's fec config (not the delta or repair track).\n\t */\n\tdeltaProbeIntervalMs?: number;\n\t/**\n\t * Jitter + one-way-delay + reorder budget in MILLISECONDS (draft \u00A79).\n\t *\n\t * An explicit ADDITIVE margin over the complete signaled FEC block span.\n\t * OPTIONAL: absence does not authorize a local default or a span multiplier.\n\t */\n\tjitterBufferMs?: number;\n}\n\n/**\n * Whether a catalog `fec` block carries the FEC instance geometry a receiver\n * configures, its block profile aside: a supported `algorithm`, positive\n * `sourceSymbols`, `repairSymbols` and `symbolSize` (8-byte aligned for\n * RaptorQ), and a non-empty `repairTrack`. A block that fails this is\n * malformed whatever its profile. Total: never throws.\n */\nexport function isFecCatalogGeometry(obj: unknown): boolean {\n\tif (typeof obj !== \"object\" || obj === null) return false;\n\tconst o = obj as Record<string, unknown>;\n\tconst algorithm = o[\"algorithm\"];\n\tconst symbolSize = o[\"symbolSize\"];\n\treturn (\n\t\ttypeof algorithm === \"string\" &&\n\t\t(algorithm === \"none\" || algorithm === \"raptorq\" || algorithm === \"reed-solomon\") &&\n\t\ttypeof o[\"sourceSymbols\"] === \"number\" && o[\"sourceSymbols\"] >= 1 &&\n\t\ttypeof o[\"repairSymbols\"] === \"number\" && o[\"repairSymbols\"] >= 1 &&\n\t\ttypeof symbolSize === \"number\" && symbolSize >= 1 &&\n\t\t(algorithm !== \"raptorq\" || symbolSize % 8 === 0) &&\n\t\ttypeof o[\"repairTrack\"] === \"string\" && (o[\"repairTrack\"] as string).length > 0\n\t);\n}\n\n/**\n * Whether a catalog `fec` block is a complete `FecCatalogConfig`: its geometry\n * holds (`isFecCatalogGeometry`) and it names the implemented block profile,\n * `iso-ssbg1-v1` (BLO-35597). On `algorithm: \"none\"` the profile may be\n * omitted. Any other token, the retired `k-padded-v1` included, is not a\n * known profile, so this is false for it; a receiver resolves such a track to\n * no FEC (`TrackFecUnavailable`) rather than failing the session.\n */\nexport function isFecCatalogConfig(obj: unknown): obj is FecCatalogConfig {\n\tif (!isFecCatalogGeometry(obj)) return false;\n\tconst o = obj as Record<string, unknown>;\n\treturn isFecBlockProfile(o[\"blockProfile\"]) || (o[\"algorithm\"] === \"none\" && o[\"blockProfile\"] === undefined);\n}\n\n// \u2500\u2500 Multicast Catalog Extension (CARP \u00A74.1-4.4) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type MulticastProtocol = \"ssm\" | \"asm\" | \"amt\" | \"atsc3\";\n\n/**\n * Per-track endpoint membership, per draft-ramadan-moq-multicast-00 \u00A74.1.\n *\n * `packetId` is the MMTP packet_id used for packet-level demux on the\n * multicast wire, in 1..65535: packet_id 0 (`MMTP_SIGNALING_PACKET_ID`) is\n * the MMTP signaling flow and is never a track's. New emitters MUST use this\n * object form; the legacy\n * `string` form is accepted by parsers for one release for backward\n * compatibility (BLO-3323 migration).\n */\nexport interface MulticastTrackObject {\n\tname: string;\n\tpacketId: number;\n}\n\n/** Track entry in a multicast endpoint \u2014 accepts both legacy and v2 shape. */\nexport type MulticastTrack = string | MulticastTrackObject;\n\nexport interface MulticastEndpoint {\n\t/** Transport protocol. Defaults to SSM when sourceAddress is present. */\n\tprotocol?: MulticastProtocol;\n\t/** SSM source IP address (REQUIRED for SSM). */\n\tsourceAddress?: string;\n\t/** Multicast group address (REQUIRED). */\n\tgroupAddress: string;\n\t/** UDP port number (REQUIRED). */\n\tport: number;\n\t/**\n\t * Tracks available on this endpoint (REQUIRED).\n\t *\n\t * Accepts both legacy `string` (track name only) and v2 object form\n\t * (`{ name, packetId }`) for backward compatibility. New emitters\n\t * MUST use the object form (BLO-3323 / draft \u00A74.1). Consumers can\n\t * type-narrow with `typeof t === \"string\"` to handle both.\n\t */\n\ttracks: MulticastTrack[];\n\t/**\n\t * AMT relay hostname.\n\t *\n\t * @deprecated Superseded by catalog-level `multicast.networkSource`\n\t * (`{type: \"amt\", discovery, relay}`). Retained so a catalog published\n\t * before the migration still parses; emitters MUST NOT write it, and\n\t * `catalog.schema.json` already rejects it (`additionalProperties: false`\n\t * on the endpoint), as does moq-rs `MulticastEndpoint`\n\t * (`deny_unknown_fields`, and it carries no relay field at all).\n\t */\n\trelayAddress?: string;\n\t/**\n\t * Aggregate bandwidth of this endpoint in bits per second.\n\t * RECOMMENDED per draft \u00A74.1 \u2014 enables capacity-aware joins.\n\t */\n\tbandwidth?: number;\n}\n\nexport type AmtDiscovery = \"driad\" | \"manual\";\n\n/**\n * One `multicast.networkSource[]` entry, per draft-ramadan-moq-multicast \u00A74.1.\n *\n * Mirrors moq-rs `NetworkSource` (the normative validator) and the\n * `networkSource` `$def` in `catalog.schema.json` 1:1 \u2014 a change here needs the\n * same change in both. `discovery: \"manual\"` requires `relay`; that relay is\n * the one the player joins. Under `discovery: \"driad\"` the relay is resolved at\n * runtime, so `relay` is OPTIONAL and, when present, only a discovery hint\n * (e.g. the `amtFallback.relayDns` name the derived path carries over): readers\n * do not join it (`findAmtRelay` in packages/transport\n * multicast-catalog-policy.ts and `catalogAmtNetworkSource` in\n * catalog-network-source.ts both skip it), so a DRIAD entry never pins a relay.\n * `port` is OPTIONAL on the wire: the join boundary itself has no default, but\n * every reader resolves one before that boundary, so a manual relay without a\n * port joins on RFC 7450's 2268 rather than failing (BLO-38176).\n *\n * Not to be confused with `CatalogNetworkSource` in `catalog-network-source.ts`,\n * which is also exported from the package root and also describes\n * `$defs/networkSource`. The split is deliberate and the two must NOT be\n * unified: this type is the strict EMITTER/wire shape, so tightening it is what\n * keeps published catalogs normative. That one is the tolerant READER \u2014 every\n * field optional, and it also accepts the bare-object form that hang's zod\n * RootSchema and older hand-authored catalogs emit. Tightening the reader would\n * reject catalogs already in the wild, so when this type changes, widen the\n * reader only if the new shape must be consumed; never narrow it to match.\n *\n * This is the one place a catalog declares an AMT relay. It is catalog-level,\n * not per-endpoint: the normative endpoint model carries no relay field.\n */\nexport type NetworkSource =\n\t| { type: \"amt\"; discovery: AmtDiscovery; relay?: string; port?: number }\n\t| { type: \"atsc3\"; frequency: number; plpId: number; serviceId: number; slsUri: string };\n\nexport interface MulticastCatalogConfig {\n\t/** Enable/disable multicast (OPTIONAL, default true). */\n\tenabled?: boolean;\n\t/** Multicast endpoints (REQUIRED). */\n\tendpoints: MulticastEndpoint[];\n\t/**\n\t * Relay/tuning discovery for these endpoints. Required whenever an endpoint\n\t * declares a bearer protocol (`amt`, `atsc3`), which must name its own entry\n\t * type here; omitted for native joins (`ssm`, `asm`), whose endpoint tuple is\n\t * the whole join.\n\t */\n\tnetworkSource?: NetworkSource[];\n\t/**\n\t * AMT fallback configuration.\n\t *\n\t * @deprecated Superseded by `networkSource` \u2014 `{discovery, relayDns}` maps\n\t * onto `{type: \"amt\", discovery, relay}`. `multicastFromConfig` lifts it and\n\t * no longer emits it; the schema rejects it.\n\t */\n\tamtFallback?: {\n\t\tdiscovery: AmtDiscovery;\n\t\trelayDns?: string;\n\t};\n\t/**\n\t * Monotonic catalog version. Publishers MUST bump on any\n\t * `endpoints[]` mutation (BLO-3323 catalog freshness model).\n\t */\n\tversion?: number;\n\t/**\n\t * Unix-ms timestamp after which subscribers SHOULD refetch the\n\t * catalog. Refetch-on-multicast-receive-timeout short-circuits this.\n\t */\n\tvalidUntil?: number;\n}\n\nexport function isMulticastTrackObject(obj: unknown): obj is MulticastTrackObject {\n\tif (typeof obj !== \"object\" || obj === null) return false;\n\tconst o = obj as Record<string, unknown>;\n\treturn typeof o[\"name\"] === \"string\" && typeof o[\"packetId\"] === \"number\";\n}\n\n/**\n * Wire-shape predicate: returns `true` when `obj` matches the\n * MulticastEndpoint JSON shape strictly enough to be safely cast.\n *\n * Intentionally less strict than `multicastFromConfig`'s\n * `validateEndpoint` (in `multicast-input.ts`):\n *\n * - `tracks: []` is accepted here (Array.isArray check only) but\n * rejected by `validateEndpoint` as `empty_tracks`.\n * - SSM-specific group-range and source-required checks are not\n * enforced here.\n * - Bandwidth/AmtFallback/version are not type-checked.\n *\n * That asymmetry is deliberate: this predicate guards JSON parsed off\n * the wire (already-published catalogs that downstream may have\n * trimmed), while `validateEndpoint` guards publisher input *before*\n * a catalog is composed. Subscribers are tolerant; publishers are\n * strict \u2014 the catalog format never re-validates upstream invariants.\n */\nexport function isMulticastEndpoint(obj: unknown): obj is MulticastEndpoint {\n\tif (typeof obj !== \"object\" || obj === null) return false;\n\tconst o = obj as Record<string, unknown>;\n\tconst protocol = o[\"protocol\"] ??\n\t\t(typeof o[\"sourceAddress\"] === \"string\" && o[\"sourceAddress\"].length > 0 ? \"ssm\" : undefined);\n\treturn (\n\t\ttypeof protocol === \"string\" &&\n\t\t(protocol === \"ssm\" || protocol === \"asm\" || protocol === \"amt\" || protocol === \"atsc3\") &&\n\t\ttypeof o[\"groupAddress\"] === \"string\" && (o[\"groupAddress\"] as string).length > 0 &&\n\t\ttypeof o[\"port\"] === \"number\" && (o[\"port\"] as number) > 0 && (o[\"port\"] as number) <= 65535 &&\n\t\tArray.isArray(o[\"tracks\"]) &&\n\t\t(o[\"tracks\"] as unknown[]).every((t: unknown) => typeof t === \"string\" || isMulticastTrackObject(t))\n\t);\n}\n\nexport function isMulticastCatalogConfig(obj: unknown): obj is MulticastCatalogConfig {\n\tif (typeof obj !== \"object\" || obj === null) return false;\n\tconst o = obj as Record<string, unknown>;\n\treturn (\n\t\tArray.isArray(o[\"endpoints\"]) &&\n\t\t(o[\"endpoints\"] as unknown[]).every((ep: unknown) => isMulticastEndpoint(ep)) &&\n\t\t// Shape only, not contents \u2014 entry validation is `multicastFromConfig`'s.\n\t\t// `networkSource` now carries the relay, so a non-array here must not\n\t\t// narrow clean: every reader of it indexes it.\n\t\t//\n\t\t// `=== undefined`, not `!(\"networkSource\" in o)`: `multicastFromConfig`\n\t\t// tests definedness, and the two must agree on what \"set\" means. A key\n\t\t// present with value `undefined` is a legal `MulticastCatalogConfig`\n\t\t// (`strict` without `exactOptionalPropertyTypes`) and is what a spread\n\t\t// of an optional field produces \u2014 `in` would narrow that valid config\n\t\t// to `false` and a consumer gating on this guard would discard a\n\t\t// working multicast block.\n\t\t(o[\"networkSource\"] === undefined || Array.isArray(o[\"networkSource\"]))\n\t);\n}\n\n// \u2500\u2500 Track Timing (mmt-00 \u00A74.4.4) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Per-track group-duration signaling.\n *\n * The catalog publishes either `groupDurationMs` (integer ms) or the exact\n * `groupDurationTicks` value used by `groupIdForTicks` in align.ts. The tick\n * form is required for cases where milliseconds cannot represent the cadence\n * exactly:\n *\n * - Per-frame group durations at non-integer-ms cadences\n * (1/30 s = 33.333\u2026 ms; 1/60 s = 16.666\u2026 ms).\n * - Audio sample-rate timescales not divisible by 1000 Hz\n * (e.g. 44 100 Hz \u00D7 333 ms = 14 685.3 ticks).\n *\n * If both fields are present, `groupDurationTicks` wins (matches the\n * spec, mmt-00 \u00A74.4.4).\n *\n * @see draft-ramadan-moq-mmt-00 \u00A74.4.4\n * @see ./align.ts groupIdForTicks\n */\nexport interface TrackTimingConfig {\n\t/** Group duration in milliseconds (positive integer). Required unless an\n\t * exact groupDurationTicks value is supplied. */\n\tgroupDurationMs?: number;\n\t/** Integer-tick override (OPTIONAL, positive integer). Wins over the\n\t * ms field when both are present. Required when\n\t * `(groupDurationMs * timescale) % 1000 != 0`. */\n\tgroupDurationTicks?: number;\n}\n\nexport function isTrackTimingConfig(obj: unknown): obj is TrackTimingConfig {\n\tif (typeof obj !== \"object\" || obj === null) return false;\n\tconst o = obj as Record<string, unknown>;\n\tconst ms = o[\"groupDurationMs\"];\n\tconst ticks = o[\"groupDurationTicks\"];\n\tif (ms === undefined && ticks === undefined) return false;\n\tif (ms !== undefined && (typeof ms !== \"number\" || !Number.isInteger(ms) || ms <= 0)) return false;\n\tif (\"groupDurationTicks\" in o) {\n\t\tif (ticks === undefined) return ms !== undefined;\n\t\tif (typeof ticks !== \"number\" || !Number.isInteger(ticks) || ticks <= 0) return false;\n\t}\n\treturn true;\n}\n\n/** Resolve catalog group timing to an operational wall-clock duration.\n *\n * `groupDurationTicks` wins when both wire fields are present, matching\n * mmt-00 \u00A74.4.4. The result may be fractional because exact frame cadences\n * such as 33_333 ticks at 1 MHz cannot be represented by the wire catalog's\n * integer `groupDurationMs` field.\n */\nexport function resolveGroupDurationMs(\n\ttiming: TrackTimingConfig,\n\ttimescale: number,\n): number {\n\tif (!Number.isSafeInteger(timescale) || timescale <= 0) {\n\t\tthrow new Error(\n\t\t\t`[carp-catalog] resolveGroupDurationMs: timescale must be a positive safe integer Hz, got ${timescale}`,\n\t\t);\n\t}\n\tif (!isTrackTimingConfig(timing)) {\n\t\tthrow new Error(\n\t\t\t`[carp-catalog] resolveGroupDurationMs: invalid TrackTimingConfig: ${JSON.stringify(timing)}`,\n\t\t);\n\t}\n\tif (timing.groupDurationTicks === undefined) {\n\t\treturn timing.groupDurationMs!;\n\t}\n\n\tconst durationMs = (timing.groupDurationTicks / timescale) * 1000;\n\tif (!Number.isFinite(durationMs) || durationMs <= 0 || durationMs > Number.MAX_SAFE_INTEGER) {\n\t\tthrow new Error(\n\t\t\t`[carp-catalog] resolveGroupDurationMs: groupDurationTicks=${timing.groupDurationTicks} ` +\n\t\t\t\t`at timescale=${timescale} cannot be represented as a positive safe millisecond duration`,\n\t\t);\n\t}\n\treturn durationMs;\n}\n\n/**\n * Optional unicast archive tier (BLO-41290).\n *\n * `origin` is scheme-and-authority only. Object paths are never advertised:\n * a consumer derives them via the v1 ContentId profile \u2014\n * `/v1/{ns}/{track}/{group}[/{object}]` under this origin\n * (`mmt-archive/src/content_id.rs`). Advertising the pair a second time would\n * be a second source of truth that can disagree with `tracks[]`.\n *\n * `{track}` is the track's `name`. `{ns}` is the track's own `namespace` when\n * it declares one, and otherwise the MoQ subscription's Track Namespace tuple,\n * of which `ContentId::from_moq` admits exactly one element. The catalog does\n * not carry that tuple, so for a track omitting `namespace` the derivation\n * needs the subscription context \u2014 the catalog alone is not sufficient. That\n * inheritance is the load-bearing half of the contract: `namespace` is\n * optional in the schema, so a catalog can advertise an archive whose object\n * paths only resolve in a subscribing consumer.\n *\n * One origin at the top level, not one per track: the `(namespace, track)` key\n * lives in the derived path rather than in the origin, so a single origin\n * already disambiguates every track however many namespaces the catalog spans.\n * A per-track origin would only buy splitting one catalog across several\n * archives, which nothing asks for.\n *\n * Absence means \"no archive advertised\". It is never a default URL.\n */\nexport interface ArchiveConfig {\n\torigin: string;\n}\n\n/**\n * The advertised archive origin, or `undefined` when none is advertised.\n *\n * Takes a catalog already through `validateCmsfMmtpCatalog`, so the returned\n * string is canonical and safe to use as a base \u2014 no string surgery on a\n * config blob, and no second place that decides what an origin may look like.\n */\nexport function archiveOrigin(catalog: CmsfCatalog): string | undefined {\n\treturn catalog.archive?.origin;\n}\n\n/**\n * Pin the archive origin to exactly one spelling per archive.\n *\n * The ContentId codec rejects percent-encoding so every identity has exactly\n * one URL path; an advertised base must not reintroduce aliasing above it.\n * `value === new URL(value).origin` is most of the rule \u2014 `URL.origin` drops\n * path, query, fragment and userinfo, lowercases scheme and host, strips the\n * default port and any trailing slash, and normalizes percent-escapes. So a\n * separate percent-encoding check would be redundant, not missing: `%6d` in a\n * host decodes and fails the comparison. `https:` is checked separately\n * because `http://host` is already its own origin and passes the comparison.\n *\n * The comparison alone admits one alias, so the trailing dot is rejected\n * explicitly: `https://host.` is its own `URL.origin` verbatim, while `host.`\n * and `host` are the same DNS name. That aliases the advertised *base*, not\n * the codec input \u2014 the origin contributes no path segments, so a trailing\n * dot never reaches `ContentId::parse_path` \u2014 but it still gives one archive\n * two cache keys, and some TLS stacks treat `host.` as a distinct SNI/CORS\n * origin.\n *\n * `additionalProperties: false` on `archive` is enforced here as well as in\n * the schema. The runtime validator is the gate `archiveOrigin` documents\n * itself against, so a second spelling of the origin smuggled in under\n * another key must be rejected by both, not just by AJV.\n */\nfunction validateArchive(catalog: Record<string, unknown>): void {\n\tconst archive = catalog[\"archive\"];\n\tif (archive === undefined) return;\n\tif (typeof archive !== \"object\" || archive === null || Array.isArray(archive)) {\n\t\tcatalogError(\"archive_object\", \"$.archive\", \"archive must be an object\");\n\t}\n\tconst unknown = Object.keys(archive as Record<string, unknown>).filter(key => key !== \"origin\");\n\tif (unknown.length > 0) {\n\t\tcatalogError(\"archive_object\", \"$.archive\", `archive has unknown field(s) ${unknown.map(key => JSON.stringify(key)).join(\", \")}; only \"origin\" is allowed`);\n\t}\n\tconst origin = (archive as Record<string, unknown>)[\"origin\"];\n\t// Absent and present-but-wrong-type are different mistakes and the schema\n\t// already distinguishes them (`required` vs `type`). Collapsing both into\n\t// \"must be a string\" tells someone hand-writing a catalog to fix a field\n\t// they never wrote. Same wording convention as `codec is required`.\n\t//\n\t// This is the one archive rule where the two gates report different fields,\n\t// deliberately: AJV raises `required` at the parent, `/archive` (its\n\t// placement, not a choice \u2014 same shape as `fec-block-profile-missing`),\n\t// while this raises at `$.archive.origin`. Pointing at the field someone has\n\t// to add is the more useful of the two, so the \"same field\" convention\n\t// documented above bends here rather than the message getting vaguer.\n\tif (origin === undefined) {\n\t\tcatalogError(\"archive_origin\", \"$.archive.origin\", \"origin is required when an archive block is present\");\n\t}\n\tif (typeof origin !== \"string\") {\n\t\tcatalogError(\"archive_origin\", \"$.archive.origin\", \"origin must be a string\");\n\t}\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(origin);\n\t} catch {\n\t\tcatalogError(\"archive_origin\", \"$.archive.origin\", `origin must be an absolute URL, got ${JSON.stringify(origin)}`);\n\t}\n\tif (parsed.protocol !== \"https:\") {\n\t\tcatalogError(\"archive_origin\", \"$.archive.origin\", `origin must use https, got ${JSON.stringify(parsed.protocol)}`);\n\t}\n\tif (origin !== parsed.origin) {\n\t\tcatalogError(\n\t\t\t\"archive_origin\",\n\t\t\t\"$.archive.origin\",\n\t\t\t`origin must be scheme and authority in canonical form with no path, query, fragment, userinfo or trailing slash; expected ${JSON.stringify(parsed.origin)}, got ${JSON.stringify(origin)}`,\n\t\t);\n\t}\n\tif (parsed.hostname.endsWith(\".\")) {\n\t\tcatalogError(\"archive_origin\", \"$.archive.origin\", `origin host must not end in a trailing dot; ${JSON.stringify(parsed.hostname)} and ${JSON.stringify(parsed.hostname.slice(0, -1))} are the same host`);\n\t}\n}\n\nexport type CmsfCatalog = {\n\tversion: number;\n\ttracks: Record<string, unknown>[];\n\tmulticast?: Record<string, unknown>;\n\t\"multicast.auth\"?: MulticastAuthConfig;\n\tarchive?: ArchiveConfig;\n\t[key: string]: unknown;\n};\n\nexport type CmsfCatalogErrorCode =\n\t| \"catalog_object\"\n\t| \"catalog_version\"\n\t| \"catalog_tracks\"\n\t| \"catalog_common_track_fields\"\n\t| \"track_object\"\n\t| \"track_name\"\n\t| \"track_packaging\"\n\t| \"track_selection_params\"\n\t| \"track_is_live\"\n\t| \"track_codec\"\n\t| \"track_mmtp_mode\"\n\t| \"track_timescale\"\n\t| \"track_group_duration_ms\"\n\t| \"track_group_duration_ticks\"\n\t| \"track_group_duration_exact\"\n\t| \"track_keyframe_interval\"\n\t| \"track_fec\"\n\t| \"track_fec_bounds\"\n\t| \"track_fec_block_profile\"\n\t| \"track_fec_block_profile_unimplemented\"\n\t| \"track_repair_track\"\n\t| \"track_repair_depends\"\n\t| \"track_repair_layer\"\n\t| \"track_repair_priority\"\n\t| \"track_repair_overlay\"\n\t| \"track_repair_geometry\"\n\t| \"track_switching_set\"\n\t| \"multicast_auth\"\n\t| \"multicast_track\"\n\t| \"multicast_packet_id\"\n\t| \"archive_object\"\n\t| \"archive_origin\";\n\nexport class CmsfCatalogValidationError extends Error {\n\tconstructor(\n\t\tpublic readonly code: CmsfCatalogErrorCode,\n\t\tpublic readonly path: string,\n\t\tmessage: string,\n\t) {\n\t\tsuper(`[cmsf:${code}] ${path}: ${message}`);\n\t\tthis.name = \"CmsfCatalogValidationError\";\n\t}\n}\n\nfunction catalogError(code: CmsfCatalogErrorCode, path: string, message: string): never {\n\tthrow new CmsfCatalogValidationError(code, path, message);\n}\n\n/** Validate the common CMSF shape and the MMTP required-field profile. */\nexport function validateCmsfMmtpCatalog(value: unknown): CmsfCatalog {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tcatalogError(\"catalog_object\", \"$\", \"catalog must be an object\");\n\t}\n\tconst catalog = value as Record<string, unknown>;\n\t// IETF-draft catalogs (cast `ladder_catalog.rs`, moq-server\n\t// `catalog-synthesizer.ts`, consumed by the hang player) carry a string\n\t// version tag such as \"draft-ietf-moq-catalogformat-01\" instead of the CMSF\n\t// `version: 1` integer. Nothing keys behavior off the catalog version, so\n\t// normalize the tag to a number rather than rejecting the whole catalog \u2014\n\t// mirrors the relay (dual-stack-relay `bridge/catalog.rs`\n\t// `deserialize_optional_u32_lenient`, #825) and moqtail-ts\n\t// `normalizeCatalogVersion`.\n\tconst rawVersion = catalog[\"version\"];\n\tconst normalizedVersion =\n\t\ttypeof rawVersion === \"string\"\n\t\t\t? Number.isFinite(Number(rawVersion))\n\t\t\t\t? Number(rawVersion)\n\t\t\t\t: rawVersion.startsWith(\"draft-ietf-moq-catalogformat\")\n\t\t\t\t\t? 1\n\t\t\t\t\t: NaN\n\t\t\t: rawVersion;\n\tif (!Number.isInteger(normalizedVersion) || (normalizedVersion as number) <= 0) {\n\t\tcatalogError(\"catalog_version\", \"$.version\", \"version must be a positive integer or a recognized format tag\");\n\t}\n\tcatalog[\"version\"] = normalizedVersion;\n\tif (!Array.isArray(catalog[\"tracks\"])) {\n\t\tcatalogError(\"catalog_tracks\", \"$.tracks\", \"tracks must be an array\");\n\t}\n\n\tif (\"commonTrackFields\" in catalog) {\n\t\tcatalogError(\n\t\t\t\"catalog_common_track_fields\",\n\t\t\t\"$.commonTrackFields\",\n\t\t\t\"commonTrackFields is retired; every track carries its own fields\",\n\t\t);\n\t}\n\tconst tracks = catalog[\"tracks\"] as unknown[];\n\tconst trackRecords: Record<string, unknown>[] = [];\n\tconst positiveInteger = (input: unknown): input is number => Number.isSafeInteger(input) && (input as number) > 0;\n\tconst positiveNumber = (input: unknown): input is number =>\n\t\ttypeof input === \"number\" && Number.isFinite(input) && input > 0;\n\n\tfor (let index = 0; index < tracks.length; index++) {\n\t\tconst raw = tracks[index];\n\t\tconst path = `$.tracks[${index}]`;\n\t\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n\t\t\tcatalogError(\"track_object\", path, \"track must be an object\");\n\t\t}\n\t\tconst track = raw as Record<string, unknown>;\n\t\tconst name = track[\"name\"];\n\t\tif (typeof name !== \"string\" || name.length === 0) {\n\t\t\tcatalogError(\"track_name\", `${path}.name`, \"name must be a non-empty string\");\n\t\t}\n\t\ttrackRecords.push(track);\n\n\t\tconst packaging = track[\"packaging\"];\n\t\tif (typeof packaging !== \"string\" || packaging.length === 0) {\n\t\t\tcatalogError(\"track_packaging\", `${path}.packaging`, \"packaging must be a non-empty string\");\n\t\t}\n\t\tconst hasIsLive = Object.prototype.hasOwnProperty.call(track, \"isLive\");\n\t\tconst isLive = track[\"isLive\"];\n\t\tif (hasIsLive && typeof isLive !== \"boolean\") {\n\t\t\tcatalogError(\"track_is_live\", `${path}.isLive`, \"isLive must be a boolean\");\n\t\t}\n\t\ttrack[\"isLive\"] = isLive ?? true;\n\t\tif (\"selectionParams\" in track) {\n\t\t\tcatalogError(\n\t\t\t\t\"track_selection_params\",\n\t\t\t\t`${path}.selectionParams`,\n\t\t\t\t\"selectionParams is retired; codec and the other selection fields are flat on the track\",\n\t\t\t);\n\t\t}\n\t\tconst codec = track[\"codec\"];\n\t\tif (packaging !== \"timeline\" && packaging !== \"mediatimeline\" && packaging !== \"eventtimeline\" && packaging !== \"fec-repair\" &&\n\t\t\t(typeof codec !== \"string\" || codec.length === 0)) {\n\t\t\tcatalogError(\"track_codec\", `${path}.codec`, `codec is required for packaging=${packaging}`);\n\t\t}\n\n\t\tconst timescale = track[\"timescale\"];\n\t\tconst groupDurationMs = track[\"groupDurationMs\"];\n\t\tconst groupDurationTicks = track[\"groupDurationTicks\"];\n\t\tconst keyframeIntervalMs = track[\"keyframeIntervalMs\"];\n\t\tconst keyframeIntervalTicks = track[\"keyframeIntervalTicks\"];\n\t\tif (groupDurationMs !== undefined && !positiveInteger(groupDurationMs)) {\n\t\t\tcatalogError(\"track_group_duration_ms\", `${path}.groupDurationMs`, \"must be a positive integer\");\n\t\t}\n\t\tif (groupDurationTicks !== undefined && !positiveInteger(groupDurationTicks)) {\n\t\t\tcatalogError(\"track_group_duration_ticks\", `${path}.groupDurationTicks`, \"must be a positive integer\");\n\t\t}\n\t\tif ((keyframeIntervalMs !== undefined && !positiveNumber(keyframeIntervalMs)) ||\n\t\t\t(keyframeIntervalTicks !== undefined && !positiveInteger(keyframeIntervalTicks))) {\n\t\t\tcatalogError(\"track_keyframe_interval\", path, \"keyframe interval fields must be positive numbers\");\n\t\t}\n\t\t// draft-ramadan-moq-fec \u00A76.3: repair tracks describe themselves and carry\n\t\t// no block geometry; media tracks never carry repair-track fields.\n\t\tif (packaging === \"fec-repair\") {\n\t\t\trejectRepairTrackGeometry(path, track, catalogError);\n\t\t\tvalidateRepairTrackFields(describeRepairTrack(name, path, track), catalogError);\n\t\t} else {\n\t\t\trejectRepairFieldsOnMediaTrack(path, track, catalogError);\n\t\t}\n\t\tif (packaging === \"mmtp\") {\n\t\t\tconst mmtpMode = track[\"mmtpMode\"];\n\t\t\tif (mmtpMode !== \"mpu\" && mmtpMode !== \"mfu\") {\n\t\t\t\tcatalogError(\"track_mmtp_mode\", `${path}.mmtpMode`, \"must be mpu or mfu\");\n\t\t\t}\n\t\t\tif (!positiveInteger(timescale)) {\n\t\t\t\tcatalogError(\"track_timescale\", `${path}.timescale`, \"must be a positive integer\");\n\t\t\t}\n\t\t\tif (groupDurationMs === undefined && groupDurationTicks === undefined) {\n\t\t\t\tcatalogError(\n\t\t\t\t\t\"track_group_duration_ms\",\n\t\t\t\t\tpath,\n\t\t\t\t\t\"groupDurationMs or groupDurationTicks must be a positive integer\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (groupDurationTicks === undefined &&\n\t\t\t\t(BigInt(groupDurationMs as number) * BigInt(timescale)) % 1000n !== 0n) {\n\t\t\t\tcatalogError(\"track_group_duration_exact\", `${path}.groupDurationMs`, \"does not resolve to an exact integer tick count\");\n\t\t\t}\n\t\t}\n\n\t\tif (track[\"fec\"] !== undefined) {\n\t\t\tconst fec = track[\"fec\"];\n\t\t\tif (typeof fec !== \"object\" || fec === null || Array.isArray(fec)) {\n\t\t\t\tcatalogError(\"track_fec\", `${path}.fec`, \"FEC must be an object\");\n\t\t\t}\n\t\t\tconst config = fec as Record<string, unknown>;\n\t\t\tconst algorithm = config[\"algorithm\"];\n\t\t\tif (algorithm !== \"none\" && algorithm !== \"raptorq\" && algorithm !== \"reed-solomon\") {\n\t\t\t\tcatalogError(\"track_fec\", `${path}.fec.algorithm`, \"algorithm is not supported\");\n\t\t\t}\n\t\t\tif (\"interleaveDepth\" in config || \"interleaveMs\" in config) {\n\t\t\t\tcatalogError(\"track_fec\", `${path}.fec`, \"interleaveDepthMs is the only supported interleave field\");\n\t\t\t}\n\t\t\tif (\"enhancementRepair\" in config) {\n\t\t\t\tcatalogError(\n\t\t\t\t\t\"track_fec\",\n\t\t\t\t\t`${path}.fec.enhancementRepair`,\n\t\t\t\t\t\"repair layers are self-describing fec-repair tracks (depends, repairLayer); fec.enhancementRepair is not supported\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (config[\"interleaveDepthMs\"] !== undefined && !positiveInteger(config[\"interleaveDepthMs\"])) {\n\t\t\t\tcatalogError(\"track_fec\", `${path}.fec.interleaveDepthMs`, \"must be a positive integer\");\n\t\t\t}\n\t\t\tif (algorithm !== \"none\") {\n\t\t\t\tfor (const field of [\"sourceSymbols\", \"repairSymbols\", \"symbolSize\"] as const) {\n\t\t\t\t\tif (!positiveInteger(config[field])) {\n\t\t\t\t\t\tcatalogError(\"track_fec\", `${path}.fec.${field}`, \"must be a positive integer\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (typeof config[\"repairTrack\"] !== \"string\" || config[\"repairTrack\"].length === 0) {\n\t\t\t\t\tcatalogError(\"track_fec\", `${path}.fec.repairTrack`, \"must be a non-empty string\");\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Required on a declared FEC instance, optional but implemented on \"none\".\n\t\t\treadFecBlockProfile(config[\"blockProfile\"], `${path}.fec.blockProfile`, algorithm !== \"none\", catalogError);\n\t\t}\n\t}\n\n\t// Repair tracks (draft-ramadan-moq-fec \u00A76.3). Every fec-repair track is\n\t// described once; each FEC source then assembles its base layer chain and\n\t// overlays through the shared contract, which fails closed with a code and\n\t// the JSON path of the offending field.\n\tconst repairDescriptors = trackRecords.flatMap((track, index) =>\n\t\ttrack[\"packaging\"] === \"fec-repair\"\n\t\t\t? [describeRepairTrack(track[\"name\"] as string, `$.tracks[${index}]`, track)]\n\t\t\t: [],\n\t);\n\t// Rule 9 (no orphan repair track) reads the same derivation the resolver\n\t// uses; see `repairReachability` in repair-layers.ts for its scope.\n\tconst reachability = repairReachability(\n\t\ttrackRecords.filter((track) => track[\"packaging\"] !== \"fec-repair\").map((track) => track[\"fec\"]),\n\t);\n\tfor (const descriptor of repairDescriptors) {\n\t\tassertRepairTrackReachable(descriptor, reachability, catalogError);\n\t\tconst target = dependsTarget(descriptor);\n\t\tif (target === undefined) continue;\n\t\tconst source = trackRecords.find((candidate) => candidate[\"name\"] === target);\n\t\tconst sourceFec = source?.[\"fec\"] as Record<string, unknown> | undefined;\n\t\tif (!source || source[\"packaging\"] !== \"mmtp\" || !sourceFec || sourceFec[\"algorithm\"] === \"none\") {\n\t\t\tcatalogError(\n\t\t\t\t\"track_repair_depends\",\n\t\t\t\t`${descriptor.path}.depends`,\n\t\t\t\t`${JSON.stringify(target)} is not an mmtp track with FEC`,\n\t\t\t);\n\t\t}\n\t}\n\tfor (let index = 0; index < trackRecords.length; index++) {\n\t\tconst track = trackRecords[index]!;\n\t\tconst fec = track[\"fec\"] as Record<string, unknown> | undefined;\n\t\tif (!fec || fec[\"algorithm\"] === \"none\") continue;\n\t\t// A dangling fec.repairTrack (missing, or not a fec-repair track) fails\n\t\t// inside the shared contract, so the resolver and this validator agree.\n\t\tassembleRepairLayers(\n\t\t\t{\n\t\t\t\tname: track[\"name\"] as string,\n\t\t\t\tpath: `$.tracks[${index}].fec`,\n\t\t\t\talgorithm: fec[\"algorithm\"],\n\t\t\t\tsourceSymbols: fec[\"sourceSymbols\"],\n\t\t\t\trepairSymbols: fec[\"repairSymbols\"],\n\t\t\t\trepairTrack: fec[\"repairTrack\"],\n\t\t\t},\n\t\t\trepairDescriptors,\n\t\t\tcatalogError,\n\t\t\t{ layerZeroMustBeDeclared: true },\n\t\t);\n\t}\n\n\tconst switchingSets = new Map<number, { name: string; ticks: bigint; timescale: bigint }>();\n\tfor (let index = 0; index < trackRecords.length; index++) {\n\t\tconst track = trackRecords[index]!;\n\t\tif (track[\"packaging\"] !== \"mmtp\") continue;\n\t\tconst altGroup = track[\"altGroup\"];\n\t\tif (!Number.isSafeInteger(altGroup)) continue;\n\t\tconst timescale = BigInt(track[\"timescale\"] as number);\n\t\tconst durationTicks = track[\"groupDurationTicks\"];\n\t\tconst ticks = durationTicks !== undefined\n\t\t\t? BigInt(durationTicks as number)\n\t\t\t: BigInt(track[\"groupDurationMs\"] as number) * timescale / 1000n;\n\t\tconst anchor = switchingSets.get(altGroup as number);\n\t\tif (anchor && ticks * anchor.timescale !== anchor.ticks * timescale) {\n\t\t\tcatalogError(\"track_switching_set\", `$.tracks[${index}]`, `group duration does not match ${JSON.stringify(anchor.name)} in altGroup ${altGroup}`);\n\t\t}\n\t\tif (!anchor) switchingSets.set(altGroup as number, { name: track[\"name\"] as string, ticks, timescale });\n\t}\n\n\tconst multicast = catalog[\"multicast\"];\n\tif (typeof multicast === \"object\" && multicast !== null && !Array.isArray(multicast)) {\n\t\tconst endpoints = (multicast as Record<string, unknown>)[\"endpoints\"];\n\t\tif (Array.isArray(endpoints)) {\n\t\t\tfor (let endpointIndex = 0; endpointIndex < endpoints.length; endpointIndex++) {\n\t\t\t\tconst endpoint = endpoints[endpointIndex];\n\t\t\t\tif (typeof endpoint !== \"object\" || endpoint === null || Array.isArray(endpoint)) continue;\n\t\t\t\tconst refs = (endpoint as Record<string, unknown>)[\"tracks\"];\n\t\t\t\tif (!Array.isArray(refs)) continue;\n\t\t\t\tconst packetIds = new Map<number, string>();\n\t\t\t\tfor (let refIndex = 0; refIndex < refs.length; refIndex++) {\n\t\t\t\t\tconst ref = refs[refIndex];\n\t\t\t\t\tif (typeof ref !== \"object\" || ref === null || Array.isArray(ref)) continue;\n\t\t\t\t\tconst trackRef = ref as Record<string, unknown>;\n\t\t\t\t\tconst refPath = `$.multicast.endpoints[${endpointIndex}].tracks[${refIndex}]`;\n\t\t\t\t\tconst name = trackRef[\"name\"];\n\t\t\t\t\tif (typeof name !== \"string\" || !trackRecords.some((track) => track[\"name\"] === name)) {\n\t\t\t\t\t\tcatalogError(\"multicast_track\", `${refPath}.name`, `unknown catalog track ${JSON.stringify(name)}`);\n\t\t\t\t\t}\n\t\t\t\t\tconst packetId = trackRef[\"packetId\"];\n\t\t\t\t\tif (packetId === MMTP_SIGNALING_PACKET_ID) {\n\t\t\t\t\t\tcatalogError(\n\t\t\t\t\t\t\t\"multicast_packet_id\",\n\t\t\t\t\t\t\t`${refPath}.packetId`,\n\t\t\t\t\t\t\t`packetId ${packetId} is the MMTP signaling flow and must not be advertised for a track; packetId must be an integer from 1 to ${MMTP_PACKET_ID_MAX}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (!Number.isSafeInteger(packetId) || (packetId as number) < 1 || (packetId as number) > MMTP_PACKET_ID_MAX) {\n\t\t\t\t\t\tcatalogError(\"multicast_packet_id\", `${refPath}.packetId`, `packetId must be an integer from 1 to ${MMTP_PACKET_ID_MAX}`);\n\t\t\t\t\t}\n\t\t\t\t\tconst previous = packetIds.get(packetId as number);\n\t\t\t\t\tif (previous !== undefined) {\n\t\t\t\t\t\tcatalogError(\"multicast_packet_id\", `${refPath}.packetId`, `packetId is already assigned to ${JSON.stringify(previous)}`);\n\t\t\t\t\t}\n\t\t\t\t\tpacketIds.set(packetId as number, name as string);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\ttry {\n\t\tresolveMulticastAuthCatalog(catalog);\n\t} catch (error) {\n\t\tcatalogError(\n\t\t\t\"multicast_auth\",\n\t\t\t\"$.multicast.auth\",\n\t\t\terror instanceof Error ? error.message : \"invalid multicast.auth v2 block\",\n\t\t);\n\t}\n\n\tvalidateArchive(catalog);\n\n\treturn catalog as CmsfCatalog;\n}\n\n/**\n * Resolve a track's group duration to integer ticks using the supplied\n * timescale (Hz). Implements mmt-00 \u00A74.4.4:\n *\n * - If `groupDurationTicks` is present, return it (override wins).\n * - Else compute `groupDurationMs * timescale / 1000`. The result MUST\n * be an exact integer; throws if `(ms * timescale) % 1000 != 0`.\n *\n * Throws on invalid timing config or non-positive timescale (CLAUDE.md:\n * error on bad init).\n */\nexport function resolveGroupDurationTicks(\n\ttiming: TrackTimingConfig,\n\ttimescale: number,\n): number {\n\tif (!Number.isFinite(timescale) || !Number.isInteger(timescale) || timescale <= 0) {\n\t\tthrow new Error(\n\t\t\t`[carp-catalog] resolveGroupDurationTicks: timescale must be a positive integer Hz, got ${timescale}`,\n\t\t);\n\t}\n\tif (!isTrackTimingConfig(timing)) {\n\t\tthrow new Error(\n\t\t\t`[carp-catalog] resolveGroupDurationTicks: invalid TrackTimingConfig: ${JSON.stringify(timing)}`,\n\t\t);\n\t}\n\tif (timing.groupDurationTicks !== undefined) {\n\t\treturn timing.groupDurationTicks;\n\t}\n\tif (timing.groupDurationMs === undefined) {\n\t\tthrow new Error(\n\t\t\t\"[carp-catalog] resolveGroupDurationTicks: groupDurationMs is required when groupDurationTicks is absent\",\n\t\t);\n\t}\n\tconst product = timing.groupDurationMs * timescale;\n\tif (product % 1000 !== 0) {\n\t\tthrow new Error(\n\t\t\t`[carp-catalog] resolveGroupDurationTicks: groupDurationMs=${timing.groupDurationMs} at timescale=${timescale} ` +\n\t\t\t\t`produces non-integer ticks (${product / 1000}); publisher must use groupDurationTicks override (mmt-00 \u00A74.4.4)`,\n\t\t);\n\t}\n\treturn product / 1000;\n}\n\n/**\n * Validate that every track in a switching set agrees on the same\n * wall-clock group duration (mmt-00 \u00A74.4.4). Per-track timescales may\n * differ, so raw tick counts cannot be compared directly.\n *\n * Returns `{ ok: true }` when all tracks agree (or the set is empty);\n * `{ ok: false, mismatches }` listing each track whose effective ticks\n * differ from the first track's value. Subscribers per spec SHOULD\n * reject the catalog on mismatch.\n *\n * Throws if any track's timing config is invalid (per\n * `resolveGroupDurationTicks`).\n */\nexport function validateSwitchingSetTiming(\n\ttracks: Array<{ name: string; timing: TrackTimingConfig; timescale: number }>,\n):\n\t| { ok: true }\n\t| { ok: false; expectedTicks: number; mismatches: Array<{ name: string; ticks: number }> } {\n\tif (tracks.length === 0) return { ok: true };\n\tconst ticksByTrack = tracks.map((t) => ({\n\t\tname: t.name,\n\t\tticks: resolveGroupDurationTicks(t.timing, t.timescale),\n\t\ttimescale: t.timescale,\n\t}));\n\tconst expectedTicks = ticksByTrack[0].ticks;\n\tconst expectedTimescale = ticksByTrack[0].timescale;\n\tconst mismatches = ticksByTrack\n\t\t.slice(1)\n\t\t.filter(\n\t\t\t(t) =>\n\t\t\t\tBigInt(t.ticks) * BigInt(expectedTimescale) !==\n\t\t\t\tBigInt(expectedTicks) * BigInt(t.timescale),\n\t\t)\n\t\t.map(({ name, ticks }) => ({ name, ticks }));\n\tif (mismatches.length === 0) return { ok: true };\n\treturn { ok: false, expectedTicks, mismatches };\n}\n", "// Catalog \u2192 per-track FEC geometry resolution.\n//\n// Moved from hang-mmt-fec's js/watch/src/track-fec-params.ts so every player\n// (moq-lib/hang, moqtail, shaka MSF) resolves catalog FEC semantics through\n// one shared implementation \u2014 players keep only wiring, not MMT/catalog\n// interpretation. Structural track shape ({ name, type }) keeps this module\n// player-agnostic.\n\nimport {\n\tFEC_BLOCK_PROFILES,\n\tisFecBlockProfile,\n\tparseFecBlockProfile,\n\ttype FecBlockProfile,\n} from \"@blockcast/mmt-base\";\nimport { catalogFecInterleaveMs, type FecInterleaveCatalog } from \"@blockcast/mmt-fec\";\nimport { resolveGroupDurationMs } from \"./carp-catalog.js\";\nimport { MIN_INTERLEAVE_DEPTH_MS, catalogDeclaresFec, isoBlockSpanUsable } from \"./fec-state.js\";\nimport {\n\tassembleRepairLayers,\n\tassertRepairTrackReachable,\n\tdescribeRepairTrack,\n\trejectRepairFieldsOnMediaTrack,\n\trejectRepairTrackGeometry,\n\trepairReachability,\n\tdependsTarget,\n\treadFecBlockProfile,\n\tRepairContractError,\n\ttype RepairLayerParams,\n\ttype RepairLayersResolution,\n\ttype RepairOverlayParams,\n\ttype RepairSourceGeometry,\n\ttype RepairTrackDescriptor,\n\tthrowRepairContractError,\n\tvalidateRepairTrackFields,\n} from \"./repair-layers.js\";\nimport type { RepairTarget } from \"./repair-fetch.js\";\n\nexport { catalogFecInterleaveMs, type FecInterleaveCatalog };\nexport { FEC_BLOCK_PROFILES, isFecBlockProfile, parseFecBlockProfile, type FecBlockProfile };\nexport {\n\tassembleRepairLayers,\n\tassertRepairTrackReachable,\n\trepairReachability,\n\ttype AssembleRepairLayersOptions,\n\ttype RepairReachability,\n\tdependsTarget,\n\tdescribeRepairTrack,\n\treadFecBlockProfile,\n\trejectRepairFieldsOnMediaTrack,\n\trejectRepairTrackGeometry,\n\tREPAIR_TRACK_GEOMETRY_KEYS,\n\tREPAIR_TRACK_ONLY_KEYS,\n\tvalidateRepairTrackFields,\n\tDEFAULT_REPAIR_PRIORITY,\n\tMAX_ENCODING_SYMBOL_ID,\n\tMAX_RAPTORQ_SOURCE_SYMBOLS,\n\tMAX_REPAIR_SYMBOLS,\n\tREPAIR_PRIORITY_MAX,\n\tREPAIR_PRIORITY_MIN,\n\tRepairContractError,\n\ttype RepairContractCode,\n\ttype RepairContractFail,\n\ttype RepairLayerParams,\n\ttype RepairLayersResolution,\n\ttype RepairOverlayParams,\n\ttype RepairSourceGeometry,\n\ttype RepairTrackDescriptor,\n} from \"./repair-layers.js\";\n\n/**\n * Minimal structural shape of a resolved endpoint track. Callers may carry\n * extra fields (e.g. hang's ResolvedEndpointTrack adds `pid`) \u2014 the resolver\n * only reads `name` and `type`.\n */\nexport type CatalogTrackRef = {\n\tname: string;\n\ttype: \"video\" | \"audio\" | (string & {});\n};\n\ntype FecCatalog = FecInterleaveCatalog & {\n\talgorithm?: unknown;\n\t/** FEC block profile token (BLO-35597): required on a declared FEC\n\t * instance, read through `readFecBlockProfile`. */\n\tblockProfile?: unknown;\n\tsourceSymbols?: number;\n\trepairSymbols?: number;\n\tsymbolSize?: number;\n\t/** Signaled jitter/reorder allowance in ms (added to the block span). */\n\treorderToleranceMs?: number;\n\t/** Layer 0 repair track. Further layers and overlays describe themselves\n\t * (draft-ramadan-moq-fec \u00A76.3): MSF `tracks[]` entries with\n\t * `packaging: \"fec-repair\"`, or hang `repair.renditions` entries, that\n\t * `depend` on this rendition. */\n\trepairTrack?: string;\n};\n\n/** One self-describing repair rendition of the hang catalog's `repair` block\n * (draft-ramadan-moq-fec \u00A76.3). Mirrors the MSF fec-repair track fields. */\nexport type RepairRenditionCatalog = {\n\tdepends?: unknown;\n\trepairLayer?: unknown;\n\trepairSymbols?: unknown;\n\tpriority?: unknown;\n\tscope?: unknown;\n\tsourceSymbols?: unknown;\n};\n\n/** The container fields the FEC and group-duration readers consult. */\ntype ContainerCatalog = { kind?: unknown; timescale?: unknown };\n\ntype RenditionCatalog = KeyframeCadenceCatalog & {\n\tcontainer?: ContainerCatalog;\n\tfec?: FecCatalog;\n\tframerate?: unknown;\n\tsampleRate?: unknown;\n\tsamplerate?: unknown;\n};\n\ntype CatalogWithRenditions = {\n\tvideo?: { renditions?: Record<string, RenditionCatalog> };\n\taudio?: { renditions?: Record<string, RenditionCatalog> };\n\t/** hang-native repair renditions: every repair track beyond layer 0. */\n\trepair?: { renditions?: Record<string, RepairRenditionCatalog> };\n};\n\n/** An MSF/CMSF catalog: `tracks[]` entries, each self-describing. */\ntype CatalogWithTracks = {\n\ttracks?: unknown;\n};\n\n/** Either catalog dialect `resolveRepairLayers` reads. */\nexport type RepairLayerCatalog = CatalogWithRenditions | CatalogWithTracks;\n\nexport type GroupDurationCatalog = {\n\tgroupDurationTicks?: number;\n\tgroupDurationMs?: number;\n\ttimescale?: number;\n};\n\n/** Rendition fields the group-duration reader consumes. */\nexport type GroupDurationRendition = GroupDurationCatalog & {\n\tcontainer?: ContainerCatalog;\n};\n\n/**\n * Largest tick count per Hz of timescale whose millisecond duration\n * `resolveGroupDurationMs` still accepts: `(ticks / timescale) * 1000` must not\n * exceed `Number.MAX_SAFE_INTEGER`, so `ticks <= timescale * (MAX_SAFE / 1000)`\n * (Hz times seconds is ticks).\n */\nconst MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1000;\n\n/** A positive safe-integer Hz timescale, or undefined. */\nfunction timescaleHz(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isSafeInteger(value) && value > 0 ? value : undefined;\n}\n\n/**\n * A rendition's publisher group duration in ms, or undefined when the catalog\n * carries none.\n *\n * `groupDurationTicks` over a timescale wins when both are present, through\n * `resolveGroupDurationMs` (draft-ramadan-moq-mmt-00 \u00A74.4.4). The timescale is\n * the MMTP container's when the rendition declares one, else the rendition's\n * own `timescale`. Only the ticks are forwarded: a catalog `groupDurationMs`\n * is often a rounded fraction (21.33 for 1024/48000), which the ticks path\n * never reads but `resolveGroupDurationMs` would reject as a non-integral ms.\n * Ticks with no usable timescale, and ticks that `resolveGroupDurationMs`\n * would reject, fall back to `groupDurationMs` rather than guessing a clock.\n * That rejection is on the product, not the operand: the count must be a safe\n * integer and `(ticks / timescale) * 1000` must stay within\n * `Number.MAX_SAFE_INTEGER`, which a safe-integer count can still exceed when\n * `timescale < 1000`, so the guard below bounds the count by\n * `timescale * (Number.MAX_SAFE_INTEGER / 1000)` and the reader never throws.\n * The production catalog signals both fields on every rendition and no\n * rendition-level timescale, so the `groupDurationMs` fallback is the path it\n * takes.\n */\nexport function catalogGroupDurationMs(rendition: GroupDurationRendition | undefined): number | undefined {\n\tif (!rendition) return undefined;\n\tconst ticks = numericField(rendition.groupDurationTicks);\n\tconst ms = numericField(rendition.groupDurationMs);\n\tconst timescale =\n\t\ttimescaleHz(rendition.container?.kind === \"mmtp\" ? rendition.container.timescale : undefined) ??\n\t\ttimescaleHz(rendition.timescale);\n\tif (\n\t\tticks !== undefined &&\n\t\ttimescale !== undefined &&\n\t\tNumber.isSafeInteger(ticks) &&\n\t\tticks <= timescale * MAX_SAFE_TICKS_PER_HZ\n\t) {\n\t\treturn resolveGroupDurationMs({ groupDurationTicks: ticks }, timescale);\n\t}\n\treturn ms;\n}\n\n/**\n * The group duration of the catalog rendition an endpoint track was resolved\n * from, by type and name (the same lookup `resolveCatalogTrackFec` performs\n * for FEC geometry). Undefined when the rendition is missing or carries no\n * group duration, so a caller that needs one can fail closed by name.\n */\nexport function resolveCatalogTrackGroupDurationMs(\n\tcatalog: CatalogWithRenditions | undefined,\n\ttrack: CatalogTrackRef,\n): number | undefined {\n\tconst renditions = track.type === \"audio\" ? catalog?.audio?.renditions : catalog?.video?.renditions;\n\treturn catalogGroupDurationMs(renditions?.[track.name]);\n}\n\nexport type KeyframeCadenceCatalog = GroupDurationCatalog & {\n\tkeyframeDurationTicks?: number;\n\tkeyframeDurationMs?: number;\n\tkeyframeIntervalTicks?: number;\n\tkeyframeIntervalMs?: number;\n\tgopDurationTicks?: number;\n\tgopDurationMs?: number;\n};\n\n/**\n * Why a track's catalog `fec` block resolved to no FEC although it is not a\n * plain no-FEC declaration (BLO-35597, draft-ramadan-moq-fec \u00A75.1):\n *\n * - `track_fec_block_profile_unimplemented`: its `blockProfile` is a token\n * this library does not implement. Only `iso-ssbg1-v1` is implemented; the\n * retired `k-padded-v1` lands here.\n * - `track_fec_block_profile`: it declares an FEC instance but names no\n * `blockProfile`, or the value is not a non-empty string.\n * - `track_fec_block_span` (on `interleaveDepthMs`): it names `iso-ssbg1-v1`\n * but declares no block span, which bounds that profile's block location.\n * Valid catalog shape, so no validator reports it.\n * - `track_fec` (on `interleaveDepthMs`): it names `iso-ssbg1-v1` with a\n * declared span that is not a finite number of at least\n * `MIN_INTERLEAVE_DEPTH_MS` milliseconds.\n *\n * The profile selects the block math and is never defaulted or inferred, and\n * the span is never borrowed from another track. Per \u00A75.1 a receiver consumes\n * such a track's source without FEC and does not join its repair tracks, so it\n * carries no FEC geometry and no repair track; it is the only track affected,\n * and neither the session nor any other track's FEC fails on its account.\n */\nexport type TrackFecUnavailable = {\n\t/** The repair-contract code a catalog validator reports for the same field\n\t * (`track_fec_block_span` excepted: an omitted span is valid shape). */\n\treadonly code: \"track_fec_block_profile\" | \"track_fec_block_profile_unimplemented\" | \"track_fec_block_span\" | \"track_fec\";\n\t/** JSON path of the offending field: `blockProfile`, or `interleaveDepthMs`. */\n\treadonly path: string;\n\t/** What is wrong, worded as the `RepairContractError` a validator throws. */\n\treadonly message: string;\n};\n\nexport type TrackFecParams = {\n\tfecK: number;\n\t/** The catalog `fec.blockProfile` (BLO-35597, ISO/IEC 23008-1 Annex C).\n\t * Present exactly when the track has FEC (`fecK > 0`) and absent on no\n\t * FEC. The only profile is `iso-ssbg1-v1` (ssbg_mode1, K' = SSB_length,\n\t * consecutive SS_IDs); it is never defaulted. A declared instance that\n\t * names no implemented profile has no FEC; `fecUnavailable` says why. */\n\tfecBlockProfile?: FecBlockProfile;\n\t/** Present exactly when the track's `fec` block names no implemented\n\t * `blockProfile`, or names `iso-ssbg1-v1` without a usable block span\n\t * (see `TrackFecUnavailable`). The track then resolves to\n\t * no FEC (`fecK` 0, no `fecBlockProfile`, no repair track) and this is\n\t * the reason a caller surfaces. Absent on every other track, with FEC or\n\t * without. */\n\tfecUnavailable?: TrackFecUnavailable;\n\t/** P_0: repair symbols per block on layer 0 (the track-level repairSymbols). */\n\tfecRepairCount: number;\n\t/** Total repair symbols per block across the BASE layers: the wire\n\t * RSB_length on every base layer packet. Equals fecRepairCount on a\n\t * single-layer track. A keyframe overlay is a separate FEC instance with\n\t * its own RSB_length and is not included. */\n\tfecTotalRepairCount: number;\n\tfecSymbolSize: number;\n\tfecInterleaveMs: number;\n\t/** Catalog fec.reorderToleranceMs \u2014 signaled jitter/reorder allowance, ADDED\n\t * to the block span to form a deadline. Deliberately `number | undefined`\n\t * and NOT floored to 0: a declared 0 (\"this path needs no slack\") and an\n\t * absent value (\"the encoder never said\") must stay distinguishable, or a\n\t * consumer that wants to fail loud on absence silently reads a default\n\t * instead \u2014 which is exactly how the magic 3.0 survived. */\n\tfecReorderToleranceMs?: number;\n\trepairTrackName?: string;\n\t/** Every base repair layer in order, layer 0 first, with derived RS_ID\n\t * offsets. Present whenever repairTrackName is. */\n\trepairLayers?: readonly RepairLayerParams[];\n\t/** Keyframe overlay instances declared for this track. Present (possibly\n\t * empty) whenever repairLayers is. */\n\trepairOverlays?: readonly RepairOverlayParams[];\n\tfps?: number;\n\tsampleRate?: number;\n};\n\nconst NO_FEC: TrackFecParams = {\n\tfecK: 0,\n\tfecRepairCount: 0,\n\tfecTotalRepairCount: 0,\n\tfecSymbolSize: 0,\n\tfecInterleaveMs: 0,\n};\n\n/**\n * Repair symbols per block a receiver has actually joined: the sum of\n * `repairSymbols` over the BASE layers whose repair track it joined\n * (draft-ramadan-moq-fec 6.3). This is a symbol count, not a count of layers.\n *\n * A keyframe overlay is a separate FEC instance with its own K, its own\n * RSB_length and its own RS_ID space, so it is never added here however it was\n * joined. Counting it would overstate the base instance's P and misreport\n * CMCD `x-bc-fec-p`.\n *\n * Falls back to P_0 when the track declares no layers, which is the\n * single-layer identity rather than a default: with one layer, P_0 is the\n * total.\n */\nexport function joinedRepairSymbolCount(\n\tparams: Pick<TrackFecParams, \"fecRepairCount\" | \"repairLayers\">,\n\tjoinedRepairTrackNames: Iterable<string>,\n): number {\n\tconst layers = params.repairLayers;\n\tif (layers === undefined || layers.length === 0) return params.fecRepairCount;\n\tconst joined = new Set(joinedRepairTrackNames);\n\tlet total = 0;\n\tfor (const layer of layers) {\n\t\tif (joined.has(layer.repairTrackName)) total += layer.repairSymbols;\n\t}\n\treturn total;\n}\n\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;\n}\n\n/** The MSF `tracks[]` view of a catalog, or undefined for the hang dialect. */\nfunction msfTracks(catalog: RepairLayerCatalog): { tracks: Record<string, unknown>[] } | undefined {\n\tconst raw = (catalog as CatalogWithTracks).tracks;\n\tif (!Array.isArray(raw)) return undefined;\n\treturn { tracks: raw.map((t) => recordOf(t) ?? {}) };\n}\n\n/**\n * Resolve every repair track of one source track: base layers with derived\n * RS_ID offsets and keyframe overlays (draft-ramadan-moq-fec \u00A76.3), from either\n * catalog dialect.\n *\n * - MSF `tracks[]`: the source is the entry named `source`; repair tracks are\n * `packaging: \"fec-repair\"` entries. Layer 0 is the source's\n * `fec.repairTrack`; layers 1..n and overlays `depend` on the source.\n * - hang: the source is a `video`/`audio` rendition; repair tracks beyond\n * layer 0 are `repair.renditions` entries that `depend` on it.\n *\n * Returns undefined when the source carries no FEC repair track and nothing\n * depends on it, and when the source's FEC is unavailable\n * (`catalogFecUnavailable`: no implemented `blockProfile`, the retired\n * `k-padded-v1` included, or an `iso-ssbg1-v1` span it cannot use). Per\n * draft-ramadan-moq-fec \u00A75.1 a receiver joins none of that source's repair\n * tracks, so it gets no layers; the catalog's repair contract is still\n * enforced for it as for every source. Throws `RepairContractError` (code +\n * JSON path) on any violation, including a repair rendition whose `depends`\n * names a rendition without FEC, so a player fails closed before joining any\n * repair track.\n */\nexport function resolveRepairLayers(\n\tcatalog: RepairLayerCatalog | undefined,\n\tsource: string | CatalogTrackRef,\n): RepairLayersResolution | undefined {\n\tif (!catalog) return undefined;\n\tconst sourceName = typeof source === \"string\" ? source : source.name;\n\n\t// hang dialect first. When a catalog carries hang-native rendition blocks\n\t// beside a tracks[] switching-set index (the unified ABR ladder catalog),\n\t// the rendition blocks are authoritative, exactly as they are for hang's own\n\t// catalog decode. A typed ref resolves only within its own role.\n\tconst hang = catalog as CatalogWithRenditions;\n\tconst roles: Array<\"video\" | \"audio\"> =\n\t\ttypeof source === \"string\"\n\t\t\t? [\"video\", \"audio\"]\n\t\t\t: source.type === \"audio\"\n\t\t\t\t? [\"audio\"]\n\t\t\t\t: source.type === \"video\"\n\t\t\t\t\t? [\"video\"]\n\t\t\t\t\t: [\"video\", \"audio\"];\n\tconst role = roles.find((r) => recordOf(hang[r]?.renditions?.[sourceName]) !== undefined);\n\tif (role !== undefined) {\n\t\tconst renditionPath = `$.${role}.renditions[${JSON.stringify(sourceName)}]`;\n\t\tconst rendition = recordOf(hang[role]!.renditions![sourceName])!;\n\t\trejectRepairFieldsOnMediaTrack(renditionPath, rendition, throwRepairContractError);\n\t\tconst repairRenditions = recordOf(hang.repair?.renditions) ?? {};\n\t\tconst descriptors: RepairTrackDescriptor[] = Object.entries(repairRenditions).map(([name, entry]) => {\n\t\t\tconst path = `$.repair.renditions[${JSON.stringify(name)}]`;\n\t\t\tconst fields = recordOf(entry);\n\t\t\tif (fields === undefined) {\n\t\t\t\tthrowRepairContractError(\"track_repair_layer\", path, \"a repair rendition must be an object\");\n\t\t\t}\n\t\t\trejectRepairTrackGeometry(path, fields, throwRepairContractError);\n\t\t\treturn describeRepairTrack(name, path, fields);\n\t\t});\n\t\tconst fecOfRendition = (entry: unknown): Record<string, unknown> | undefined => {\n\t\t\tconst fec = recordOf(recordOf(entry)?.[\"fec\"]);\n\t\t\treturn fec !== undefined && fec[\"algorithm\"] !== \"none\" ? fec : undefined;\n\t\t};\n\t\tconst mediaRenditions = [\n\t\t\t...Object.values(recordOf(hang.video?.renditions) ?? {}),\n\t\t\t...Object.values(recordOf(hang.audio?.renditions) ?? {}),\n\t\t];\n\t\tconst reachability = repairReachability(mediaRenditions.map((entry) => recordOf(entry)?.[\"fec\"]));\n\t\tfor (const d of descriptors) {\n\t\t\tvalidateRepairTrackFields(d, throwRepairContractError);\n\t\t\tassertRepairTrackReachable(d, reachability, throwRepairContractError);\n\t\t\tconst target = dependsTarget(d);\n\t\t\tif (target === undefined) continue;\n\t\t\tconst targetFec =\n\t\t\t\tfecOfRendition(hang.video?.renditions?.[target]) ?? fecOfRendition(hang.audio?.renditions?.[target]);\n\t\t\tif (targetFec === undefined) {\n\t\t\t\tthrowRepairContractError(\n\t\t\t\t\t\"track_repair_depends\",\n\t\t\t\t\t`${d.path}.depends`,\n\t\t\t\t\t`${JSON.stringify(target)} is not a video or audio rendition with FEC`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tconst layers = assembleRepairLayers(\n\t\t\tgeometryOf(sourceName, `${renditionPath}.fec`, recordOf(rendition[\"fec\"])),\n\t\t\tdescriptors,\n\t\t\tthrowRepairContractError,\n\t\t);\n\t\treturn catalogFecUnavailable(rendition[\"fec\"], `${renditionPath}.fec`) === undefined ? layers : undefined;\n\t}\n\n\tconst msf = msfTracks(catalog);\n\tconst msfIndex = msf ? msf.tracks.findIndex((t) => t[\"name\"] === sourceName) : -1;\n\tif (!msf || msfIndex < 0) return undefined;\n\tconst track = msf.tracks[msfIndex]!;\n\tconst fec = recordOf(track[\"fec\"]);\n\tconst packagingOf = (t: Record<string, unknown>): unknown => t[\"packaging\"];\n\tconst fecOfTrack = (t: Record<string, unknown> | undefined): Record<string, unknown> | undefined => {\n\t\tconst trackFec = recordOf(t?.[\"fec\"]);\n\t\treturn trackFec !== undefined && trackFec[\"algorithm\"] !== \"none\" ? trackFec : undefined;\n\t};\n\t// Rule 8 on the resolver path too: a repair track carries no block geometry\n\t// and a media track carries no repair-track fields. Without this a repair\n\t// track with its own `fec` would count as a FEC source below and name\n\t// itself as layer 0, so rule 9 would never see it.\n\tconst descriptors: RepairTrackDescriptor[] = [];\n\tconst mediaTracks: Record<string, unknown>[] = [];\n\tmsf.tracks.forEach((t, i) => {\n\t\tconst path = `$.tracks[${i}]`;\n\t\tif (packagingOf(t) === \"fec-repair\") {\n\t\t\trejectRepairTrackGeometry(path, t, throwRepairContractError);\n\t\t\tif (typeof t[\"name\"] === \"string\") descriptors.push(describeRepairTrack(t[\"name\"], path, t));\n\t\t\treturn;\n\t\t}\n\t\trejectRepairFieldsOnMediaTrack(path, t, throwRepairContractError);\n\t\tmediaTracks.push(t);\n\t});\n\tconst reachability = repairReachability(mediaTracks.map((t) => t[\"fec\"]));\n\tfor (const d of descriptors) {\n\t\tvalidateRepairTrackFields(d, throwRepairContractError);\n\t\tassertRepairTrackReachable(d, reachability, throwRepairContractError);\n\t\tconst target = dependsTarget(d);\n\t\tif (target === undefined) continue;\n\t\tconst targetTrack = msf.tracks.find((t) => t[\"name\"] === target);\n\t\tif (targetTrack === undefined || packagingOf(targetTrack) !== \"mmtp\" || fecOfTrack(targetTrack) === undefined) {\n\t\t\tthrowRepairContractError(\n\t\t\t\t\"track_repair_depends\",\n\t\t\t\t`${d.path}.depends`,\n\t\t\t\t`${JSON.stringify(target)} is not an mmtp track with FEC`,\n\t\t\t);\n\t\t}\n\t}\n\tconst layers = assembleRepairLayers(\n\t\tgeometryOf(sourceName, `$.tracks[${msfIndex}].fec`, fec),\n\t\tdescriptors,\n\t\tthrowRepairContractError,\n\t\t{ layerZeroMustBeDeclared: true },\n\t);\n\treturn catalogFecUnavailable(fec, `$.tracks[${msfIndex}].fec`) === undefined ? layers : undefined;\n}\n\n/** A MoQ Transport priority (0..255, lower first), or null. */\nfunction moqtPriority(value: unknown): number | null {\n\treturn Number.isSafeInteger(value) && (value as number) >= 0 && (value as number) <= 255 ? (value as number) : null;\n}\n\n/**\n * The on-demand repair target of one source track, read from the catalog:\n * its base layer 0 repair track and the two catalog priorities\n * draft-ramadan-moq-fec \u00A711.4.5 derives the FETCH's SUBSCRIBER_PRIORITY from\n * (see `repairFetchSubscriberPriority`). This is what a player's\n * `ResolveRepairTarget` returns once it knows which track owns the block.\n *\n * - `repairPriority`: layer 0's catalog `priority`, or 240 when it declares\n * none; that value is defined by draft-ramadan-moq-fec \u00A710, not chosen\n * here (`resolveRepairLayers` applies it).\n * - `sourcePriority`: the source track's MSF `tracks[]` entry `priority`\n * (MoQ Transport scale). A catalog with no MSF entry for the track (a hang-only catalog,\n * whose renditions carry no MoQ Transport priority) yields null, and the\n * keyframe repair FETCH is then sent without the parameter.\n *\n * Returns null when the track has no repair track, including when its FEC is\n * unavailable (`catalogFecUnavailable`): draft-ramadan-moq-fec \u00A75.1 forbids\n * joining that track's repair tracks, so there is nothing to FETCH.\n *\n * @throws RepairContractError exactly as `resolveRepairLayers` does.\n */\nexport function catalogRepairTarget(\n\tcatalog: RepairLayerCatalog | undefined,\n\tsource: string | CatalogTrackRef,\n): RepairTarget | null {\n\tconst repair = resolveRepairLayers(catalog, source);\n\tconst layerZero = repair?.baseLayers[0];\n\tif (!catalog || !layerZero) return null;\n\tconst sourceName = typeof source === \"string\" ? source : source.name;\n\tconst msf = msfTracks(catalog);\n\tconst entry = msf?.tracks.find((t) => t[\"name\"] === sourceName);\n\tconst sourcePriority =\n\t\tentry === undefined ? null : moqtPriority(entry[\"priority\"]);\n\treturn { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };\n}\n\nfunction geometryOf(name: string, path: string, fec: Record<string, unknown> | undefined): RepairSourceGeometry {\n\treturn {\n\t\tname,\n\t\tpath,\n\t\talgorithm: fec?.[\"algorithm\"],\n\t\tsourceSymbols: fec?.[\"sourceSymbols\"],\n\t\trepairSymbols: fec?.[\"repairSymbols\"],\n\t\trepairTrack: fec?.[\"repairTrack\"],\n\t};\n}\n\nfunction numericField(value: unknown): number | undefined {\n\tconst n = typeof value === \"number\" ? value : typeof value === \"string\" ? Number(value) : Number.NaN;\n\treturn Number.isFinite(n) && n > 0 ? n : undefined;\n}\n\n/** Like `numericField` but admits an explicit 0, and still rejects absent,\n * negative and non-finite input. Absence returns undefined so callers can\n * tell \"declared none\" from \"never declared\". */\nfunction nonNegativeField(value: unknown): number | undefined {\n\tconst n = typeof value === \"number\" ? value : typeof value === \"string\" ? Number(value) : Number.NaN;\n\treturn Number.isFinite(n) && n >= 0 ? n : undefined;\n}\n\n/**\n * The track-scoped reading of `readFecBlockProfile` (BLO-35597): the same one\n * rule every catalog reader applies (a declared FEC instance names the\n * implemented profile; a block declaring none may omit it, but a token it\n * carries must be implemented), with its two failures,\n * `track_fec_block_profile_unimplemented` and `track_fec_block_profile`,\n * returned as the track's reason instead of thrown. A profile problem costs\n * only its own track: it never aborts resolving the catalog's other tracks, and\n * the token is still never defaulted or inferred.\n */\nfunction blockProfileUnavailable(\n\tvalue: unknown,\n\tpath: string,\n\tdeclaresFecInstance: boolean,\n): TrackFecUnavailable | undefined {\n\ttry {\n\t\treadFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);\n\t\treturn undefined;\n\t} catch (error) {\n\t\tif (\n\t\t\t!(error instanceof RepairContractError) ||\n\t\t\t(error.code !== \"track_fec_block_profile\" && error.code !== \"track_fec_block_profile_unimplemented\")\n\t\t) {\n\t\t\tthrow error;\n\t\t}\n\t\treturn Object.freeze({ code: error.code, path: error.path, message: error.message });\n\t}\n}\n\n/**\n * An `iso-ssbg1-v1` instance without a usable block span (`isoBlockSpanUsable`)\n * has no FEC for its track alone, instead of resolving to a configuration the\n * FEC manager refuses for every track it holds. An omitted span\n * (`track_fec_block_span`) and a declared one the floor rejects (`track_fec`)\n * are reported apart, so the reason never names a value the catalog lacks.\n */\nfunction isoSpanUnavailable(\n\tfec: Record<string, unknown>,\n\tpath: string,\n\tdeclaresFecInstance: boolean,\n): TrackFecUnavailable | undefined {\n\tif (!declaresFecInstance || isoBlockSpanUsable(fec)) return undefined;\n\tif (fec[\"interleaveDepthMs\"] === undefined) {\n\t\treturn Object.freeze({\n\t\t\tcode: \"track_fec_block_span\",\n\t\t\tpath,\n\t\t\tmessage:\n\t\t\t\t`[repair-layers:track_fec_block_span] ${path}: is absent; iso-ssbg1-v1 requires a declared ` +\n\t\t\t\t`block span, which bounds its block location`,\n\t\t});\n\t}\n\treturn Object.freeze({\n\t\tcode: \"track_fec\",\n\t\tpath,\n\t\tmessage:\n\t\t\t`[repair-layers:track_fec] ${path}: iso-ssbg1-v1 requires a block span of at least ` +\n\t\t\t`${MIN_INTERLEAVE_DEPTH_MS} ms, which bounds its block location`,\n\t});\n}\n\n/**\n * Whether one catalog `fec` block leaves its track without FEC although it is\n * not a plain no-FEC declaration (draft-ramadan-moq-fec \u00A75.1), and why: the\n * one track-scoped availability rule both catalog dialects apply. The hang\n * resolver (`resolveCatalogTrackFec`, `resolveRepairLayers`) and the MSF\n * `tracks[]` ingest (`MulticastManager.configureMultiTrack`) read it, so they\n * cannot disagree about which tracks keep FEC.\n *\n * Returns the reason (`TrackFecUnavailable`) when the block names no\n * implemented `blockProfile` (the retired `k-padded-v1` included; on\n * `algorithm: \"none\"` only a present token is read) or, for a declared\n * `iso-ssbg1-v1` instance, carries no usable block span. Returns undefined for\n * a non-object and for every block whose FEC is available. It reads only the\n * profile and the span: malformed geometry (`sourceSymbols`, `symbolSize`, ...)\n * is the caller's to reject, and takes precedence. Never throws.\n *\n * @param fec the track's catalog `fec` value.\n * @param fecPath JSON path of that `fec` block; the reason's `path` extends it.\n */\nexport function catalogFecUnavailable(fec: unknown, fecPath: string): TrackFecUnavailable | undefined {\n\tconst block = recordOf(fec);\n\tif (block === undefined) return undefined;\n\tconst declaresFecInstance = block[\"algorithm\"] !== \"none\";\n\treturn (\n\t\tblockProfileUnavailable(block[\"blockProfile\"], `${fecPath}.blockProfile`, declaresFecInstance) ??\n\t\tisoSpanUnavailable(block, `${fecPath}.interleaveDepthMs`, declaresFecInstance)\n\t);\n}\n\n/**\n * Per-track FEC geometry, or `NO_FEC` when the rendition declares none (no\n * `fec`, or `algorithm: \"none\"`) or its FEC is unavailable\n * (`catalogFecUnavailable`: no implemented `blockProfile`, or an\n * `iso-ssbg1-v1` span it cannot use), which carries `fecUnavailable`.\n *\n * @throws RepairContractError from `resolveRepairLayers` when the rendition\n * declares a `fec` block (any algorithm, `none` included) and the catalog's\n * repair contract is malformed, and `track_fec` when its `fec` declares an FEC\n * instance whose `sourceSymbols` or `symbolSize` is not a positive integer;\n * see `resolveCatalogTrackFec`. An absent or unimplemented `blockProfile`, or\n * an `iso-ssbg1-v1` span that is absent or below `MIN_INTERLEAVE_DEPTH_MS`,\n * never throws: it is this track's `fecUnavailable`.\n */\nfunction fecParams(\n\tcatalog: CatalogWithRenditions | undefined,\n\ttrack: CatalogTrackRef | undefined,\n\trendition: RenditionCatalog | undefined,\n\tfps?: number,\n\tsampleRate?: number,\n): TrackFecParams {\n\tconst mediaRate = sampleRate === undefined ? { fps } : { fps, sampleRate };\n\tconst fec = rendition?.fec;\n\t// Every caller passes a track with its rendition.\n\tif (!fec || !track) return { ...NO_FEC, ...mediaRate };\n\t// The repair contract is enforced for every declared `fec` block, including\n\t// one that declares no FEC instance (a repair layer may not depend on it).\n\tconst repair = resolveRepairLayers(catalog, track);\n\tconst fecPath = `$.${track.type}.renditions[${JSON.stringify(track.name)}].fec`;\n\t// One predicate for \"this track has FEC\", shared with resolveFecState: K\n\t// and T are positive integers, the algorithm is not `none`, and the block\n\t// profile is known.\n\tif (!catalogDeclaresFec(fec)) {\n\t\tconst declaresFecInstance = fec.algorithm !== \"none\";\n\t\tconst kValid = Number.isSafeInteger(fec.sourceSymbols) && (fec.sourceSymbols ?? 0) > 0;\n\t\tconst tValid = Number.isSafeInteger(fec.symbolSize) && (fec.symbolSize ?? 0) > 0;\n\t\tif (declaresFecInstance && !(kValid && tValid)) {\n\t\t\t// Any algorithm but `none` declares an FEC instance, so geometry a\n\t\t\t// decoder cannot configure is a malformed catalog, not \"no FEC\".\n\t\t\t// Returning NO_FEC would silently drop the track's repair\n\t\t\t// subscription (repairTrackName, repairLayers, repairOverlays).\n\t\t\tthrowRepairContractError(\n\t\t\t\t\"track_fec\",\n\t\t\t\t`${fecPath}.${kValid ? \"symbolSize\" : \"sourceSymbols\"}`,\n\t\t\t\t\"must be a positive integer\",\n\t\t\t);\n\t\t}\n\t\t// What remains is `algorithm: \"none\"` (which carries no profile; a token\n\t\t// it does carry must still be implemented) or a declared instance whose\n\t\t// FEC is unavailable (\u00A75.1). Neither has FEC; the reason says why, for\n\t\t// this track alone.\n\t\tconst fecUnavailable = catalogFecUnavailable(fec, fecPath);\n\t\treturn fecUnavailable === undefined ? { ...NO_FEC, ...mediaRate } : { ...NO_FEC, ...mediaRate, fecUnavailable };\n\t}\n\t// The predicate held, so the profile is implemented: this read cannot fail.\n\tconst fecBlockProfile = readFecBlockProfile(fec.blockProfile, `${fecPath}.blockProfile`, true, throwRepairContractError);\n\treturn {\n\t\tfecK: fec.sourceSymbols ?? 0,\n\t\tfecBlockProfile,\n\t\tfecRepairCount: fec.repairSymbols ?? 0,\n\t\t// The wire RSB_length: every base layer's packets carry the base total (\u00A77.1).\n\t\tfecTotalRepairCount: repair?.totalBaseRepairSymbols ?? fec.repairSymbols ?? 0,\n\t\tfecSymbolSize: fec.symbolSize ?? 0,\n\t\t// interleaveDepthMs is the FEC block span in MILLISECONDS (\u00A75.1) \u2014 the\n\t\t// only interleave key the catalog carries post-BLO-14526. Receivers\n\t\t// derive D by positive round-half-up of span / GOP duration where needed.\n\t\tfecInterleaveMs: catalogFecInterleaveMs(fec) ?? 0,\n\t\t// Additive, never a multiplier on the span: jitter belongs to the network\n\t\t// path and the span belongs to the encoder, so scaling one by the other\n\t\t// hands one network different slack per track. `numericField` rejects 0,\n\t\t// so it cannot be reused here \u2014 a declared 0 is a legitimate value.\n\t\tfecReorderToleranceMs: nonNegativeField(fec.reorderToleranceMs),\n\t\trepairTrackName: fec.repairTrack,\n\t\trepairLayers: repair?.baseLayers,\n\t\trepairOverlays: repair?.overlays,\n\t\t...mediaRate,\n\t};\n}\n\n/**\n * Resolves per-track FEC geometry from the catalog for the active endpoint tracks.\n *\n * `video`/`audio` (and the top-level names and `catalogFps`) describe the\n * FIRST video and audio track of `tracks`. `forTrack` resolves any track from\n * its OWN rendition, scoped by its type: a second video track (an ABR rung or\n * simulcast variant, possibly sharing a packetId) gets its own framerate,\n * interleave and repair layers, never the first rendition's. A track with no\n * rendition of its type resolves to no FEC (`fecK` 0) and no media rate.\n *\n * The block profile is track-scoped (BLO-35597, draft-ramadan-moq-fec \u00A75.1).\n * `iso-ssbg1-v1` is the only implemented profile. A rendition whose `fec`\n * declares an FEC instance but names no `blockProfile`\n * (`track_fec_block_profile`), or whose `fec` (a `none` block included) names\n * any other token (`track_fec_block_profile_unimplemented`; the retired\n * `k-padded-v1` is one), resolves to no FEC: `fecK` 0, no `fecBlockProfile`,\n * no repair track, layers or overlays, and `fecUnavailable` carrying the\n * reason at the field's JSON path for the caller to surface. Every other track\n * resolves as usual. The profile is never defaulted or inferred;\n * `algorithm: \"none\"` may omit it.\n *\n * @throws RepairContractError `track_fec` when a rendition's `fec` declares an\n * FEC instance (any `algorithm` but `none`) whose `sourceSymbols` or\n * `symbolSize` is not a positive integer, and when a rendition that declares a\n * `fec` block (any algorithm, `none` included) carries a repair contract that\n * violates draft-ramadan-moq-fec 6.3 (a gap or\n * duplicate in the layer chain, a bad `depends` target, an unreachable repair\n * track, a bound violation). Because the hang dialect validates every `repair.renditions`\n * entry of the catalog, resolving the video track fails on a malformed audio\n * repair rendition too. This is deliberate: a player must fail closed before it\n * joins any repair track. Callers that used to treat this function as total\n * must catch it and surface the catalog error.\n */\nexport function resolveCatalogTrackFec(\n\tcatalog: CatalogWithRenditions | undefined,\n\ttracks: readonly CatalogTrackRef[],\n): {\n\tvideoTrackName?: string;\n\taudioTrackName?: string;\n\tmissingVideoRendition?: string;\n\tmissingAudioRendition?: string;\n\tvideoForKeyframeRepair?: KeyframeCadenceCatalog;\n\tcatalogFps?: number;\n\tvideo: TrackFecParams;\n\taudio: TrackFecParams;\n\tforTrack(track: CatalogTrackRef): TrackFecParams;\n} {\n\tconst videoTrackName = tracks.find((t) => t.type === \"video\")?.name;\n\tconst audioTrackName = tracks.find((t) => t.type === \"audio\")?.name;\n\tconst videoRenditions = catalog?.video?.renditions;\n\tconst audioRenditions = catalog?.audio?.renditions;\n\tconst video = videoTrackName ? videoRenditions?.[videoTrackName] : undefined;\n\tconst audio = audioTrackName ? audioRenditions?.[audioTrackName] : undefined;\n\tconst catalogFps = numericField(video?.framerate);\n\tconst sampleRate = numericField(audio?.sampleRate ?? audio?.samplerate);\n\tconst audioFps = sampleRate ? sampleRate / 1024 : undefined;\n\tconst videoParams = fecParams(catalog, videoTrackName ? { name: videoTrackName, type: \"video\" } : undefined, video, catalogFps);\n\tconst audioParams = fecParams(\n\t\tcatalog,\n\t\taudioTrackName ? { name: audioTrackName, type: \"audio\" } : undefined,\n\t\taudio,\n\t\taudioFps,\n\t\tsampleRate,\n\t);\n\n\tconst forTrack = (track: CatalogTrackRef): TrackFecParams => {\n\t\tif (track.type === \"audio\") {\n\t\t\tif (track.name === audioTrackName) return audioParams;\n\t\t\tconst rendition = audioRenditions?.[track.name];\n\t\t\tif (!rendition) return { ...NO_FEC };\n\t\t\tconst rate = numericField(rendition.sampleRate ?? rendition.samplerate);\n\t\t\treturn fecParams(catalog, { name: track.name, type: \"audio\" }, rendition, rate ? rate / 1024 : undefined, rate);\n\t\t}\n\t\tif (track.name === videoTrackName) return videoParams;\n\t\tconst rendition = videoRenditions?.[track.name];\n\t\tif (!rendition) return { ...NO_FEC };\n\t\treturn fecParams(catalog, { name: track.name, type: \"video\" }, rendition, numericField(rendition.framerate));\n\t};\n\n\treturn {\n\t\tvideoTrackName,\n\t\taudioTrackName,\n\t\tmissingVideoRendition: videoTrackName && videoRenditions && !video ? videoTrackName : undefined,\n\t\tmissingAudioRendition: audioTrackName && audioRenditions && !audio ? audioTrackName : undefined,\n\t\tvideoForKeyframeRepair: video,\n\t\tcatalogFps,\n\t\tvideo: videoParams,\n\t\taudio: audioParams,\n\t\tforTrack,\n\t};\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["MMTP_HEADER_SIZE", "MPU_HEADER_SIZE", "MPU_LENGTH_FIELD_SIZE", "MPU_HEADER_REMAINDER_SIZE", "PACKET_TYPE_MPU", "MMTP_HEADER_SIZE", "view", "MPU_HEADER_SIZE", "view", "MPU_HEADER_REMAINDER_SIZE", "MPU_LENGTH_FIELD_SIZE", "MMTP_HEADER_SIZE", "MPU_HEADER_SIZE", "PACKET_TYPE_MPU", "MPU_HEADER_SIZE", "MMTP_HEADER_SIZE", "MPU_HEADER_SIZE", "PACKET_TYPE_MPU", "MPU_HEADER_REMAINDER_SIZE", "MPU_LENGTH_FIELD_SIZE", "extractAltaTrailer", "deriveFecInterleaveDepth", "block", "span", "window", "deriveFecInterleaveDepth", "runKey", "result", "active", "objectKey", "view", "view", "box", "children", "INT32_MIN", "INT32_MAX", "keyframe", "box", "trex", "isU32", "PACKET_TYPE_MPU", "extractAltaTrailer", "resolver", "serial32Precedes", "dedupedAgainstSource", "droppedBeforeSync", "cb", "MoqFecAlgorithm", "MPU_HEADER_SIZE", "MPU_LENGTH_FIELD_SIZE", "MPU_HEADER_REMAINDER_SIZE", "MPU_HEADER_SIZE", "MPU_LENGTH_FIELD_SIZE", "MPU_HEADER_REMAINDER_SIZE", "MPU_HEADER_SIZE", "MMTP_SIGNALING_PACKET_ID", "MMTP_PACKET_ID_MAX", "DEFAULT_AMT_RELAY_PORT", "NTP_SHORT_BITS", "NTP_SHORT_MODULUS", "NTP_SHORT_HALF_RANGE", "FEC_BLOCK_PROFILES", "DEFAULT_AMT_RELAY_PORT", "MMTP_PACKET_ID_MAX", "MMTP_SIGNALING_PACKET_ID", "entry", "DEFAULT_AMT_RELAY_PORT", "packetData", "deriveFecInterleaveDepth", "FecBlockMapper", "recordOf", "recordOf", "deriveFecInterleaveDepth", "FecBlockMapper"]
|
|
7
|
+
}
|