@blockcast/mmt-container 0.1.0-main.0399efef1f07
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +432 -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 +93 -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/browser-runtime.js +20087 -0
- package/dist/browser-runtime.js.map +7 -0
- package/dist/carp-catalog.d.ts +318 -0
- package/dist/carp-catalog.d.ts.map +1 -0
- package/dist/carp-catalog.js +873 -0
- package/dist/carp-catalog.js.map +7 -0
- package/dist/catalog-fec-params.d.ts +202 -0
- package/dist/catalog-fec-params.d.ts.map +1 -0
- package/dist/catalog-fec-params.js +568 -0
- package/dist/catalog-fec-params.js.map +7 -0
- package/dist/catalog-network-source.d.ts +19 -0
- package/dist/catalog-network-source.d.ts.map +1 -0
- package/dist/catalog-network-source.js +22 -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 +217 -0
- package/dist/cmaf-block-assembler.d.ts.map +1 -0
- package/dist/cmaf-block-assembler.js +1769 -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 +195 -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 +207 -0
- package/dist/cmcd-producer.js.map +7 -0
- package/dist/codec-utils.d.ts +248 -0
- package/dist/codec-utils.d.ts.map +1 -0
- package/dist/codec-utils.js +1268 -0
- package/dist/codec-utils.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 +307 -0
- package/dist/fec-manager.d.ts.map +1 -0
- package/dist/fec-manager.js +1530 -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 +75 -0
- package/dist/fec-starvation-detector.js.map +7 -0
- package/dist/fec-worker-setup.d.ts +88 -0
- package/dist/fec-worker-setup.d.ts.map +1 -0
- package/dist/fec-worker-setup.js +614 -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 +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +380 -0
- package/dist/index.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 +618 -0
- package/dist/iso-bmff-timing.js.map +7 -0
- package/dist/loc.d.ts +68 -0
- package/dist/loc.d.ts.map +1 -0
- package/dist/loc.js +143 -0
- package/dist/loc.js.map +7 -0
- 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 +1285 -0
- package/dist/manifest-auth-plan.js.map +7 -0
- package/dist/media-router.d.ts +251 -0
- package/dist/media-router.d.ts.map +1 -0
- package/dist/media-router.js +624 -0
- package/dist/media-router.js.map +7 -0
- package/dist/mfu-reassembler.d.ts +396 -0
- package/dist/mfu-reassembler.d.ts.map +1 -0
- package/dist/mfu-reassembler.js +3085 -0
- package/dist/mfu-reassembler.js.map +7 -0
- 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 +434 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -0
- package/dist/mmtp-container-parser.js +6164 -0
- package/dist/mmtp-container-parser.js.map +7 -0
- package/dist/mmtp-container-parser.no-modules.js +8999 -0
- package/dist/mmtp-container-parser.no-modules.js.map +7 -0
- package/dist/mmtp-decode-pipeline.d.ts +132 -0
- package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
- package/dist/mmtp-decode-pipeline.js +6413 -0
- package/dist/mmtp-decode-pipeline.js.map +7 -0
- package/dist/mmtp-router.d.ts +390 -0
- package/dist/mmtp-router.d.ts.map +1 -0
- package/dist/mmtp-router.js +9242 -0
- package/dist/mmtp-router.js.map +7 -0
- package/dist/mmtp-to-cmaf.d.ts +181 -0
- package/dist/mmtp-to-cmaf.d.ts.map +1 -0
- package/dist/mmtp-to-cmaf.js +802 -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 +107 -0
- package/dist/multicast-input.d.ts.map +1 -0
- package/dist/multicast-input.js +256 -0
- package/dist/multicast-input.js.map +7 -0
- package/dist/multicast-manager.d.ts +369 -0
- package/dist/multicast-manager.d.ts.map +1 -0
- package/dist/multicast-manager.js +13400 -0
- package/dist/multicast-manager.js.map +7 -0
- package/dist/multicast-transport.d.ts +61 -0
- package/dist/multicast-transport.d.ts.map +1 -0
- package/dist/multicast-transport.js +1665 -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 +384 -0
- package/dist/multicast-unwrap.js.map +7 -0
- package/dist/no-modules.d.ts +9 -0
- package/dist/no-modules.d.ts.map +1 -0
- package/dist/no-modules.js +8965 -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 +58 -0
- package/dist/repair-fetch.d.ts.map +1 -0
- package/dist/repair-fetch.js +127 -0
- package/dist/repair-fetch.js.map +7 -0
- package/dist/repair-layers.d.ts +141 -0
- package/dist/repair-layers.d.ts.map +1 -0
- package/dist/repair-layers.js +303 -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 +616 -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 +232 -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 +1238 -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 +85 -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 +456 -0
- package/dist/timestamp-codec.js.map +7 -0
- package/dist/track-pipeline-setup.d.ts +72 -0
- package/dist/track-pipeline-setup.d.ts.map +1 -0
- package/dist/track-pipeline-setup.js +306 -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 +229 -0
- package/dist/track-pipeline.js.map +7 -0
- package/dist/track-session.d.ts +398 -0
- package/dist/track-session.d.ts.map +1 -0
- package/dist/track-session.js +767 -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 +88 -0
- package/dist/transport-pool.js.map +7 -0
- package/dist/wasm-mfu-reassembler.d.ts +92 -0
- package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
- package/dist/wasm-mfu-reassembler.js +2139 -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 +6418 -0
- package/dist/wasm-mmtp-container-parser.js.map +7 -0
- package/node_modules/@blockcast/fec-worker/README.md +213 -0
- package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +87 -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 +279 -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 +252 -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 +565 -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 +349 -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 +179 -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 +47 -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/fec-worker-client.ts +319 -0
- package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +268 -0
- package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +1048 -0
- package/node_modules/@blockcast/fec-worker/src/index.ts +32 -0
- package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +558 -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 +194 -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 +120 -0
- package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
- package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +276 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js +645 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/package.json +49 -0
- package/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
- package/node_modules/@blockcast/mmt-base/src/mmtp.ts +899 -0
- package/node_modules/@blockcast/mmt-fec/README.md +270 -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 +72 -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 +192 -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 +185 -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 +273 -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 +22 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.js +22 -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 +172 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.js +254 -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/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 +120 -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/index.d.ts +2 -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 +2 -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 +276 -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 +645 -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/package.json +49 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +899 -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 +277 -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 +332 -0
- package/node_modules/@blockcast/mmt-fec/src/index.ts +77 -0
- package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +34 -0
- package/node_modules/@blockcast/mmt-fec/src/oti.ts +317 -0
- package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +101 -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 +203 -0
- package/schemas/catalog.schema.json +360 -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/carp-catalog.ts +845 -0
- package/src/catalog-fec-params.ts +535 -0
- package/src/catalog-network-source.ts +49 -0
- package/src/catalog-validator.ts +34 -0
- package/src/cmaf-block-assembler.ts +1167 -0
- package/src/cmaf-group-batcher.ts +104 -0
- package/src/cmcd-producer.ts +334 -0
- package/src/codec-utils.ts +1611 -0
- package/src/fec-codec.ts +61 -0
- package/src/fec-manager.ts +2081 -0
- package/src/fec-starvation-detector.ts +128 -0
- package/src/fec-worker-setup.ts +213 -0
- package/src/fmp4-completion.ts +47 -0
- package/src/fmp4-reader.ts +112 -0
- package/src/index.ts +486 -0
- package/src/iso-bmff-timing.ts +813 -0
- package/src/loc.ts +181 -0
- package/src/manifest-auth-plan.ts +107 -0
- package/src/media-router.ts +795 -0
- package/src/mfu-reassembler.ts +3741 -0
- package/src/mfu-reassembly-policy.ts +134 -0
- package/src/mmtp-container-parser.ts +1836 -0
- package/src/mmtp-decode-pipeline.ts +389 -0
- package/src/mmtp-router.ts +3136 -0
- package/src/mmtp-to-cmaf.ts +1024 -0
- package/src/multicast-auth-catalog.ts +340 -0
- package/src/multicast-input.ts +439 -0
- package/src/multicast-manager.ts +1720 -0
- package/src/multicast-transport.ts +321 -0
- package/src/multicast-unwrap.ts +132 -0
- package/src/no-modules.ts +20 -0
- package/src/phase14-catalog-override.ts +336 -0
- package/src/repair-fetch.ts +238 -0
- package/src/repair-layers.ts +490 -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 +189 -0
- package/src/track-pipeline.ts +332 -0
- package/src/track-session.ts +994 -0
- package/src/transport-pool.ts +128 -0
- package/src/wasm-mfu-reassembler.ts +389 -0
- package/src/wasm-mmtp-container-parser.ts +23 -0
- package/test-vectors/align.json +199 -0
- package/test-vectors/catalog/README.md +40 -0
- package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +27 -0
- package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +32 -0
- package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +30 -0
- package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +31 -0
- package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +33 -0
- package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +23 -0
- package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +32 -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/network-source-object.json +25 -0
- package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +23 -0
- package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +26 -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/fec-layered-repair.json +104 -0
- package/test-vectors/catalog/positive/fec-multicast.json +51 -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/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 +148 -0
- package/test-vectors/catalog-validator-public-api.mjs +25 -0
- package/test-vectors/generate-source-auth-fixture.mjs +114 -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
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MediaRouter — source arbitration for MoQ ↔ multicast switching.
|
|
3
|
+
*
|
|
4
|
+
* Both moq-lib and moqtail wire their MoQ and multicast frame outputs
|
|
5
|
+
* through this router. The router decides which source reaches the decoder:
|
|
6
|
+
*
|
|
7
|
+
* - MoQ frames pass through while multicast is inactive.
|
|
8
|
+
* - Multicast video frames are suppressed until the first keyframe.
|
|
9
|
+
* - Once multicast has both a video keyframe and audio, takeover is requested.
|
|
10
|
+
* - MoQ is suppressed only after the source-switch callback completes.
|
|
11
|
+
* - failbackToMoq() reverses the switch immediately.
|
|
12
|
+
*
|
|
13
|
+
* Per-track setTrackSource() allows individual tracks to be wired to a
|
|
14
|
+
* specific transport source (video=multicast, audio=moq, overlay=amt) without
|
|
15
|
+
* changing the binary MoQ↔multicast switching for other tracks.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { computeFecDeliveryWindowMs } from './track-session.js'
|
|
19
|
+
|
|
20
|
+
export type FrameMeta = {
|
|
21
|
+
timestamp: number
|
|
22
|
+
keyframe: boolean
|
|
23
|
+
packetId?: number
|
|
24
|
+
packetSequenceNumber?: number
|
|
25
|
+
source?: 'moq' | 'multicast' | 'repair'
|
|
26
|
+
recovered?: boolean
|
|
27
|
+
sourceDiscontinuity?: boolean
|
|
28
|
+
/**
|
|
29
|
+
* Raw MMTP header timestamp (RFC 5905 short NTP, 32 bits): the publisher's
|
|
30
|
+
* send-wall clock for the packet that carried this frame. On timed media it
|
|
31
|
+
* is distinct from `timestamp`, the authored media presentation time. On a
|
|
32
|
+
* non-timed MFU there is no authored presentation time, so `MmtpRouter`
|
|
33
|
+
* derives `timestamp` from this same clock and the two carry no latency
|
|
34
|
+
* between them. Populated from `RoutedFrameMeta.mmtpTimestamp` and
|
|
35
|
+
* preserved by every re-emit on this router (multicast, MoQ, and repair) so
|
|
36
|
+
* players can measure network ingress on the multicast legs; absent when
|
|
37
|
+
* the packet header could not be read.
|
|
38
|
+
*/
|
|
39
|
+
mmtpTimestamp?: bigint
|
|
40
|
+
}
|
|
41
|
+
export type FrameKind = 'video' | 'audio'
|
|
42
|
+
export type SourceKind = 'moq' | 'multicast'
|
|
43
|
+
|
|
44
|
+
type FrameCb = (data: Uint8Array, meta: FrameMeta) => void
|
|
45
|
+
type SwitchCb = (source: SourceKind) => void | Promise<void>
|
|
46
|
+
|
|
47
|
+
export interface MediaRouterOptions {
|
|
48
|
+
/** Set false for video-only streams. Default: true. */
|
|
49
|
+
requireAudio?: boolean
|
|
50
|
+
/**
|
|
51
|
+
* Audio fallback timeout (ms): force multicast activation if video keyframe
|
|
52
|
+
* arrived but audio hasn't within this window. Catalog-derived via
|
|
53
|
+
* computeFecDeliveryWindowMs (the single FEC recovery-window source of truth).
|
|
54
|
+
* Omit to disable the independent audio-fallback timer (no magic default).
|
|
55
|
+
*/
|
|
56
|
+
audioFallbackMs?: number
|
|
57
|
+
/**
|
|
58
|
+
* Number of binary-path frames to allow through during a crossfade transition.
|
|
59
|
+
* Derive from FEC block size (e.g., ceil(K / groupsPerBlock)). Default: 10.
|
|
60
|
+
*/
|
|
61
|
+
crossfadeFrames?: number
|
|
62
|
+
/**
|
|
63
|
+
* Max pre-boundary overlay frames to buffer before dropping.
|
|
64
|
+
* Derive from FEC config (e.g., ceil(K * 1.5)). Default: 128.
|
|
65
|
+
*/
|
|
66
|
+
maxOverlayBuffer?: number
|
|
67
|
+
/**
|
|
68
|
+
* Max concurrent in-flight repairs. Prevents relay SUBSCRIBE flood (T-13-01 mitigation).
|
|
69
|
+
* Default: 2.
|
|
70
|
+
*/
|
|
71
|
+
maxRepairInFlight?: number
|
|
72
|
+
/**
|
|
73
|
+
* Allow multicast takeover from a delta frame after the player has confirmed
|
|
74
|
+
* that MoQ already seeded the same decoder timeline with a video keyframe.
|
|
75
|
+
* Default false: decoder-reset paths still need multicast to provide a RAP.
|
|
76
|
+
*/
|
|
77
|
+
allowDeltaMulticastTakeoverAfterMoqKeyframe?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A per-track source that emits frames independently of the binary MoQ/multicast path.
|
|
82
|
+
* Used by setTrackSource() to wire specific tracks to specific transport sources.
|
|
83
|
+
*/
|
|
84
|
+
export interface TrackSource {
|
|
85
|
+
name: string
|
|
86
|
+
/**
|
|
87
|
+
* Register a frame callback. Returns an unsubscribe function.
|
|
88
|
+
* MediaRouter calls unsubscribe() when clearTrackSource() is called.
|
|
89
|
+
*/
|
|
90
|
+
onFrame(cb: (data: Uint8Array, meta: FrameMeta) => void): () => void
|
|
91
|
+
/**
|
|
92
|
+
* Optional error callback for when the source fails.
|
|
93
|
+
* MediaRouter uses this to trigger failback to binary switching for this track.
|
|
94
|
+
*/
|
|
95
|
+
onError?(cb: () => void): () => void
|
|
96
|
+
/** Optional dispose hook called when clearTrackSource() is called. */
|
|
97
|
+
dispose?(): void
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Thrown when setTrackSource() is called for a track that already has a source
|
|
102
|
+
* without an intervening clearTrackSource(). Prevents silent concurrent-assignment
|
|
103
|
+
* races (T-02-16 mitigation).
|
|
104
|
+
*/
|
|
105
|
+
export class TrackSourceConflictError extends Error {
|
|
106
|
+
constructor(public readonly trackName: string) {
|
|
107
|
+
super(`Track source already assigned for '${trackName}'; call clearTrackSource first`)
|
|
108
|
+
this.name = 'TrackSourceConflictError'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Migration policy for setTrackSource() transport switching.
|
|
114
|
+
*
|
|
115
|
+
* - 'crossfade': Overlap old (binary) and new source briefly (CROSSFADE_WINDOW_FRAMES),
|
|
116
|
+
* then suppress the binary path. Default for audio.
|
|
117
|
+
* - 'drop': Cut to new source immediately. Binary path suppressed at once.
|
|
118
|
+
* Suitable for latency-critical tracks.
|
|
119
|
+
* - 'wait': Defer migration until first FEC block boundary (same as overlay=true).
|
|
120
|
+
* Default for video — avoids decoder reset mid-block.
|
|
121
|
+
*/
|
|
122
|
+
export type MigrationPolicy = 'crossfade' | 'drop' | 'wait'
|
|
123
|
+
|
|
124
|
+
/** Default crossfade window — override via MediaRouterOptions.crossfadeFrames. */
|
|
125
|
+
const DEFAULT_CROSSFADE_FRAMES = 10
|
|
126
|
+
|
|
127
|
+
/** Options passed to setTrackSource(). */
|
|
128
|
+
export interface SetTrackSourceOptions {
|
|
129
|
+
/**
|
|
130
|
+
* If true, this source is an overlay track that must wait for the first FEC
|
|
131
|
+
* block boundary signal (via notifyFecBlockBoundary()) before emitting frames.
|
|
132
|
+
* Frames arriving before the first boundary are buffered up to MAX_OVERLAY_BUFFER.
|
|
133
|
+
*
|
|
134
|
+
* Backwards compat: equivalent to policy: 'wait'.
|
|
135
|
+
*/
|
|
136
|
+
overlay?: boolean
|
|
137
|
+
/**
|
|
138
|
+
* Migration policy for the transport switch. Overrides `overlay` when provided.
|
|
139
|
+
* See MigrationPolicy for semantics.
|
|
140
|
+
*/
|
|
141
|
+
policy?: MigrationPolicy
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Default overlay buffer cap — override via MediaRouterOptions.maxOverlayBuffer. */
|
|
145
|
+
const DEFAULT_MAX_OVERLAY_BUFFER = 128
|
|
146
|
+
|
|
147
|
+
/** Internal per-track source record. */
|
|
148
|
+
interface TrackSourceRecord {
|
|
149
|
+
source: TrackSource
|
|
150
|
+
unsubscribeFrame: () => void
|
|
151
|
+
unsubscribeError?: () => void
|
|
152
|
+
emitCount: number
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export class MediaRouter {
|
|
156
|
+
#active: SourceKind = 'moq'
|
|
157
|
+
#multicastVideoKeyed = false
|
|
158
|
+
#multicastAudioReceived = false
|
|
159
|
+
#moqVideoKeyframeEmitted = false
|
|
160
|
+
#requireAudio: boolean
|
|
161
|
+
#suppressed = false // true after multicast confirmed — suppresses MoQ
|
|
162
|
+
#audioFallbackTimer: ReturnType<typeof setTimeout> | null = null
|
|
163
|
+
|
|
164
|
+
// Configurable constants (from catalog/FEC config, not magic numbers)
|
|
165
|
+
readonly #audioFallbackMs: number
|
|
166
|
+
readonly #crossfadeFrames: number
|
|
167
|
+
readonly #maxOverlayBuffer: number
|
|
168
|
+
readonly #allowDeltaMulticastTakeoverAfterMoqKeyframe: boolean
|
|
169
|
+
|
|
170
|
+
// ── Keyframe repair state ────────────────────────────────────────
|
|
171
|
+
/** Groups currently being repaired — idempotent guard per Pitfall 2 (T-13-01 mitigation) */
|
|
172
|
+
#repairInFlight = new Set<number>()
|
|
173
|
+
/** Max concurrent repairs — prevents relay SUBSCRIBE flood (T-13-01 mitigation) */
|
|
174
|
+
readonly #maxRepairInFlight: number
|
|
175
|
+
/** Callback to request MoQ subscribe for a specific group (wired by player) */
|
|
176
|
+
#onRepairRequest: ((expectedGroupId: number, trackName: string) => void) | null = null
|
|
177
|
+
|
|
178
|
+
#onVideoFrame: FrameCb | null = null
|
|
179
|
+
#onAudioFrame: FrameCb | null = null
|
|
180
|
+
#onOverlayFrame: FrameCb | null = null
|
|
181
|
+
#onSourceSwitch: SwitchCb | null = null
|
|
182
|
+
#sourceSwitchReady: Promise<void> | null = null
|
|
183
|
+
#sourceSwitchToken = 0
|
|
184
|
+
#pendingMulticastToken: number | null = null
|
|
185
|
+
#pendingMulticastVideo: { data: Uint8Array; meta: FrameMeta } | null = null
|
|
186
|
+
#pendingMulticastAudio: { data: Uint8Array; meta: FrameMeta } | null = null
|
|
187
|
+
|
|
188
|
+
// Per-track source state
|
|
189
|
+
#trackSources = new Map<string, TrackSourceRecord>()
|
|
190
|
+
|
|
191
|
+
// Track type registry: trackName → { type, source }.
|
|
192
|
+
// Init data / inline signaling is authoritative until catalog updates.
|
|
193
|
+
// Priority: 'init' > 'catalog' > 'name' (backward compat fallback).
|
|
194
|
+
#trackTypes = new Map<string, { type: FrameKind; source: 'catalog' | 'init' }>()
|
|
195
|
+
|
|
196
|
+
// Crossfade state: maps trackName → remaining binary-path frames allowed during window
|
|
197
|
+
#crossfadeCounters = new Map<string, number>()
|
|
198
|
+
|
|
199
|
+
// Overlay alignment state
|
|
200
|
+
#overlayBoundaryReached = false
|
|
201
|
+
#overlayBuffer: Array<{ data: Uint8Array; meta: FrameMeta }> = []
|
|
202
|
+
|
|
203
|
+
// ── Phase 14: deltaActive state machine (D-11..D-14) ─────────────
|
|
204
|
+
// No timers — state transitions driven entirely by notifyDeltaBlock() callbacks.
|
|
205
|
+
// All thresholds derived from catalog fields or throw at configureDelta() time.
|
|
206
|
+
#deltaConfigured = false
|
|
207
|
+
#deltaActive: boolean = true
|
|
208
|
+
#deltaWindow: boolean[] = [] // ring buffer: true = block failed
|
|
209
|
+
#deltaWindowSize: number = 0
|
|
210
|
+
#deltaFailThresholdRatio: number = 0 // P/K
|
|
211
|
+
#deltaRecoverThresholdRatio: number = 0 // (P/K)/2 — hysteresis (D-13)
|
|
212
|
+
#deltaProbeIntervalMs: number = 0
|
|
213
|
+
#lastProbeAt: number = 0
|
|
214
|
+
#onDeltaFallbackCb: (() => void) | null = null
|
|
215
|
+
#onDeltaRecoverCb: (() => void) | null = null
|
|
216
|
+
|
|
217
|
+
constructor(options?: MediaRouterOptions) {
|
|
218
|
+
this.#requireAudio = options?.requireAudio ?? true
|
|
219
|
+
// Catalog-derived — the caller passes an audioFallbackMs computed from the
|
|
220
|
+
// FEC recovery window (computeFecDeliveryWindowMs). No magic default: when
|
|
221
|
+
// absent, the independent audio-fallback timer is disabled (0) and
|
|
222
|
+
// activation waits for real audio arrival.
|
|
223
|
+
this.#audioFallbackMs = options?.audioFallbackMs ?? 0
|
|
224
|
+
this.#crossfadeFrames = options?.crossfadeFrames ?? DEFAULT_CROSSFADE_FRAMES
|
|
225
|
+
this.#maxOverlayBuffer = options?.maxOverlayBuffer ?? DEFAULT_MAX_OVERLAY_BUFFER
|
|
226
|
+
this.#maxRepairInFlight = options?.maxRepairInFlight ?? 2
|
|
227
|
+
this.#allowDeltaMulticastTakeoverAfterMoqKeyframe =
|
|
228
|
+
options?.allowDeltaMulticastTakeoverAfterMoqKeyframe ?? false
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
onVideoFrame(cb: FrameCb) { this.#onVideoFrame = cb }
|
|
232
|
+
onAudioFrame(cb: FrameCb) { this.#onAudioFrame = cb }
|
|
233
|
+
onOverlayFrame(cb: FrameCb) { this.#onOverlayFrame = cb }
|
|
234
|
+
onSourceSwitch(cb: SwitchCb) { this.#onSourceSwitch = cb }
|
|
235
|
+
|
|
236
|
+
get activeSource(): SourceKind { return this.#active }
|
|
237
|
+
|
|
238
|
+
// ── Keyframe repair API ──────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
/** Register callback for keyframe repair requests. Player wires this to issue MoQ SUBSCRIBE. */
|
|
241
|
+
onRepairRequest(cb: (expectedGroupId: number, trackName: string) => void): void {
|
|
242
|
+
this.#onRepairRequest = cb
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Request repair for a lost keyframe group. Only fires when activeSource is 'multicast'.
|
|
247
|
+
* Idempotent per groupId — duplicate calls for the same group are suppressed.
|
|
248
|
+
* @param expectedGroupId - The group ID expected but not received
|
|
249
|
+
* @param trackName - Track name for the SUBSCRIBE (e.g. 'video'). Default: 'video'.
|
|
250
|
+
*/
|
|
251
|
+
repairLostGroup(expectedGroupId: number, trackName = 'video'): void {
|
|
252
|
+
if (this.#active !== 'multicast') return // only repair on multicast path
|
|
253
|
+
if (this.#repairInFlight.has(expectedGroupId)) return // idempotent
|
|
254
|
+
if (this.#repairInFlight.size >= this.#maxRepairInFlight) {
|
|
255
|
+
console.warn(
|
|
256
|
+
`[MediaRouter] max repair in flight (${this.#maxRepairInFlight}), dropping repair for group ${expectedGroupId}`,
|
|
257
|
+
)
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
this.#repairInFlight.add(expectedGroupId)
|
|
261
|
+
this.#onRepairRequest?.(expectedGroupId, trackName)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Feed a recovered keyframe frame from the repair path.
|
|
266
|
+
* Bypasses source arbitration entirely — goes directly to decoder callbacks.
|
|
267
|
+
* Does NOT change activeSource. Does NOT trigger failbackToMoq.
|
|
268
|
+
*/
|
|
269
|
+
feedRepair(data: Uint8Array, meta: FrameMeta, kind: FrameKind): void {
|
|
270
|
+
// Repair only makes sense when multicast is active.
|
|
271
|
+
// Silently drop if MoQ became active (race with failbackToMoq) to avoid
|
|
272
|
+
// injecting stale-timestamp frames into the MoQ decoder path (WR-02).
|
|
273
|
+
if (this.#active !== 'multicast') return
|
|
274
|
+
const routedMeta = { ...meta, source: 'repair' as const, recovered: true }
|
|
275
|
+
if (kind === 'video') this.#onVideoFrame?.(data, routedMeta)
|
|
276
|
+
else this.#onAudioFrame?.(data, routedMeta)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Clear repair-in-flight state for a group (called after repair completes or times out).
|
|
281
|
+
*/
|
|
282
|
+
clearRepairInFlight(groupId: number): void {
|
|
283
|
+
this.#repairInFlight.delete(groupId)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ── Phase 14: deltaActive state machine API ──────────────────────
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Configure the delta-track fallback state machine from catalog parameters.
|
|
290
|
+
*
|
|
291
|
+
* D-11: Thresholds derive from fec.sourceSymbols, fec.repairSymbols, framerate.
|
|
292
|
+
* D-13: Probe interval derives from deltaProbeIntervalMs or interleaveDepthMs*2.
|
|
293
|
+
* CLAUDE.md: error on bad init — throws if required inputs are absent.
|
|
294
|
+
*
|
|
295
|
+
* Calling again resets deltaActive=true and clears the window buffer (stream restart).
|
|
296
|
+
*/
|
|
297
|
+
configureDelta(cfg: {
|
|
298
|
+
framerate: number
|
|
299
|
+
sourceSymbols: number
|
|
300
|
+
repairSymbols: number
|
|
301
|
+
deltaFailWindowBlocks?: number
|
|
302
|
+
deltaProbeIntervalMs?: number
|
|
303
|
+
interleaveDepthMs?: number
|
|
304
|
+
}): void {
|
|
305
|
+
if (typeof cfg.framerate !== 'number' || !Number.isFinite(cfg.framerate) || cfg.framerate <= 0) {
|
|
306
|
+
throw new Error('[MediaRouter] configureDelta: framerate required and must be > 0')
|
|
307
|
+
}
|
|
308
|
+
if (typeof cfg.sourceSymbols !== 'number' || cfg.sourceSymbols <= 0) {
|
|
309
|
+
throw new Error('[MediaRouter] configureDelta: sourceSymbols (K_delta) required and must be > 0')
|
|
310
|
+
}
|
|
311
|
+
if (typeof cfg.repairSymbols !== 'number' || cfg.repairSymbols <= 0) {
|
|
312
|
+
throw new Error('[MediaRouter] configureDelta: repairSymbols (P_delta) required and must be > 0')
|
|
313
|
+
}
|
|
314
|
+
if (cfg.deltaProbeIntervalMs === undefined && cfg.interleaveDepthMs === undefined) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
'[MediaRouter] configureDelta: either deltaProbeIntervalMs or interleaveDepthMs required for probe interval derivation'
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Derivations — no magic floors, no hardcoded defaults (CLAUDE.md).
|
|
321
|
+
this.#deltaWindowSize = cfg.deltaFailWindowBlocks ?? Math.ceil(cfg.framerate)
|
|
322
|
+
this.#deltaFailThresholdRatio = cfg.repairSymbols / cfg.sourceSymbols
|
|
323
|
+
this.#deltaRecoverThresholdRatio = this.#deltaFailThresholdRatio / 2
|
|
324
|
+
// Without an explicit probe interval, use one complete signaled block span.
|
|
325
|
+
this.#deltaProbeIntervalMs = cfg.deltaProbeIntervalMs ?? computeFecDeliveryWindowMs({
|
|
326
|
+
interleaveDepthMs: cfg.interleaveDepthMs!,
|
|
327
|
+
sourceSymbols: cfg.sourceSymbols,
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
// Reset state — useful for stream restart.
|
|
331
|
+
this.#deltaActive = true
|
|
332
|
+
this.#deltaWindow = []
|
|
333
|
+
this.#lastProbeAt = 0
|
|
334
|
+
this.#deltaConfigured = true
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Notify the state machine of a completed FEC block for the delta track.
|
|
339
|
+
*
|
|
340
|
+
* @param failed - true if the block was unrecoverable (FEC failure)
|
|
341
|
+
* @param nowMs - caller-injected monotonic ms (no internal timer — D-13, CLAUDE.md)
|
|
342
|
+
*
|
|
343
|
+
* If configureDelta() has not been called, this is a silent no-op (pre-catalog state).
|
|
344
|
+
*/
|
|
345
|
+
notifyDeltaBlock(failed: boolean, nowMs: number): void {
|
|
346
|
+
if (!this.#deltaConfigured) return
|
|
347
|
+
|
|
348
|
+
// Maintain sliding window — ring buffer semantics.
|
|
349
|
+
this.#deltaWindow.push(failed)
|
|
350
|
+
if (this.#deltaWindow.length > this.#deltaWindowSize) {
|
|
351
|
+
this.#deltaWindow.shift()
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Window not yet full — insufficient data for decision.
|
|
355
|
+
if (this.#deltaWindow.length < this.#deltaWindowSize) return
|
|
356
|
+
|
|
357
|
+
const failedCount = this.#deltaWindow.filter(x => x).length
|
|
358
|
+
const ratio = failedCount / this.#deltaWindowSize
|
|
359
|
+
|
|
360
|
+
if (this.#deltaActive) {
|
|
361
|
+
// Fallback transition: active → inactive (T-14-03: fires exactly once)
|
|
362
|
+
if (ratio > this.#deltaFailThresholdRatio) {
|
|
363
|
+
this.#deltaActive = false
|
|
364
|
+
this.#lastProbeAt = nowMs
|
|
365
|
+
this.#onDeltaFallbackCb?.()
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
// Recovery transition: inactive → active (after probe interval + low ratio)
|
|
369
|
+
if (
|
|
370
|
+
(nowMs - this.#lastProbeAt) >= this.#deltaProbeIntervalMs &&
|
|
371
|
+
ratio < this.#deltaRecoverThresholdRatio
|
|
372
|
+
) {
|
|
373
|
+
this.#deltaActive = true
|
|
374
|
+
this.#lastProbeAt = nowMs
|
|
375
|
+
this.#onDeltaRecoverCb?.()
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Register callback fired when delta track degrades past threshold (D-12). */
|
|
381
|
+
onDeltaFallback(cb: () => void): void {
|
|
382
|
+
this.#onDeltaFallbackCb = cb
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Register callback fired when delta track recovers below recovery threshold (D-13). */
|
|
386
|
+
onDeltaRecover(cb: () => void): void {
|
|
387
|
+
this.#onDeltaRecoverCb = cb
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Whether the delta track is currently considered healthy (true = active). */
|
|
391
|
+
get deltaActive(): boolean {
|
|
392
|
+
return this.#deltaActive
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ── Test-visible accessors for threshold verification ────────────
|
|
396
|
+
// These expose internal derived values for test assertions (Tests 3,4,5,6,7).
|
|
397
|
+
// Prefixed with 'delta' to namespace clearly within the public API surface.
|
|
398
|
+
|
|
399
|
+
/** Derived window size — exposed for test assertions (Test 3, 4). */
|
|
400
|
+
get deltaWindowSize(): number { return this.#deltaWindowSize }
|
|
401
|
+
/** Derived fail threshold ratio (P/K) — exposed for test assertions (Test 6). */
|
|
402
|
+
get deltaFailThresholdRatio(): number { return this.#deltaFailThresholdRatio }
|
|
403
|
+
/** Derived recovery threshold ratio ((P/K)/2) — exposed for test assertions (Test 7). */
|
|
404
|
+
get deltaRecoverThresholdRatio(): number { return this.#deltaRecoverThresholdRatio }
|
|
405
|
+
/** Derived probe interval ms — exposed for test assertions (Test 5a). */
|
|
406
|
+
get deltaProbeIntervalMs(): number { return this.#deltaProbeIntervalMs }
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Register a track name → type mapping from catalog.
|
|
410
|
+
* Catalog is the default authority. Init data / inline signaling
|
|
411
|
+
* takes precedence until the next catalog update.
|
|
412
|
+
*/
|
|
413
|
+
registerTrackType(trackName: string, type: FrameKind): void {
|
|
414
|
+
// Catalog overwrites any previous registration (including init).
|
|
415
|
+
this.#trackTypes.set(trackName, { type, source: 'catalog' })
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Register a track name → type from init data / inline signaling (MMTP FT=0).
|
|
420
|
+
* Authoritative until the next catalog update per §5.3.
|
|
421
|
+
* Does NOT overwrite an existing catalog registration.
|
|
422
|
+
*/
|
|
423
|
+
registerTrackTypeFromInit(trackName: string, type: FrameKind): void {
|
|
424
|
+
const existing = this.#trackTypes.get(trackName)
|
|
425
|
+
if (existing?.source === 'catalog') return // catalog already set — don't downgrade
|
|
426
|
+
this.#trackTypes.set(trackName, { type, source: 'init' })
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Assign a per-track source. Frames from this source bypass the binary
|
|
431
|
+
* MoQ↔multicast switching for this track only.
|
|
432
|
+
*
|
|
433
|
+
* Throws TrackSourceConflictError if a source is already assigned for this
|
|
434
|
+
* track without a clearTrackSource() in between.
|
|
435
|
+
*/
|
|
436
|
+
setTrackSource(trackName: string, source: TrackSource, opts?: SetTrackSourceOptions): void {
|
|
437
|
+
if (this.#trackSources.has(trackName)) {
|
|
438
|
+
throw new TrackSourceConflictError(trackName)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Resolve effective policy. opts.policy takes precedence; overlay=true is legacy 'wait'.
|
|
442
|
+
const effectivePolicy: MigrationPolicy | undefined =
|
|
443
|
+
opts?.policy ?? (opts?.overlay ? 'wait' : undefined)
|
|
444
|
+
|
|
445
|
+
// For crossfade: open the binary-path window immediately so the first per-track frame
|
|
446
|
+
// triggers the window (counter starts before the frame callback is wired).
|
|
447
|
+
if (effectivePolicy === 'crossfade') {
|
|
448
|
+
this.#crossfadeCounters.set(trackName, this.#crossfadeFrames)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const record: TrackSourceRecord = {
|
|
452
|
+
source,
|
|
453
|
+
unsubscribeFrame: () => {},
|
|
454
|
+
emitCount: 0,
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Wire the frame callback
|
|
458
|
+
record.unsubscribeFrame = source.onFrame((data, meta) => {
|
|
459
|
+
record.emitCount++
|
|
460
|
+
if (effectivePolicy === 'wait') {
|
|
461
|
+
this.#handleOverlayFrame(data, meta)
|
|
462
|
+
} else {
|
|
463
|
+
// 'drop', 'crossfade', or undefined: emit immediately
|
|
464
|
+
this.#emitForTrack(trackName, data, meta)
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
// Wire the optional error callback — failback to binary switching
|
|
469
|
+
if (source.onError) {
|
|
470
|
+
record.unsubscribeError = source.onError(() => {
|
|
471
|
+
this.clearTrackSource(trackName)
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
this.#trackSources.set(trackName, record)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Remove the per-track source for the given track.
|
|
480
|
+
* Binary switching resumes for this track after this call.
|
|
481
|
+
*/
|
|
482
|
+
clearTrackSource(trackName: string): void {
|
|
483
|
+
const record = this.#trackSources.get(trackName)
|
|
484
|
+
if (!record) return
|
|
485
|
+
|
|
486
|
+
record.unsubscribeFrame()
|
|
487
|
+
record.unsubscribeError?.()
|
|
488
|
+
record.source.dispose?.()
|
|
489
|
+
this.#trackSources.delete(trackName)
|
|
490
|
+
|
|
491
|
+
// Clean up crossfade window if one was active for this track
|
|
492
|
+
this.#crossfadeCounters.delete(trackName)
|
|
493
|
+
|
|
494
|
+
// Reset overlay buffer if removing a non-media track (overlay/unknown)
|
|
495
|
+
const reg = this.#trackTypes.get(trackName)
|
|
496
|
+
const isMedia = reg?.type === 'video' || reg?.type === 'audio' ||
|
|
497
|
+
(reg === undefined && (trackName === 'video' || trackName === 'audio'))
|
|
498
|
+
if (!isMedia) {
|
|
499
|
+
this.#overlayBoundaryReached = false
|
|
500
|
+
this.#overlayBuffer = []
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Signal that a FEC block boundary has been reached on the base track.
|
|
506
|
+
* Overlay tracks waiting for alignment flush their buffered frames now.
|
|
507
|
+
*/
|
|
508
|
+
notifyFecBlockBoundary(): void {
|
|
509
|
+
if (this.#overlayBoundaryReached) return
|
|
510
|
+
this.#overlayBoundaryReached = true
|
|
511
|
+
|
|
512
|
+
// Flush buffered overlay frames
|
|
513
|
+
const buffered = this.#overlayBuffer.splice(0)
|
|
514
|
+
for (const { data, meta } of buffered) {
|
|
515
|
+
this.#onOverlayFrame?.(data, meta)
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Return the number of frames emitted from the per-track source for a given
|
|
521
|
+
* track. Used in test-mode to verify per-track routing histogram.
|
|
522
|
+
* Returns 0 if no per-track source is active for the track.
|
|
523
|
+
*/
|
|
524
|
+
trackSourceEmitCount(trackName: string): number {
|
|
525
|
+
return this.#trackSources.get(trackName)?.emitCount ?? 0
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Feed a frame from the MoQ path. Suppressed when multicast is active OR a per-track source owns this track. */
|
|
529
|
+
feedMoq(data: Uint8Array, meta: FrameMeta, kind: FrameKind) {
|
|
530
|
+
// Per-track source takes precedence; allow through during crossfade window
|
|
531
|
+
if (this.#trackSources.has(kind)) {
|
|
532
|
+
const remaining = this.#crossfadeCounters.get(kind)
|
|
533
|
+
if (remaining && remaining > 0) {
|
|
534
|
+
// Decrement crossfade counter and allow this frame through
|
|
535
|
+
const next = remaining - 1
|
|
536
|
+
if (next === 0) {
|
|
537
|
+
this.#crossfadeCounters.delete(kind)
|
|
538
|
+
} else {
|
|
539
|
+
this.#crossfadeCounters.set(kind, next)
|
|
540
|
+
}
|
|
541
|
+
// Fall through to emit below (no return)
|
|
542
|
+
} else {
|
|
543
|
+
return // suppressed — no crossfade window active
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (this.#suppressed) return
|
|
547
|
+
// Audio suppression is handled by SessionManager onSuppress callback —
|
|
548
|
+
// do NOT independently suppress based on multicastAudioReceived.
|
|
549
|
+
// The state machine decides when to switch; the router just routes.
|
|
550
|
+
const routedMeta = { ...meta, source: 'moq' as const }
|
|
551
|
+
if (kind === 'video') this.#onVideoFrame?.(data, routedMeta)
|
|
552
|
+
else this.#onAudioFrame?.(data, routedMeta)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** Feed a frame from the multicast path. Video waits for keyframe before activating. */
|
|
556
|
+
feedMulticast(data: Uint8Array, meta: FrameMeta, kind: FrameKind) {
|
|
557
|
+
// Per-track source takes precedence; allow through during crossfade window
|
|
558
|
+
if (this.#trackSources.has(kind)) {
|
|
559
|
+
const remaining = this.#crossfadeCounters.get(kind)
|
|
560
|
+
if (remaining && remaining > 0) {
|
|
561
|
+
const next = remaining - 1
|
|
562
|
+
if (next === 0) {
|
|
563
|
+
this.#crossfadeCounters.delete(kind)
|
|
564
|
+
} else {
|
|
565
|
+
this.#crossfadeCounters.set(kind, next)
|
|
566
|
+
}
|
|
567
|
+
// Fall through to emit below (no return)
|
|
568
|
+
} else {
|
|
569
|
+
return // suppressed
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (kind === 'video') {
|
|
574
|
+
if (!this.#multicastVideoKeyed) {
|
|
575
|
+
const moqSeededDelta =
|
|
576
|
+
!meta.keyframe &&
|
|
577
|
+
this.#allowDeltaMulticastTakeoverAfterMoqKeyframe &&
|
|
578
|
+
this.#moqVideoKeyframeEmitted
|
|
579
|
+
if (!meta.keyframe && !moqSeededDelta) return // drop until first safe decode point
|
|
580
|
+
this.#multicastVideoKeyed = true
|
|
581
|
+
const pendingMeta: FrameMeta = { ...meta, source: 'multicast' }
|
|
582
|
+
if (!moqSeededDelta) pendingMeta.sourceDiscontinuity = true
|
|
583
|
+
this.#pendingMulticastVideo = {
|
|
584
|
+
data: data.slice(),
|
|
585
|
+
meta: pendingMeta,
|
|
586
|
+
}
|
|
587
|
+
// If audio is required but hasn't arrived yet, start a fallback timer.
|
|
588
|
+
// Audio FEC blocks may recover after video — don't block activation forever.
|
|
589
|
+
// Only arms when a catalog-derived audioFallbackMs was supplied (>0).
|
|
590
|
+
if (this.#requireAudio && this.#audioFallbackMs > 0 && !this.#multicastAudioReceived && !this.#audioFallbackTimer) {
|
|
591
|
+
this.#audioFallbackTimer = setTimeout(() => {
|
|
592
|
+
if (!this.#multicastAudioReceived && this.#active !== 'multicast') {
|
|
593
|
+
this.#multicastAudioReceived = true // force
|
|
594
|
+
// Also release the MoQ-audio-warmup gate so the fallback actually
|
|
595
|
+
// activates — otherwise #tryActivateMulticast would still bail.
|
|
596
|
+
this.#moqAudioWarmed = true
|
|
597
|
+
this.#tryActivateMulticast()
|
|
598
|
+
}
|
|
599
|
+
}, this.#audioFallbackMs)
|
|
600
|
+
}
|
|
601
|
+
} else if (this.#active !== 'multicast') {
|
|
602
|
+
// Do not feed video into the multicast decoder while the source switch
|
|
603
|
+
// is still pending. Keep the newest keyframe as the post-activation
|
|
604
|
+
// decoder opener and drop deltas until takeover commits.
|
|
605
|
+
if (meta.keyframe) {
|
|
606
|
+
this.#pendingMulticastVideo = {
|
|
607
|
+
data: data.slice(),
|
|
608
|
+
meta: { ...meta, source: 'multicast', sourceDiscontinuity: true },
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
this.#tryActivateMulticast()
|
|
612
|
+
return
|
|
613
|
+
}
|
|
614
|
+
if (this.#active === 'multicast') this.#onVideoFrame?.(data, { ...meta, source: 'multicast' })
|
|
615
|
+
this.#tryActivateMulticast()
|
|
616
|
+
} else {
|
|
617
|
+
this.#multicastAudioReceived = true
|
|
618
|
+
if (this.#audioFallbackTimer) {
|
|
619
|
+
clearTimeout(this.#audioFallbackTimer)
|
|
620
|
+
this.#audioFallbackTimer = null
|
|
621
|
+
}
|
|
622
|
+
// BLO-3035 layer 5: in a multicast-first scenario (no MoQ unicast audio
|
|
623
|
+
// source) `notifyMoqAudioEmit()` is never called and the audio-fallback
|
|
624
|
+
// timer's force-set is bypassed (the timer's inner `!multicastAudioReceived`
|
|
625
|
+
// check returns early because audio already arrived). Without this gate
|
|
626
|
+
// `#moqAudioWarmed` stays false forever and `#tryActivateMulticast` never
|
|
627
|
+
// fires `onSourceSwitch('multicast')` — the player can't know to emit
|
|
628
|
+
// `__MULTICAST_TAKEOVER_TIME`. Treat the arrival of multicast audio as
|
|
629
|
+
// sufficient evidence to release the warmup gate. For the MoQ→multicast
|
|
630
|
+
// handoff case `notifyMoqAudioEmit()` will already have set this flag, so
|
|
631
|
+
// the assignment is idempotent.
|
|
632
|
+
if (!this.#moqAudioWarmed) this.#moqAudioWarmed = true
|
|
633
|
+
// Activate BEFORE delivering the frame — the source switch callback
|
|
634
|
+
// (e.g. ring buffer reset) must fire before the first multicast audio
|
|
635
|
+
// frame reaches the backend. Otherwise the NTP timestamp hits the
|
|
636
|
+
// MoQ-era ring buffer base → overflow → silence gap.
|
|
637
|
+
this.#tryActivateMulticast()
|
|
638
|
+
// Only emit multicast audio AFTER takeover succeeds. Before takeover,
|
|
639
|
+
// MoQ audio is still flowing through feedMoq → #onAudioFrame; emitting
|
|
640
|
+
// multicast audio too would double-feed the backend (2x audio frames,
|
|
641
|
+
// pitch wobble and distortion). Post-takeover, feedMoq is suppressed
|
|
642
|
+
// (#suppressed=true) so only multicast audio reaches the backend.
|
|
643
|
+
if (this.#active === 'multicast') {
|
|
644
|
+
this.#onAudioFrame?.(data, { ...meta, source: 'multicast' })
|
|
645
|
+
} else if (this.#pendingMulticastToken !== null) {
|
|
646
|
+
this.#pendingMulticastAudio = { data: data.slice(), meta: { ...meta, source: 'multicast' } }
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* MoQ unicast audio warmup gate. When audio is required, multicast takeover
|
|
653
|
+
* waits until the MoQ unicast audio decoder has emitted at least one frame
|
|
654
|
+
* (so downstream samples-before-takeover counters observe nonzero) or until
|
|
655
|
+
* the existing audio-fallback timer forces the flag true.
|
|
656
|
+
*/
|
|
657
|
+
#moqAudioWarmed = false
|
|
658
|
+
|
|
659
|
+
/** Called by the player when the MoQ unicast audio decoder emits first frame. */
|
|
660
|
+
public notifyMoqAudioEmit(): void {
|
|
661
|
+
if (this.#moqAudioWarmed) return
|
|
662
|
+
this.#moqAudioWarmed = true
|
|
663
|
+
this.#tryActivateMulticast()
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Called by the player after a MoQ video keyframe has reached the active
|
|
668
|
+
* decoder path. Used only by MSE/MMTP continuity switching; decoder-reset
|
|
669
|
+
* paths leave allowDeltaMulticastTakeoverAfterMoqKeyframe disabled.
|
|
670
|
+
*/
|
|
671
|
+
public notifyMoqVideoKeyframeEmit(): void {
|
|
672
|
+
this.#moqVideoKeyframeEmitted = true
|
|
673
|
+
this.#tryActivateMulticast()
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
#tryActivateMulticast() {
|
|
677
|
+
if (this.#active === 'multicast') return
|
|
678
|
+
if (this.#pendingMulticastToken !== null) return
|
|
679
|
+
if (!this.#multicastVideoKeyed) return
|
|
680
|
+
if (this.#requireAudio && !this.#multicastAudioReceived) return
|
|
681
|
+
if (this.#requireAudio && !this.#moqAudioWarmed) return
|
|
682
|
+
const token = ++this.#sourceSwitchToken
|
|
683
|
+
const maybeReady = this.#onSourceSwitch?.('multicast')
|
|
684
|
+
if (!maybeReady || typeof (maybeReady as Promise<void>).then !== 'function') {
|
|
685
|
+
this.#commitMulticastActivation(token)
|
|
686
|
+
return
|
|
687
|
+
}
|
|
688
|
+
const ready = Promise.resolve(maybeReady)
|
|
689
|
+
this.#pendingMulticastToken = token
|
|
690
|
+
this.#sourceSwitchReady = ready
|
|
691
|
+
void ready.then(
|
|
692
|
+
() => this.#commitMulticastActivation(token),
|
|
693
|
+
err => this.#rejectMulticastActivation(token, err),
|
|
694
|
+
).finally(() => {
|
|
695
|
+
if (this.#sourceSwitchReady === ready) this.#sourceSwitchReady = null
|
|
696
|
+
})
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
#commitMulticastActivation(token: number) {
|
|
700
|
+
if (token !== this.#sourceSwitchToken) return
|
|
701
|
+
this.#active = 'multicast'
|
|
702
|
+
this.#suppressed = true
|
|
703
|
+
this.#pendingMulticastToken = null
|
|
704
|
+
const video = this.#pendingMulticastVideo
|
|
705
|
+
this.#pendingMulticastVideo = null
|
|
706
|
+
const audio = this.#pendingMulticastAudio
|
|
707
|
+
this.#pendingMulticastAudio = null
|
|
708
|
+
if (video) this.#onVideoFrame?.(video.data, video.meta)
|
|
709
|
+
if (audio) this.#onAudioFrame?.(audio.data, audio.meta)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
#rejectMulticastActivation(token: number, err: unknown) {
|
|
713
|
+
if (token !== this.#sourceSwitchToken) return
|
|
714
|
+
if (this.#pendingMulticastToken === token) this.#pendingMulticastToken = null
|
|
715
|
+
this.#pendingMulticastVideo = null
|
|
716
|
+
this.#pendingMulticastAudio = null
|
|
717
|
+
console.warn('[MediaRouter] onSourceSwitch(multicast) failed', err)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** Switch back to MoQ immediately (called on multicast failback). */
|
|
721
|
+
failbackToMoq() {
|
|
722
|
+
const hadPendingMulticast = this.#pendingMulticastToken !== null
|
|
723
|
+
if (this.#active === 'moq' && !hadPendingMulticast) return // already on MoQ, no-op
|
|
724
|
+
++this.#sourceSwitchToken
|
|
725
|
+
this.#active = 'moq'
|
|
726
|
+
this.#suppressed = false
|
|
727
|
+
this.#pendingMulticastToken = null
|
|
728
|
+
this.#pendingMulticastVideo = null
|
|
729
|
+
this.#pendingMulticastAudio = null
|
|
730
|
+
this.#sourceSwitchReady = null
|
|
731
|
+
this.#multicastVideoKeyed = false
|
|
732
|
+
this.#multicastAudioReceived = false
|
|
733
|
+
this.#moqVideoKeyframeEmitted = false
|
|
734
|
+
// A pending audio-fallback timer belongs to the abandoned takeover attempt.
|
|
735
|
+
// Left running it would force-set the audio flags against the next attempt's
|
|
736
|
+
// fresh state (BLO-9413 failback coherence — every takeover-era flag/timer
|
|
737
|
+
// unwinds on this edge).
|
|
738
|
+
if (this.#audioFallbackTimer) {
|
|
739
|
+
clearTimeout(this.#audioFallbackTimer)
|
|
740
|
+
this.#audioFallbackTimer = null
|
|
741
|
+
}
|
|
742
|
+
this.#onSourceSwitch?.('moq')
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** Dispose timers and per-track sources. Call on teardown to prevent
|
|
746
|
+
* stale timer callbacks firing into garbage-collected closures (WR-03). */
|
|
747
|
+
dispose(): void {
|
|
748
|
+
if (this.#audioFallbackTimer) {
|
|
749
|
+
clearTimeout(this.#audioFallbackTimer)
|
|
750
|
+
this.#audioFallbackTimer = null
|
|
751
|
+
}
|
|
752
|
+
for (const [name] of this.#trackSources) {
|
|
753
|
+
this.clearTrackSource(name)
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// ── Private helpers ──────────────────────────────────────────────────────
|
|
758
|
+
|
|
759
|
+
/** Emit a frame on the correct per-track callback using the type registry.
|
|
760
|
+
* Falls back to name match if no type registered (backward compat). */
|
|
761
|
+
#emitForTrack(trackName: string, data: Uint8Array, meta: FrameMeta): void {
|
|
762
|
+
// Prefer explicit type registry (catalog or init data)
|
|
763
|
+
const reg = this.#trackTypes.get(trackName)
|
|
764
|
+
if (reg?.type === 'video') {
|
|
765
|
+
this.#onVideoFrame?.(data, meta)
|
|
766
|
+
} else if (reg?.type === 'audio') {
|
|
767
|
+
this.#onAudioFrame?.(data, meta)
|
|
768
|
+
} else if (reg !== undefined) {
|
|
769
|
+
// Registered but unknown type — overlay
|
|
770
|
+
this.#onOverlayFrame?.(data, meta)
|
|
771
|
+
} else {
|
|
772
|
+
// Not registered — fall back to name match (backward compat for tests/legacy)
|
|
773
|
+
if (trackName === 'video') this.#onVideoFrame?.(data, meta)
|
|
774
|
+
else if (trackName === 'audio') this.#onAudioFrame?.(data, meta)
|
|
775
|
+
else this.#onOverlayFrame?.(data, meta)
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/** Handle an overlay frame: buffer until first FEC block boundary, then pass through. */
|
|
780
|
+
#handleOverlayFrame(data: Uint8Array, meta: FrameMeta): void {
|
|
781
|
+
if (this.#overlayBoundaryReached) {
|
|
782
|
+
this.#onOverlayFrame?.(data, meta)
|
|
783
|
+
return
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Buffer until boundary — enforcing cap for T-02-17 DoS mitigation
|
|
787
|
+
if (this.#overlayBuffer.length >= this.#maxOverlayBuffer) {
|
|
788
|
+
console.warn(
|
|
789
|
+
`[MediaRouter] overlay buffer full (${this.#maxOverlayBuffer} frames); dropping frame ts=${meta.timestamp}`
|
|
790
|
+
)
|
|
791
|
+
return
|
|
792
|
+
}
|
|
793
|
+
this.#overlayBuffer.push({ data, meta })
|
|
794
|
+
}
|
|
795
|
+
}
|