@blockcast/mmt-container 0.1.0-main.149f78a4adae
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +148 -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 +17109 -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 +549 -0
- package/dist/carp-catalog.js.map +7 -0
- package/dist/catalog-fec-params.d.ts +82 -0
- package/dist/catalog-fec-params.d.ts.map +1 -0
- package/dist/catalog-fec-params.js +66 -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 +168 -0
- package/dist/cmaf-block-assembler.d.ts.map +1 -0
- package/dist/cmaf-block-assembler.js +1755 -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 +235 -0
- package/dist/codec-utils.d.ts.map +1 -0
- package/dist/codec-utils.js +1225 -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 +266 -0
- package/dist/fec-manager.d.ts.map +1 -0
- package/dist/fec-manager.js +1447 -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 +76 -0
- package/dist/fec-worker-setup.d.ts.map +1 -0
- package/dist/fec-worker-setup.js +603 -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 +330 -0
- package/dist/index.js.map +7 -0
- package/dist/iso-bmff-timing.d.ts +47 -0
- package/dist/iso-bmff-timing.d.ts.map +1 -0
- package/dist/iso-bmff-timing.js +526 -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 +1267 -0
- package/dist/manifest-auth-plan.js.map +7 -0
- package/dist/media-router.d.ts +338 -0
- package/dist/media-router.d.ts.map +1 -0
- package/dist/media-router.js +802 -0
- package/dist/media-router.js.map +7 -0
- package/dist/mfu-reassembler.d.ts +264 -0
- package/dist/mfu-reassembler.d.ts.map +1 -0
- package/dist/mfu-reassembler.js +2668 -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 +304 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -0
- package/dist/mmtp-container-parser.js +5378 -0
- package/dist/mmtp-container-parser.js.map +7 -0
- package/dist/mmtp-container-parser.no-modules.js +7853 -0
- package/dist/mmtp-container-parser.no-modules.js.map +7 -0
- package/dist/mmtp-decode-pipeline.d.ts +96 -0
- package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
- package/dist/mmtp-decode-pipeline.js +5589 -0
- package/dist/mmtp-decode-pipeline.js.map +7 -0
- package/dist/mmtp-router.d.ts +383 -0
- package/dist/mmtp-router.d.ts.map +1 -0
- package/dist/mmtp-router.js +8384 -0
- package/dist/mmtp-router.js.map +7 -0
- package/dist/mmtp-to-cmaf.d.ts +166 -0
- package/dist/mmtp-to-cmaf.d.ts.map +1 -0
- package/dist/mmtp-to-cmaf.js +781 -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 +330 -0
- package/dist/multicast-manager.d.ts.map +1 -0
- package/dist/multicast-manager.js +11032 -0
- package/dist/multicast-manager.js.map +7 -0
- package/dist/multicast-transport.d.ts +62 -0
- package/dist/multicast-transport.d.ts.map +1 -0
- package/dist/multicast-transport.js +338 -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 +7819 -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 +117 -0
- package/dist/repair-fetch.js.map +7 -0
- package/dist/sample-combiner.d.ts +182 -0
- package/dist/sample-combiner.d.ts.map +1 -0
- package/dist/sample-combiner.js +571 -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 +1220 -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 +269 -0
- package/dist/track-session.d.ts.map +1 -0
- package/dist/track-session.js +597 -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 +81 -0
- package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
- package/dist/wasm-mfu-reassembler.js +1915 -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 +5635 -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 +80 -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 +270 -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 +309 -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 +275 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js +644 -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 +898 -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 +80 -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 +294 -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 +174 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.js +256 -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 +275 -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 +644 -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 +898 -0
- package/node_modules/@blockcast/mmt-fec/package.json +79 -0
- package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +369 -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 +319 -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 +90 -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 +462 -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 +636 -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 +283 -0
- package/src/align.ts +235 -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 +776 -0
- package/src/catalog-fec-params.ts +167 -0
- package/src/catalog-network-source.ts +49 -0
- package/src/catalog-validator.ts +34 -0
- package/src/cmaf-block-assembler.ts +1058 -0
- package/src/cmaf-group-batcher.ts +104 -0
- package/src/cmcd-producer.ts +329 -0
- package/src/codec-utils.ts +1539 -0
- package/src/fec-codec.ts +61 -0
- package/src/fec-manager.ts +1939 -0
- package/src/fec-starvation-detector.ts +128 -0
- package/src/fec-worker-setup.ts +177 -0
- package/src/fmp4-completion.ts +47 -0
- package/src/fmp4-reader.ts +112 -0
- package/src/index.ts +443 -0
- package/src/iso-bmff-timing.ts +688 -0
- package/src/loc.ts +181 -0
- package/src/manifest-auth-plan.ts +107 -0
- package/src/media-router.ts +1185 -0
- package/src/mfu-reassembler.ts +2842 -0
- package/src/mfu-reassembly-policy.ts +134 -0
- package/src/mmtp-container-parser.ts +1498 -0
- package/src/mmtp-decode-pipeline.ts +315 -0
- package/src/mmtp-router.ts +3027 -0
- package/src/mmtp-to-cmaf.ts +956 -0
- package/src/multicast-auth-catalog.ts +340 -0
- package/src/multicast-input.ts +439 -0
- package/src/multicast-manager.ts +1503 -0
- package/src/multicast-transport.ts +320 -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 +217 -0
- package/src/sample-combiner.ts +846 -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 +778 -0
- package/src/transport-pool.ts +128 -0
- package/src/wasm-mfu-reassembler.ts +372 -0
- package/src/wasm-mmtp-container-parser.ts +23 -0
- package/test-vectors/align.json +199 -0
- package/test-vectors/catalog/README.md +25 -0
- package/test-vectors/catalog/negative/legacy-selection-params.json +15 -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-unaligned-symbol.json +26 -0
- package/test-vectors/catalog/negative/repair-track-legacy-shape.json +18 -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-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/subsample-placement-v1.json +230 -0
|
@@ -0,0 +1,1058 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FEC-block-aware CMAF segment assembler for MSE multicast.
|
|
3
|
+
*
|
|
4
|
+
* Buffers reassembled frames per FEC source block (SBN). On block completion
|
|
5
|
+
* (K source symbols received clean, or FEC recovery), sorts frames by ESI
|
|
6
|
+
* and builds a single multi-sample moof+mdat CMAF segment. On block timeout,
|
|
7
|
+
* generates fill frames (green video / silence audio) for missing ESI slots.
|
|
8
|
+
*
|
|
9
|
+
* One assembler instance per track (video or audio). MulticastManager owns
|
|
10
|
+
* these instances and calls them directly with SBN/ESI from the FEC pipeline.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
encodeDataSegment,
|
|
15
|
+
buildVideoInitSegment,
|
|
16
|
+
buildAudioInitSegment,
|
|
17
|
+
generateGreenFillNAL,
|
|
18
|
+
normalizeAacSampleForCmaf,
|
|
19
|
+
} from './mmtp-to-cmaf.js'
|
|
20
|
+
import {
|
|
21
|
+
classifyH264Keyframe,
|
|
22
|
+
detectH264Keyframe,
|
|
23
|
+
splitH264SampleNalUnits,
|
|
24
|
+
} from './codec-utils.js'
|
|
25
|
+
import {
|
|
26
|
+
scaleAuthoredTicks,
|
|
27
|
+
scaleAuthoredTiming,
|
|
28
|
+
type AuthoredTiming,
|
|
29
|
+
} from './authored-timing.js'
|
|
30
|
+
import type { MmtpDeliveryProvenance } from './mfu-reassembler.js'
|
|
31
|
+
|
|
32
|
+
const INT32_MIN = -0x80000000
|
|
33
|
+
const INT32_MAX = 0x7fffffff
|
|
34
|
+
|
|
35
|
+
interface BlockFrame {
|
|
36
|
+
data: Uint8Array
|
|
37
|
+
timestamp: number
|
|
38
|
+
keyframe: boolean
|
|
39
|
+
/** Exact ISO-BMFF timing retained from the parser for FEC/source ordering. */
|
|
40
|
+
authoredTiming?: AuthoredTiming
|
|
41
|
+
/** MMTP packet_sequence_number identifying the delivered access unit. */
|
|
42
|
+
packetSequenceNumber?: number
|
|
43
|
+
/** Every authoritative MMTP source-packet sequence that formed this sample. */
|
|
44
|
+
packetSequenceNumbers?: number[]
|
|
45
|
+
/** Subset of packetSequenceNumbers reconstructed by FEC. */
|
|
46
|
+
repairedPacketSequenceNumbers?: number[]
|
|
47
|
+
/** Native MMTP MPU group coordinate retained across source/FEC ordering. */
|
|
48
|
+
mpuSequenceNumber?: number
|
|
49
|
+
catalogTrackName?: string
|
|
50
|
+
/**
|
|
51
|
+
* Whole-frame delivery verdict folded across every contributing fragment.
|
|
52
|
+
* Absent means no caller declared one; it is never an implied 'multicast'.
|
|
53
|
+
*/
|
|
54
|
+
deliveryProvenance?: MmtpDeliveryProvenance
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CmafBlockSample {
|
|
58
|
+
data: Uint8Array
|
|
59
|
+
timestamp: number
|
|
60
|
+
duration: number
|
|
61
|
+
keyframe: boolean
|
|
62
|
+
/** Signed PTS-DTS offset in the configured CMAF timescale. */
|
|
63
|
+
compositionTimeOffset?: number
|
|
64
|
+
/** Exact ISO-BMFF timing in the media track's native timescale. */
|
|
65
|
+
authoredTiming?: AuthoredTiming
|
|
66
|
+
/** MMTP packet_sequence_number identifying the delivered access unit. */
|
|
67
|
+
packetSequenceNumber?: number
|
|
68
|
+
/** Every authoritative MMTP source-packet sequence that formed this sample. */
|
|
69
|
+
packetSequenceNumbers?: number[]
|
|
70
|
+
/** Subset of packetSequenceNumbers reconstructed by FEC. */
|
|
71
|
+
repairedPacketSequenceNumbers?: number[]
|
|
72
|
+
/** Native MMTP MPU group coordinate retained for exact replay selection. */
|
|
73
|
+
mpuSequenceNumber?: number
|
|
74
|
+
/** Immutable catalog identity supplied by the owning ingress subscription. */
|
|
75
|
+
catalogTrackName?: string
|
|
76
|
+
/**
|
|
77
|
+
* Whole-frame delivery verdict, carried so MSE `onSample` consumers can see
|
|
78
|
+
* the same evidence the non-MSE frame path gets. Without it the MSE branch
|
|
79
|
+
* silently loses a computed 'mixed' or 'multicast' finding.
|
|
80
|
+
*/
|
|
81
|
+
deliveryProvenance?: MmtpDeliveryProvenance
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type CmafSampleOrderPolicy =
|
|
85
|
+
| { mode: 'arrival' }
|
|
86
|
+
| {
|
|
87
|
+
/** Release samples according to media progression, without a wall clock. */
|
|
88
|
+
mode: 'media-window'
|
|
89
|
+
/** Delivery window in the same units as CmafBlockSample.timestamp. */
|
|
90
|
+
deliveryWindow: number
|
|
91
|
+
/** Optional modulo period for wrapped input timestamps, in timestamp units. */
|
|
92
|
+
timestampWrapPeriod?: number
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface CmafSampleOrderStats {
|
|
96
|
+
staleSamples: number
|
|
97
|
+
duplicateSamples: number
|
|
98
|
+
preRapDeadlineDrops: number
|
|
99
|
+
noRapFlushDrops: number
|
|
100
|
+
forcedReleases: number
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface BlockAssemblerConfig {
|
|
104
|
+
/** Number of source symbols per FEC block */
|
|
105
|
+
k: number
|
|
106
|
+
/** Timescale for CMAF segments (default 1_000_000 = microseconds) */
|
|
107
|
+
timescale?: number
|
|
108
|
+
/** ISO track ID (default 1) */
|
|
109
|
+
trackId?: number
|
|
110
|
+
/** Track type — determines fill frame generation and init segment format */
|
|
111
|
+
trackType: 'video' | 'audio'
|
|
112
|
+
/** Video/audio codec string (e.g. 'avc1.64002a', 'mp4a.40.2'). */
|
|
113
|
+
codec?: string
|
|
114
|
+
// Video config (required when trackType='video')
|
|
115
|
+
/** Raw avcC payload from catalog description — for init + green fill */
|
|
116
|
+
avcC?: Uint8Array
|
|
117
|
+
/** Video width in pixels */
|
|
118
|
+
width?: number
|
|
119
|
+
/** Video height in pixels */
|
|
120
|
+
height?: number
|
|
121
|
+
// Audio config (required when trackType='audio')
|
|
122
|
+
/** Audio codec string (e.g. 'mp4a.40.2', 'opus') — for silence fill generation */
|
|
123
|
+
audioCodec?: string
|
|
124
|
+
/** Audio sample rate (e.g. 48000) */
|
|
125
|
+
sampleRate?: number
|
|
126
|
+
/** Audio channel count (e.g. 1 or 2) */
|
|
127
|
+
channels?: number
|
|
128
|
+
/** Raw AudioSpecificConfig from catalog */
|
|
129
|
+
asc?: Uint8Array
|
|
130
|
+
// Shared
|
|
131
|
+
/** Frames per second — fallback for duration estimation */
|
|
132
|
+
fps?: number
|
|
133
|
+
/** Explicit sample ordering policy. Datagram/FEC callers use media-window. */
|
|
134
|
+
sampleOrder?: CmafSampleOrderPolicy
|
|
135
|
+
/**
|
|
136
|
+
* Bounded sample reorder depth for callback mode.
|
|
137
|
+
*
|
|
138
|
+
* Default 0 preserves immediate emission. When set on video tracks with
|
|
139
|
+
* onSample enabled, samples are sorted by timestamp and the oldest sample is
|
|
140
|
+
* emitted only after this many newer samples are available.
|
|
141
|
+
* @deprecated Use sampleOrder with an explicit policy.
|
|
142
|
+
*/
|
|
143
|
+
sampleReorderWindow?: number
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface OrderedSample extends CmafBlockSample {
|
|
147
|
+
advancesHorizon: boolean
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export class CmafBlockAssembler {
|
|
151
|
+
#config: Required<Pick<BlockAssemblerConfig, 'k' | 'timescale' | 'trackId' | 'trackType'>>
|
|
152
|
+
#blocks = new Map<number, Map<number, BlockFrame>>()
|
|
153
|
+
#pendingFlush = new Set<number>()
|
|
154
|
+
#pendingRecovered = new Map<number, BlockFrame>()
|
|
155
|
+
#sequence = 1
|
|
156
|
+
#baseTs = -1
|
|
157
|
+
#fillFrame: Uint8Array
|
|
158
|
+
#lastDecodableAudioFrame: Uint8Array | null = null
|
|
159
|
+
#frameDuration: number
|
|
160
|
+
#lastEmittedTs = -1
|
|
161
|
+
#lastSourceTs = -1
|
|
162
|
+
#sampleReorderWindow = 0
|
|
163
|
+
#sampleOrder: CmafSampleOrderPolicy = { mode: 'arrival' }
|
|
164
|
+
#sampleOrderCapacity = 0
|
|
165
|
+
#sampleReorderQueue: OrderedSample[] = []
|
|
166
|
+
#recoveredHorizonTs = -1
|
|
167
|
+
#sampleOrderStats: CmafSampleOrderStats = {
|
|
168
|
+
staleSamples: 0,
|
|
169
|
+
duplicateSamples: 0,
|
|
170
|
+
preRapDeadlineDrops: 0,
|
|
171
|
+
noRapFlushDrops: 0,
|
|
172
|
+
forcedReleases: 0,
|
|
173
|
+
}
|
|
174
|
+
#validateH264Keyframes = false
|
|
175
|
+
#keyframeRejectDumpCount = 0
|
|
176
|
+
|
|
177
|
+
/** Callback: emits a complete moof+mdat CMAF segment */
|
|
178
|
+
onSegment: ((segment: Uint8Array) => void) | null = null
|
|
179
|
+
/** Callback: emits a gated/deduped raw sample for callers that already own the MSE timeline. */
|
|
180
|
+
onSample: ((sample: CmafBlockSample) => void) | null = null
|
|
181
|
+
/** Callback: emits an init segment (ftyp+moov) */
|
|
182
|
+
onInitSegment: ((init: Uint8Array) => void) | null = null
|
|
183
|
+
|
|
184
|
+
// Video keyframe gate (BLO-3035 layer 3): Chrome's CHUNK_DEMUXER rejects a
|
|
185
|
+
// moof+mdat whose first sample is non-sync with no prior reference frames
|
|
186
|
+
// ("CHUNK_DEMUXER_ERROR_APPEND_FAILED: Failed to prepare video sample for
|
|
187
|
+
// decode") and detaches the SourceBuffer for the rest of the session.
|
|
188
|
+
// Mid-stream join almost guarantees the first FEC blocks are mid-GOP — all
|
|
189
|
+
// delta frames. Gate emission until the first block containing an IDR;
|
|
190
|
+
// within that block, splice samples to start at the IDR index. Audio passes
|
|
191
|
+
// unconditionally — valid for the codecs supported here (AAC, Opus) where
|
|
192
|
+
// every frame is independently decodable. SBR-only AAC, USAC, or HE-AAC v2
|
|
193
|
+
// with PCE re-entry would need a similar gate.
|
|
194
|
+
#armedVideo = false
|
|
195
|
+
|
|
196
|
+
// Legacy SBN emission watermark for the old block-buffered path. Direct
|
|
197
|
+
// source emission is now timestamp-ordered instead; recovered frames are
|
|
198
|
+
// queued by media timestamp so they can fill gaps without racing ahead of
|
|
199
|
+
// source frames.
|
|
200
|
+
#lastEmittedSbn = -1
|
|
201
|
+
|
|
202
|
+
/** Attempt to claim emission for `sbn`. Returns true if `sbn` is past
|
|
203
|
+
* the watermark (and advances it); false if this sbn has already been
|
|
204
|
+
* claimed or is older (caller should drop). */
|
|
205
|
+
#claimSbnEmission(sbn: number): boolean {
|
|
206
|
+
if (sbn <= this.#lastEmittedSbn) return false
|
|
207
|
+
this.#lastEmittedSbn = sbn
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Truncate `frames` to start at the first keyframe for video tracks before
|
|
212
|
+
* the first emission. Returns the (possibly spliced) frames, or null to
|
|
213
|
+
* drop the block entirely. Audio + already-armed video: identity. */
|
|
214
|
+
#applyVideoKeyframeGate<T extends { data: Uint8Array; keyframe: boolean }>(frames: T[]): T[] | null {
|
|
215
|
+
const keyedFrames = this.#normalizeVideoKeyframes(frames)
|
|
216
|
+
if (this.#config.trackType !== 'video' || this.#armedVideo) return keyedFrames
|
|
217
|
+
const firstKfIdx = keyedFrames.findIndex(f => f.keyframe)
|
|
218
|
+
if (firstKfIdx < 0) return null
|
|
219
|
+
return firstKfIdx === 0 ? keyedFrames : keyedFrames.slice(firstKfIdx)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#normalizeVideoKeyframes<T extends { data: Uint8Array; keyframe: boolean }>(frames: T[]): T[] {
|
|
223
|
+
if (this.#config.trackType !== 'video' || !this.#validateH264Keyframes) return frames
|
|
224
|
+
return frames.map((frame) => this.#withEffectiveVideoKeyframe(frame))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#withEffectiveVideoKeyframe<T extends { data: Uint8Array; keyframe: boolean }>(frame: T): T {
|
|
228
|
+
if (this.#config.trackType !== 'video' || !this.#validateH264Keyframes) return frame
|
|
229
|
+
const keyframe = detectH264Keyframe(frame.data)
|
|
230
|
+
if (!keyframe) this.#dumpRejectedVideoKeyframe(frame.data, frame.keyframe)
|
|
231
|
+
return keyframe === frame.keyframe ? frame : { ...frame, keyframe }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#dumpRejectedVideoKeyframe(data: Uint8Array, metadataKeyframe: boolean): void {
|
|
235
|
+
if (this.#keyframeRejectDumpCount >= 12) return
|
|
236
|
+
this.#keyframeRejectDumpCount++
|
|
237
|
+
const sample = splitH264SampleNalUnits(data)
|
|
238
|
+
const nalTypes = describeH264NalTypes(sample.nals)
|
|
239
|
+
const classify = classifyH264Keyframe(data)
|
|
240
|
+
const hex = Array.from(data.subarray(0, Math.min(48, data.length)))
|
|
241
|
+
.map(b => (b as number).toString(16).padStart(2, '0'))
|
|
242
|
+
.join(' ')
|
|
243
|
+
console.log(
|
|
244
|
+
`[L4-KF-GATE] reject#${this.#keyframeRejectDumpCount} len=${data.length} ` +
|
|
245
|
+
`metadataKf=${metadataKeyframe} classify=${classify} format=${sample.format} ` +
|
|
246
|
+
`nalTypes=[${nalTypes.join(',')}] first48=[${hex}]`,
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Mark video as already synchronized by an external decoder path.
|
|
251
|
+
* Used by MoQ→multicast MSE continuity when MoQ has already appended a
|
|
252
|
+
* keyframe to the same SourceBuffer timeline before multicast takeover. */
|
|
253
|
+
markVideoSynced(): void {
|
|
254
|
+
if (this.#config.trackType === 'video') this.#armedVideo = true
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Emit each frame as its own single-sample moof+mdat segment. Matches the
|
|
258
|
+
* case-20 path (`MmtpToCmaf.wrapVideoSample` → `encodeDataSegment`) byte-
|
|
259
|
+
* for-byte per frame. Working hypothesis (pending Chromium-source
|
|
260
|
+
* validation against `media/filters/chunk_demuxer.cc`):
|
|
261
|
+
* `encodeMultiSampleSegment` blobs are accepted by ChunkDemuxer in this
|
|
262
|
+
* pipeline but only the first sample renders for live, possibly because
|
|
263
|
+
* the pipeline expects per-frame moof boundaries. Track in BLO-3035.
|
|
264
|
+
*
|
|
265
|
+
* Stale-tfdt defense-in-depth: drops any frame whose timestamp is older
|
|
266
|
+
* than `#lastEmittedTs`. The SBN watermark already prevents out-of-order
|
|
267
|
+
* FEC-block emissions, but `addBypassFrame` (oversized MFU / no-FEC path)
|
|
268
|
+
* doesn't touch the watermark — a bypass frame at t=T followed by a FEC
|
|
269
|
+
* block whose frames precede T would otherwise inject earlier tfdt and
|
|
270
|
+
* detach the SourceBuffer. */
|
|
271
|
+
#ordersSamples(): boolean {
|
|
272
|
+
return this.#sampleOrder.mode === 'media-window' || this.#sampleReorderWindow > 0
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
#unwrapTimestamp(timestamp: number): number {
|
|
276
|
+
const wrapPeriod = this.#sampleOrder.mode === 'media-window'
|
|
277
|
+
? this.#sampleOrder.timestampWrapPeriod
|
|
278
|
+
: undefined
|
|
279
|
+
if (!wrapPeriod) return timestamp
|
|
280
|
+
const reference = this.#lastSourceTs >= 0
|
|
281
|
+
? this.#lastSourceTs
|
|
282
|
+
: this.#recoveredHorizonTs >= 0
|
|
283
|
+
? this.#recoveredHorizonTs
|
|
284
|
+
: this.#lastEmittedTs
|
|
285
|
+
if (reference < 0) return timestamp
|
|
286
|
+
return timestamp + Math.round((reference - timestamp) / wrapPeriod) * wrapPeriod
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#emitSample(
|
|
290
|
+
data: Uint8Array,
|
|
291
|
+
timestamp: number,
|
|
292
|
+
duration: number,
|
|
293
|
+
keyframe: boolean,
|
|
294
|
+
advancesHorizon: boolean,
|
|
295
|
+
catalogTrackName: string | undefined,
|
|
296
|
+
mpuSequenceNumber: number | undefined,
|
|
297
|
+
packetSequenceNumber: number | undefined,
|
|
298
|
+
packetSequenceNumbers: number[] | undefined,
|
|
299
|
+
repairedPacketSequenceNumbers: number[] | undefined,
|
|
300
|
+
authoredTiming: AuthoredTiming | undefined,
|
|
301
|
+
// Required, not optional: an omitted optional argument is silently
|
|
302
|
+
// undefined, which is exactly how this field went missing from the
|
|
303
|
+
// recovered path in the first place. Callers with nothing to declare pass
|
|
304
|
+
// `undefined` on purpose.
|
|
305
|
+
deliveryProvenance: MmtpDeliveryProvenance | undefined,
|
|
306
|
+
): void {
|
|
307
|
+
const scaledTiming = authoredTiming === undefined
|
|
308
|
+
? null
|
|
309
|
+
: scaleAuthoredTiming(authoredTiming, this.#config.timescale)
|
|
310
|
+
if (authoredTiming !== undefined && scaledTiming === null) return
|
|
311
|
+
const authoredTimestamp = scaledTiming?.timestamp ?? timestamp
|
|
312
|
+
const authoredDuration = scaledTiming?.duration ?? duration
|
|
313
|
+
let compositionTimeOffset: number | undefined
|
|
314
|
+
if (authoredTiming !== undefined) {
|
|
315
|
+
const scaledOffset = scaleAuthoredTicks(
|
|
316
|
+
authoredTiming.presentationTime - authoredTiming.decodeTime,
|
|
317
|
+
authoredTiming.mediaTimescale,
|
|
318
|
+
this.#config.timescale,
|
|
319
|
+
)
|
|
320
|
+
if (scaledOffset === null) return
|
|
321
|
+
// trun stores composition offsets as integer target-timescale ticks.
|
|
322
|
+
// Preserve the nearest representable tick, then enforce the signed
|
|
323
|
+
// int32 serialization range on the rounded value.
|
|
324
|
+
const roundedOffset = Math.round(scaledOffset)
|
|
325
|
+
if (
|
|
326
|
+
!Number.isSafeInteger(roundedOffset) ||
|
|
327
|
+
roundedOffset < INT32_MIN ||
|
|
328
|
+
roundedOffset > INT32_MAX
|
|
329
|
+
) return
|
|
330
|
+
compositionTimeOffset = roundedOffset
|
|
331
|
+
}
|
|
332
|
+
const unwrappedTimestamp = this.#unwrapTimestamp(authoredTimestamp)
|
|
333
|
+
if (unwrappedTimestamp <= this.#lastEmittedTs) {
|
|
334
|
+
this.#sampleOrderStats.staleSamples++
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const sample = this.#withEffectiveVideoKeyframe({
|
|
339
|
+
data,
|
|
340
|
+
timestamp: unwrappedTimestamp,
|
|
341
|
+
duration: authoredDuration,
|
|
342
|
+
keyframe,
|
|
343
|
+
...(compositionTimeOffset === undefined ? {} : { compositionTimeOffset }),
|
|
344
|
+
advancesHorizon,
|
|
345
|
+
...(authoredTiming === undefined ? {} : { authoredTiming }),
|
|
346
|
+
...(packetSequenceNumber === undefined ? {} : { packetSequenceNumber }),
|
|
347
|
+
...(packetSequenceNumbers === undefined ? {} : { packetSequenceNumbers }),
|
|
348
|
+
...(repairedPacketSequenceNumbers === undefined
|
|
349
|
+
? {}
|
|
350
|
+
: { repairedPacketSequenceNumbers }),
|
|
351
|
+
...(mpuSequenceNumber === undefined ? {} : { mpuSequenceNumber }),
|
|
352
|
+
...(catalogTrackName === undefined ? {} : { catalogTrackName }),
|
|
353
|
+
...(deliveryProvenance === undefined ? {} : { deliveryProvenance }),
|
|
354
|
+
})
|
|
355
|
+
if (advancesHorizon && unwrappedTimestamp > this.#lastSourceTs) {
|
|
356
|
+
this.#lastSourceTs = unwrappedTimestamp
|
|
357
|
+
} else if (!advancesHorizon && unwrappedTimestamp > this.#recoveredHorizonTs) {
|
|
358
|
+
this.#recoveredHorizonTs = unwrappedTimestamp
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (this.#ordersSamples()) {
|
|
362
|
+
this.#queueSample(sample)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
this.#emitArrivalOrdered(sample)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
#emitArrivalOrdered(sample: OrderedSample): void {
|
|
369
|
+
if (this.#config.trackType === 'video' && !this.#armedVideo && !sample.keyframe) {
|
|
370
|
+
this.#sampleOrderStats.preRapDeadlineDrops++
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
this.#emitSampleNow(sample)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#queueSample(sample: OrderedSample): void {
|
|
377
|
+
if (this.#sampleReorderQueue.some(s => s.timestamp === sample.timestamp)) {
|
|
378
|
+
this.#sampleOrderStats.duplicateSamples++
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
this.#sampleReorderQueue.push(sample)
|
|
382
|
+
this.#sampleReorderQueue.sort((a, b) => a.timestamp - b.timestamp)
|
|
383
|
+
this.#drainSampleReorder(false)
|
|
384
|
+
this.#enforceSampleOrderCapacity()
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
#releaseHorizon(): number {
|
|
388
|
+
return this.#lastSourceTs >= 0 ? this.#lastSourceTs : this.#recoveredHorizonTs
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
#sampleIsReleasable(sample: OrderedSample, flushAll: boolean): boolean {
|
|
392
|
+
if (flushAll) return true
|
|
393
|
+
if (this.#sampleOrder.mode === 'media-window') {
|
|
394
|
+
const horizon = this.#releaseHorizon()
|
|
395
|
+
return horizon >= 0 && sample.timestamp < horizon - this.#sampleOrder.deliveryWindow
|
|
396
|
+
}
|
|
397
|
+
return this.#sampleReorderQueue.length > this.#sampleReorderWindow
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
#dropPreRapSamples(flushAll: boolean): boolean {
|
|
401
|
+
if (this.#config.trackType !== 'video' || this.#armedVideo) return false
|
|
402
|
+
|
|
403
|
+
const firstRap = this.#sampleReorderQueue.findIndex(
|
|
404
|
+
sample => sample.keyframe && this.#sampleIsReleasable(sample, flushAll),
|
|
405
|
+
)
|
|
406
|
+
if (firstRap >= 0) {
|
|
407
|
+
if (firstRap > 0) {
|
|
408
|
+
this.#sampleOrderStats.preRapDeadlineDrops += firstRap
|
|
409
|
+
this.#sampleReorderQueue.splice(0, firstRap)
|
|
410
|
+
}
|
|
411
|
+
return false
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let dropped = 0
|
|
415
|
+
while (
|
|
416
|
+
this.#sampleReorderQueue[0] &&
|
|
417
|
+
this.#sampleIsReleasable(this.#sampleReorderQueue[0]!, flushAll)
|
|
418
|
+
) {
|
|
419
|
+
this.#sampleReorderQueue.shift()
|
|
420
|
+
dropped++
|
|
421
|
+
}
|
|
422
|
+
if (flushAll) this.#sampleOrderStats.noRapFlushDrops += dropped
|
|
423
|
+
else this.#sampleOrderStats.preRapDeadlineDrops += dropped
|
|
424
|
+
return dropped > 0
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#drainSampleReorder(flushAll: boolean): void {
|
|
428
|
+
while (this.#sampleReorderQueue.length > 0) {
|
|
429
|
+
if (this.#dropPreRapSamples(flushAll) && this.#sampleReorderQueue.length === 0) return
|
|
430
|
+
const sample = this.#sampleReorderQueue[0]
|
|
431
|
+
if (!sample || !this.#sampleIsReleasable(sample, flushAll)) return
|
|
432
|
+
this.#sampleReorderQueue.shift()
|
|
433
|
+
this.#emitQueuedSample(sample)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#emitQueuedSample(sample: OrderedSample): void {
|
|
438
|
+
const next = this.#sampleReorderQueue.find(s => s.timestamp > sample.timestamp)
|
|
439
|
+
const duration = sample.authoredTiming
|
|
440
|
+
? sample.duration
|
|
441
|
+
: next ? next.timestamp - sample.timestamp : sample.duration
|
|
442
|
+
this.#emitSampleNow({
|
|
443
|
+
...sample,
|
|
444
|
+
duration: duration > 0
|
|
445
|
+
? duration
|
|
446
|
+
: sample.authoredTiming ? sample.duration : this.#frameDuration,
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
#enforceSampleOrderCapacity(): void {
|
|
451
|
+
if (this.#sampleOrder.mode !== 'media-window') return
|
|
452
|
+
while (this.#sampleReorderQueue.length > this.#sampleOrderCapacity) {
|
|
453
|
+
const sample = this.#sampleReorderQueue.shift()
|
|
454
|
+
if (!sample) return
|
|
455
|
+
this.#sampleOrderStats.forcedReleases++
|
|
456
|
+
console.warn(
|
|
457
|
+
`[CmafBlockAssembler] ${this.#config.trackType} media-window queue exceeded ` +
|
|
458
|
+
`${this.#sampleOrderCapacity} samples; force-releasing timestamp=${sample.timestamp}`,
|
|
459
|
+
)
|
|
460
|
+
if (this.#config.trackType === 'video' && !this.#armedVideo && !sample.keyframe) {
|
|
461
|
+
this.#sampleOrderStats.preRapDeadlineDrops++
|
|
462
|
+
continue
|
|
463
|
+
}
|
|
464
|
+
this.#emitQueuedSample(sample)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#emitSampleNow(sample: CmafBlockSample): void {
|
|
469
|
+
if (sample.timestamp <= this.#lastEmittedTs) {
|
|
470
|
+
this.#sampleOrderStats.staleSamples++
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
this.#emitAudioGapFillNowBefore(sample.timestamp)
|
|
474
|
+
this.#writeSampleNow(sample, true)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
#writeSampleNow(sample: CmafBlockSample, rememberAudio: boolean): void {
|
|
478
|
+
const { timestamp, duration, keyframe } = sample
|
|
479
|
+
this.#lastEmittedTs = timestamp
|
|
480
|
+
if (this.#config.trackType === 'video' && keyframe) this.#armedVideo = true
|
|
481
|
+
const data = this.#normalizeSampleData(sample.data)
|
|
482
|
+
if (this.#config.trackType === 'audio' && rememberAudio && data.length > 0) {
|
|
483
|
+
// Keep decoder-proven codec data for concealment only after the FEC
|
|
484
|
+
// delivery deadline has passed without the missing audio sample.
|
|
485
|
+
this.#lastDecodableAudioFrame = data.slice()
|
|
486
|
+
}
|
|
487
|
+
if (this.onSample) {
|
|
488
|
+
this.onSample(data === sample.data ? sample : { ...sample, data })
|
|
489
|
+
} else {
|
|
490
|
+
const baseTime = this.#rebase(timestamp)
|
|
491
|
+
const segment = encodeDataSegment(
|
|
492
|
+
data,
|
|
493
|
+
baseTime,
|
|
494
|
+
duration,
|
|
495
|
+
keyframe,
|
|
496
|
+
this.#sequence++,
|
|
497
|
+
this.#config.trackId,
|
|
498
|
+
sample.compositionTimeOffset,
|
|
499
|
+
)
|
|
500
|
+
this.onSegment?.(segment)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
#emitAudioGapFillNowBefore(timestamp: number): void {
|
|
505
|
+
if (
|
|
506
|
+
this.#config.trackType !== 'audio' ||
|
|
507
|
+
this.#lastEmittedTs < 0 ||
|
|
508
|
+
this.#lastDecodableAudioFrame === null
|
|
509
|
+
) return
|
|
510
|
+
|
|
511
|
+
const gap = timestamp - this.#lastEmittedTs
|
|
512
|
+
if (gap <= this.#frameDuration * 1.5) return
|
|
513
|
+
|
|
514
|
+
const missingFrames = Math.max(0, Math.round(gap / this.#frameDuration) - 1)
|
|
515
|
+
for (let i = 0; i < missingFrames; i++) {
|
|
516
|
+
const fillTs = this.#lastEmittedTs + this.#frameDuration
|
|
517
|
+
this.#writeSampleNow({
|
|
518
|
+
data: this.#lastDecodableAudioFrame.slice(),
|
|
519
|
+
timestamp: fillTs,
|
|
520
|
+
duration: this.#frameDuration,
|
|
521
|
+
keyframe: true,
|
|
522
|
+
}, false)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
#emitPerSample(
|
|
527
|
+
frames: BlockFrame[],
|
|
528
|
+
audioDurationOverride?: number,
|
|
529
|
+
advancesHorizon = false,
|
|
530
|
+
): void {
|
|
531
|
+
for (let i = 0; i < frames.length; i++) {
|
|
532
|
+
const f = frames[i]!
|
|
533
|
+
// Per-frame duration from delta to next frame, or fallback.
|
|
534
|
+
let duration: number
|
|
535
|
+
if (this.#config.trackType === 'audio') {
|
|
536
|
+
duration = audioDurationOverride ?? this.#frameDuration
|
|
537
|
+
} else if (i < frames.length - 1) {
|
|
538
|
+
duration = frames[i + 1]!.timestamp - f.timestamp
|
|
539
|
+
} else if (i > 0) {
|
|
540
|
+
duration = f.timestamp - frames[i - 1]!.timestamp
|
|
541
|
+
} else {
|
|
542
|
+
duration = audioDurationOverride ?? this.#frameDuration
|
|
543
|
+
}
|
|
544
|
+
if (duration <= 0) duration = audioDurationOverride ?? this.#frameDuration
|
|
545
|
+
this.#emitSample(
|
|
546
|
+
f.data,
|
|
547
|
+
f.timestamp,
|
|
548
|
+
duration,
|
|
549
|
+
f.keyframe,
|
|
550
|
+
advancesHorizon,
|
|
551
|
+
f.catalogTrackName,
|
|
552
|
+
f.mpuSequenceNumber,
|
|
553
|
+
f.packetSequenceNumber,
|
|
554
|
+
f.packetSequenceNumbers,
|
|
555
|
+
f.repairedPacketSequenceNumbers,
|
|
556
|
+
f.authoredTiming,
|
|
557
|
+
f.deliveryProvenance,
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// [BLO-3035-LAYER4-DIAG] Byte-dump for the first 6 video emits + 1Hz
|
|
563
|
+
// aggregate counters that survive the whole run. The counters tell us
|
|
564
|
+
// whether "emits=low" (assembler emits too few segments) or
|
|
565
|
+
// "addFrame=low" (parser delivers too few frames) — which routes the
|
|
566
|
+
// root-cause hunt to assembler vs router vs publisher.
|
|
567
|
+
#dumpCount = 0
|
|
568
|
+
#emitCount = 0
|
|
569
|
+
#sampleCount = 0
|
|
570
|
+
#keyframeCount = 0
|
|
571
|
+
#addFrameCount = 0
|
|
572
|
+
#recoveredBlockCount = 0
|
|
573
|
+
#completeBlockCount = 0
|
|
574
|
+
#failBlockCount = 0
|
|
575
|
+
#aggregateTimer: ReturnType<typeof setInterval> | null = null
|
|
576
|
+
#startAggregateTimer(): void {
|
|
577
|
+
if (this.#aggregateTimer) return
|
|
578
|
+
this.#aggregateTimer = setInterval(() => {
|
|
579
|
+
console.log(
|
|
580
|
+
`[L4-AGG-${this.#config.trackType}] emits=${this.#emitCount} samples=${this.#sampleCount} ` +
|
|
581
|
+
`kf=${this.#keyframeCount} addFrame=${this.#addFrameCount} ` +
|
|
582
|
+
`recovered=${this.#recoveredBlockCount} complete=${this.#completeBlockCount} ` +
|
|
583
|
+
`fail=${this.#failBlockCount} pendingBlocks=${this.#blocks.size} ` +
|
|
584
|
+
`pendingFlushes=${this.#pendingFlush.size} armedVideo=${this.#armedVideo}`,
|
|
585
|
+
)
|
|
586
|
+
this.#emitCount = 0
|
|
587
|
+
this.#sampleCount = 0
|
|
588
|
+
this.#keyframeCount = 0
|
|
589
|
+
this.#addFrameCount = 0
|
|
590
|
+
this.#recoveredBlockCount = 0
|
|
591
|
+
this.#completeBlockCount = 0
|
|
592
|
+
this.#failBlockCount = 0
|
|
593
|
+
}, 1000)
|
|
594
|
+
}
|
|
595
|
+
#dumpEmit(path: string, samples: Array<{ data: Uint8Array; keyframe: boolean; timestamp?: number }>, sbn?: number): void {
|
|
596
|
+
this.#emitCount++
|
|
597
|
+
this.#sampleCount += samples.length
|
|
598
|
+
this.#keyframeCount += samples.filter(s => s.keyframe).length
|
|
599
|
+
// Always log video keyframes (no cap) — needed to see bytes of every IDR
|
|
600
|
+
// including the t=5s one that Chrome's decoder rejected (BLO-3035 L4).
|
|
601
|
+
const anyKf = this.#config.trackType === 'video' && samples.some(s => s.keyframe)
|
|
602
|
+
if (this.#config.trackType !== 'video' || (this.#dumpCount >= 6 && !anyKf)) return
|
|
603
|
+
this.#dumpCount++
|
|
604
|
+
const s0 = samples[0]
|
|
605
|
+
if (!s0) return
|
|
606
|
+
const d = s0.data
|
|
607
|
+
const hex = Array.from(d.subarray(0, Math.min(48, d.length)))
|
|
608
|
+
.map(b => (b as number).toString(16).padStart(2, '0'))
|
|
609
|
+
.join(' ')
|
|
610
|
+
// djb2 hash of full s0 buffer — used to compare clean vs FEC-recovered emit bytes.
|
|
611
|
+
let h = 5381
|
|
612
|
+
for (let i = 0; i < d.length; i++) h = (h * 33 + (d[i] as number)) | 0
|
|
613
|
+
const hash = (h >>> 0).toString(16).padStart(8, '0')
|
|
614
|
+
const ts0 = s0.timestamp ?? -1
|
|
615
|
+
const tsN = samples[samples.length - 1]?.timestamp ?? -1
|
|
616
|
+
console.log(
|
|
617
|
+
`[L4-DIAG] EMIT#${this.#dumpCount} path=${path}${sbn !== undefined ? ` sbn=${sbn}` : ''} ` +
|
|
618
|
+
`samples=${samples.length} s0.kf=${s0.keyframe} s0.len=${d.length} ` +
|
|
619
|
+
`ts0=${ts0} tsN=${tsN} dur=${tsN - ts0} hash=${hash} first48=[${hex}]`,
|
|
620
|
+
)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
constructor(config: BlockAssemblerConfig) {
|
|
624
|
+
this.#config = {
|
|
625
|
+
k: config.k,
|
|
626
|
+
timescale: config.timescale ?? 1_000_000,
|
|
627
|
+
trackId: config.trackId ?? 1,
|
|
628
|
+
trackType: config.trackType,
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Compute default frame duration from fps (updated from timestamp deltas at runtime)
|
|
632
|
+
if (config.fps && config.fps > 0) {
|
|
633
|
+
this.#frameDuration = Math.round(this.#config.timescale / config.fps)
|
|
634
|
+
} else if (config.trackType === 'audio' && config.sampleRate) {
|
|
635
|
+
this.#frameDuration = Math.round(this.#config.timescale * 1024 / config.sampleRate)
|
|
636
|
+
} else {
|
|
637
|
+
// No fps info — will be refined from actual timestamp deltas
|
|
638
|
+
this.#frameDuration = 33333
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Build fill frame based on track type (requires codec config from catalog)
|
|
642
|
+
if (config.trackType === 'video' && config.avcC && config.width && config.height) {
|
|
643
|
+
this.#fillFrame = generateGreenFillNAL(config.avcC, config.width, config.height)
|
|
644
|
+
} else {
|
|
645
|
+
this.#fillFrame = new Uint8Array(0)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const sampleReorderWindow = config.sampleReorderWindow ?? 0
|
|
649
|
+
if (!Number.isFinite(sampleReorderWindow) || sampleReorderWindow < 0) {
|
|
650
|
+
throw new Error('[CmafBlockAssembler] sampleReorderWindow must be finite and non-negative')
|
|
651
|
+
}
|
|
652
|
+
if (config.sampleOrder && sampleReorderWindow > 0) {
|
|
653
|
+
throw new Error('[CmafBlockAssembler] configure sampleOrder or sampleReorderWindow, not both')
|
|
654
|
+
}
|
|
655
|
+
this.#sampleReorderWindow = Math.floor(sampleReorderWindow)
|
|
656
|
+
this.#sampleOrder = config.sampleOrder ?? { mode: 'arrival' }
|
|
657
|
+
if (this.#sampleOrder.mode === 'media-window') {
|
|
658
|
+
const { deliveryWindow, timestampWrapPeriod } = this.#sampleOrder
|
|
659
|
+
if (!Number.isSafeInteger(deliveryWindow) || deliveryWindow <= 0) {
|
|
660
|
+
throw new Error(
|
|
661
|
+
'[CmafBlockAssembler] media-window deliveryWindow must be a positive safe integer',
|
|
662
|
+
)
|
|
663
|
+
}
|
|
664
|
+
if (
|
|
665
|
+
timestampWrapPeriod !== undefined &&
|
|
666
|
+
(!Number.isSafeInteger(timestampWrapPeriod) || timestampWrapPeriod <= deliveryWindow * 2)
|
|
667
|
+
) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
'[CmafBlockAssembler] timestampWrapPeriod must be a safe integer larger than two delivery windows',
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
const samplesPerSecond = config.trackType === 'audio' && config.sampleRate
|
|
673
|
+
? config.sampleRate / 1024
|
|
674
|
+
: config.fps
|
|
675
|
+
if (!samplesPerSecond || !Number.isFinite(samplesPerSecond) || samplesPerSecond <= 0) {
|
|
676
|
+
throw new Error(
|
|
677
|
+
'[CmafBlockAssembler] media-window ordering requires catalog-derived media cadence',
|
|
678
|
+
)
|
|
679
|
+
}
|
|
680
|
+
this.#sampleOrderCapacity =
|
|
681
|
+
Math.ceil(deliveryWindow * samplesPerSecond / this.#config.timescale) + 1
|
|
682
|
+
}
|
|
683
|
+
this.#validateH264Keyframes =
|
|
684
|
+
config.trackType === 'video' &&
|
|
685
|
+
(!!config.avcC || !config.codec || isH264Codec(config.codec))
|
|
686
|
+
if (config.trackType === 'video') {
|
|
687
|
+
console.log(
|
|
688
|
+
`[L4-KF-CONFIG] validate=${this.#validateH264Keyframes} ` +
|
|
689
|
+
`codec=${config.codec ?? ''} avcC=${config.avcC?.length ?? 0}`,
|
|
690
|
+
)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Store config for init segment generation
|
|
694
|
+
this._videoConfig = config.trackType === 'video' && config.avcC
|
|
695
|
+
? { avcC: config.avcC, width: config.width ?? 0, height: config.height ?? 0 }
|
|
696
|
+
: null
|
|
697
|
+
this._audioConfig = config.trackType === 'audio' && config.sampleRate && config.channels
|
|
698
|
+
? { sampleRate: config.sampleRate, channels: config.channels, asc: config.asc }
|
|
699
|
+
: null
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private _videoConfig: { avcC?: Uint8Array; width: number; height: number } | null
|
|
703
|
+
private _audioConfig: { sampleRate: number; channels: number; asc?: Uint8Array } | null
|
|
704
|
+
|
|
705
|
+
get sampleOrderStats(): Readonly<CmafSampleOrderStats> {
|
|
706
|
+
return { ...this.#sampleOrderStats }
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/** Emit a single frame immediately as a one-sample moof+mdat segment.
|
|
710
|
+
*
|
|
711
|
+
* BLO-3035 layer 4 (4b): the prior SBN-grouped buffering scheme stranded
|
|
712
|
+
* frames whenever a multi-packet frame spanned FEC block boundaries —
|
|
713
|
+
* intermediate SBNs had packets-but-no-frames and got `completeBlock`
|
|
714
|
+
* events that landed on empty `#blocks` (→ `pendingFlush` leak), while
|
|
715
|
+
* the SBN where the multi-packet frame finally completed received the
|
|
716
|
+
* `addFrame` but its `trackBlock` count never reached K (→ `pendingBlocks`
|
|
717
|
+
* leak). Result: ~50% of received frames never reached MSE.
|
|
718
|
+
*
|
|
719
|
+
* This path now mirrors `addBypassFrame` — emit on arrival, no buffering.
|
|
720
|
+
* FEC recovery (`addRecoveredBlock`) keeps its SBN-watermark dedup against
|
|
721
|
+
* the same SBN being re-emitted from two paths. Out-of-order regression
|
|
722
|
+
* is guarded by the per-frame `lastEmittedTs` check (also enforced in
|
|
723
|
+
* `#emitPerSample`). The `sbn`/`esi` parameters are kept for the call-site
|
|
724
|
+
* signature but no longer affect routing. */
|
|
725
|
+
addFrame(
|
|
726
|
+
sbn: number,
|
|
727
|
+
esi: number,
|
|
728
|
+
data: Uint8Array,
|
|
729
|
+
timestamp: number,
|
|
730
|
+
keyframe: boolean,
|
|
731
|
+
catalogTrackName?: string,
|
|
732
|
+
mpuSequenceNumber?: number,
|
|
733
|
+
packetSequenceNumber?: number,
|
|
734
|
+
packetSequenceNumbers?: number[],
|
|
735
|
+
repairedPacketSequenceNumbers?: number[],
|
|
736
|
+
authoredTiming?: AuthoredTiming,
|
|
737
|
+
deliveryProvenance?: MmtpDeliveryProvenance,
|
|
738
|
+
): void {
|
|
739
|
+
void sbn; void esi
|
|
740
|
+
this.#addFrameCount++
|
|
741
|
+
this.#startAggregateTimer()
|
|
742
|
+
|
|
743
|
+
if (!this.#ordersSamples()) this.#drainRecoveredUpTo(timestamp)
|
|
744
|
+
|
|
745
|
+
// Per-frame duration: default to catalog-derived `#frameDuration`. For
|
|
746
|
+
// variable-rate content Chrome refines duration from the next sample's
|
|
747
|
+
// DTS at append time, so a fallback here is safe.
|
|
748
|
+
this.#emitSample(
|
|
749
|
+
data,
|
|
750
|
+
timestamp,
|
|
751
|
+
this.#frameDuration,
|
|
752
|
+
keyframe,
|
|
753
|
+
true,
|
|
754
|
+
catalogTrackName,
|
|
755
|
+
mpuSequenceNumber,
|
|
756
|
+
packetSequenceNumber,
|
|
757
|
+
packetSequenceNumbers,
|
|
758
|
+
repairedPacketSequenceNumbers,
|
|
759
|
+
authoredTiming,
|
|
760
|
+
deliveryProvenance,
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
// Stats — #dumpEmit increments emit/sample/keyframe counters internally.
|
|
764
|
+
this.#dumpEmit('addFrame-direct', [{ data, timestamp, keyframe }])
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/** Mark a block as complete (all K source symbols received or FEC recovery succeeded).
|
|
768
|
+
* If all frames are already buffered, flushes immediately. Otherwise sets pending-flush. */
|
|
769
|
+
completeBlock(sbn: number): void {
|
|
770
|
+
void sbn
|
|
771
|
+
this.#completeBlockCount++
|
|
772
|
+
this.#startAggregateTimer()
|
|
773
|
+
// Source samples are emitted directly from addFrame(). A clean FEC block
|
|
774
|
+
// completion is still useful for stats, but it must not enqueue stale
|
|
775
|
+
// pending flushes from the retired block-buffered path.
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/** Handle block timeout — emit whatever frames we have + fill for missing slots. */
|
|
779
|
+
failBlock(sbn: number): void {
|
|
780
|
+
this.#failBlockCount++
|
|
781
|
+
this.#startAggregateTimer()
|
|
782
|
+
this.#pendingFlush.delete(sbn)
|
|
783
|
+
const block = this.#blocks.get(sbn)
|
|
784
|
+
if (!block || block.size === 0) {
|
|
785
|
+
this.#blocks.delete(sbn)
|
|
786
|
+
return
|
|
787
|
+
}
|
|
788
|
+
this.#flushBlockWithFill(sbn)
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/** Add all frames from a FEC-recovered block at once and emit immediately. */
|
|
792
|
+
addRecoveredBlock(sbn: number, frames: BlockFrame[]): void {
|
|
793
|
+
void sbn
|
|
794
|
+
this.#recoveredBlockCount++
|
|
795
|
+
this.#startAggregateTimer()
|
|
796
|
+
// Build samples directly — recovered frames are already sorted
|
|
797
|
+
this.#pendingFlush.delete(sbn)
|
|
798
|
+
this.#blocks.delete(sbn)
|
|
799
|
+
|
|
800
|
+
if (frames.length === 0) return
|
|
801
|
+
const normalized = this.#normalizeVideoKeyframes(frames)
|
|
802
|
+
this.#dumpEmit('addRecoveredBlock', normalized, sbn)
|
|
803
|
+
if (this.#ordersSamples()) {
|
|
804
|
+
this.#emitPerSample(normalized, undefined, false)
|
|
805
|
+
return
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Arrival mode preserves the legacy protection against future recovered
|
|
809
|
+
// samples advancing past source media. Explicit media-window mode routes
|
|
810
|
+
// both paths through one queue instead.
|
|
811
|
+
if (this.#lastSourceTs >= 0) {
|
|
812
|
+
this.#enqueueRecovered(normalized)
|
|
813
|
+
this.#drainRecoveredUpTo(this.#lastSourceTs)
|
|
814
|
+
} else {
|
|
815
|
+
const gated = this.#applyVideoKeyframeGate(normalized)
|
|
816
|
+
if (gated) this.#emitPerSample(gated, undefined, false)
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/** Emit a single frame immediately as a one-sample segment (for oversized packets bypassing FEC). */
|
|
821
|
+
addBypassFrame(
|
|
822
|
+
data: Uint8Array,
|
|
823
|
+
timestamp: number,
|
|
824
|
+
keyframe: boolean,
|
|
825
|
+
catalogTrackName?: string,
|
|
826
|
+
mpuSequenceNumber?: number,
|
|
827
|
+
packetSequenceNumber?: number,
|
|
828
|
+
packetSequenceNumbers?: number[],
|
|
829
|
+
repairedPacketSequenceNumbers?: number[],
|
|
830
|
+
authoredTiming?: AuthoredTiming,
|
|
831
|
+
deliveryProvenance?: MmtpDeliveryProvenance,
|
|
832
|
+
): void {
|
|
833
|
+
if (!this.#ordersSamples()) this.#drainRecoveredUpTo(timestamp)
|
|
834
|
+
this.#emitSample(
|
|
835
|
+
data,
|
|
836
|
+
timestamp,
|
|
837
|
+
this.#frameDuration,
|
|
838
|
+
keyframe,
|
|
839
|
+
true,
|
|
840
|
+
catalogTrackName,
|
|
841
|
+
mpuSequenceNumber,
|
|
842
|
+
packetSequenceNumber,
|
|
843
|
+
packetSequenceNumbers,
|
|
844
|
+
repairedPacketSequenceNumbers,
|
|
845
|
+
authoredTiming,
|
|
846
|
+
deliveryProvenance,
|
|
847
|
+
)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/** Build and emit init segment from catalog config. */
|
|
851
|
+
emitInit(): void {
|
|
852
|
+
let init: Uint8Array | null = null
|
|
853
|
+
if (this._videoConfig?.avcC) {
|
|
854
|
+
init = buildVideoInitSegment(
|
|
855
|
+
this._videoConfig.avcC,
|
|
856
|
+
this._videoConfig.width,
|
|
857
|
+
this._videoConfig.height,
|
|
858
|
+
this.#config.timescale,
|
|
859
|
+
this.#config.trackId,
|
|
860
|
+
)
|
|
861
|
+
} else if (this._audioConfig) {
|
|
862
|
+
init = buildAudioInitSegment(
|
|
863
|
+
this._audioConfig.sampleRate,
|
|
864
|
+
this._audioConfig.channels,
|
|
865
|
+
this._audioConfig.asc,
|
|
866
|
+
this.#config.timescale,
|
|
867
|
+
this.#config.trackId,
|
|
868
|
+
)
|
|
869
|
+
}
|
|
870
|
+
if (init) this.onInitSegment?.(init)
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/** Drain all ordered samples at end-of-stream or before reconfiguration. */
|
|
874
|
+
flush(): void {
|
|
875
|
+
if (this.#ordersSamples()) {
|
|
876
|
+
this.#drainSampleReorder(true)
|
|
877
|
+
return
|
|
878
|
+
}
|
|
879
|
+
if (this.#pendingRecovered.size === 0) return
|
|
880
|
+
const frames = [...this.#pendingRecovered.values()].sort((a, b) => a.timestamp - b.timestamp)
|
|
881
|
+
this.#pendingRecovered.clear()
|
|
882
|
+
const gated = this.#applyVideoKeyframeGate(frames)
|
|
883
|
+
if (gated) this.#emitPerSample(gated, undefined, false)
|
|
884
|
+
else if (this.#config.trackType === 'video') {
|
|
885
|
+
this.#sampleOrderStats.noRapFlushDrops += frames.length
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/** Drop all queued state and begin a fresh timestamp epoch. */
|
|
890
|
+
reset(): void {
|
|
891
|
+
this.#blocks.clear()
|
|
892
|
+
this.#pendingFlush.clear()
|
|
893
|
+
this.#pendingRecovered.clear()
|
|
894
|
+
this.#sequence = 1
|
|
895
|
+
this.#baseTs = -1
|
|
896
|
+
this.#lastEmittedTs = -1
|
|
897
|
+
this.#lastSourceTs = -1
|
|
898
|
+
this.#recoveredHorizonTs = -1
|
|
899
|
+
this.#lastDecodableAudioFrame = null
|
|
900
|
+
this.#armedVideo = false
|
|
901
|
+
this.#lastEmittedSbn = -1
|
|
902
|
+
this.#sampleReorderQueue = []
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
dispose(): void {
|
|
906
|
+
this.reset()
|
|
907
|
+
this.onSegment = null
|
|
908
|
+
this.onSample = null
|
|
909
|
+
this.onInitSegment = null
|
|
910
|
+
if (this.#aggregateTimer) {
|
|
911
|
+
clearInterval(this.#aggregateTimer)
|
|
912
|
+
this.#aggregateTimer = null
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// ── Private ──
|
|
917
|
+
|
|
918
|
+
/** Rebase an absolute timestamp to relative media time (starting near 0). */
|
|
919
|
+
#rebase(timestamp: number): number {
|
|
920
|
+
if (this.#baseTs < 0) this.#baseTs = timestamp
|
|
921
|
+
return timestamp - this.#baseTs
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
#normalizeSampleData(data: Uint8Array): Uint8Array {
|
|
925
|
+
return this.#config.trackType === 'audio' ? normalizeAacSampleForCmaf(data) : data
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
#enqueueRecovered(frames: BlockFrame[]): void {
|
|
929
|
+
for (const frame of frames) {
|
|
930
|
+
if (this.#lastEmittedTs >= 0 && frame.timestamp <= this.#lastEmittedTs) {
|
|
931
|
+
this.#sampleOrderStats.staleSamples++
|
|
932
|
+
continue
|
|
933
|
+
}
|
|
934
|
+
if (this.#pendingRecovered.has(frame.timestamp)) {
|
|
935
|
+
this.#sampleOrderStats.duplicateSamples++
|
|
936
|
+
continue
|
|
937
|
+
}
|
|
938
|
+
this.#pendingRecovered.set(frame.timestamp, frame)
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
#drainRecoveredUpTo(horizonTs: number): void {
|
|
943
|
+
if (this.#pendingRecovered.size === 0) return
|
|
944
|
+
const ready = [...this.#pendingRecovered.values()]
|
|
945
|
+
.filter(frame => frame.timestamp <= horizonTs)
|
|
946
|
+
.sort((a, b) => a.timestamp - b.timestamp)
|
|
947
|
+
|
|
948
|
+
for (const frame of ready) {
|
|
949
|
+
this.#pendingRecovered.delete(frame.timestamp)
|
|
950
|
+
this.#emitSample(
|
|
951
|
+
frame.data,
|
|
952
|
+
frame.timestamp,
|
|
953
|
+
this.#frameDuration,
|
|
954
|
+
frame.keyframe,
|
|
955
|
+
false,
|
|
956
|
+
frame.catalogTrackName,
|
|
957
|
+
frame.mpuSequenceNumber,
|
|
958
|
+
frame.packetSequenceNumber,
|
|
959
|
+
frame.packetSequenceNumbers,
|
|
960
|
+
frame.repairedPacketSequenceNumbers,
|
|
961
|
+
frame.authoredTiming,
|
|
962
|
+
frame.deliveryProvenance,
|
|
963
|
+
)
|
|
964
|
+
this.#dumpEmit('recovered-drain', [frame])
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/** Flush a block's buffered frames as a multi-sample CMAF segment. */
|
|
969
|
+
#flushBlock(sbn: number): void {
|
|
970
|
+
const block = this.#blocks.get(sbn)
|
|
971
|
+
if (!block || block.size === 0) {
|
|
972
|
+
this.#blocks.delete(sbn)
|
|
973
|
+
return
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Sort by ESI to restore original frame order
|
|
977
|
+
const entries = [...block.entries()].sort((a, b) => a[0] - b[0])
|
|
978
|
+
const sortedFrames = entries.map(([, f]) => f)
|
|
979
|
+
this.#blocks.delete(sbn)
|
|
980
|
+
|
|
981
|
+
if (!this.#claimSbnEmission(sbn)) return
|
|
982
|
+
const frames = this.#applyVideoKeyframeGate(sortedFrames)
|
|
983
|
+
if (!frames) return
|
|
984
|
+
|
|
985
|
+
this.#dumpEmit('flushBlock', frames, sbn)
|
|
986
|
+
this.#emitPerSample(frames)
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** Flush a block with fill frames for missing ESI slots (FEC timeout path). */
|
|
990
|
+
#flushBlockWithFill(sbn: number): void {
|
|
991
|
+
const block = this.#blocks.get(sbn)
|
|
992
|
+
if (!block) return
|
|
993
|
+
|
|
994
|
+
const k = this.#config.k
|
|
995
|
+
const isVideo = this.#config.trackType === 'video'
|
|
996
|
+
const firstAudioFrame = !isVideo
|
|
997
|
+
? [...block.values()].sort((a, b) => a.timestamp - b.timestamp)[0]?.data
|
|
998
|
+
: undefined
|
|
999
|
+
const audioConcealmentFrame = this.#lastDecodableAudioFrame ?? firstAudioFrame
|
|
1000
|
+
|
|
1001
|
+
// Build full K-slot array: real frames + fill for missing
|
|
1002
|
+
const allFrames: BlockFrame[] = []
|
|
1003
|
+
let lastRealTs = -1
|
|
1004
|
+
let firstRealTs = -1
|
|
1005
|
+
|
|
1006
|
+
// Find min/max timestamps from received frames to interpolate missing ones
|
|
1007
|
+
for (const [, f] of block) {
|
|
1008
|
+
if (firstRealTs < 0 || f.timestamp < firstRealTs) firstRealTs = f.timestamp
|
|
1009
|
+
if (f.timestamp > lastRealTs) lastRealTs = f.timestamp
|
|
1010
|
+
}
|
|
1011
|
+
const estimatedBlockDuration = block.size > 1
|
|
1012
|
+
? (lastRealTs - firstRealTs) / (block.size - 1) * k
|
|
1013
|
+
: this.#frameDuration * k
|
|
1014
|
+
|
|
1015
|
+
for (let esi = 0; esi < k; esi++) {
|
|
1016
|
+
const real = block.get(esi)
|
|
1017
|
+
if (real) {
|
|
1018
|
+
allFrames.push(real)
|
|
1019
|
+
} else {
|
|
1020
|
+
// Interpolate timestamp for missing slot
|
|
1021
|
+
const interpolatedTs = firstRealTs >= 0
|
|
1022
|
+
? firstRealTs + Math.round(esi * estimatedBlockDuration / k)
|
|
1023
|
+
: (this.#lastEmittedTs >= 0
|
|
1024
|
+
? this.#lastEmittedTs + this.#frameDuration * (esi + 1)
|
|
1025
|
+
: 0)
|
|
1026
|
+
allFrames.push({
|
|
1027
|
+
// A synthesized AAC access unit is not portable across every
|
|
1028
|
+
// AudioSpecificConfig. Repeat known-good encoded audio instead;
|
|
1029
|
+
// if this is the first block, use its earliest received sample.
|
|
1030
|
+
data: isVideo
|
|
1031
|
+
? this.#fillFrame
|
|
1032
|
+
: (audioConcealmentFrame?.slice() ?? new Uint8Array(0)),
|
|
1033
|
+
timestamp: interpolatedTs,
|
|
1034
|
+
// Video: first fill is keyframe (IDR). Audio: all fill marked keyframe.
|
|
1035
|
+
keyframe: isVideo ? !block.has(0) && esi === 0 : true,
|
|
1036
|
+
})
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
this.#blocks.delete(sbn)
|
|
1041
|
+
if (!this.#claimSbnEmission(sbn)) return
|
|
1042
|
+
const gatedFrames = this.#applyVideoKeyframeGate(allFrames)
|
|
1043
|
+
if (!gatedFrames) return
|
|
1044
|
+
|
|
1045
|
+
this.#emitPerSample(gatedFrames)
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function isH264Codec(codec?: string): boolean {
|
|
1050
|
+
const c = codec?.toLowerCase()
|
|
1051
|
+
return c?.startsWith('avc1') === true || c?.startsWith('avc3') === true
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function describeH264NalTypes(nals: Uint8Array[]): number[] {
|
|
1055
|
+
return nals.slice(0, 16).map((nal) => (
|
|
1056
|
+
nal[0] !== undefined ? nal[0] & 0x1f : -1
|
|
1057
|
+
))
|
|
1058
|
+
}
|