@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,846 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mmt/container - Sample-level combining of subsample data units (moqcast #37)
|
|
3
|
+
*
|
|
4
|
+
* {@link MFUReassembler} reassembles exactly one data unit -- a whole-sample
|
|
5
|
+
* DU, or one subsample DU of a partitioned sample. When a timed sample
|
|
6
|
+
* exceeds one data unit's 256-payload capacity, the origin packetizer
|
|
7
|
+
* partitions it into multiple subsample data units (each its own FI chain,
|
|
8
|
+
* its own authored base offset). This module combines those
|
|
9
|
+
* already-reassembled data units back into one contiguous sample.
|
|
10
|
+
*
|
|
11
|
+
* Container-blind, matching {@link MFUReassembler}'s own doctrine: it never
|
|
12
|
+
* parses ISOBMFF itself. The authored sample size a subsample-partitioned
|
|
13
|
+
* sample must total to comes from the host's own FT=1 (movie-fragment-
|
|
14
|
+
* metadata) parse, supplied via {@link SampleCombiner.registerAuthoredSize}.
|
|
15
|
+
* Completion is a protocol event -- every constituent data unit reassembled,
|
|
16
|
+
* byte-adjacent from zero, totaling the registered size -- never a
|
|
17
|
+
* timer-based guess; on lossy paths the existing per-data-unit deadline in
|
|
18
|
+
* {@link MFUReassembler} already bounds each constituent, so this module adds
|
|
19
|
+
* no second timer tier, matching moqcast #37 section 5.2.1.
|
|
20
|
+
*
|
|
21
|
+
* A whole-sample data unit whose length already equals the registered
|
|
22
|
+
* authored size passes through immediately, with no buffering. When
|
|
23
|
+
* `requireAuthoredSize` is false, an offset-zero data unit with no registered
|
|
24
|
+
* size at all also passes through immediately -- the pre-amendment shape, for
|
|
25
|
+
* a host that does not drive FT=1. When true, that same data unit instead
|
|
26
|
+
* buffers and waits for a size to be registered: FT=1 always publishes before
|
|
27
|
+
* any FT=2 sample referencing it (moqcast #37), but on a lossy path FT=1 can
|
|
28
|
+
* still *arrive* after an offset-zero data unit (reordering, or FT=1's own DU
|
|
29
|
+
* FEC-recovered late) -- treating "no size yet" as "whole sample" in that
|
|
30
|
+
* window would silently truncate a subsample-partitioned sample.
|
|
31
|
+
*
|
|
32
|
+
* Both `#pending` (incomplete samples) and `#authoredSizes` (registered but
|
|
33
|
+
* not yet consumed) are bounded. `#pending` is bounded by `maxBuffered`
|
|
34
|
+
* samples and `maxSegmentsPerSample` data units per sample; a full table
|
|
35
|
+
* evicts its oldest sample by insertion order rather than throwing, so one
|
|
36
|
+
* FEC-lost subsample DU cannot wedge every future sample. `#authoredSizes` is
|
|
37
|
+
* bounded by retiring each key the moment its sample completes, keeping only a
|
|
38
|
+
* bounded recent-retirement set so a late duplicate or FEC-recovered
|
|
39
|
+
* retransmission for that same sample is dropped rather than silently
|
|
40
|
+
* re-emitted or misread as a fresh whole sample.
|
|
41
|
+
*
|
|
42
|
+
* @packageDocumentation
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import type {
|
|
46
|
+
MmtpSourcePath,
|
|
47
|
+
ReassembledMFU,
|
|
48
|
+
MmtpDeliveryProvenance,
|
|
49
|
+
} from './mfu-reassembler.js'
|
|
50
|
+
|
|
51
|
+
/** Identifies one authored sample: `(catalogTrackName, mpu, movieFragmentSequenceNumber, sampleNumber)`. */
|
|
52
|
+
type SampleKey = string
|
|
53
|
+
|
|
54
|
+
function sampleKey(
|
|
55
|
+
catalogTrackName: string | undefined,
|
|
56
|
+
mpuSequenceNumber: number,
|
|
57
|
+
movieFragmentSequenceNumber: number,
|
|
58
|
+
sampleNumber: number,
|
|
59
|
+
): SampleKey {
|
|
60
|
+
return JSON.stringify([catalogTrackName ?? null, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber])
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The `catalogTrackName` a key was built from, or undefined when it had none. */
|
|
64
|
+
function trackNameOfKey(key: SampleKey): string | undefined {
|
|
65
|
+
const name = (JSON.parse(key) as [string | null, number, number, number])[0]
|
|
66
|
+
return name ?? undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** One authored sample combined from its constituent data unit(s). */
|
|
70
|
+
export interface CombinedSample {
|
|
71
|
+
mpuSequenceNumber: number
|
|
72
|
+
movieFragmentSequenceNumber: number
|
|
73
|
+
sampleNumber: number
|
|
74
|
+
/** The full sample's media bytes, in ascending-offset order. */
|
|
75
|
+
data: Uint8Array
|
|
76
|
+
timestamp: bigint
|
|
77
|
+
/**
|
|
78
|
+
* The sample's RAP flag is the RAP flag of the FI=0 or FI=1 object of its
|
|
79
|
+
* offset-0 data unit (moqcast #37).
|
|
80
|
+
*/
|
|
81
|
+
rapFlag: boolean
|
|
82
|
+
priority?: number
|
|
83
|
+
/** Every authoritative source-packet sequence across all constituent data units, in offset order. */
|
|
84
|
+
packetSequenceNumbers?: number[]
|
|
85
|
+
/** Subset of packetSequenceNumbers reconstructed by FEC. */
|
|
86
|
+
repairedPacketSequenceNumbers?: number[]
|
|
87
|
+
catalogTrackName?: string
|
|
88
|
+
/** Number of subsample data units combined (1 for a whole-sample DU or a non-timed passthrough). */
|
|
89
|
+
subsampleCount: number
|
|
90
|
+
/** MMTP fragments that formed the sample, summed across constituent data units. */
|
|
91
|
+
fragmentCount: number
|
|
92
|
+
/**
|
|
93
|
+
* Transport that supplied the offset-zero data unit's head fragment. Taken
|
|
94
|
+
* from that data unit alone -- like {@link CombinedSample.rapFlag} -- rather
|
|
95
|
+
* than from whichever sibling happened to complete last.
|
|
96
|
+
*/
|
|
97
|
+
source?: MmtpSourcePath
|
|
98
|
+
/**
|
|
99
|
+
* Strict provenance folded across EVERY constituent data unit, and within
|
|
100
|
+
* each of them across every MMTP fragment. Unlike {@link CombinedSample.source},
|
|
101
|
+
* which is the offset-zero DU's routing tag, this is whole-sample evidence:
|
|
102
|
+
* only 'multicast' proves that no byte of this sample arrived another way.
|
|
103
|
+
*/
|
|
104
|
+
deliveryProvenance: MmtpDeliveryProvenance
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const SAMPLE_COMBINE_ERROR_CODES = [
|
|
108
|
+
'duplicate-subsample-offset',
|
|
109
|
+
'overlapping-subsample',
|
|
110
|
+
'exceeds-authored-size',
|
|
111
|
+
'too-many-subsample-segments',
|
|
112
|
+
] as const
|
|
113
|
+
|
|
114
|
+
export type SampleCombineErrorCode = typeof SAMPLE_COMBINE_ERROR_CODES[number]
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Fail-closed rejection reasons for subsample combining. Distinct from
|
|
118
|
+
* {@link MfuReassemblyError}, which governs one data unit's own fragment
|
|
119
|
+
* chain; these govern combining already-reassembled data units into a
|
|
120
|
+
* sample.
|
|
121
|
+
*/
|
|
122
|
+
export class SampleCombineError extends Error {
|
|
123
|
+
readonly name = 'SampleCombineError'
|
|
124
|
+
|
|
125
|
+
constructor(readonly code: SampleCombineErrorCode, message: string) {
|
|
126
|
+
super(message)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface SampleCombinerStats {
|
|
131
|
+
combinedSamples: number
|
|
132
|
+
passthroughSamples: number
|
|
133
|
+
timedOutSamples: number
|
|
134
|
+
/** Incomplete samples discarded to enforce `maxBuffered`. */
|
|
135
|
+
evictedSamples: number
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface SampleCombinerOptions {
|
|
139
|
+
/**
|
|
140
|
+
* Max concurrent incomplete samples. Enforced by evicting the oldest by
|
|
141
|
+
* insertion order, never by throwing, so a host that never calls
|
|
142
|
+
* {@link SampleCombiner.cleanupTimeouts} still recovers from a permanently
|
|
143
|
+
* lost subsample data unit instead of wedging.
|
|
144
|
+
*/
|
|
145
|
+
maxBuffered: number
|
|
146
|
+
/**
|
|
147
|
+
* When true, an offset-zero data unit with no registered authored size
|
|
148
|
+
* buffers and waits for one rather than passing through immediately. See
|
|
149
|
+
* the module doc.
|
|
150
|
+
*/
|
|
151
|
+
requireAuthoredSize: boolean
|
|
152
|
+
/**
|
|
153
|
+
* Max subsample data units held for one sample at once. Bounds both memory
|
|
154
|
+
* and the per-insert overlap scan; a legitimate subsample-partitioned
|
|
155
|
+
* sample needs at most `ceil(sampleSize / (256 * maxPayload))` data units.
|
|
156
|
+
*/
|
|
157
|
+
maxSegmentsPerSample: number
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface PendingSample {
|
|
161
|
+
/** Held data units keyed by their authored base offset. */
|
|
162
|
+
segments: Map<number, ReassembledMFU>
|
|
163
|
+
startedAt: number
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Combines subsample data units of one sample into a contiguous whole. */
|
|
167
|
+
export class SampleCombiner {
|
|
168
|
+
#maxBuffered: number
|
|
169
|
+
#requireAuthoredSize: boolean
|
|
170
|
+
#maxSegmentsPerSample: number
|
|
171
|
+
#authoredSizes: Map<SampleKey, number> = new Map()
|
|
172
|
+
/** Map iteration is insertion-ordered, so the first key is the oldest. */
|
|
173
|
+
#pending: Map<SampleKey, PendingSample> = new Map()
|
|
174
|
+
/**
|
|
175
|
+
* Bounded, insertion-ordered set of recently-completed sample keys, so a
|
|
176
|
+
* late duplicate or FEC-recovered retransmission is dropped rather than
|
|
177
|
+
* re-emitted or misread as a fresh whole sample.
|
|
178
|
+
*/
|
|
179
|
+
#retired: Set<SampleKey> = new Set()
|
|
180
|
+
#stats: SampleCombinerStats = {
|
|
181
|
+
combinedSamples: 0,
|
|
182
|
+
passthroughSamples: 0,
|
|
183
|
+
timedOutSamples: 0,
|
|
184
|
+
evictedSamples: 0,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
constructor(options: SampleCombinerOptions) {
|
|
188
|
+
const { maxBuffered, requireAuthoredSize, maxSegmentsPerSample } = options
|
|
189
|
+
if (!Number.isInteger(maxBuffered) || maxBuffered <= 0) {
|
|
190
|
+
throw new Error('[SampleCombiner] maxBuffered must be a positive integer')
|
|
191
|
+
}
|
|
192
|
+
if (!Number.isInteger(maxSegmentsPerSample) || maxSegmentsPerSample <= 0) {
|
|
193
|
+
throw new Error('[SampleCombiner] maxSegmentsPerSample must be a positive integer')
|
|
194
|
+
}
|
|
195
|
+
this.#maxBuffered = maxBuffered
|
|
196
|
+
this.#requireAuthoredSize = requireAuthoredSize
|
|
197
|
+
this.#maxSegmentsPerSample = maxSegmentsPerSample
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Register the authored sample size from the host's own FT=1 parse. Per
|
|
202
|
+
* moqcast #37, FT=1 for a movie fragment publishes before any FT=2 sample
|
|
203
|
+
* referencing it, so a host driving FT=1 as it arrives registers each
|
|
204
|
+
* sample's size before that sample's own data unit(s) arrive.
|
|
205
|
+
*
|
|
206
|
+
* On a lossy path FT=1 can still *arrive* late, after data units this
|
|
207
|
+
* combiner has already buffered. Registering a size therefore re-drives the
|
|
208
|
+
* completion check and returns the combined sample when the buffered data
|
|
209
|
+
* units already satisfy the newly-known size. Callers MUST consume the
|
|
210
|
+
* return value: for a sample carried by a single data unit there is no
|
|
211
|
+
* later {@link SampleCombiner.addMfu} to re-enter, so discarding it
|
|
212
|
+
* silently drops the sample.
|
|
213
|
+
*
|
|
214
|
+
* Registering also clears the key from the retired set. Retirement exists
|
|
215
|
+
* to drop data units that arrive *passively* after a sample completed — a
|
|
216
|
+
* late duplicate, an FEC-recovered retransmission. A host calling this
|
|
217
|
+
* method is instead asserting that a sample with this identity is authored
|
|
218
|
+
* now, which is what happens when a publisher restarts and its MPU
|
|
219
|
+
* sequence numbers begin again, or when MPU sequence wraps. Without this,
|
|
220
|
+
* such a stream would silently drop every sample whose identity collided
|
|
221
|
+
* with a retired one until the bounded set aged the key out.
|
|
222
|
+
*/
|
|
223
|
+
registerAuthoredSize(
|
|
224
|
+
catalogTrackName: string | undefined,
|
|
225
|
+
mpuSequenceNumber: number,
|
|
226
|
+
movieFragmentSequenceNumber: number,
|
|
227
|
+
sampleNumber: number,
|
|
228
|
+
size: number,
|
|
229
|
+
): CombinedSample | null {
|
|
230
|
+
const key = sampleKey(catalogTrackName, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber)
|
|
231
|
+
this.#retired.delete(key)
|
|
232
|
+
this.#authoredSizes.set(key, size)
|
|
233
|
+
return this.#tryComplete(key, size)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Add one reassembled data unit. Returns the combined sample once every
|
|
238
|
+
* constituent data unit has arrived and totals the authored size.
|
|
239
|
+
*/
|
|
240
|
+
addMfu(mfu: ReassembledMFU): CombinedSample | null {
|
|
241
|
+
if (mfu.movieFragmentSequenceNumber === undefined || mfu.sampleNumber === undefined) {
|
|
242
|
+
// Non-timed (Item) MFU: sample combining does not apply.
|
|
243
|
+
this.#stats.passthroughSamples++
|
|
244
|
+
return fromPassthrough(mfu)
|
|
245
|
+
}
|
|
246
|
+
const offset = mfu.sampleOffset ?? 0
|
|
247
|
+
const key = sampleKey(
|
|
248
|
+
mfu.catalogTrackName,
|
|
249
|
+
mfu.mpuSequenceNumber,
|
|
250
|
+
mfu.movieFragmentSequenceNumber,
|
|
251
|
+
mfu.sampleNumber,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if (this.#retired.has(key)) {
|
|
255
|
+
// Duplicate or late data unit for an already-retired sample (a
|
|
256
|
+
// FEC-recovered retransmission, reordering after completion, or a
|
|
257
|
+
// sample discarded by eviction/timeout). Drop it rather than
|
|
258
|
+
// re-emitting a duplicate downstream or misreading a stray fragment as
|
|
259
|
+
// a fresh whole sample.
|
|
260
|
+
return null
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const dataLen = mfu.data.byteLength
|
|
264
|
+
const authoredSize = this.#authoredSizes.get(key)
|
|
265
|
+
|
|
266
|
+
if (offset === 0 && !this.#pending.has(key)) {
|
|
267
|
+
const passesThrough = authoredSize === undefined
|
|
268
|
+
? !this.#requireAuthoredSize
|
|
269
|
+
: dataLen === authoredSize
|
|
270
|
+
if (passesThrough) {
|
|
271
|
+
this.#stats.passthroughSamples++
|
|
272
|
+
this.#retire(key)
|
|
273
|
+
return fromPassthrough(mfu)
|
|
274
|
+
}
|
|
275
|
+
// Either shorter than the authored total (a subsample), or awaiting a
|
|
276
|
+
// size under requireAuthoredSize -- buffer below.
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Reject BEFORE mutating any state. An earlier revision allocated the
|
|
280
|
+
// pending entry (evicting the oldest sample to make room) and only then
|
|
281
|
+
// validated, so a data unit the combiner went on to reject could destroy
|
|
282
|
+
// an unrelated in-flight sample and leave an empty entry in its slot.
|
|
283
|
+
const thisEnd = offset + dataLen
|
|
284
|
+
if (authoredSize !== undefined && thisEnd > authoredSize) {
|
|
285
|
+
throw new SampleCombineError(
|
|
286
|
+
'exceeds-authored-size',
|
|
287
|
+
'combined subsample data would exceed the registered authored sample size',
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
const existing = this.#pending.get(key)
|
|
291
|
+
if (existing !== undefined) {
|
|
292
|
+
if (existing.segments.has(offset)) {
|
|
293
|
+
throw new SampleCombineError(
|
|
294
|
+
'duplicate-subsample-offset',
|
|
295
|
+
'subsample data unit duplicates an offset already held for this sample',
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
if (existing.segments.size >= this.#maxSegmentsPerSample) {
|
|
299
|
+
throw new SampleCombineError(
|
|
300
|
+
'too-many-subsample-segments',
|
|
301
|
+
'a sample already holds the maximum number of subsample data units',
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
for (const [segOffset, seg] of existing.segments) {
|
|
305
|
+
const segEnd = segOffset + seg.data.byteLength
|
|
306
|
+
if (offset < segEnd && segOffset < thisEnd) {
|
|
307
|
+
throw new SampleCombineError(
|
|
308
|
+
'overlapping-subsample',
|
|
309
|
+
"subsample data unit overlaps a preceding data unit's byte range",
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let pending = existing
|
|
316
|
+
if (pending === undefined) {
|
|
317
|
+
if (this.#pending.size >= this.#maxBuffered) this.#evictOldestPending()
|
|
318
|
+
pending = { segments: new Map(), startedAt: Date.now() }
|
|
319
|
+
this.#pending.set(key, pending)
|
|
320
|
+
}
|
|
321
|
+
pending.segments.set(offset, mfu)
|
|
322
|
+
|
|
323
|
+
if (authoredSize === undefined) return null
|
|
324
|
+
return this.#tryComplete(key, authoredSize)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Emit the sample when its held data units are byte-adjacent from zero and
|
|
329
|
+
* total `size`. Completion is a protocol event, never a timer.
|
|
330
|
+
*/
|
|
331
|
+
#tryComplete(key: SampleKey, size: number): CombinedSample | null {
|
|
332
|
+
const pending = this.#pending.get(key)
|
|
333
|
+
if (pending === undefined) return null
|
|
334
|
+
let expected = 0
|
|
335
|
+
const sortedOffsets = [...pending.segments.keys()].sort((a, b) => a - b)
|
|
336
|
+
for (const segOffset of sortedOffsets) {
|
|
337
|
+
if (segOffset !== expected) return null // gap: not yet contiguous from zero.
|
|
338
|
+
expected += pending.segments.get(segOffset)!.data.byteLength
|
|
339
|
+
}
|
|
340
|
+
if (expected !== size) return null // contiguous so far, short of the authored total.
|
|
341
|
+
|
|
342
|
+
this.#pending.delete(key)
|
|
343
|
+
this.#stats.combinedSamples++
|
|
344
|
+
this.#retire(key)
|
|
345
|
+
return fromSegments(pending.segments, sortedOffsets)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Retire a sample's key: free its registered authored size and remember the
|
|
350
|
+
* key in a bounded recency set. Called on every path that removes a sample
|
|
351
|
+
* -- completion, passthrough, eviction, and timeout -- so `#authoredSizes`
|
|
352
|
+
* cannot accumulate entries for samples that never complete, and so a later
|
|
353
|
+
* data unit for a discarded sample is dropped rather than starting a fresh
|
|
354
|
+
* buffer or being misread as a whole sample.
|
|
355
|
+
*
|
|
356
|
+
* The recency set holds `maxBuffered` keys. A duplicate delayed by more
|
|
357
|
+
* than that many retirements escapes it; on a reliable path there is no
|
|
358
|
+
* such duplicate, and on a lossy path the per-data-unit deadline in
|
|
359
|
+
* {@link MFUReassembler} bounds the delay well inside this window.
|
|
360
|
+
*/
|
|
361
|
+
#retire(key: SampleKey): void {
|
|
362
|
+
this.#authoredSizes.delete(key)
|
|
363
|
+
this.#retired.add(key)
|
|
364
|
+
while (this.#retired.size > this.#maxBuffered) {
|
|
365
|
+
const oldest = this.#retired.values().next().value
|
|
366
|
+
if (oldest === undefined) break
|
|
367
|
+
this.#retired.delete(oldest)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Discard the oldest pending sample by insertion order. This is the only
|
|
373
|
+
* backstop against one FEC-lost subsample data unit wedging
|
|
374
|
+
* {@link SampleCombiner.addMfu} for every future sample once `maxBuffered`
|
|
375
|
+
* is reached, for a host that never drives
|
|
376
|
+
* {@link SampleCombiner.cleanupTimeouts}.
|
|
377
|
+
*/
|
|
378
|
+
#evictOldestPending(): void {
|
|
379
|
+
const oldest = this.#pending.keys().next().value
|
|
380
|
+
if (oldest === undefined) return
|
|
381
|
+
this.#pending.delete(oldest)
|
|
382
|
+
this.#stats.evictedSamples++
|
|
383
|
+
this.#retire(oldest)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Discard samples whose oldest held segment has waited past `timeoutMs`.
|
|
388
|
+
* Mirrors {@link MFUReassembler}'s clock-driven shape; the per-data-unit
|
|
389
|
+
* deadline inside {@link MFUReassembler} already bounds each constituent,
|
|
390
|
+
* so this is a backstop against a host that registers an authored size but
|
|
391
|
+
* never supplies every subsample DU.
|
|
392
|
+
*/
|
|
393
|
+
cleanupTimeouts(timeoutMs: number): void {
|
|
394
|
+
const now = Date.now()
|
|
395
|
+
for (const [key, pending] of [...this.#pending]) {
|
|
396
|
+
if (now - pending.startedAt > timeoutMs) {
|
|
397
|
+
this.#pending.delete(key)
|
|
398
|
+
this.#stats.timedOutSamples++
|
|
399
|
+
this.#retire(key)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
flush(): void {
|
|
405
|
+
this.#pending.clear()
|
|
406
|
+
this.#authoredSizes.clear()
|
|
407
|
+
this.#retired.clear()
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Drop one stream's in-flight, registered-size, and retired state while
|
|
412
|
+
* preserving every other stream (mirrors
|
|
413
|
+
* {@link MFUReassembler.resetStream}).
|
|
414
|
+
*/
|
|
415
|
+
resetStream(catalogTrackName: string): void {
|
|
416
|
+
for (const key of this.#pending.keys()) {
|
|
417
|
+
if (trackNameOfKey(key) === catalogTrackName) this.#pending.delete(key)
|
|
418
|
+
}
|
|
419
|
+
for (const key of this.#authoredSizes.keys()) {
|
|
420
|
+
if (trackNameOfKey(key) === catalogTrackName) this.#authoredSizes.delete(key)
|
|
421
|
+
}
|
|
422
|
+
for (const key of this.#retired) {
|
|
423
|
+
if (trackNameOfKey(key) === catalogTrackName) this.#retired.delete(key)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
get stats(): SampleCombinerStats {
|
|
428
|
+
return { ...this.#stats }
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
get pendingCount(): number {
|
|
432
|
+
return this.#pending.size
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Registered authored sizes still held. Diagnostic only: it exists so tests
|
|
437
|
+
* and health reporting can assert this table stays bounded, since an
|
|
438
|
+
* unbounded one is invisible from `pendingCount` alone.
|
|
439
|
+
*/
|
|
440
|
+
get debugAuthoredSizeCount(): number {
|
|
441
|
+
return this.#authoredSizes.size
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Retired keys still remembered. Diagnostic only, bounded by `maxBuffered`. */
|
|
445
|
+
get debugRetiredCount(): number {
|
|
446
|
+
return this.#retired.size
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function fromPassthrough(mfu: ReassembledMFU): CombinedSample {
|
|
451
|
+
return {
|
|
452
|
+
mpuSequenceNumber: mfu.mpuSequenceNumber,
|
|
453
|
+
movieFragmentSequenceNumber: mfu.movieFragmentSequenceNumber ?? 0,
|
|
454
|
+
sampleNumber: mfu.sampleNumber ?? 0,
|
|
455
|
+
data: mfu.data,
|
|
456
|
+
timestamp: mfu.timestamp,
|
|
457
|
+
rapFlag: mfu.rapFlag,
|
|
458
|
+
priority: mfu.priority,
|
|
459
|
+
packetSequenceNumbers: mfu.packetSequenceNumbers,
|
|
460
|
+
repairedPacketSequenceNumbers: mfu.repairedPacketSequenceNumbers,
|
|
461
|
+
catalogTrackName: mfu.catalogTrackName,
|
|
462
|
+
subsampleCount: 1,
|
|
463
|
+
fragmentCount: mfu.fragmentCount,
|
|
464
|
+
source: mfu.source,
|
|
465
|
+
// One data unit in, one sample out: its provenance IS the sample's.
|
|
466
|
+
deliveryProvenance: mfu.deliveryProvenance
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function fromSegments(
|
|
471
|
+
segments: Map<number, ReassembledMFU>,
|
|
472
|
+
sortedOffsets: number[],
|
|
473
|
+
): CombinedSample {
|
|
474
|
+
const totalSize = sortedOffsets.reduce((sum, offset) => sum + segments.get(offset)!.data.byteLength, 0)
|
|
475
|
+
const data = new Uint8Array(totalSize)
|
|
476
|
+
let packetSequenceNumbers: number[] | undefined
|
|
477
|
+
let repairedPacketSequenceNumbers: number[] | undefined
|
|
478
|
+
let writeOffset = 0
|
|
479
|
+
let first: ReassembledMFU | undefined
|
|
480
|
+
let fragmentCount = 0
|
|
481
|
+
// Fold across data units the same way the reassembler folds across the
|
|
482
|
+
// fragments inside one: a sample is multicast-exclusive only if every
|
|
483
|
+
// subsample that formed it was.
|
|
484
|
+
let combinedProvenance: MmtpDeliveryProvenance =
|
|
485
|
+
sortedOffsets.length === 0 ? 'unknown' : 'multicast'
|
|
486
|
+
for (const offset of sortedOffsets) {
|
|
487
|
+
const seg = segments.get(offset)!
|
|
488
|
+
combinedProvenance =
|
|
489
|
+
combinedProvenance === 'mixed' || seg.deliveryProvenance === 'mixed'
|
|
490
|
+
? 'mixed'
|
|
491
|
+
: combinedProvenance === 'unknown' || seg.deliveryProvenance === 'unknown'
|
|
492
|
+
? 'unknown'
|
|
493
|
+
: 'multicast'
|
|
494
|
+
data.set(seg.data, writeOffset)
|
|
495
|
+
writeOffset += seg.data.byteLength
|
|
496
|
+
fragmentCount += seg.fragmentCount
|
|
497
|
+
if (seg.packetSequenceNumbers !== undefined) {
|
|
498
|
+
(packetSequenceNumbers ??= []).push(...seg.packetSequenceNumbers)
|
|
499
|
+
}
|
|
500
|
+
if (seg.repairedPacketSequenceNumbers !== undefined) {
|
|
501
|
+
(repairedPacketSequenceNumbers ??= []).push(...seg.repairedPacketSequenceNumbers)
|
|
502
|
+
}
|
|
503
|
+
if (first === undefined) first = seg
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
mpuSequenceNumber: first!.mpuSequenceNumber,
|
|
507
|
+
movieFragmentSequenceNumber: first!.movieFragmentSequenceNumber!,
|
|
508
|
+
sampleNumber: first!.sampleNumber!,
|
|
509
|
+
data,
|
|
510
|
+
timestamp: first!.timestamp,
|
|
511
|
+
rapFlag: first!.rapFlag,
|
|
512
|
+
priority: first!.priority,
|
|
513
|
+
packetSequenceNumbers,
|
|
514
|
+
repairedPacketSequenceNumbers,
|
|
515
|
+
catalogTrackName: first!.catalogTrackName,
|
|
516
|
+
subsampleCount: segments.size,
|
|
517
|
+
fragmentCount,
|
|
518
|
+
source: first!.source,
|
|
519
|
+
deliveryProvenance: combinedProvenance,
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// =============================================================================
|
|
524
|
+
// Inline Tests (vitest)
|
|
525
|
+
// =============================================================================
|
|
526
|
+
|
|
527
|
+
if (import.meta.vitest) {
|
|
528
|
+
const { describe, test, expect } = import.meta.vitest
|
|
529
|
+
|
|
530
|
+
const combiner = (overrides: Partial<SampleCombinerOptions> = {}) =>
|
|
531
|
+
new SampleCombiner({
|
|
532
|
+
maxBuffered: 10,
|
|
533
|
+
requireAuthoredSize: false,
|
|
534
|
+
maxSegmentsPerSample: 8,
|
|
535
|
+
...overrides,
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
const mfu = (
|
|
539
|
+
mpu: number,
|
|
540
|
+
movie: number,
|
|
541
|
+
sample: number,
|
|
542
|
+
offset: number,
|
|
543
|
+
data: number[],
|
|
544
|
+
timestamp: bigint,
|
|
545
|
+
rapFlag: boolean,
|
|
546
|
+
deliveryProvenance: MmtpDeliveryProvenance = 'multicast',
|
|
547
|
+
): ReassembledMFU => ({
|
|
548
|
+
mpuSequenceNumber: mpu,
|
|
549
|
+
data: new Uint8Array(data),
|
|
550
|
+
timestamp,
|
|
551
|
+
fragmentCount: 1,
|
|
552
|
+
rapFlag,
|
|
553
|
+
sampleNumber: sample,
|
|
554
|
+
movieFragmentSequenceNumber: movie,
|
|
555
|
+
sampleOffset: offset,
|
|
556
|
+
packetSequenceNumbers: [offset],
|
|
557
|
+
deliveryProvenance,
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
describe('SampleCombiner', () => {
|
|
561
|
+
test('whole-sample DU passes through immediately with no registered size', () => {
|
|
562
|
+
const c = combiner()
|
|
563
|
+
const out = c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))
|
|
564
|
+
expect(out).not.toBeNull()
|
|
565
|
+
expect(out!.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]))
|
|
566
|
+
expect(out!.subsampleCount).toBe(1)
|
|
567
|
+
expect(c.pendingCount).toBe(0)
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
test('whole-sample DU passes through when it matches the registered size', () => {
|
|
571
|
+
const c = combiner()
|
|
572
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 5)
|
|
573
|
+
const out = c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))
|
|
574
|
+
expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]))
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
test('subsample data units combine once the authored size is met', () => {
|
|
578
|
+
const c = combiner()
|
|
579
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 10)
|
|
580
|
+
|
|
581
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
|
|
582
|
+
expect(c.pendingCount).toBe(1)
|
|
583
|
+
|
|
584
|
+
const out = c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))
|
|
585
|
+
expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
|
|
586
|
+
expect(out?.subsampleCount).toBe(2)
|
|
587
|
+
expect(out?.rapFlag).toBe(true) // offset-0 DU's RAP flag, per moqcast #37.
|
|
588
|
+
expect(out?.timestamp).toBe(1000n)
|
|
589
|
+
expect(c.pendingCount).toBe(0)
|
|
590
|
+
expect(c.stats.combinedSamples).toBe(1)
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
test('subsample data units combine out of order', () => {
|
|
594
|
+
const c = combiner()
|
|
595
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 10)
|
|
596
|
+
|
|
597
|
+
expect(c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))).toBeNull()
|
|
598
|
+
const out = c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))
|
|
599
|
+
expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
test('subsample without a registered size never completes', () => {
|
|
603
|
+
// A non-zero-offset DU always buffers (it can never be a whole
|
|
604
|
+
// sample). Without a registered authored size there is nothing to
|
|
605
|
+
// combine against, so it -- and a sibling that arrives after it --
|
|
606
|
+
// stay pending rather than being guessed complete.
|
|
607
|
+
const c = combiner()
|
|
608
|
+
expect(c.addMfu(mfu(1, 9, 17, 3, [1, 2, 3], 1001n, false))).toBeNull()
|
|
609
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3], 1000n, true))).toBeNull()
|
|
610
|
+
expect(c.pendingCount).toBe(1)
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
test('requireAuthoredSize buffers an offset-zero DU instead of guessing complete', () => {
|
|
614
|
+
// A moqcast-#37-aware host opts into fail-closed buffering: on a lossy
|
|
615
|
+
// path FT=1 can arrive after an offset-zero data unit (reordering, or
|
|
616
|
+
// FT=1's own DU recovered late by FEC). Treating "no size yet" as
|
|
617
|
+
// "whole sample" would silently truncate a partitioned sample.
|
|
618
|
+
const c = combiner({ requireAuthoredSize: true })
|
|
619
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
|
|
620
|
+
expect(c.pendingCount).toBe(1)
|
|
621
|
+
|
|
622
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 10)
|
|
623
|
+
const out = c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))
|
|
624
|
+
expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
test('duplicate offset is rejected', () => {
|
|
628
|
+
const c = combiner()
|
|
629
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 10)
|
|
630
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
|
|
631
|
+
expect(() => c.addMfu(mfu(1, 9, 17, 0, [9, 9, 9, 9, 9], 2000n, true)))
|
|
632
|
+
.toThrow(SampleCombineError)
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
test('overlapping subsample is rejected', () => {
|
|
636
|
+
const c = combiner()
|
|
637
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 10)
|
|
638
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
|
|
639
|
+
expect(() => c.addMfu(mfu(1, 9, 17, 3, [9, 9, 9, 9, 9], 2000n, true)))
|
|
640
|
+
.toThrow(SampleCombineError)
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
test('exceeding the authored size is rejected', () => {
|
|
644
|
+
const c = combiner()
|
|
645
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 8)
|
|
646
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
|
|
647
|
+
expect(() => c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 2000n, false)))
|
|
648
|
+
.toThrow(SampleCombineError)
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
test('exceeding maxSegmentsPerSample is rejected', () => {
|
|
652
|
+
const c = combiner({ maxSegmentsPerSample: 2 })
|
|
653
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 100)
|
|
654
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2], 1000n, true))).toBeNull()
|
|
655
|
+
expect(c.addMfu(mfu(1, 9, 17, 2, [3, 4], 1001n, false))).toBeNull()
|
|
656
|
+
expect(() => c.addMfu(mfu(1, 9, 17, 4, [5, 6], 1002n, false)))
|
|
657
|
+
.toThrow(SampleCombineError)
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
test('a full combiner evicts the oldest pending sample instead of throwing', () => {
|
|
661
|
+
// Eviction is by insertion order, which is also the only backstop
|
|
662
|
+
// against one permanently-lost subsample DU wedging every future
|
|
663
|
+
// sample once maxBuffered incomplete samples accumulate.
|
|
664
|
+
const c = combiner({ maxBuffered: 2 })
|
|
665
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 10)
|
|
666
|
+
c.registerAuthoredSize(undefined, 1, 9, 18, 10)
|
|
667
|
+
c.registerAuthoredSize(undefined, 1, 9, 19, 10)
|
|
668
|
+
|
|
669
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
|
|
670
|
+
expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()
|
|
671
|
+
expect(c.pendingCount).toBe(2)
|
|
672
|
+
|
|
673
|
+
// A third distinct incomplete sample evicts sample 17, the oldest.
|
|
674
|
+
expect(c.addMfu(mfu(1, 9, 19, 0, [3, 3, 3, 3, 3], 1002n, true))).toBeNull()
|
|
675
|
+
expect(c.pendingCount).toBe(2)
|
|
676
|
+
expect(c.stats.evictedSamples).toBe(1)
|
|
677
|
+
|
|
678
|
+
// The surviving samples are undisturbed and still complete normally.
|
|
679
|
+
expect(c.addMfu(mfu(1, 9, 18, 5, [8, 8, 8, 8, 8], 2001n, false))?.data)
|
|
680
|
+
.toEqual(new Uint8Array([2, 2, 2, 2, 2, 8, 8, 8, 8, 8]))
|
|
681
|
+
expect(c.addMfu(mfu(1, 9, 19, 5, [9, 9, 9, 9, 9], 2002n, false))?.data)
|
|
682
|
+
.toEqual(new Uint8Array([3, 3, 3, 3, 3, 9, 9, 9, 9, 9]))
|
|
683
|
+
|
|
684
|
+
// The combiner still accepts new work rather than being wedged.
|
|
685
|
+
c.registerAuthoredSize(undefined, 1, 9, 20, 10)
|
|
686
|
+
expect(c.addMfu(mfu(1, 9, 20, 0, [4, 4, 4, 4, 4], 3000n, true))).toBeNull()
|
|
687
|
+
expect(c.addMfu(mfu(1, 9, 20, 5, [7, 7, 7, 7, 7], 3001n, false))?.data)
|
|
688
|
+
.toEqual(new Uint8Array([4, 4, 4, 4, 4, 7, 7, 7, 7, 7]))
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
test('a completed sample frees its authored size and drops a late duplicate', () => {
|
|
692
|
+
const c = combiner()
|
|
693
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 10)
|
|
694
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
|
|
695
|
+
expect(c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))).not.toBeNull()
|
|
696
|
+
|
|
697
|
+
// A FEC-recovered retransmission of the completed sample's offset-0
|
|
698
|
+
// data unit must not be re-emitted as a second complete sample.
|
|
699
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 2000n, true))).toBeNull()
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
test('resetStream drops one track and preserves the others', () => {
|
|
703
|
+
const c = combiner()
|
|
704
|
+
c.registerAuthoredSize('video/720p', 1, 9, 17, 10)
|
|
705
|
+
c.registerAuthoredSize('audio', 1, 9, 17, 10)
|
|
706
|
+
const video = { ...mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true), catalogTrackName: 'video/720p' }
|
|
707
|
+
const audio = { ...mfu(1, 9, 17, 0, [2, 2, 2, 2, 2], 1000n, true), catalogTrackName: 'audio' }
|
|
708
|
+
expect(c.addMfu(video)).toBeNull()
|
|
709
|
+
expect(c.addMfu(audio)).toBeNull()
|
|
710
|
+
|
|
711
|
+
c.resetStream('video/720p')
|
|
712
|
+
expect(c.pendingCount).toBe(1)
|
|
713
|
+
|
|
714
|
+
const audioTail = { ...mfu(1, 9, 17, 5, [3, 3, 3, 3, 3], 1001n, false), catalogTrackName: 'audio' }
|
|
715
|
+
expect(c.addMfu(audioTail)?.data).toEqual(new Uint8Array([2, 2, 2, 2, 2, 3, 3, 3, 3, 3]))
|
|
716
|
+
})
|
|
717
|
+
|
|
718
|
+
test('flush clears registered authored sizes', () => {
|
|
719
|
+
const c = combiner()
|
|
720
|
+
c.registerAuthoredSize(undefined, 1, 9, 17, 5)
|
|
721
|
+
c.flush()
|
|
722
|
+
// A now-unregistered size means an offset-zero DU passes through
|
|
723
|
+
// immediately again (requireAuthoredSize is false here) rather than
|
|
724
|
+
// waiting on a size flush() already discarded.
|
|
725
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))?.data)
|
|
726
|
+
.toEqual(new Uint8Array([1, 2, 3, 4, 5]))
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
test('a healthy stream accumulates no bookkeeping', () => {
|
|
730
|
+
// Regression: retirement ran only on completion paths, so an
|
|
731
|
+
// abandoned sample's registered size stayed resident forever.
|
|
732
|
+
const c = combiner({ maxBuffered: 4, requireAuthoredSize: true })
|
|
733
|
+
for (let i = 0; i < 1000; i++) {
|
|
734
|
+
expect(c.registerAuthoredSize(undefined, 1, 9, i, 10)).toBeNull()
|
|
735
|
+
expect(c.addMfu(mfu(1, 9, i, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
|
|
736
|
+
expect(c.addMfu(mfu(1, 9, i, 5, [2, 2, 2, 2, 2], 1001n, false))).not.toBeNull()
|
|
737
|
+
}
|
|
738
|
+
expect(c.stats.combinedSamples).toBe(1000)
|
|
739
|
+
expect(c.pendingCount).toBe(0)
|
|
740
|
+
expect(c.debugAuthoredSizeCount).toBe(0)
|
|
741
|
+
expect(c.debugRetiredCount).toBeLessThanOrEqual(4)
|
|
742
|
+
})
|
|
743
|
+
|
|
744
|
+
test('samples that never complete release their authored size', () => {
|
|
745
|
+
const c = combiner({ maxBuffered: 4, requireAuthoredSize: true })
|
|
746
|
+
for (let i = 0; i < 1000; i++) {
|
|
747
|
+
expect(c.registerAuthoredSize(undefined, 1, 9, i, 10)).toBeNull()
|
|
748
|
+
expect(c.addMfu(mfu(1, 9, i, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
|
|
749
|
+
// Deliberately never supply the tail.
|
|
750
|
+
}
|
|
751
|
+
c.cleanupTimeouts(-1) // expire everything still held
|
|
752
|
+
expect(c.pendingCount).toBe(0)
|
|
753
|
+
expect(c.debugAuthoredSizeCount).toBe(0)
|
|
754
|
+
})
|
|
755
|
+
|
|
756
|
+
test('a late authored size completes an already-buffered sample', () => {
|
|
757
|
+
// Regression: with requireAuthoredSize the completion check ran only
|
|
758
|
+
// inside addMfu, so a sample carried by a SINGLE data unit whose FT=1
|
|
759
|
+
// arrived afterwards had no later data unit to re-enter on and was
|
|
760
|
+
// silently lost.
|
|
761
|
+
const c = combiner({ requireAuthoredSize: true })
|
|
762
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
|
|
763
|
+
expect(c.pendingCount).toBe(1)
|
|
764
|
+
|
|
765
|
+
const out = c.registerAuthoredSize(undefined, 1, 9, 17, 5)
|
|
766
|
+
expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]))
|
|
767
|
+
expect(out?.subsampleCount).toBe(1)
|
|
768
|
+
expect(c.pendingCount).toBe(0)
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
test('a rejected data unit does not evict a live sample', () => {
|
|
772
|
+
// Regression: the pending slot was allocated (evicting the oldest
|
|
773
|
+
// sample to make room) BEFORE validation, so a data unit the combiner
|
|
774
|
+
// went on to reject destroyed an unrelated in-flight sample.
|
|
775
|
+
const c = combiner({ maxBuffered: 2 })
|
|
776
|
+
for (const s of [17, 18, 19]) c.registerAuthoredSize(undefined, 1, 9, s, 10)
|
|
777
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
|
|
778
|
+
expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()
|
|
779
|
+
expect(c.pendingCount).toBe(2)
|
|
780
|
+
|
|
781
|
+
// 8 + 5 = 13 exceeds sample 19's authored size of 10.
|
|
782
|
+
expect(() => c.addMfu(mfu(1, 9, 19, 8, [9, 9, 9, 9, 9], 2000n, false)))
|
|
783
|
+
.toThrow(SampleCombineError)
|
|
784
|
+
expect(c.stats.evictedSamples).toBe(0)
|
|
785
|
+
expect(c.pendingCount).toBe(2)
|
|
786
|
+
|
|
787
|
+
// Sample 17 is untouched and still completes.
|
|
788
|
+
expect(c.addMfu(mfu(1, 9, 17, 5, [3, 3, 3, 3, 3], 2001n, false))?.data)
|
|
789
|
+
.toEqual(new Uint8Array([1, 1, 1, 1, 1, 3, 3, 3, 3, 3]))
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
test('an evicted sample is retired rather than reopened', () => {
|
|
793
|
+
const c = combiner({ maxBuffered: 1 })
|
|
794
|
+
for (const s of [17, 18]) c.registerAuthoredSize(undefined, 1, 9, s, 10)
|
|
795
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
|
|
796
|
+
expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()
|
|
797
|
+
expect(c.stats.evictedSamples).toBe(1)
|
|
798
|
+
|
|
799
|
+
// Sample 17's tail arrives after its eviction; it must not reopen.
|
|
800
|
+
expect(c.addMfu(mfu(1, 9, 17, 5, [3, 3, 3, 3, 3], 2000n, false))).toBeNull()
|
|
801
|
+
expect(c.pendingCount).toBe(1)
|
|
802
|
+
})
|
|
803
|
+
})
|
|
804
|
+
|
|
805
|
+
describe('sample-level delivery provenance', () => {
|
|
806
|
+
test('one non-multicast subsample makes the whole sample non-exclusive', () => {
|
|
807
|
+
// A 4K/8K sample is split across subsample DUs. If any one of them
|
|
808
|
+
// arrived over QUIC, the assembled sample contains bytes that never
|
|
809
|
+
// crossed a multicast socket, so the sample is not physical evidence
|
|
810
|
+
// even though every other DU was multicast.
|
|
811
|
+
const c = combiner()
|
|
812
|
+
c.registerAuthoredSize(undefined, 1, 1, 1, 4)
|
|
813
|
+
expect(c.addMfu(mfu(1, 1, 1, 0, [1, 2], 100n, true, 'multicast'))).toBeNull()
|
|
814
|
+
const done = c.addMfu(mfu(1, 1, 1, 2, [3, 4], 100n, false, 'mixed'))
|
|
815
|
+
expect(done).not.toBeNull()
|
|
816
|
+
expect(done!.subsampleCount).toBe(2)
|
|
817
|
+
expect(done!.deliveryProvenance).toBe('mixed')
|
|
818
|
+
})
|
|
819
|
+
|
|
820
|
+
test('an unattributed subsample degrades the sample to unknown', () => {
|
|
821
|
+
const c = combiner()
|
|
822
|
+
c.registerAuthoredSize(undefined, 2, 1, 1, 4)
|
|
823
|
+
expect(c.addMfu(mfu(2, 1, 1, 0, [1, 2], 100n, true, 'multicast'))).toBeNull()
|
|
824
|
+
const done = c.addMfu(mfu(2, 1, 1, 2, [3, 4], 100n, false, 'unknown'))
|
|
825
|
+
expect(done).not.toBeNull()
|
|
826
|
+
expect(done!.deliveryProvenance).toBe('unknown')
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
test('an all-multicast sample stays multicast-exclusive', () => {
|
|
830
|
+
const c = combiner()
|
|
831
|
+
c.registerAuthoredSize(undefined, 3, 1, 1, 4)
|
|
832
|
+
expect(c.addMfu(mfu(3, 1, 1, 0, [1, 2], 100n, true, 'multicast'))).toBeNull()
|
|
833
|
+
const done = c.addMfu(mfu(3, 1, 1, 2, [3, 4], 100n, false, 'multicast'))
|
|
834
|
+
expect(done).not.toBeNull()
|
|
835
|
+
expect(done!.deliveryProvenance).toBe('multicast')
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
test('a passthrough sample inherits its data unit provenance verbatim', () => {
|
|
839
|
+
const c = combiner()
|
|
840
|
+
const done = c.addMfu(mfu(4, 1, 1, 0, [1, 2], 100n, true, 'mixed'))
|
|
841
|
+
expect(done).not.toBeNull()
|
|
842
|
+
expect(done!.subsampleCount).toBe(1)
|
|
843
|
+
expect(done!.deliveryProvenance).toBe('mixed')
|
|
844
|
+
})
|
|
845
|
+
})
|
|
846
|
+
}
|