@blockcast/mmt-container 0.3.0 → 0.4.0-main.d6f2737828fc
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 +22 -8
- package/dist/browser-runtime.js +1185 -1049
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.d.ts +24 -8
- package/dist/carp-catalog.d.ts.map +1 -1
- package/dist/carp-catalog.js +21 -8
- package/dist/carp-catalog.js.map +2 -2
- package/dist/catalog-fec-params.d.ts +88 -28
- package/dist/catalog-fec-params.d.ts.map +1 -1
- package/dist/catalog-fec-params.js +59 -17
- package/dist/catalog-fec-params.js.map +3 -3
- package/dist/catalog-network-source.js +0 -1
- package/dist/catalog-network-source.js.map +2 -2
- package/dist/cmaf-block-assembler.d.ts +0 -2
- package/dist/cmaf-block-assembler.d.ts.map +1 -1
- package/dist/cmaf-block-assembler.js +0 -1
- package/dist/cmaf-block-assembler.js.map +2 -2
- package/dist/delta-fallback-config.d.ts +42 -0
- package/dist/delta-fallback-config.d.ts.map +1 -0
- package/dist/delta-fallback-config.js +59 -0
- package/dist/delta-fallback-config.js.map +7 -0
- package/dist/fec-manager.d.ts +35 -26
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +300 -273
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-state.d.ts +19 -31
- package/dist/fec-state.d.ts.map +1 -1
- package/dist/fec-state.js +3 -4
- package/dist/fec-state.js.map +2 -2
- package/dist/fec-worker-setup.d.ts +16 -6
- package/dist/fec-worker-setup.d.ts.map +1 -1
- package/dist/fec-worker-setup.js +72 -34
- package/dist/fec-worker-setup.js.map +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.js +0 -1
- package/dist/iso-block-locator.js.map +2 -2
- package/dist/media-router.d.ts +20 -8
- package/dist/media-router.d.ts.map +1 -1
- package/dist/media-router.js +46 -21
- package/dist/media-router.js.map +2 -2
- package/dist/mfu-reassembler.js +0 -1
- package/dist/mfu-reassembler.js.map +2 -2
- package/dist/mmtp-container-parser.d.ts +5 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +22 -1
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +37 -9
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +22 -1
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.d.ts +36 -16
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +126 -213
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-input.js +0 -1
- package/dist/multicast-input.js.map +2 -2
- package/dist/multicast-manager.d.ts +33 -3
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +660 -529
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +1 -1
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +42 -14
- package/dist/multicast-transport.js.map +3 -3
- package/dist/multicast-unwrap.js +0 -1
- package/dist/multicast-unwrap.js.map +2 -2
- package/dist/no-modules.js +36 -8
- package/dist/no-modules.js.map +2 -2
- package/dist/repair-fetch.d.ts +30 -19
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +32 -18
- package/dist/repair-fetch.js.map +3 -3
- package/dist/repair-layers.d.ts +18 -7
- package/dist/repair-layers.d.ts.map +1 -1
- package/dist/repair-layers.js +11 -4
- package/dist/repair-layers.js.map +2 -2
- package/dist/signaling.js +0 -4
- package/dist/signaling.js.map +2 -2
- package/dist/timestamp-codec.js +0 -1
- package/dist/timestamp-codec.js.map +2 -2
- package/dist/track-session.d.ts +7 -0
- package/dist/track-session.d.ts.map +1 -1
- package/dist/track-session.js +15 -8
- package/dist/track-session.js.map +2 -2
- package/dist/wasm-mfu-reassembler.js +0 -1
- package/dist/wasm-mfu-reassembler.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +22 -1
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +8 -14
- package/schemas/catalog.schema.json +3 -3
- package/src/carp-catalog.ts +35 -13
- package/src/catalog-fec-params.ts +173 -64
- package/src/cmaf-block-assembler.ts +1 -4
- package/src/delta-fallback-config.ts +72 -0
- package/src/fec-manager.ts +413 -317
- package/src/fec-state.ts +20 -32
- package/src/fec-worker-setup.ts +105 -55
- package/src/index.ts +10 -0
- package/src/media-router.ts +59 -21
- package/src/mmtp-container-parser.ts +26 -0
- package/src/mmtp-router.ts +145 -303
- package/src/multicast-manager.ts +172 -60
- package/src/multicast-transport.ts +16 -9
- package/src/repair-fetch.ts +46 -27
- package/src/repair-layers.ts +28 -8
- package/src/track-session.ts +28 -9
- package/test-vectors/catalog/README.md +34 -17
- package/test-vectors/catalog/negative/fec-block-profile-retired.json +52 -0
- package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +1 -1
- package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +1 -1
- package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +1 -1
- package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +1 -1
- package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +1 -1
- package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +1 -1
- package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +1 -1
- package/test-vectors/catalog/negative/multicast-network-source-empty.json +1 -1
- package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +1 -1
- package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +1 -1
- package/test-vectors/catalog/positive/fec-layered-repair.json +2 -2
- package/test-vectors/catalog/positive/fec-multicast.json +2 -1
- package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +2 -1
- package/test-vectors/catalog-timing.json +356 -0
- package/test-vectors/catalog-validation.json +40 -5
- package/node_modules/@blockcast/fec-worker/README.md +0 -223
- package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts +0 -38
- package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/block-order.js +0 -78
- package/node_modules/@blockcast/fec-worker/dist/block-order.js.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +0 -93
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +0 -346
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +0 -380
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +0 -11
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +0 -14
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +0 -859
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +0 -7
- package/node_modules/@blockcast/fec-worker/dist/index.d.ts +0 -10
- package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/index.js +0 -8
- package/node_modules/@blockcast/fec-worker/dist/index.js.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +0 -20
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +0 -370
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +0 -7
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +0 -104
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +0 -181
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +0 -194
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +0 -62
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +0 -9
- package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +0 -1
- package/node_modules/@blockcast/fec-worker/dist/transfer.js +0 -13
- package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +0 -1
- package/node_modules/@blockcast/fec-worker/package.json +0 -59
- package/node_modules/@blockcast/fec-worker/src/block-order.ts +0 -93
- package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +0 -392
- package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +0 -399
- package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +0 -1502
- package/node_modules/@blockcast/fec-worker/src/index.ts +0 -35
- package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +0 -589
- package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +0 -222
- package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +0 -209
- package/node_modules/@blockcast/fec-worker/src/transfer.ts +0 -12
- package/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
- package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
- package/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
- package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
- package/node_modules/@blockcast/mmt-base/README.md +0 -134
- package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
- package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
- package/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
- package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
- package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
- package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
- package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
- package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
- package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
- package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
- package/node_modules/@blockcast/mmt-base/package.json +0 -49
- package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
- package/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
- package/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
- package/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
- package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
- package/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
- package/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
- package/node_modules/@blockcast/mmt-fec/README.md +0 -271
- package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +0 -82
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +0 -296
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +0 -106
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +0 -288
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +0 -46
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +0 -96
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +0 -33
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +0 -57
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +0 -205
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +0 -240
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +0 -28
- package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/index.js +0 -28
- package/node_modules/@blockcast/mmt-fec/dist/index.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +0 -20
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +0 -28
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +0 -258
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/oti.js +0 -382
- package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +0 -41
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +0 -81
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts +0 -82
- package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/dist/repair-group.js +0 -170
- package/node_modules/@blockcast/mmt-fec/dist/repair-group.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
- 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 +0 -57
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +0 -134
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +0 -49
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
- package/node_modules/@blockcast/mmt-fec/package.json +0 -79
- package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +0 -371
- package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +0 -439
- package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +0 -127
- package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +0 -68
- package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +0 -310
- package/node_modules/@blockcast/mmt-fec/src/index.ts +0 -94
- package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +0 -34
- package/node_modules/@blockcast/mmt-fec/src/oti.ts +0 -463
- package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +0 -101
- package/node_modules/@blockcast/mmt-fec/src/repair-group.ts +0 -195
- package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +0 -28
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +0 -128
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +0 -1
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +0 -4
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +0 -4
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +0 -1
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +0 -37
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +0 -179
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +0 -1
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +0 -99
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +0 -1
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +0 -492
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +0 -1
- package/node_modules/@blockcast/mmt-manifest-verify/package.json +0 -45
- package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +0 -171
- package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +0 -3
- package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +0 -230
- package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +0 -669
- package/node_modules/@noble/hashes/LICENSE +0 -21
- package/node_modules/@noble/hashes/README.md +0 -521
- package/node_modules/@noble/hashes/_assert.d.ts +0 -17
- package/node_modules/@noble/hashes/_assert.d.ts.map +0 -1
- package/node_modules/@noble/hashes/_assert.js +0 -18
- package/node_modules/@noble/hashes/_assert.js.map +0 -1
- package/node_modules/@noble/hashes/_blake.d.ts +0 -14
- package/node_modules/@noble/hashes/_blake.d.ts.map +0 -1
- package/node_modules/@noble/hashes/_blake.js +0 -50
- package/node_modules/@noble/hashes/_blake.js.map +0 -1
- package/node_modules/@noble/hashes/_md.d.ts +0 -51
- package/node_modules/@noble/hashes/_md.d.ts.map +0 -1
- package/node_modules/@noble/hashes/_md.js +0 -162
- package/node_modules/@noble/hashes/_md.js.map +0 -1
- package/node_modules/@noble/hashes/_u64.d.ts +0 -55
- package/node_modules/@noble/hashes/_u64.d.ts.map +0 -1
- package/node_modules/@noble/hashes/_u64.js +0 -90
- package/node_modules/@noble/hashes/_u64.js.map +0 -1
- package/node_modules/@noble/hashes/argon2.d.ts +0 -32
- package/node_modules/@noble/hashes/argon2.d.ts.map +0 -1
- package/node_modules/@noble/hashes/argon2.js +0 -401
- package/node_modules/@noble/hashes/argon2.js.map +0 -1
- package/node_modules/@noble/hashes/blake1.d.ts +0 -106
- package/node_modules/@noble/hashes/blake1.d.ts.map +0 -1
- package/node_modules/@noble/hashes/blake1.js +0 -459
- package/node_modules/@noble/hashes/blake1.js.map +0 -1
- package/node_modules/@noble/hashes/blake2.d.ts +0 -116
- package/node_modules/@noble/hashes/blake2.d.ts.map +0 -1
- package/node_modules/@noble/hashes/blake2.js +0 -420
- package/node_modules/@noble/hashes/blake2.js.map +0 -1
- package/node_modules/@noble/hashes/blake2b.d.ts +0 -11
- package/node_modules/@noble/hashes/blake2b.d.ts.map +0 -1
- package/node_modules/@noble/hashes/blake2b.js +0 -14
- package/node_modules/@noble/hashes/blake2b.js.map +0 -1
- package/node_modules/@noble/hashes/blake2s.d.ts +0 -20
- package/node_modules/@noble/hashes/blake2s.d.ts.map +0 -1
- package/node_modules/@noble/hashes/blake2s.js +0 -24
- package/node_modules/@noble/hashes/blake2s.js.map +0 -1
- package/node_modules/@noble/hashes/blake3.d.ts +0 -54
- package/node_modules/@noble/hashes/blake3.d.ts.map +0 -1
- package/node_modules/@noble/hashes/blake3.js +0 -255
- package/node_modules/@noble/hashes/blake3.js.map +0 -1
- package/node_modules/@noble/hashes/crypto.d.ts +0 -2
- package/node_modules/@noble/hashes/crypto.d.ts.map +0 -1
- package/node_modules/@noble/hashes/crypto.js +0 -5
- package/node_modules/@noble/hashes/crypto.js.map +0 -1
- package/node_modules/@noble/hashes/cryptoNode.d.ts +0 -2
- package/node_modules/@noble/hashes/cryptoNode.d.ts.map +0 -1
- package/node_modules/@noble/hashes/cryptoNode.js +0 -18
- package/node_modules/@noble/hashes/cryptoNode.js.map +0 -1
- package/node_modules/@noble/hashes/eskdf.d.ts +0 -47
- package/node_modules/@noble/hashes/eskdf.d.ts.map +0 -1
- package/node_modules/@noble/hashes/eskdf.js +0 -166
- package/node_modules/@noble/hashes/eskdf.js.map +0 -1
- package/node_modules/@noble/hashes/esm/_assert.d.ts +0 -17
- package/node_modules/@noble/hashes/esm/_assert.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/_assert.js +0 -15
- package/node_modules/@noble/hashes/esm/_assert.js.map +0 -1
- package/node_modules/@noble/hashes/esm/_blake.d.ts +0 -14
- package/node_modules/@noble/hashes/esm/_blake.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/_blake.js +0 -45
- package/node_modules/@noble/hashes/esm/_blake.js.map +0 -1
- package/node_modules/@noble/hashes/esm/_md.d.ts +0 -51
- package/node_modules/@noble/hashes/esm/_md.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/_md.js +0 -155
- package/node_modules/@noble/hashes/esm/_md.js.map +0 -1
- package/node_modules/@noble/hashes/esm/_u64.d.ts +0 -55
- package/node_modules/@noble/hashes/esm/_u64.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/_u64.js +0 -67
- package/node_modules/@noble/hashes/esm/_u64.js.map +0 -1
- package/node_modules/@noble/hashes/esm/argon2.d.ts +0 -32
- package/node_modules/@noble/hashes/esm/argon2.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/argon2.js +0 -392
- package/node_modules/@noble/hashes/esm/argon2.js.map +0 -1
- package/node_modules/@noble/hashes/esm/blake1.d.ts +0 -106
- package/node_modules/@noble/hashes/esm/blake1.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/blake1.js +0 -452
- package/node_modules/@noble/hashes/esm/blake1.js.map +0 -1
- package/node_modules/@noble/hashes/esm/blake2.d.ts +0 -116
- package/node_modules/@noble/hashes/esm/blake2.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/blake2.js +0 -413
- package/node_modules/@noble/hashes/esm/blake2.js.map +0 -1
- package/node_modules/@noble/hashes/esm/blake2b.d.ts +0 -11
- package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/blake2b.js +0 -11
- package/node_modules/@noble/hashes/esm/blake2b.js.map +0 -1
- package/node_modules/@noble/hashes/esm/blake2s.d.ts +0 -20
- package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/blake2s.js +0 -21
- package/node_modules/@noble/hashes/esm/blake2s.js.map +0 -1
- package/node_modules/@noble/hashes/esm/blake3.d.ts +0 -54
- package/node_modules/@noble/hashes/esm/blake3.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/blake3.js +0 -251
- package/node_modules/@noble/hashes/esm/blake3.js.map +0 -1
- package/node_modules/@noble/hashes/esm/crypto.d.ts +0 -2
- package/node_modules/@noble/hashes/esm/crypto.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/crypto.js +0 -2
- package/node_modules/@noble/hashes/esm/crypto.js.map +0 -1
- package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +0 -2
- package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/cryptoNode.js +0 -15
- package/node_modules/@noble/hashes/esm/cryptoNode.js.map +0 -1
- package/node_modules/@noble/hashes/esm/eskdf.d.ts +0 -47
- package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/eskdf.js +0 -160
- package/node_modules/@noble/hashes/esm/eskdf.js.map +0 -1
- package/node_modules/@noble/hashes/esm/hkdf.d.ts +0 -36
- package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/hkdf.js +0 -82
- package/node_modules/@noble/hashes/esm/hkdf.js.map +0 -1
- package/node_modules/@noble/hashes/esm/hmac.d.ts +0 -35
- package/node_modules/@noble/hashes/esm/hmac.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/hmac.js +0 -86
- package/node_modules/@noble/hashes/esm/hmac.js.map +0 -1
- package/node_modules/@noble/hashes/esm/index.d.ts +0 -2
- package/node_modules/@noble/hashes/esm/index.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/index.js +0 -33
- package/node_modules/@noble/hashes/esm/index.js.map +0 -1
- package/node_modules/@noble/hashes/esm/legacy.d.ts +0 -71
- package/node_modules/@noble/hashes/esm/legacy.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/legacy.js +0 -281
- package/node_modules/@noble/hashes/esm/legacy.js.map +0 -1
- package/node_modules/@noble/hashes/esm/package.json +0 -10
- package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +0 -23
- package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/pbkdf2.js +0 -97
- package/node_modules/@noble/hashes/esm/pbkdf2.js.map +0 -1
- package/node_modules/@noble/hashes/esm/ripemd160.d.ts +0 -13
- package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/ripemd160.js +0 -13
- package/node_modules/@noble/hashes/esm/ripemd160.js.map +0 -1
- package/node_modules/@noble/hashes/esm/scrypt.d.ts +0 -34
- package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/scrypt.js +0 -228
- package/node_modules/@noble/hashes/esm/scrypt.js.map +0 -1
- package/node_modules/@noble/hashes/esm/sha1.d.ts +0 -11
- package/node_modules/@noble/hashes/esm/sha1.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/sha1.js +0 -11
- package/node_modules/@noble/hashes/esm/sha1.js.map +0 -1
- package/node_modules/@noble/hashes/esm/sha2.d.ts +0 -159
- package/node_modules/@noble/hashes/esm/sha2.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/sha2.js +0 -375
- package/node_modules/@noble/hashes/esm/sha2.js.map +0 -1
- package/node_modules/@noble/hashes/esm/sha256.d.ts +0 -20
- package/node_modules/@noble/hashes/esm/sha256.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/sha256.js +0 -20
- package/node_modules/@noble/hashes/esm/sha256.js.map +0 -1
- package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +0 -142
- package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/sha3-addons.js +0 -393
- package/node_modules/@noble/hashes/esm/sha3-addons.js.map +0 -1
- package/node_modules/@noble/hashes/esm/sha3.d.ts +0 -53
- package/node_modules/@noble/hashes/esm/sha3.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/sha3.js +0 -234
- package/node_modules/@noble/hashes/esm/sha3.js.map +0 -1
- package/node_modules/@noble/hashes/esm/sha512.d.ts +0 -26
- package/node_modules/@noble/hashes/esm/sha512.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/sha512.js +0 -26
- package/node_modules/@noble/hashes/esm/sha512.js.map +0 -1
- package/node_modules/@noble/hashes/esm/utils.d.ts +0 -161
- package/node_modules/@noble/hashes/esm/utils.d.ts.map +0 -1
- package/node_modules/@noble/hashes/esm/utils.js +0 -281
- package/node_modules/@noble/hashes/esm/utils.js.map +0 -1
- package/node_modules/@noble/hashes/hkdf.d.ts +0 -36
- package/node_modules/@noble/hashes/hkdf.d.ts.map +0 -1
- package/node_modules/@noble/hashes/hkdf.js +0 -88
- package/node_modules/@noble/hashes/hkdf.js.map +0 -1
- package/node_modules/@noble/hashes/hmac.d.ts +0 -35
- package/node_modules/@noble/hashes/hmac.d.ts.map +0 -1
- package/node_modules/@noble/hashes/hmac.js +0 -91
- package/node_modules/@noble/hashes/hmac.js.map +0 -1
- package/node_modules/@noble/hashes/index.d.ts +0 -1
- package/node_modules/@noble/hashes/index.d.ts.map +0 -1
- package/node_modules/@noble/hashes/index.js +0 -33
- package/node_modules/@noble/hashes/index.js.map +0 -1
- package/node_modules/@noble/hashes/legacy.d.ts +0 -71
- package/node_modules/@noble/hashes/legacy.d.ts.map +0 -1
- package/node_modules/@noble/hashes/legacy.js +0 -287
- package/node_modules/@noble/hashes/legacy.js.map +0 -1
- package/node_modules/@noble/hashes/package.json +0 -266
- package/node_modules/@noble/hashes/pbkdf2.d.ts +0 -23
- package/node_modules/@noble/hashes/pbkdf2.d.ts.map +0 -1
- package/node_modules/@noble/hashes/pbkdf2.js +0 -101
- package/node_modules/@noble/hashes/pbkdf2.js.map +0 -1
- package/node_modules/@noble/hashes/ripemd160.d.ts +0 -13
- package/node_modules/@noble/hashes/ripemd160.d.ts.map +0 -1
- package/node_modules/@noble/hashes/ripemd160.js +0 -16
- package/node_modules/@noble/hashes/ripemd160.js.map +0 -1
- package/node_modules/@noble/hashes/scrypt.d.ts +0 -34
- package/node_modules/@noble/hashes/scrypt.d.ts.map +0 -1
- package/node_modules/@noble/hashes/scrypt.js +0 -232
- package/node_modules/@noble/hashes/scrypt.js.map +0 -1
- package/node_modules/@noble/hashes/sha1.d.ts +0 -11
- package/node_modules/@noble/hashes/sha1.d.ts.map +0 -1
- package/node_modules/@noble/hashes/sha1.js +0 -14
- package/node_modules/@noble/hashes/sha1.js.map +0 -1
- package/node_modules/@noble/hashes/sha2.d.ts +0 -159
- package/node_modules/@noble/hashes/sha2.d.ts.map +0 -1
- package/node_modules/@noble/hashes/sha2.js +0 -384
- package/node_modules/@noble/hashes/sha2.js.map +0 -1
- package/node_modules/@noble/hashes/sha256.d.ts +0 -20
- package/node_modules/@noble/hashes/sha256.d.ts.map +0 -1
- package/node_modules/@noble/hashes/sha256.js +0 -23
- package/node_modules/@noble/hashes/sha256.js.map +0 -1
- package/node_modules/@noble/hashes/sha3-addons.d.ts +0 -142
- package/node_modules/@noble/hashes/sha3-addons.d.ts.map +0 -1
- package/node_modules/@noble/hashes/sha3-addons.js +0 -402
- package/node_modules/@noble/hashes/sha3-addons.js.map +0 -1
- package/node_modules/@noble/hashes/sha3.d.ts +0 -53
- package/node_modules/@noble/hashes/sha3.d.ts.map +0 -1
- package/node_modules/@noble/hashes/sha3.js +0 -239
- package/node_modules/@noble/hashes/sha3.js.map +0 -1
- package/node_modules/@noble/hashes/sha512.d.ts +0 -26
- package/node_modules/@noble/hashes/sha512.d.ts.map +0 -1
- package/node_modules/@noble/hashes/sha512.js +0 -29
- package/node_modules/@noble/hashes/sha512.js.map +0 -1
- package/node_modules/@noble/hashes/src/_assert.ts +0 -22
- package/node_modules/@noble/hashes/src/_blake.ts +0 -50
- package/node_modules/@noble/hashes/src/_md.ts +0 -176
- package/node_modules/@noble/hashes/src/_u64.ts +0 -91
- package/node_modules/@noble/hashes/src/argon2.ts +0 -497
- package/node_modules/@noble/hashes/src/blake1.ts +0 -534
- package/node_modules/@noble/hashes/src/blake2.ts +0 -486
- package/node_modules/@noble/hashes/src/blake2b.ts +0 -10
- package/node_modules/@noble/hashes/src/blake2s.ts +0 -20
- package/node_modules/@noble/hashes/src/blake3.ts +0 -272
- package/node_modules/@noble/hashes/src/crypto.ts +0 -9
- package/node_modules/@noble/hashes/src/cryptoNode.ts +0 -15
- package/node_modules/@noble/hashes/src/eskdf.ts +0 -187
- package/node_modules/@noble/hashes/src/hkdf.ts +0 -88
- package/node_modules/@noble/hashes/src/hmac.ts +0 -94
- package/node_modules/@noble/hashes/src/index.ts +0 -31
- package/node_modules/@noble/hashes/src/legacy.ts +0 -293
- package/node_modules/@noble/hashes/src/pbkdf2.ts +0 -122
- package/node_modules/@noble/hashes/src/ripemd160.ts +0 -12
- package/node_modules/@noble/hashes/src/scrypt.ts +0 -257
- package/node_modules/@noble/hashes/src/sha1.ts +0 -10
- package/node_modules/@noble/hashes/src/sha2.ts +0 -402
- package/node_modules/@noble/hashes/src/sha256.ts +0 -24
- package/node_modules/@noble/hashes/src/sha3-addons.ts +0 -499
- package/node_modules/@noble/hashes/src/sha3.ts +0 -258
- package/node_modules/@noble/hashes/src/sha512.ts +0 -34
- package/node_modules/@noble/hashes/src/utils.ts +0 -395
- package/node_modules/@noble/hashes/utils.d.ts +0 -161
- package/node_modules/@noble/hashes/utils.d.ts.map +0 -1
- package/node_modules/@noble/hashes/utils.js +0 -313
- package/node_modules/@noble/hashes/utils.js.map +0 -1
package/dist/browser-runtime.js
CHANGED
|
@@ -87,6 +87,8 @@ var init_fec_worker_client = __esm({
|
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
89
|
* Configure the Worker with FEC parameters and WASM module.
|
|
90
|
+
* `interleaveMs: 0` is a valid no-span catalog value; in that mode the
|
|
91
|
+
* caller supplies a positive receiver-local `deliveryWindowMs`.
|
|
90
92
|
* wasmModule is transferred (not neutered — WebAssembly.Module is shareable).
|
|
91
93
|
* altaPublicKey is PKCS#8 SPKI DER bytes (self-describing — algorithm detected from OID).
|
|
92
94
|
* Transferred (detached from sender).
|
|
@@ -139,8 +141,7 @@ var init_fec_worker_client = __esm({
|
|
|
139
141
|
* Caller must not access data after this call — it is detached.
|
|
140
142
|
*
|
|
141
143
|
* Carries the wire-level Repair FEC Payload ID fields (ISO 23008-1 §C.5.3):
|
|
142
|
-
* - ssStart: SS_ID of first source symbol in the block (
|
|
143
|
-
* under `k-padded-v1`; any uint32, the block key, under `iso-ssbg1-v1`)
|
|
144
|
+
* - ssStart: SS_ID of first source symbol in the block (any uint32; the block key)
|
|
144
145
|
* - ssbLength: authored source-symbol count (1..configured K)
|
|
145
146
|
* - rsId: repair symbol index within the block
|
|
146
147
|
*/
|
|
@@ -190,7 +191,7 @@ var init_fec_worker_client = __esm({
|
|
|
190
191
|
}
|
|
191
192
|
/**
|
|
192
193
|
* Trigger cleanup after a block's inactivity deadline. `currentSbn` is the
|
|
193
|
-
* block key
|
|
194
|
+
* block key, the block's SS_Start.
|
|
194
195
|
*
|
|
195
196
|
* While one queue-drain barrier is in flight, later SBNs are coalesced into
|
|
196
197
|
* the next command without losing their individual acknowledgement identity.
|
|
@@ -902,8 +903,6 @@ function sourceFecPayloadIdOf(mmtpPacket, header) {
|
|
|
902
903
|
ssId: (mmtpPacket[off] << 24 | mmtpPacket[off + 1] << 16 | mmtpPacket[off + 2] << 8 | mmtpPacket[off + 3]) >>> 0
|
|
903
904
|
};
|
|
904
905
|
}
|
|
905
|
-
var MMTP_EXT_TYPE_ALTA_AUTH2 = MMTP_EXT_TYPE_ALTA_AUTH;
|
|
906
|
-
var MMTP_EXT_TRAILER_HEADER_SIZE2 = MMTP_EXT_TRAILER_HEADER_SIZE;
|
|
907
906
|
var extractAltaTrailer2 = extractAltaTrailer;
|
|
908
907
|
var REPAIR_FEC_PID_SIZE = 13;
|
|
909
908
|
function readRepairFecPayloadIdFields(mmtpPacket, parsedHeader) {
|
|
@@ -987,49 +986,21 @@ function serial32Precedes(a, b) {
|
|
|
987
986
|
const distance = serial32Distance(a, b);
|
|
988
987
|
return distance !== 0 && distance < SERIAL32_HALF;
|
|
989
988
|
}
|
|
990
|
-
function kStrideBlockStart(sbn, k) {
|
|
991
|
-
assertUint32(sbn, "sbn");
|
|
992
|
-
if (!Number.isInteger(k) || k < 1 || k >= SERIAL32_HALF) {
|
|
993
|
-
throw new RangeError(`k must be a positive integer below 2^31 (got ${k})`);
|
|
994
|
-
}
|
|
995
|
-
const high = Math.floor(sbn / 65536);
|
|
996
|
-
const low = sbn % 65536;
|
|
997
|
-
return (high * k % SERIAL32_SPAN * 65536 + low * k) % SERIAL32_SPAN;
|
|
998
|
-
}
|
|
999
|
-
function kStrideBlockPrecedes(a, b, k) {
|
|
1000
|
-
return serial32Precedes(kStrideBlockStart(a, k), kStrideBlockStart(b, k));
|
|
1001
|
-
}
|
|
1002
|
-
function kStrideBlocksBehind(a, b, k) {
|
|
1003
|
-
const from = kStrideBlockStart(a, k);
|
|
1004
|
-
const to = kStrideBlockStart(b, k);
|
|
1005
|
-
if (!serial32Precedes(from, to))
|
|
1006
|
-
return 0;
|
|
1007
|
-
return Math.ceil(serial32Distance(from, to) / k);
|
|
1008
|
-
}
|
|
1009
|
-
function isKStrideBlock(sbn, k) {
|
|
1010
|
-
return Number.isInteger(sbn) && sbn >= 0 && sbn < SERIAL32_SPAN && Number.isInteger(k) && k >= 1 && k < SERIAL32_HALF;
|
|
1011
|
-
}
|
|
1012
|
-
function kStrideNextBlock(sbn, k) {
|
|
1013
|
-
assertUint32(sbn, "sbn");
|
|
1014
|
-
kStrideBlockStart(sbn, k);
|
|
1015
|
-
return (sbn + 1) * k >= SERIAL32_SPAN ? 0 : sbn + 1;
|
|
1016
|
-
}
|
|
1017
989
|
|
|
1018
990
|
// ../mmt-base/dist/fec-block-profile.js
|
|
1019
991
|
var FEC_BLOCK_PROFILES = Object.freeze([
|
|
1020
|
-
"k-padded-v1",
|
|
1021
992
|
"iso-ssbg1-v1"
|
|
1022
993
|
]);
|
|
1023
994
|
function isFecBlockProfile(value) {
|
|
1024
|
-
return value === "
|
|
995
|
+
return value === "iso-ssbg1-v1";
|
|
1025
996
|
}
|
|
1026
997
|
function parseFecBlockProfile(value, where) {
|
|
1027
998
|
if (isFecBlockProfile(value))
|
|
1028
999
|
return value;
|
|
1029
1000
|
if (value === void 0) {
|
|
1030
|
-
throw new Error(`${where} is required: name the FEC block profile ("
|
|
1001
|
+
throw new Error(`${where} is required: name the FEC block profile ("iso-ssbg1-v1")`);
|
|
1031
1002
|
}
|
|
1032
|
-
throw new Error(`${where} must be "
|
|
1003
|
+
throw new Error(`${where} must be "iso-ssbg1-v1" (got ${JSON.stringify(value)})`);
|
|
1033
1004
|
}
|
|
1034
1005
|
var SSBG1_LENGTH_FIELD_BYTES = 2;
|
|
1035
1006
|
function ssbg1MaxPacketLength(symbolSize) {
|
|
@@ -6264,6 +6235,14 @@ var throwRepairContractError = (code, path, message) => {
|
|
|
6264
6235
|
};
|
|
6265
6236
|
function readFecBlockProfile(value, path, declaresFecInstance, fail) {
|
|
6266
6237
|
if (value === void 0 && !declaresFecInstance) return void 0;
|
|
6238
|
+
if (isFecBlockProfile(value)) return value;
|
|
6239
|
+
if (typeof value === "string" && value.length > 0) {
|
|
6240
|
+
return fail(
|
|
6241
|
+
"track_fec_block_profile_unimplemented",
|
|
6242
|
+
path,
|
|
6243
|
+
`${JSON.stringify(value)} is not an implemented FEC block profile; only "iso-ssbg1-v1" is implemented`
|
|
6244
|
+
);
|
|
6245
|
+
}
|
|
6267
6246
|
try {
|
|
6268
6247
|
return parseFecBlockProfile(value, "blockProfile");
|
|
6269
6248
|
} catch (error) {
|
|
@@ -6574,12 +6553,17 @@ function hasSwitchingSetRole(names) {
|
|
|
6574
6553
|
return suffix === "base" || suffix === "delta";
|
|
6575
6554
|
});
|
|
6576
6555
|
}
|
|
6577
|
-
function
|
|
6556
|
+
function isFecCatalogGeometry(obj) {
|
|
6578
6557
|
if (typeof obj !== "object" || obj === null) return false;
|
|
6579
6558
|
const o = obj;
|
|
6580
6559
|
const algorithm = o["algorithm"];
|
|
6581
6560
|
const symbolSize = o["symbolSize"];
|
|
6582
|
-
return typeof algorithm === "string" && (algorithm === "none" || algorithm === "raptorq" || algorithm === "reed-solomon") && typeof o["sourceSymbols"] === "number" && o["sourceSymbols"] >= 1 && typeof o["repairSymbols"] === "number" && o["repairSymbols"] >= 1 && typeof symbolSize === "number" && symbolSize >= 1 && (algorithm !== "raptorq" || symbolSize % 8 === 0) && typeof o["repairTrack"] === "string" && o["repairTrack"].length > 0
|
|
6561
|
+
return typeof algorithm === "string" && (algorithm === "none" || algorithm === "raptorq" || algorithm === "reed-solomon") && typeof o["sourceSymbols"] === "number" && o["sourceSymbols"] >= 1 && typeof o["repairSymbols"] === "number" && o["repairSymbols"] >= 1 && typeof symbolSize === "number" && symbolSize >= 1 && (algorithm !== "raptorq" || symbolSize % 8 === 0) && typeof o["repairTrack"] === "string" && o["repairTrack"].length > 0;
|
|
6562
|
+
}
|
|
6563
|
+
function isFecCatalogConfig(obj) {
|
|
6564
|
+
if (!isFecCatalogGeometry(obj)) return false;
|
|
6565
|
+
const o = obj;
|
|
6566
|
+
return isFecBlockProfile(o["blockProfile"]) || o["algorithm"] === "none" && o["blockProfile"] === void 0;
|
|
6583
6567
|
}
|
|
6584
6568
|
function isMulticastTrackObject(obj) {
|
|
6585
6569
|
if (typeof obj !== "object" || obj === null) return false;
|
|
@@ -6718,7 +6702,7 @@ function validateCmsfMmtpCatalog(value) {
|
|
|
6718
6702
|
const tracks = catalog["tracks"];
|
|
6719
6703
|
const trackRecords = [];
|
|
6720
6704
|
const positiveInteger2 = (input) => Number.isSafeInteger(input) && input > 0;
|
|
6721
|
-
const
|
|
6705
|
+
const positiveNumber2 = (input) => typeof input === "number" && Number.isFinite(input) && input > 0;
|
|
6722
6706
|
for (let index = 0; index < tracks.length; index++) {
|
|
6723
6707
|
const raw = tracks[index];
|
|
6724
6708
|
const path = `$.tracks[${index}]`;
|
|
@@ -6763,7 +6747,7 @@ function validateCmsfMmtpCatalog(value) {
|
|
|
6763
6747
|
if (groupDurationTicks !== void 0 && !positiveInteger2(groupDurationTicks)) {
|
|
6764
6748
|
catalogError("track_group_duration_ticks", `${path}.groupDurationTicks`, "must be a positive integer");
|
|
6765
6749
|
}
|
|
6766
|
-
if (keyframeIntervalMs !== void 0 && !
|
|
6750
|
+
if (keyframeIntervalMs !== void 0 && !positiveNumber2(keyframeIntervalMs) || keyframeIntervalTicks !== void 0 && !positiveInteger2(keyframeIntervalTicks)) {
|
|
6767
6751
|
catalogError("track_keyframe_interval", path, "keyframe interval fields must be positive numbers");
|
|
6768
6752
|
}
|
|
6769
6753
|
if (packaging === "fec-repair") {
|
|
@@ -6811,8 +6795,8 @@ function validateCmsfMmtpCatalog(value) {
|
|
|
6811
6795
|
"repair layers are self-describing fec-repair tracks (depends, repairLayer); fec.enhancementRepair is not supported"
|
|
6812
6796
|
);
|
|
6813
6797
|
}
|
|
6814
|
-
if (config["interleaveDepthMs"] !== void 0 && !
|
|
6815
|
-
catalogError("track_fec", `${path}.fec.interleaveDepthMs`, "must be a
|
|
6798
|
+
if (config["interleaveDepthMs"] !== void 0 && (!Number.isSafeInteger(config["interleaveDepthMs"]) || config["interleaveDepthMs"] < 0)) {
|
|
6799
|
+
catalogError("track_fec", `${path}.fec.interleaveDepthMs`, "must be a non-negative safe integer");
|
|
6816
6800
|
}
|
|
6817
6801
|
if (algorithm !== "none") {
|
|
6818
6802
|
for (const field of ["sourceSymbols", "repairSymbols", "symbolSize"]) {
|
|
@@ -8689,7 +8673,6 @@ var CmafBlockAssembler = class {
|
|
|
8689
8673
|
failedBlocks: 0
|
|
8690
8674
|
};
|
|
8691
8675
|
this.#config = {
|
|
8692
|
-
k: config.k,
|
|
8693
8676
|
timescale: config.timescale ?? 1e6,
|
|
8694
8677
|
trackId: config.trackId ?? 1,
|
|
8695
8678
|
trackType: config.trackType,
|
|
@@ -9518,49 +9501,6 @@ var TransportPool = class {
|
|
|
9518
9501
|
};
|
|
9519
9502
|
|
|
9520
9503
|
// ../fec/dist/block-processor.js
|
|
9521
|
-
function hasUniformPaddingFrom(data, start) {
|
|
9522
|
-
if (start >= data.length)
|
|
9523
|
-
return false;
|
|
9524
|
-
const pad = data[start];
|
|
9525
|
-
if (pad !== 0 && pad !== 238)
|
|
9526
|
-
return false;
|
|
9527
|
-
for (let i = start + 1; i < data.length; i++) {
|
|
9528
|
-
if (data[i] !== pad)
|
|
9529
|
-
return false;
|
|
9530
|
-
}
|
|
9531
|
-
return true;
|
|
9532
|
-
}
|
|
9533
|
-
function maybeExtendAltaTrailer(symbolData, packetEnd) {
|
|
9534
|
-
if (packetEnd + MMTP_EXT_TRAILER_HEADER_SIZE2 > symbolData.length)
|
|
9535
|
-
return packetEnd;
|
|
9536
|
-
const view2 = new DataView(symbolData.buffer, symbolData.byteOffset + packetEnd, symbolData.byteLength - packetEnd);
|
|
9537
|
-
const extType = view2.getUint16(0, false);
|
|
9538
|
-
if (extType !== MMTP_EXT_TYPE_ALTA_AUTH2)
|
|
9539
|
-
return packetEnd;
|
|
9540
|
-
const extLen = view2.getUint16(2, false);
|
|
9541
|
-
const trailerEnd = packetEnd + MMTP_EXT_TRAILER_HEADER_SIZE2 + extLen;
|
|
9542
|
-
return trailerEnd <= symbolData.length ? trailerEnd : packetEnd;
|
|
9543
|
-
}
|
|
9544
|
-
function trimDecodedSourceSymbol(symbolData) {
|
|
9545
|
-
if (symbolData.length < MMTP_HEADER_SIZE2 + MPU_HEADER_SIZE2)
|
|
9546
|
-
return null;
|
|
9547
|
-
const headerLength = parseMMTPHeader(symbolData).headerLength;
|
|
9548
|
-
if (symbolData.length < headerLength + MPU_HEADER_SIZE2)
|
|
9549
|
-
return null;
|
|
9550
|
-
const payload = symbolData.subarray(headerLength);
|
|
9551
|
-
const mpu = parseMPUHeader(payload);
|
|
9552
|
-
const trailingMpuHeaderSize = MPU_HEADER_SIZE2 - 2;
|
|
9553
|
-
if (mpu.payloadLength < trailingMpuHeaderSize)
|
|
9554
|
-
return null;
|
|
9555
|
-
const declaredEnd = headerLength + 2 + mpu.payloadLength;
|
|
9556
|
-
if (declaredEnd > symbolData.length)
|
|
9557
|
-
return null;
|
|
9558
|
-
const packetEnd = maybeExtendAltaTrailer(symbolData, declaredEnd);
|
|
9559
|
-
if (packetEnd < symbolData.length && !hasUniformPaddingFrom(symbolData, packetEnd)) {
|
|
9560
|
-
return null;
|
|
9561
|
-
}
|
|
9562
|
-
return symbolData.subarray(0, packetEnd);
|
|
9563
|
-
}
|
|
9564
9504
|
function emptyProcessResult() {
|
|
9565
9505
|
return {
|
|
9566
9506
|
symbols: [],
|
|
@@ -9624,17 +9564,6 @@ function parseProtectedPacket(packetData, hdr, index, bump) {
|
|
|
9624
9564
|
rawSymbol: packetData
|
|
9625
9565
|
};
|
|
9626
9566
|
}
|
|
9627
|
-
function parseKPaddedSymbol(symbolData, index, bump) {
|
|
9628
|
-
const hdr = gateMpuHeader(symbolData, bump);
|
|
9629
|
-
if (!hdr)
|
|
9630
|
-
return null;
|
|
9631
|
-
const packetData = trimDecodedSourceSymbol(symbolData);
|
|
9632
|
-
if (!packetData) {
|
|
9633
|
-
bump("bad_length");
|
|
9634
|
-
return null;
|
|
9635
|
-
}
|
|
9636
|
-
return parseProtectedPacket(packetData, hdr, index, bump);
|
|
9637
|
-
}
|
|
9638
9567
|
function parseSsbg1Symbol(symbolData, index, bump) {
|
|
9639
9568
|
const packetData = trimSsbg1SourceSymbol(symbolData);
|
|
9640
9569
|
if (!packetData) {
|
|
@@ -9647,7 +9576,7 @@ function parseSsbg1Symbol(symbolData, index, bump) {
|
|
|
9647
9576
|
return parseProtectedPacket(packetData, hdr, index, bump);
|
|
9648
9577
|
}
|
|
9649
9578
|
function processFecSymbol(symbolData, options) {
|
|
9650
|
-
|
|
9579
|
+
parseFecBlockProfile(options?.blockProfile, "processFecSymbol options.blockProfile");
|
|
9651
9580
|
const failReasons = {};
|
|
9652
9581
|
let failedCount = 0;
|
|
9653
9582
|
const bump = (reason) => {
|
|
@@ -9659,26 +9588,26 @@ function processFecSymbol(symbolData, options) {
|
|
|
9659
9588
|
const index = options.index ?? 0;
|
|
9660
9589
|
let parsed;
|
|
9661
9590
|
try {
|
|
9662
|
-
parsed =
|
|
9591
|
+
parsed = parseSsbg1Symbol(symbolData, index, bump);
|
|
9663
9592
|
} catch {
|
|
9664
9593
|
bump("exception");
|
|
9665
9594
|
parsed = null;
|
|
9666
9595
|
}
|
|
9667
9596
|
return resultFromSymbols(parsed ? [parsed] : [], failedCount, failReasons, options.ownerPacketId);
|
|
9668
9597
|
}
|
|
9669
|
-
function requireSymbolSize(symbolSize
|
|
9598
|
+
function requireSymbolSize(symbolSize) {
|
|
9670
9599
|
if (symbolSize === void 0) {
|
|
9671
9600
|
throw new Error("processFecBlock options.symbolSize is required: name the catalog fec.symbolSize (T)");
|
|
9672
9601
|
}
|
|
9673
9602
|
if (typeof symbolSize !== "number" || !Number.isSafeInteger(symbolSize) || symbolSize <= 0) {
|
|
9674
9603
|
throw new Error(`processFecBlock options.symbolSize must be a positive integer (got ${String(symbolSize)})`);
|
|
9675
9604
|
}
|
|
9676
|
-
if (
|
|
9605
|
+
if (symbolSize <= SSBG1_LENGTH_FIELD_BYTES) {
|
|
9677
9606
|
throw new Error(`processFecBlock options.symbolSize must exceed the ${SSBG1_LENGTH_FIELD_BYTES}-byte ssbg_mode1 length field (got ${symbolSize})`);
|
|
9678
9607
|
}
|
|
9679
9608
|
return symbolSize;
|
|
9680
9609
|
}
|
|
9681
|
-
function requireSourceSymbolCount(k, blockLength, symbolSize
|
|
9610
|
+
function requireSourceSymbolCount(k, blockLength, symbolSize) {
|
|
9682
9611
|
if (k === void 0) {
|
|
9683
9612
|
if (blockLength % symbolSize !== 0) {
|
|
9684
9613
|
throw new Error(`processFecBlock: ${blockLength} bytes is not a whole number of ${symbolSize}-byte symbols; pass options.k`);
|
|
@@ -9688,17 +9617,17 @@ function requireSourceSymbolCount(k, blockLength, symbolSize, blockProfile) {
|
|
|
9688
9617
|
if (!Number.isSafeInteger(k) || k <= 0) {
|
|
9689
9618
|
throw new Error(`processFecBlock options.k must be a positive integer (got ${k})`);
|
|
9690
9619
|
}
|
|
9691
|
-
if (
|
|
9620
|
+
if (blockLength !== k * symbolSize) {
|
|
9692
9621
|
throw new Error(`processFecBlock: an iso-ssbg1-v1 block of ${k} symbols of ${symbolSize} bytes is ${k * symbolSize} bytes (got ${blockLength})`);
|
|
9693
9622
|
}
|
|
9694
9623
|
return k;
|
|
9695
9624
|
}
|
|
9696
9625
|
function processFecBlock(blockData, options) {
|
|
9697
9626
|
const blockProfile = parseFecBlockProfile(options?.blockProfile, "processFecBlock options.blockProfile");
|
|
9698
|
-
const symbolSize = requireSymbolSize(options.symbolSize
|
|
9627
|
+
const symbolSize = requireSymbolSize(options.symbolSize);
|
|
9699
9628
|
if (!blockData || blockData.length === 0)
|
|
9700
9629
|
return emptyProcessResult();
|
|
9701
|
-
const k = requireSourceSymbolCount(options.k, blockData.length, symbolSize
|
|
9630
|
+
const k = requireSourceSymbolCount(options.k, blockData.length, symbolSize);
|
|
9702
9631
|
const failReasons = {};
|
|
9703
9632
|
let failedCount = 0;
|
|
9704
9633
|
const parsed = [];
|
|
@@ -9723,7 +9652,7 @@ function processFecBlock(blockData, options) {
|
|
|
9723
9652
|
// ../fec/dist/oti.js
|
|
9724
9653
|
function catalogFecInterleaveMs(fec) {
|
|
9725
9654
|
const ms = fec?.interleaveDepthMs;
|
|
9726
|
-
return typeof ms === "number" &&
|
|
9655
|
+
return typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0 ? ms : void 0;
|
|
9727
9656
|
}
|
|
9728
9657
|
function deriveFecInterleaveDepth(interleaveDepthMs, mediaUnitDurationMs) {
|
|
9729
9658
|
if (!Number.isFinite(interleaveDepthMs) || interleaveDepthMs <= 0) {
|
|
@@ -9756,6 +9685,8 @@ function deriveFecRetentionBlocks(deliveryWindowMs, interleaveDepthMs) {
|
|
|
9756
9685
|
throw new Error(`[FEC] retention of ${Math.ceil(deliveryWindowMs / interleaveDepthMs) + 1} blocks exceeds the 255 the decoder and the ISO/IEC 23008-1 AL-FEC signaling byte accept (deliveryWindowMs=${deliveryWindowMs}ms over interleaveDepthMs=${interleaveDepthMs}ms). Fix the catalog geometry: a span this short cannot carry a window this wide.`);
|
|
9757
9686
|
}
|
|
9758
9687
|
var MAX_FEC_RETENTION_BLOCKS = 255;
|
|
9688
|
+
var LOCAL_FEC_RETENTION_BLOCKS = 2;
|
|
9689
|
+
var LOCAL_FEC_TIMEOUT_MS = 500;
|
|
9759
9690
|
function tryDeriveFecRetentionBlocks(deliveryWindowMs, interleaveDepthMs) {
|
|
9760
9691
|
if (!Number.isFinite(interleaveDepthMs) || !(interleaveDepthMs > 0))
|
|
9761
9692
|
return void 0;
|
|
@@ -9777,7 +9708,7 @@ var NO_FEC_INSTANCE = /* @__PURE__ */ Symbol("no FEC instance");
|
|
|
9777
9708
|
var FecBlockMapper = class {
|
|
9778
9709
|
/** Media units (GOPs/frames) per FEC block, recovered with positive round-half-up semantics. */
|
|
9779
9710
|
d;
|
|
9780
|
-
/** K:
|
|
9711
|
+
/** K: the maximum SSB_length of a block (catalog `fec.sourceSymbols`). */
|
|
9781
9712
|
k;
|
|
9782
9713
|
/** Block time span (ms) — used as pacing threshold */
|
|
9783
9714
|
interleaveDepthMs;
|
|
@@ -9803,21 +9734,14 @@ var FecBlockMapper = class {
|
|
|
9803
9734
|
this.d = deriveFecInterleaveDepth(config.interleaveDepthMs, config.gopDurationMs);
|
|
9804
9735
|
}
|
|
9805
9736
|
/**
|
|
9806
|
-
*
|
|
9807
|
-
*
|
|
9808
|
-
*
|
|
9737
|
+
* SS_ID → (SBN, ESI). Only {@link NullBlockMapper} answers: a stream with an
|
|
9738
|
+
* FEC instance is `iso-ssbg1-v1`, whose blocks are located by
|
|
9739
|
+
* `(SS_Start, SSB_length)` range membership and never from SS_ID alone.
|
|
9809
9740
|
*
|
|
9810
|
-
* @throws
|
|
9811
|
-
* `(SS_Start, SSB_length)` range membership and never from SS_ID alone
|
|
9741
|
+
* @throws for every stream with an FEC instance
|
|
9812
9742
|
*/
|
|
9813
|
-
fromSsId(
|
|
9814
|
-
|
|
9815
|
-
throw new Error(`fromSsId is the k-padded-v1 locator; a ${this.blockProfile ?? "non-FEC"} stream locates a block by (SS_Start, SSB_length) range membership, never from SS_ID alone`);
|
|
9816
|
-
}
|
|
9817
|
-
return {
|
|
9818
|
-
sbn: Math.floor(ssId / this.k),
|
|
9819
|
-
esi: ssId % this.k
|
|
9820
|
-
};
|
|
9743
|
+
fromSsId(_ssId) {
|
|
9744
|
+
throw new Error(`fromSsId: a ${this.blockProfile ?? "non-FEC"} stream locates a block by (SS_Start, SSB_length) range membership, never from SS_ID alone`);
|
|
9821
9745
|
}
|
|
9822
9746
|
/**
|
|
9823
9747
|
* Pacing threshold: one full block duration = interleaveDepthMs.
|
|
@@ -9901,20 +9825,22 @@ function computeFecLossWindowMs(fecInterleaveMs, fecK, jitterBufferMs) {
|
|
|
9901
9825
|
});
|
|
9902
9826
|
}
|
|
9903
9827
|
function computeFecDeliveryWindowMs(config) {
|
|
9904
|
-
const fecInterleaveMs = config.interleaveDepthMs;
|
|
9905
9828
|
const fecK = config.sourceSymbols;
|
|
9906
|
-
if (!Number.isFinite(
|
|
9829
|
+
if (!Number.isFinite(fecK) || fecK <= 0) {
|
|
9907
9830
|
throw new Error(
|
|
9908
|
-
`[TrackSession]
|
|
9831
|
+
`[TrackSession] fecK=${fecK}. Derive a positive FEC source-symbol count from the stream catalog.`
|
|
9909
9832
|
);
|
|
9910
9833
|
}
|
|
9911
|
-
|
|
9834
|
+
return fecSpanDeliveryWindowMs(config.interleaveDepthMs, config.jitterBufferMs);
|
|
9835
|
+
}
|
|
9836
|
+
function fecSpanDeliveryWindowMs(interleaveDepthMs, jitterBufferMs) {
|
|
9837
|
+
if (!Number.isFinite(interleaveDepthMs) || interleaveDepthMs <= 0) {
|
|
9912
9838
|
throw new Error(
|
|
9913
|
-
`[TrackSession]
|
|
9839
|
+
`[TrackSession] fecInterleaveMs=${interleaveDepthMs}. Derive a positive FEC interleave window from the stream catalog.`
|
|
9914
9840
|
);
|
|
9915
9841
|
}
|
|
9916
|
-
if (
|
|
9917
|
-
return fecBlockBudgetMs(
|
|
9842
|
+
if (jitterBufferMs === void 0) return Math.ceil(interleaveDepthMs);
|
|
9843
|
+
return fecBlockBudgetMs(interleaveDepthMs, jitterBufferMs);
|
|
9918
9844
|
}
|
|
9919
9845
|
function computeFecDeliveryWindowMediaUnits(config, timestampTimescale) {
|
|
9920
9846
|
if (!Number.isFinite(timestampTimescale) || timestampTimescale <= 0) {
|
|
@@ -10075,6 +10001,10 @@ var TrackSession = class {
|
|
|
10075
10001
|
/** multicast/amt → failing: loss detected, start hysteresis timer (Claim 9f). */
|
|
10076
10002
|
lossDetected() {
|
|
10077
10003
|
if (this.#state !== "multicast" && this.#state !== "amt") return;
|
|
10004
|
+
if (this.config.fecInterleaveMs === 0 || this.config.fecInterleaveMs === void 0) {
|
|
10005
|
+
this.transportFailed();
|
|
10006
|
+
return;
|
|
10007
|
+
}
|
|
10078
10008
|
this.#previousPrimary = this.#state;
|
|
10079
10009
|
this.#transition("failing");
|
|
10080
10010
|
const hysteresisMs = computeFecLossWindowMs(
|
|
@@ -10881,7 +10811,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
10881
10811
|
};
|
|
10882
10812
|
|
|
10883
10813
|
// src/fec-state.ts
|
|
10884
|
-
var MIN_INTERLEAVE_DEPTH_MS =
|
|
10814
|
+
var MIN_INTERLEAVE_DEPTH_MS = 1;
|
|
10885
10815
|
var FEC_INACTIVE_REASONS = Object.freeze([
|
|
10886
10816
|
"no-catalog-fec",
|
|
10887
10817
|
"reliable-moq",
|
|
@@ -10908,7 +10838,7 @@ function isoBlockSpanUsable(fec) {
|
|
|
10908
10838
|
const block = recordOf(fec);
|
|
10909
10839
|
if (block === void 0 || block["blockProfile"] !== "iso-ssbg1-v1") return true;
|
|
10910
10840
|
const ms = block["interleaveDepthMs"];
|
|
10911
|
-
return typeof ms === "number" && Number.
|
|
10841
|
+
return ms === void 0 || typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0;
|
|
10912
10842
|
}
|
|
10913
10843
|
function requireBoolean(value, field) {
|
|
10914
10844
|
if (typeof value !== "boolean") {
|
|
@@ -11442,7 +11372,6 @@ var FECManager = class _FECManager {
|
|
|
11442
11372
|
constructor() {
|
|
11443
11373
|
// Per-track WASM RaptorQ decoders, keyed by physical stream identity.
|
|
11444
11374
|
this.#fecDecoders = /* @__PURE__ */ new Map();
|
|
11445
|
-
/** @deprecated Use setFecDecoder(packetId, ...). Backwards-compat shim. */
|
|
11446
11375
|
// Per-track Worker-backed FEC decoders, keyed by physical stream identity.
|
|
11447
11376
|
// Worker-backed tracks use async decode — recovered frames arrive via onFrame callback,
|
|
11448
11377
|
// not synchronous return from feedSourceSymbol/feedRepairSymbol.
|
|
@@ -11457,10 +11386,9 @@ var FECManager = class _FECManager {
|
|
|
11457
11386
|
// source/repair symbols do not split across two RaptorQ states.
|
|
11458
11387
|
this.#syncDecoderBlockOwners = /* @__PURE__ */ new Map();
|
|
11459
11388
|
this.#decoderBlockOwners = /* @__PURE__ */ new WeakMap();
|
|
11460
|
-
this.#fecK = 4;
|
|
11461
11389
|
this.#fecSymbolSize = 0;
|
|
11462
|
-
this.#fecTimeoutMs =
|
|
11463
|
-
// default
|
|
11390
|
+
this.#fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS;
|
|
11391
|
+
// local default; refined by setFramerate() when fps known
|
|
11464
11392
|
this.#trackConfigs = /* @__PURE__ */ new Map();
|
|
11465
11393
|
this.#fecStats = newFecCounters();
|
|
11466
11394
|
this.#fecTrackStats = /* @__PURE__ */ new Map();
|
|
@@ -11500,10 +11428,10 @@ var FECManager = class _FECManager {
|
|
|
11500
11428
|
this.#blockSourceEsis = /* @__PURE__ */ new Map();
|
|
11501
11429
|
// Authored SSB_length per block (ISO/IEC 23008-1 §C.5.3), learned from the
|
|
11502
11430
|
// first repair symbol. The FFmpeg moq_mmt publisher closes a block every D
|
|
11503
|
-
// media units, usually before it holds K symbols
|
|
11504
|
-
//
|
|
11505
|
-
// SSB_length
|
|
11506
|
-
//
|
|
11431
|
+
// media units, usually before it holds K symbols; under iso-ssbg1-v1
|
|
11432
|
+
// (ssbg_mode1) that block is K' = SSB_length symbols, its repair ESIs start
|
|
11433
|
+
// at SSB_length, and K is only the maximum. Such a block is clean at
|
|
11434
|
+
// SSB_length source arrivals and recoverable from SSB_length received
|
|
11507
11435
|
// symbols; waiting for K fails every one of them.
|
|
11508
11436
|
this.#blockAuthoredLengths = /* @__PURE__ */ new Map();
|
|
11509
11437
|
this.#ssbLengthConflictCount = 0;
|
|
@@ -11620,14 +11548,16 @@ var FECManager = class _FECManager {
|
|
|
11620
11548
|
this.#resolvedBlockClasses = /* @__PURE__ */ new Map();
|
|
11621
11549
|
// Blocks already reported through onBlockRepaired (once per block).
|
|
11622
11550
|
this.#repairedBlocks = /* @__PURE__ */ new Set();
|
|
11623
|
-
// Blocks created
|
|
11551
|
+
// Blocks created before any symbol of theirs was tracked: the repair-less
|
|
11552
|
+
// spans placeIsoRepair materializes (onBlockVanished).
|
|
11624
11553
|
this.#materializedBlocks = /* @__PURE__ */ new Set();
|
|
11625
11554
|
// packetIds whose ordered-relay horizon became underivable and fell back to
|
|
11626
11555
|
// the datagram deadline. Bounds the warn in #derivableOrderedHorizon to once
|
|
11627
11556
|
// per stream. Not only throws: the ordinary out-of-range case is detected by
|
|
11628
11557
|
// arithmetic and never throws at all.
|
|
11629
11558
|
this.#orderedHorizonWarnedFor = /* @__PURE__ */ new Set();
|
|
11630
|
-
//
|
|
11559
|
+
// Newest block start (SS_Start) seen per stream and when: the frontier for
|
|
11560
|
+
// source-discontinuity detection and isoSpanResolution.
|
|
11631
11561
|
this.#highestSbn = /* @__PURE__ */ new Map();
|
|
11632
11562
|
// iso-ssbg1-v1 block location per stream (streamKey). A source belongs to a
|
|
11633
11563
|
// block only once a repair symbol stated the block's (SS_Start, SSB_length).
|
|
@@ -11721,7 +11651,10 @@ var FECManager = class _FECManager {
|
|
|
11721
11651
|
#releasedWorkerClients;
|
|
11722
11652
|
#syncDecoderBlockOwners;
|
|
11723
11653
|
#decoderBlockOwners;
|
|
11724
|
-
|
|
11654
|
+
// The stream-global K a configure() call stated, reported as fecStats.k.
|
|
11655
|
+
// It never resolves a packetId: a packetId has only its own track config
|
|
11656
|
+
// (BLO-45953), and K does not feed the delivery-window math.
|
|
11657
|
+
#streamK;
|
|
11725
11658
|
#fecSymbolSize;
|
|
11726
11659
|
#fecTimeoutMs;
|
|
11727
11660
|
#trackConfigs;
|
|
@@ -11883,7 +11816,8 @@ var FECManager = class _FECManager {
|
|
|
11883
11816
|
this.#completedBlocks.add(key);
|
|
11884
11817
|
if (clean) this.#cleanlyCompletedBlocks.add(key);
|
|
11885
11818
|
this.#indexBlockKey(key, sbn, packetId);
|
|
11886
|
-
const
|
|
11819
|
+
const config = this.#trackConfig(packetId);
|
|
11820
|
+
const expiresAtMs = nowMs + config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
|
|
11887
11821
|
this.#terminalExpiryMs.set(key, expiresAtMs);
|
|
11888
11822
|
let queue = this.#terminalQueues.get(packetId);
|
|
11889
11823
|
if (!queue) {
|
|
@@ -11919,8 +11853,9 @@ var FECManager = class _FECManager {
|
|
|
11919
11853
|
* retention the Worker and the WASM decoder are configured with, because it
|
|
11920
11854
|
* comes from the same function they do (`deriveFecRetentionBlocks`, over the
|
|
11921
11855
|
* block's delivery window and its span). The decoder retires every block
|
|
11922
|
-
* more than this many
|
|
11923
|
-
*
|
|
11856
|
+
* more than this many blocks behind the newest SS_Start it saw, counted
|
|
11857
|
+
* from below by `isoBlocksBehindLowerBound` (`cleanup_by_ss_start`), so
|
|
11858
|
+
* settling earlier than this would decide a block the decoder would still
|
|
11924
11859
|
* have recovered.
|
|
11925
11860
|
*
|
|
11926
11861
|
* This used to call `deriveFecInterleaveDepth`, which returns D -- MEDIA
|
|
@@ -11933,6 +11868,14 @@ var FECManager = class _FECManager {
|
|
|
11933
11868
|
* derivation; do not re-derive a horizon at either call site.
|
|
11934
11869
|
*/
|
|
11935
11870
|
#orderedHorizonBlocksFor(packetId, config) {
|
|
11871
|
+
if (config.interleaveDepthMs === 0) {
|
|
11872
|
+
if (Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
|
|
11873
|
+
return LOCAL_FEC_RETENTION_BLOCKS;
|
|
11874
|
+
}
|
|
11875
|
+
throw new Error(
|
|
11876
|
+
`[FECManager] packetId=${packetId}: local FEC cleanup requires a positive timeout (got ${config.timeoutMs})`
|
|
11877
|
+
);
|
|
11878
|
+
}
|
|
11936
11879
|
if (!_FECManager.#hasOrderedGeometry(config)) {
|
|
11937
11880
|
throw new Error(
|
|
11938
11881
|
`[FECManager] packetId=${packetId}: ordered-relay settlement derives the decoder's interleave horizon from the catalog interleaveDepthMs (>= ${MIN_INTERLEAVE_DEPTH_MS}ms), the media-unit rate and the block delivery window; got interleaveDepthMs=${config.interleaveDepthMs} fps=${String(config.fps)} timeoutMs=${config.timeoutMs}.`
|
|
@@ -11946,7 +11889,7 @@ var FECManager = class _FECManager {
|
|
|
11946
11889
|
* two can never disagree about which streams settle by stream order.
|
|
11947
11890
|
*/
|
|
11948
11891
|
static #hasOrderedGeometry(config) {
|
|
11949
|
-
return config
|
|
11892
|
+
return _FECManager.#hasCatalogTiming(config) && // `fps` is no longer an input to the horizon, but it still gates WHICH
|
|
11950
11893
|
// streams settle by stream order at all: a track with no usable
|
|
11951
11894
|
// media-unit rate fell back to datagram semantics before this and must
|
|
11952
11895
|
// keep doing so. Removing this widens ordered-relay settlement to streams
|
|
@@ -11986,6 +11929,9 @@ var FECManager = class _FECManager {
|
|
|
11986
11929
|
* argument: narrower IS the failing direction.
|
|
11987
11930
|
*/
|
|
11988
11931
|
#derivableOrderedHorizon(config, packetId) {
|
|
11932
|
+
if (config.interleaveDepthMs === 0) {
|
|
11933
|
+
return Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? LOCAL_FEC_RETENTION_BLOCKS : void 0;
|
|
11934
|
+
}
|
|
11989
11935
|
if (!_FECManager.#hasOrderedGeometry(config)) return void 0;
|
|
11990
11936
|
try {
|
|
11991
11937
|
const blocks = tryDeriveFecRetentionBlocks(config.timeoutMs, config.interleaveDepthMs);
|
|
@@ -12018,19 +11964,20 @@ var FECManager = class _FECManager {
|
|
|
12018
11964
|
/**
|
|
12019
11965
|
* The stream's interleave horizon, or undefined when its geometry cannot
|
|
12020
11966
|
* define one. Two different streams reach that case, with different causes:
|
|
12021
|
-
* a packetId that was never configured,
|
|
12022
|
-
*
|
|
11967
|
+
* a packetId that was never configured, which has no geometry at all
|
|
11968
|
+
* (explicitly configured tracks are refused an underivable geometry
|
|
12023
11969
|
* at config time — #upsertTrackConfig, setMulticastSymbolDelivery); or a
|
|
12024
11970
|
* configured track whose timeoutMs was later widened past the 255-block
|
|
12025
11971
|
* ceiling without re-validating, which #derivableOrderedHorizon catches
|
|
12026
11972
|
* rather than throwing from the packet-feed path. Either way the stream
|
|
12027
|
-
* keeps datagram semantics instead of throwing.
|
|
11973
|
+
* keeps datagram semantics instead of throwing. A track's configuration can
|
|
12028
11974
|
* also change under a stream that already holds blocks, so a caller cannot
|
|
12029
11975
|
* assume a horizon that was defined when a block was held is still defined
|
|
12030
11976
|
* (see #advanceOrderedFrontier).
|
|
12031
11977
|
*/
|
|
12032
11978
|
#orderedHorizonBlocks(packetId) {
|
|
12033
|
-
|
|
11979
|
+
const config = this.#trackConfigs.get(packetId);
|
|
11980
|
+
return config === void 0 ? void 0 : this.#derivableOrderedHorizon(config, packetId);
|
|
12034
11981
|
}
|
|
12035
11982
|
/** True while a block of an ordered-relay stream can still receive symbols. */
|
|
12036
11983
|
#withinOrderedHorizon(key) {
|
|
@@ -12064,7 +12011,7 @@ var FECManager = class _FECManager {
|
|
|
12064
12011
|
*/
|
|
12065
12012
|
#advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName) {
|
|
12066
12013
|
const previous = this.#orderedFrontiers.get(streamKey);
|
|
12067
|
-
const frontier = previous === void 0 || this.#blockPrecedes(previous, sbn
|
|
12014
|
+
const frontier = previous === void 0 || this.#blockPrecedes(previous, sbn) ? sbn : previous;
|
|
12068
12015
|
if (frontier !== previous) this.#orderedFrontiers.set(streamKey, frontier);
|
|
12069
12016
|
const pending = this.#orderedPendingBlocks.get(streamKey);
|
|
12070
12017
|
if (!pending) return;
|
|
@@ -12099,7 +12046,7 @@ var FECManager = class _FECManager {
|
|
|
12099
12046
|
if (this.#completedBlocks.has(key) || this.#pendingWorkerCleanups.has(key) || this.#repairRequestedBlocks.has(key)) return;
|
|
12100
12047
|
const authoredLength = this.#blockAuthoredLengths.get(key);
|
|
12101
12048
|
const received = (this.#blockSourceCounts.get(key) ?? 0) + (this.#blockRepairRsIds.get(key)?.size ?? 0);
|
|
12102
|
-
if (this.onRepairNeeded && (authoredLength === void 0 || received < authoredLength)) {
|
|
12049
|
+
if (this.#hasCatalogTimingForPacket(packetId) && this.onRepairNeeded && (authoredLength === void 0 || received < authoredLength)) {
|
|
12103
12050
|
this.#requestRepair(sbn, packetId, catalogTrackName, true);
|
|
12104
12051
|
return;
|
|
12105
12052
|
}
|
|
@@ -12255,7 +12202,7 @@ var FECManager = class _FECManager {
|
|
|
12255
12202
|
}
|
|
12256
12203
|
#requestRepair(sbn, packetId, catalogTrackName, settledByOrder = false) {
|
|
12257
12204
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
12258
|
-
if (this.#completedBlocks.has(key) || !this.onRepairNeeded || this.#repairRequestedBlocks.has(key)) return;
|
|
12205
|
+
if (this.#completedBlocks.has(key) || !this.#hasCatalogTimingForPacket(packetId) || !this.onRepairNeeded || this.#repairRequestedBlocks.has(key)) return;
|
|
12259
12206
|
const deadline = this.#repairDeadlines.get(key);
|
|
12260
12207
|
if (deadline?.handle != null) clearTimeout(deadline.handle);
|
|
12261
12208
|
this.#repairDeadlines.set(key, null);
|
|
@@ -12335,6 +12282,7 @@ var FECManager = class _FECManager {
|
|
|
12335
12282
|
}
|
|
12336
12283
|
/** A block's live class without creating classifier state for the stream. */
|
|
12337
12284
|
#peekClass(sbn, packetId, catalogTrackName) {
|
|
12285
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return "unknown";
|
|
12338
12286
|
const classifier = this.#classifiers.get(this.#streamKey(packetId, catalogTrackName));
|
|
12339
12287
|
if (classifier) return classifier.classOf(sbn);
|
|
12340
12288
|
return this.#geometryFor(packetId, catalogTrackName).trackKind === "audio" ? "audio" : "unknown";
|
|
@@ -12356,8 +12304,8 @@ var FECManager = class _FECManager {
|
|
|
12356
12304
|
// Deliberately one block tighter than deriveFecRetentionBlocks over the same
|
|
12357
12305
|
// inputs (that function's `+ 1`). Different question: the retention horizon
|
|
12358
12306
|
// asks how long the decoder still ACCEPTS symbols for a block, this asks how
|
|
12359
|
-
//
|
|
12360
|
-
// #noteSbnSeen).
|
|
12307
|
+
// many blocks a stream can live behind (or ahead of) its frontier before a
|
|
12308
|
+
// source symbol there is a discontinuity rather than loss (see #noteSbnSeen).
|
|
12361
12309
|
// The extra block of retention is slack for a block still being fed; a block
|
|
12362
12310
|
// nothing has been heard from does not get it. Not the units defect — but it
|
|
12363
12311
|
// is the next place the two can drift, so change both or neither.
|
|
@@ -12366,27 +12314,28 @@ var FECManager = class _FECManager {
|
|
|
12366
12314
|
return Math.ceil(config.timeoutMs / config.interleaveDepthMs);
|
|
12367
12315
|
}
|
|
12368
12316
|
/**
|
|
12369
|
-
* Record that a symbol for block `sbn`
|
|
12370
|
-
*
|
|
12317
|
+
* Record that a symbol for block `sbn` (the block's SS_Start) arrived and
|
|
12318
|
+
* advance the stream's frontier, the newest block start seen.
|
|
12371
12319
|
*
|
|
12372
|
-
*
|
|
12373
|
-
*
|
|
12374
|
-
*
|
|
12375
|
-
*
|
|
12376
|
-
*
|
|
12377
|
-
* its last symbol, is a source discontinuity, not loss. A repair symbol can
|
|
12378
|
-
* only advance the frontier: a D-28 fetch legitimately answers for an old
|
|
12379
|
-
* block, so it never signals a discontinuity.
|
|
12320
|
+
* A SOURCE symbol whose block goes back further than one live window, or
|
|
12321
|
+
* jumps further than the stream could have produced since its last symbol,
|
|
12322
|
+
* is a source discontinuity, not loss. A repair symbol can only advance the
|
|
12323
|
+
* frontier: a D-28 fetch legitimately answers for an old block, so it never
|
|
12324
|
+
* signals a discontinuity.
|
|
12380
12325
|
*
|
|
12381
12326
|
* The producer closes a block at its Dth media-unit boundary OR when K
|
|
12382
|
-
* source symbols filled it, so one interleave span can start several
|
|
12383
|
-
*
|
|
12384
|
-
*
|
|
12385
|
-
*
|
|
12386
|
-
*
|
|
12387
|
-
*
|
|
12388
|
-
*
|
|
12389
|
-
*
|
|
12327
|
+
* source symbols filled it, so one interleave span can start several blocks
|
|
12328
|
+
* (every large keyframe does). What bounds a forward jump is therefore
|
|
12329
|
+
* packet evidence, not a block rate: SS_ID advances by one per FEC source
|
|
12330
|
+
* packet (ISO/IEC 23008-1 C.5.2) and the MMTP packet_sequence_number by one
|
|
12331
|
+
* per packet of the packet_id, so with both source symbols'
|
|
12332
|
+
* packet_sequence_number a block start can move at most the packets sent
|
|
12333
|
+
* between them plus the anchor's own offset into its block (< K). Without
|
|
12334
|
+
* it, only the D-close rate is known.
|
|
12335
|
+
*
|
|
12336
|
+
* Vanished blocks are not enumerated here: their starts are unknown until a
|
|
12337
|
+
* repair states them. The SS_IDs between two stated blocks become one
|
|
12338
|
+
* repair-less span when the later block's repair arrives (placeIsoRepair).
|
|
12390
12339
|
*/
|
|
12391
12340
|
#noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber) {
|
|
12392
12341
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
@@ -12395,7 +12344,7 @@ var FECManager = class _FECManager {
|
|
|
12395
12344
|
const anchor = this.#sourceAnchors.get(streamKey);
|
|
12396
12345
|
const psn = symbol === "source" && packetSequenceNumber !== void 0 && Number.isSafeInteger(packetSequenceNumber) && packetSequenceNumber >= 0 && packetSequenceNumber <= 4294967295 ? packetSequenceNumber : void 0;
|
|
12397
12346
|
const anchorHere = () => {
|
|
12398
|
-
if (psn !== void 0 && (anchor === void 0 || !this.#blockPrecedes(sbn, anchor.sbn
|
|
12347
|
+
if (psn !== void 0 && (anchor === void 0 || !this.#blockPrecedes(sbn, anchor.sbn))) {
|
|
12399
12348
|
this.#sourceAnchors.set(streamKey, { sbn, psn, atMs: now });
|
|
12400
12349
|
}
|
|
12401
12350
|
};
|
|
@@ -12411,9 +12360,9 @@ var FECManager = class _FECManager {
|
|
|
12411
12360
|
anchorHere();
|
|
12412
12361
|
return;
|
|
12413
12362
|
}
|
|
12414
|
-
const config = this.#
|
|
12363
|
+
const config = this.#trackConfig(packetId);
|
|
12415
12364
|
const window2 = this.#liveWindowBlocks(config);
|
|
12416
|
-
if (!this.#blockPrecedes(previous.sbn, sbn
|
|
12365
|
+
if (!this.#blockPrecedes(previous.sbn, sbn)) {
|
|
12417
12366
|
if (symbol === "source" && window2 !== null && this.#blocksBehind(sbn, previous.sbn, packetId) > window2) {
|
|
12418
12367
|
discontinuity();
|
|
12419
12368
|
return;
|
|
@@ -12424,15 +12373,9 @@ var FECManager = class _FECManager {
|
|
|
12424
12373
|
const missing = this.#blocksBehind(previous.sbn, sbn, packetId) - 1;
|
|
12425
12374
|
let plausible = true;
|
|
12426
12375
|
if (window2 !== null) {
|
|
12427
|
-
if (
|
|
12376
|
+
if (psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn) && psn !== anchor.psn) {
|
|
12428
12377
|
const sent = psn - anchor.psn >>> 0;
|
|
12429
12378
|
plausible = sent <= 2147483647 && serial32Distance(anchor.sbn, sbn) <= sent + config.k;
|
|
12430
|
-
} else if (psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {
|
|
12431
|
-
const sent = psn - anchor.psn >>> 0;
|
|
12432
|
-
const between = this.#blocksBehind(anchor.sbn, sbn, packetId) - 1;
|
|
12433
|
-
const dCloses = window2 + Math.ceil((now - anchor.atMs) / config.interleaveDepthMs);
|
|
12434
|
-
const kCloses = config.k > 0 ? Math.ceil(sent / config.k) : sent;
|
|
12435
|
-
plausible = sent <= 2147483647 && between <= sent - 1 && between <= dCloses + kCloses;
|
|
12436
12379
|
} else {
|
|
12437
12380
|
plausible = missing <= window2 + Math.ceil((now - previous.atMs) / config.interleaveDepthMs);
|
|
12438
12381
|
}
|
|
@@ -12444,48 +12387,31 @@ var FECManager = class _FECManager {
|
|
|
12444
12387
|
}
|
|
12445
12388
|
this.#highestSbn.set(streamKey, { sbn, atMs: now });
|
|
12446
12389
|
anchorHere();
|
|
12447
|
-
if (this.#isIso(packetId)) return;
|
|
12448
|
-
if (missing <= 0 || window2 === null) return;
|
|
12449
|
-
let vanished = previous.sbn;
|
|
12450
|
-
for (let i = 0; i < missing; i++) {
|
|
12451
|
-
vanished = this.#nextBlock(vanished, packetId);
|
|
12452
|
-
this.#materializeBlock(
|
|
12453
|
-
vanished,
|
|
12454
|
-
packetId,
|
|
12455
|
-
catalogTrackName,
|
|
12456
|
-
this.#blocksBehind(vanished, sbn, packetId) <= window2
|
|
12457
|
-
);
|
|
12458
|
-
}
|
|
12459
12390
|
}
|
|
12460
12391
|
/*
|
|
12461
|
-
* Block order for one stream (BLO-35597 iso:5).
|
|
12462
|
-
* number (ISO/IEC 23008-1 C.5.2), so
|
|
12463
|
-
*
|
|
12464
|
-
*
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
#blockPrecedes(a, b, packetId) {
|
|
12469
|
-
if (this.#isIso(packetId)) return serial32Precedes(a, b);
|
|
12470
|
-
const k = this.kForPacket(packetId);
|
|
12471
|
-
return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlockPrecedes(a, b, k) : a < b;
|
|
12472
|
-
}
|
|
12473
|
-
/** Blocks `a` lies behind `b` (0 when it does not precede it). */
|
|
12474
|
-
#blocksBehind(a, b, packetId) {
|
|
12475
|
-
if (this.#isIso(packetId)) return isoBlocksBehindLowerBound(a, b, this.kForPacket(packetId));
|
|
12476
|
-
const k = this.kForPacket(packetId);
|
|
12477
|
-
return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlocksBehind(a, b, k) : Math.max(0, b - a);
|
|
12392
|
+
* Block order for one stream (BLO-35597 iso:5). A block is keyed by its
|
|
12393
|
+
* SS_Start and SS_ID is a 32-bit serial number (ISO/IEC 23008-1 C.5.2), so
|
|
12394
|
+
* blocks are ordered by RFC 1982 comparison of their starts. Only configured
|
|
12395
|
+
* streams hold blocks: every entry point refuses an unconfigured packetId.
|
|
12396
|
+
*/
|
|
12397
|
+
#blockPrecedes(a, b) {
|
|
12398
|
+
return serial32Precedes(a, b);
|
|
12478
12399
|
}
|
|
12479
|
-
/**
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12400
|
+
/**
|
|
12401
|
+
* Blocks `a` lies behind `b` (0 when it does not precede it). Blocks vary
|
|
12402
|
+
* in length (K' = SSB_length <= K), so only a lower bound is known: the one
|
|
12403
|
+
* metric the decoder and the worker retire by too.
|
|
12404
|
+
*/
|
|
12405
|
+
#blocksBehind(a, b, packetId) {
|
|
12406
|
+
return isoBlocksBehindLowerBound(a, b, this.#trackConfig(packetId).k);
|
|
12483
12407
|
}
|
|
12484
12408
|
#materializeBlock(sbn, packetId, catalogTrackName, repairable) {
|
|
12485
12409
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
12486
12410
|
if (this.#blockResolutionFor(key) !== "untracked") return;
|
|
12487
12411
|
this.#materializedBlocks.add(key);
|
|
12488
|
-
this.#
|
|
12412
|
+
if (this.#hasCatalogTimingForPacket(packetId)) {
|
|
12413
|
+
this.#classifierFor(packetId, catalogTrackName).touch(sbn);
|
|
12414
|
+
}
|
|
12489
12415
|
this.onBlockVanished?.(sbn, packetId, catalogTrackName);
|
|
12490
12416
|
if (repairable) {
|
|
12491
12417
|
this.noteBlockActivity(sbn, packetId, catalogTrackName, true);
|
|
@@ -12524,21 +12450,39 @@ var FECManager = class _FECManager {
|
|
|
12524
12450
|
});
|
|
12525
12451
|
if (this.#lifecycleTrace.length > 64) this.#lifecycleTrace.splice(0, 32);
|
|
12526
12452
|
}
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12453
|
+
/**
|
|
12454
|
+
* A configured packetId's track config. There is no manager-wide default
|
|
12455
|
+
* (BLO-45953): an unconfigured packetId does no FEC work, so every caller
|
|
12456
|
+
* here is reached only behind a configuration gate and this throw marks a
|
|
12457
|
+
* broken invariant, never a packet-path outcome.
|
|
12458
|
+
*/
|
|
12459
|
+
#trackConfig(packetId) {
|
|
12460
|
+
const config = this.#trackConfigs.get(packetId);
|
|
12461
|
+
if (config === void 0) {
|
|
12462
|
+
throw new Error(
|
|
12463
|
+
`[FECManager] packetId=${packetId} was never configured; configure the track from its catalog (configureTrack/setFecWorker/setFecDecoder) first`
|
|
12464
|
+
);
|
|
12465
|
+
}
|
|
12466
|
+
return config;
|
|
12535
12467
|
}
|
|
12536
|
-
|
|
12537
|
-
|
|
12468
|
+
/**
|
|
12469
|
+
* Whether this track has the catalog's elapsed-time FEC policy. A zero span
|
|
12470
|
+
* is a deliberate no-deadline signal; it still leaves the wire geometry
|
|
12471
|
+
* usable for in-band decode, but it cannot drive classification, repair
|
|
12472
|
+
* fetches or layer adaptation.
|
|
12473
|
+
*/
|
|
12474
|
+
static #hasCatalogTiming(config) {
|
|
12475
|
+
return Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS;
|
|
12476
|
+
}
|
|
12477
|
+
#hasCatalogTimingForPacket(packetId) {
|
|
12478
|
+
const config = this.#trackConfigs.get(packetId);
|
|
12479
|
+
return config !== void 0 && _FECManager.#hasCatalogTiming(config);
|
|
12538
12480
|
}
|
|
12539
12481
|
#computeTimeout(config) {
|
|
12540
|
-
if (config.timeoutMs && config.timeoutMs > 0)
|
|
12541
|
-
|
|
12482
|
+
if (typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
|
|
12483
|
+
return config.timeoutMs;
|
|
12484
|
+
}
|
|
12485
|
+
if (Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
|
|
12542
12486
|
return Math.max(
|
|
12543
12487
|
this.#fecTimeoutMs,
|
|
12544
12488
|
computeFecDeliveryWindowMs({
|
|
@@ -12563,14 +12507,17 @@ var FECManager = class _FECManager {
|
|
|
12563
12507
|
*/
|
|
12564
12508
|
#refreshDeliveryGapWindow() {
|
|
12565
12509
|
let windowMs;
|
|
12566
|
-
const consider = (geometry) => {
|
|
12510
|
+
const consider = (geometry, packetId) => {
|
|
12511
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return;
|
|
12567
12512
|
if (geometry?.trackKind !== "video" || geometry.mode !== "classifying") return;
|
|
12568
12513
|
const { ticks, timescale } = geometry.keyframeInterval;
|
|
12569
12514
|
const ms = ticks / timescale * 1e3;
|
|
12570
12515
|
if (Number.isFinite(ms) && ms > 0 && (windowMs === void 0 || ms > windowMs)) windowMs = ms;
|
|
12571
12516
|
};
|
|
12572
|
-
for (const geometry of this.#streamGeometries
|
|
12573
|
-
|
|
12517
|
+
for (const [streamKey, geometry] of this.#streamGeometries) {
|
|
12518
|
+
consider(geometry, this.#parseStreamKey(streamKey).packetId);
|
|
12519
|
+
}
|
|
12520
|
+
for (const [packetId, config] of this.#trackConfigs) consider(config.geometry, packetId);
|
|
12574
12521
|
this.#deliveryGaps.setWindowMs(windowMs);
|
|
12575
12522
|
}
|
|
12576
12523
|
/**
|
|
@@ -12598,6 +12545,7 @@ var FECManager = class _FECManager {
|
|
|
12598
12545
|
return Math.max(config.timeoutMs, Math.ceil(config.interleaveDepthMs + gapMs));
|
|
12599
12546
|
}
|
|
12600
12547
|
#repairDeadlineMs(config, waitMs = this.#multicastWaitMs(config)) {
|
|
12548
|
+
if (!_FECManager.#hasCatalogTiming(config)) return waitMs;
|
|
12601
12549
|
const halfFailBoundMs = Math.ceil(waitMs / 2);
|
|
12602
12550
|
if (config.k > 0 && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS && config.fps !== void 0 && Number.isFinite(config.fps) && config.fps > 0) {
|
|
12603
12551
|
const mediaUnitMs = 1e3 / config.fps;
|
|
@@ -12612,10 +12560,12 @@ var FECManager = class _FECManager {
|
|
|
12612
12560
|
}
|
|
12613
12561
|
/**
|
|
12614
12562
|
* One block lifetime: the inactivity fail bound plus the D-28 repair grace,
|
|
12615
|
-
* at the widest wait the catalog allows (#maxMulticastWaitMs).
|
|
12616
|
-
*
|
|
12617
|
-
*
|
|
12618
|
-
*
|
|
12563
|
+
* at the widest wait the catalog allows (#maxMulticastWaitMs). For a track
|
|
12564
|
+
* without a catalog span this is receiver-local cleanup time only; no repair
|
|
12565
|
+
* grace is added. Everything derived from it (terminal retention, the
|
|
12566
|
+
* release gate's hold ceiling, the duplicate ledgers, the owed-cleanup
|
|
12567
|
+
* bound) is compared across time, so it must not follow the live
|
|
12568
|
+
* measurement.
|
|
12619
12569
|
*/
|
|
12620
12570
|
#blockLifetimeMs(config) {
|
|
12621
12571
|
const waitMs = this.#maxMulticastWaitMs(config);
|
|
@@ -12631,6 +12581,7 @@ var FECManager = class _FECManager {
|
|
|
12631
12581
|
return Math.min(waitMs + this.#repairGraceMs(config, waitMs), this.#blockLifetimeMs(config));
|
|
12632
12582
|
}
|
|
12633
12583
|
#repairGraceMs(config, waitMs) {
|
|
12584
|
+
if (!_FECManager.#hasCatalogTiming(config)) return 0;
|
|
12634
12585
|
return Math.max(1, waitMs - this.#repairDeadlineMs(config, waitMs));
|
|
12635
12586
|
}
|
|
12636
12587
|
#armBlockTimeout(sbn, packetId, catalogTrackName, delayMs) {
|
|
@@ -12717,51 +12668,72 @@ var FECManager = class _FECManager {
|
|
|
12717
12668
|
* stream; every other field stays per packetId.
|
|
12718
12669
|
*/
|
|
12719
12670
|
#upsertTrackConfig(packetId, config, catalogTrackName) {
|
|
12720
|
-
const
|
|
12671
|
+
const previousTrack = this.#trackConfigs.get(packetId);
|
|
12672
|
+
const k = config.k ?? previousTrack?.k;
|
|
12673
|
+
if (k === void 0 || !Number.isSafeInteger(k) || k <= 0) {
|
|
12674
|
+
throw new Error(
|
|
12675
|
+
`[FECManager] packetId=${packetId}: fec.k=${String(k)} must be a positive integer; configure the track from its catalog`
|
|
12676
|
+
);
|
|
12677
|
+
}
|
|
12678
|
+
const previous = previousTrack ?? {};
|
|
12721
12679
|
const streamScopedGeometry = catalogTrackName !== void 0 && config.geometry !== void 0 ? config.geometry : void 0;
|
|
12722
12680
|
if (streamScopedGeometry !== void 0) {
|
|
12723
12681
|
const { geometry: _streamGeometry, ...packetLevel } = config;
|
|
12724
12682
|
config = packetLevel;
|
|
12725
12683
|
}
|
|
12726
12684
|
const timingChanged = config.k !== void 0 || config.interleaveDepthMs !== void 0 || config.fps !== void 0;
|
|
12685
|
+
if (config.timeoutMs !== void 0 && (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)) {
|
|
12686
|
+
throw new Error(
|
|
12687
|
+
`[FECManager] packetId=${packetId}: timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`
|
|
12688
|
+
);
|
|
12689
|
+
}
|
|
12727
12690
|
if (config.blockProfile !== void 0) {
|
|
12728
12691
|
parseFecBlockProfile(config.blockProfile, `packetId=${packetId} fec.blockProfile`);
|
|
12729
12692
|
}
|
|
12730
12693
|
const blockProfile = config.blockProfile ?? previous.blockProfile;
|
|
12731
12694
|
if (blockProfile === void 0) {
|
|
12732
12695
|
throw new Error(
|
|
12733
|
-
`[FECManager] packetId=${packetId}: fec.blockProfile is required ("
|
|
12696
|
+
`[FECManager] packetId=${packetId}: fec.blockProfile is required ("iso-ssbg1-v1"); configure the track from its catalog`
|
|
12734
12697
|
);
|
|
12735
12698
|
}
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
);
|
|
12742
|
-
}
|
|
12699
|
+
const ownInterleaveDepthMs = config.interleaveDepthMs ?? previousTrack?.interleaveDepthMs ?? 0;
|
|
12700
|
+
if (!Number.isSafeInteger(ownInterleaveDepthMs) || ownInterleaveDepthMs !== 0 && ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
|
|
12701
|
+
throw new Error(
|
|
12702
|
+
`[FECManager] packetId=${packetId}: fec.interleaveDepthMs must be absent, 0, or a finite value >= ${MIN_INTERLEAVE_DEPTH_MS} ms (got ${String(ownInterleaveDepthMs)})`
|
|
12703
|
+
);
|
|
12743
12704
|
}
|
|
12705
|
+
const hasCatalogTiming = _FECManager.#hasCatalogTiming({ interleaveDepthMs: ownInterleaveDepthMs });
|
|
12744
12706
|
const next = {
|
|
12745
12707
|
blockProfile,
|
|
12746
|
-
k
|
|
12747
|
-
symbolSize: config.symbolSize != null && config.symbolSize > 0 ? config.symbolSize :
|
|
12748
|
-
interleaveDepthMs:
|
|
12749
|
-
fps: config.fps ?? previous.fps,
|
|
12750
|
-
timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs),
|
|
12708
|
+
k,
|
|
12709
|
+
symbolSize: config.symbolSize != null && config.symbolSize > 0 ? config.symbolSize : previousTrack?.symbolSize ?? 0,
|
|
12710
|
+
interleaveDepthMs: ownInterleaveDepthMs,
|
|
12711
|
+
fps: config.fps ?? previous.fps ?? this.#fecFps,
|
|
12712
|
+
timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs ?? 0),
|
|
12751
12713
|
...(config.trackKind ?? previous.trackKind) === void 0 ? {} : { trackKind: config.trackKind ?? previous.trackKind },
|
|
12752
12714
|
...(config.codec ?? previous.codec) === void 0 ? {} : { codec: config.codec ?? previous.codec },
|
|
12753
12715
|
...(config.geometry ?? previous.geometry) === void 0 ? {} : { geometry: config.geometry ?? previous.geometry }
|
|
12754
12716
|
};
|
|
12755
|
-
|
|
12717
|
+
if (!hasCatalogTiming) {
|
|
12718
|
+
next.timeoutMs = config.timeoutMs !== void 0 && config.timeoutMs > 0 ? config.timeoutMs : previousTrack !== void 0 && previousTrack.interleaveDepthMs === 0 ? previousTrack.timeoutMs : LOCAL_FEC_TIMEOUT_MS;
|
|
12719
|
+
} else {
|
|
12720
|
+
next.timeoutMs = this.#computeTimeout(next);
|
|
12721
|
+
}
|
|
12756
12722
|
if (this.#multicastSymbolDelivery === "ordered-relay") {
|
|
12757
12723
|
this.#orderedHorizonBlocksFor(packetId, next);
|
|
12758
12724
|
}
|
|
12759
|
-
|
|
12725
|
+
const timingPolicyChanged = _FECManager.#hasCatalogTiming({ interleaveDepthMs: previous.interleaveDepthMs ?? 0 }) !== hasCatalogTiming;
|
|
12726
|
+
if (timingPolicyChanged && !hasCatalogTiming) {
|
|
12727
|
+
for (const key of [...this.#repairDeadlines.keys()]) {
|
|
12728
|
+
if (this.#belongsToPacket(key, packetId)) this.#clearRepairDeadline(key);
|
|
12729
|
+
}
|
|
12730
|
+
}
|
|
12731
|
+
if (previous.blockProfile !== void 0 && previous.k !== next.k) {
|
|
12760
12732
|
this.#dropIsoLocators(packetId);
|
|
12761
12733
|
}
|
|
12762
12734
|
this.#trackConfigs.set(packetId, next);
|
|
12763
12735
|
this.#refreshDeliveryGapWindow();
|
|
12764
|
-
let policyChanged = config.geometry !== void 0 && JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry) || config.trackKind !== void 0 && config.trackKind !== previous.trackKind;
|
|
12736
|
+
let policyChanged = timingPolicyChanged || config.geometry !== void 0 && JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry) || config.trackKind !== void 0 && config.trackKind !== previous.trackKind;
|
|
12765
12737
|
if (streamScopedGeometry !== void 0) {
|
|
12766
12738
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
12767
12739
|
if (JSON.stringify(streamScopedGeometry) !== JSON.stringify(this.#streamGeometries.get(streamKey))) {
|
|
@@ -12776,20 +12748,14 @@ var FECManager = class _FECManager {
|
|
|
12776
12748
|
if (!this.#belongsToPacket(key, packetId)) continue;
|
|
12777
12749
|
const stream = this.#parseStreamKey(key);
|
|
12778
12750
|
const geometry = this.#geometryFor(stream.packetId, stream.catalogTrackName);
|
|
12779
|
-
if (JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
|
|
12751
|
+
if (hasCatalogTiming && JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
|
|
12780
12752
|
this.#classifiers.delete(key);
|
|
12753
|
+
if (!hasCatalogTiming) continue;
|
|
12781
12754
|
this.#applyClassifierChanges(stream.packetId, stream.catalogTrackName, classifier.retire());
|
|
12782
12755
|
}
|
|
12783
12756
|
}
|
|
12784
12757
|
return next;
|
|
12785
12758
|
}
|
|
12786
|
-
#normalizeSymbolData(packetId, data) {
|
|
12787
|
-
const symbolSize = this.symbolSizeForPacket(packetId);
|
|
12788
|
-
if (symbolSize <= 0 || data.byteLength === symbolSize) return data;
|
|
12789
|
-
const normalized = new Uint8Array(symbolSize);
|
|
12790
|
-
normalized.set(data.subarray(0, symbolSize));
|
|
12791
|
-
return normalized;
|
|
12792
|
-
}
|
|
12793
12759
|
#blockKeysFor(sbn, packetId, catalogTrackName) {
|
|
12794
12760
|
if (packetId != null && catalogTrackName !== void 0) {
|
|
12795
12761
|
return [this.#blockKey(sbn, packetId, catalogTrackName)];
|
|
@@ -12850,7 +12816,7 @@ var FECManager = class _FECManager {
|
|
|
12850
12816
|
...stats,
|
|
12851
12817
|
packetId,
|
|
12852
12818
|
blocksComplete: trackTotal,
|
|
12853
|
-
k: this.#
|
|
12819
|
+
k: this.#trackConfigs.get(packetId)?.k,
|
|
12854
12820
|
recoveryRate: trackAffected > 0 ? stats.blocksRecovered / trackAffected * 100 : 100,
|
|
12855
12821
|
packetLossRate: trackTotal > 0 ? trackAffected / trackTotal * 100 : 0
|
|
12856
12822
|
};
|
|
@@ -12862,7 +12828,7 @@ var FECManager = class _FECManager {
|
|
|
12862
12828
|
(block) => block.multicastSymbols > 0 && block.moqSymbols > 0
|
|
12863
12829
|
).length,
|
|
12864
12830
|
recentBlockPathCounts: Array.from(this.#blockPathCounts.values()).slice(-24),
|
|
12865
|
-
k: this.#
|
|
12831
|
+
k: this.#streamK,
|
|
12866
12832
|
// True recovery rate: of blocks that HAD loss, how many were recovered?
|
|
12867
12833
|
recoveryRate: affected > 0 ? s.blocksRecovered / affected * 100 : 100,
|
|
12868
12834
|
packetLossRate: total > 0 ? affected / total * 100 : 0,
|
|
@@ -13157,15 +13123,20 @@ var FECManager = class _FECManager {
|
|
|
13157
13123
|
}
|
|
13158
13124
|
}
|
|
13159
13125
|
}
|
|
13160
|
-
/**
|
|
13126
|
+
/**
|
|
13127
|
+
* Record a transport symbol and reject later duplicates of the same
|
|
13128
|
+
* packetId/SBN/ESI. `sbn` is the block's SS_Start. A packetId with no
|
|
13129
|
+
* configured block profile has no block geometry: its symbol is refused
|
|
13130
|
+
* (false) and no block state is created for it.
|
|
13131
|
+
*/
|
|
13161
13132
|
feedSymbolFromSource(packetId, sbn, esi, _data, source, catalogTrackName) {
|
|
13133
|
+
if (!this.#hasBlockProfile(packetId)) return false;
|
|
13162
13134
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
13163
13135
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
13164
13136
|
if (source === "multicast" && this.#multicastSymbolDelivery === "ordered-relay") {
|
|
13165
13137
|
this.#advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName);
|
|
13166
13138
|
}
|
|
13167
|
-
const
|
|
13168
|
-
const sourceSymbols = this.#isIso(packetId) ? this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? k : k;
|
|
13139
|
+
const sourceSymbols = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? this.#trackConfig(packetId).k;
|
|
13169
13140
|
const isSourceSymbol = esi >= 0 && esi < sourceSymbols;
|
|
13170
13141
|
const isStartupPartial = this.#startupPartialBlocks.has(blockKey);
|
|
13171
13142
|
const isReadyMulticastRepair = this.#startupRequireRepairReady && source === "multicast" && !isSourceSymbol && this.#startupRepairReadyPackets.has(streamKey);
|
|
@@ -13283,14 +13254,14 @@ var FECManager = class _FECManager {
|
|
|
13283
13254
|
for (const [key] of this.#activeBlocks) {
|
|
13284
13255
|
const { packetId, sbn } = this.#parseBlockKey(key);
|
|
13285
13256
|
const count = this.#blockSourceCounts.get(key) ?? 0;
|
|
13286
|
-
const k = this.#blockAuthoredLengths.get(key) ?? this
|
|
13257
|
+
const k = this.#blockAuthoredLengths.get(key) ?? this.#trackConfig(packetId).k;
|
|
13287
13258
|
blocks.push({ sbn, packetId, state: "collecting", sourceCount: count, k, progress: k > 0 ? Math.min(count / k, 1) : 0 });
|
|
13288
13259
|
}
|
|
13289
13260
|
const completed = [...this.#completedBlocks].slice(-16);
|
|
13290
13261
|
for (const key of completed) {
|
|
13291
13262
|
const { packetId, sbn } = this.#parseBlockKey(key);
|
|
13292
13263
|
if (!this.#activeBlocks.has(key)) {
|
|
13293
|
-
const k = this
|
|
13264
|
+
const k = this.#trackConfig(packetId).k;
|
|
13294
13265
|
blocks.push({ sbn, packetId, state: "complete", sourceCount: k, k, progress: 1 });
|
|
13295
13266
|
}
|
|
13296
13267
|
}
|
|
@@ -13301,25 +13272,23 @@ var FECManager = class _FECManager {
|
|
|
13301
13272
|
get wasmDecoder() {
|
|
13302
13273
|
return this.#fecDecoders.values().next().value?.client ?? null;
|
|
13303
13274
|
}
|
|
13304
|
-
|
|
13305
|
-
return this.#fecK;
|
|
13306
|
-
}
|
|
13275
|
+
/** A configured packetId's K; undefined for an unconfigured one (no FEC work). */
|
|
13307
13276
|
kForPacket(packetId) {
|
|
13308
|
-
return this.#
|
|
13277
|
+
return this.#trackConfigs.get(packetId)?.k;
|
|
13309
13278
|
}
|
|
13310
13279
|
/** FEC symbol size in bytes. */
|
|
13311
13280
|
get symbolSize() {
|
|
13312
13281
|
return this.#fecSymbolSize;
|
|
13313
13282
|
}
|
|
13314
13283
|
symbolSizeForPacket(packetId) {
|
|
13315
|
-
return this.#
|
|
13284
|
+
return this.#trackConfigs.get(packetId)?.symbolSize;
|
|
13316
13285
|
}
|
|
13317
13286
|
/** FEC block span in milliseconds (0 = no interleaving). */
|
|
13318
13287
|
get interleaveDepthMs() {
|
|
13319
13288
|
return this.#fecInterleaveDepthMs;
|
|
13320
13289
|
}
|
|
13321
13290
|
interleaveDepthMsForPacket(packetId) {
|
|
13322
|
-
return this.#
|
|
13291
|
+
return this.#trackConfigs.get(packetId)?.interleaveDepthMs;
|
|
13323
13292
|
}
|
|
13324
13293
|
/**
|
|
13325
13294
|
* Record one multicast MMTP packet arrival (every packet of the leg, FEC
|
|
@@ -13337,7 +13306,8 @@ var FECManager = class _FECManager {
|
|
|
13337
13306
|
* plus the larger of the catalog tolerance and the measured delivery gap.
|
|
13338
13307
|
*/
|
|
13339
13308
|
timeoutMsForPacket(packetId) {
|
|
13340
|
-
|
|
13309
|
+
const config = this.#trackConfigs.get(packetId);
|
|
13310
|
+
return config === void 0 ? void 0 : this.#multicastWaitMs(config);
|
|
13341
13311
|
}
|
|
13342
13312
|
/**
|
|
13343
13313
|
* Largest multicast delivery gap measured over the current window (ms), and
|
|
@@ -13393,27 +13363,31 @@ var FECManager = class _FECManager {
|
|
|
13393
13363
|
}
|
|
13394
13364
|
/** Resolved per-track D-28 repair request deadline in milliseconds. */
|
|
13395
13365
|
repairDeadlineMsForPacket(packetId) {
|
|
13396
|
-
|
|
13366
|
+
const config = this.#trackConfigs.get(packetId);
|
|
13367
|
+
return config === void 0 ? void 0 : this.#repairDeadlineMs(config);
|
|
13397
13368
|
}
|
|
13398
13369
|
/**
|
|
13399
13370
|
* One block lifetime: the longest a block stays open after its last symbol
|
|
13400
13371
|
* (inactivity fail bound plus D-28 repair grace).
|
|
13401
13372
|
*/
|
|
13402
13373
|
blockLifetimeMsForPacket(packetId) {
|
|
13403
|
-
|
|
13374
|
+
const config = this.#trackConfigs.get(packetId);
|
|
13375
|
+
return config === void 0 ? void 0 : this.#blockLifetimeMs(config);
|
|
13404
13376
|
}
|
|
13405
13377
|
/**
|
|
13406
13378
|
* How long a terminal (clean, recovered or failed) block stays authoritative,
|
|
13407
13379
|
* for late symbols and for the in-order release gate. It is also the gate's
|
|
13408
13380
|
* hold ceiling (MmtpRouter's holdLimitMs): a retired block reads back as
|
|
13409
13381
|
* untracked, which the gate counts as lost, so no frame may still be waiting
|
|
13410
|
-
* on a block that can retire.
|
|
13411
|
-
*
|
|
13412
|
-
*
|
|
13413
|
-
*
|
|
13382
|
+
* on a block that can retire. With a positive catalog span the window is one
|
|
13383
|
+
* interleave depth for a gap's last block to finish arriving, one block
|
|
13384
|
+
* lifetime to fail it, and one more for an owed Worker cleanup. With an
|
|
13385
|
+
* absent/zero span it is a receiver-local cleanup bound only; it creates no
|
|
13386
|
+
* catalog FEC deadline.
|
|
13414
13387
|
*/
|
|
13415
13388
|
terminalRetentionMsForPacket(packetId) {
|
|
13416
|
-
const config = this.#
|
|
13389
|
+
const config = this.#trackConfigs.get(packetId);
|
|
13390
|
+
if (config === void 0) return void 0;
|
|
13417
13391
|
return config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
|
|
13418
13392
|
}
|
|
13419
13393
|
/** Remaining fetch budget for a specific repair-requested block. */
|
|
@@ -13424,7 +13398,8 @@ var FECManager = class _FECManager {
|
|
|
13424
13398
|
if (deadlineMs !== void 0) {
|
|
13425
13399
|
return Math.max(1, Math.ceil(deadlineMs - performance.now()));
|
|
13426
13400
|
}
|
|
13427
|
-
const trackConfig = this.#
|
|
13401
|
+
const trackConfig = this.#trackConfigs.get(packetId);
|
|
13402
|
+
if (trackConfig === void 0) return void 0;
|
|
13428
13403
|
return Math.max(1, trackConfig.timeoutMs - this.#repairDeadlineMs(trackConfig));
|
|
13429
13404
|
}
|
|
13430
13405
|
/** True once configureTrack/setFecDecoder/setFecWorker configured this packetId. */
|
|
@@ -13435,14 +13410,19 @@ var FECManager = class _FECManager {
|
|
|
13435
13410
|
blockProfileForPacket(packetId) {
|
|
13436
13411
|
return this.#trackConfigs.get(packetId)?.blockProfile;
|
|
13437
13412
|
}
|
|
13438
|
-
|
|
13439
|
-
|
|
13413
|
+
/**
|
|
13414
|
+
* Whether a packetId has a configured block geometry. iso-ssbg1-v1 is the
|
|
13415
|
+
* only profile, so every configured track has it; a packetId never
|
|
13416
|
+
* configured (or released) has none and gets no FEC block tracking.
|
|
13417
|
+
*/
|
|
13418
|
+
#hasBlockProfile(packetId) {
|
|
13419
|
+
return this.#trackConfigs.get(packetId)?.blockProfile !== void 0;
|
|
13440
13420
|
}
|
|
13441
13421
|
#isoLocatorFor(packetId, catalogTrackName) {
|
|
13442
13422
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
13443
13423
|
let locator = this.#isoLocators.get(streamKey);
|
|
13444
13424
|
if (!locator) {
|
|
13445
|
-
locator = new IsoBlockLocator(this
|
|
13425
|
+
locator = new IsoBlockLocator(this.#trackConfig(packetId).k, this.#isoRetentionBlocks(packetId));
|
|
13446
13426
|
this.#isoLocators.set(streamKey, locator);
|
|
13447
13427
|
}
|
|
13448
13428
|
return locator;
|
|
@@ -13473,12 +13453,14 @@ var FECManager = class _FECManager {
|
|
|
13473
13453
|
*/
|
|
13474
13454
|
#isoRetentionBlocks(packetId) {
|
|
13475
13455
|
const config = this.#trackConfigs.get(packetId);
|
|
13476
|
-
if (config?.blockProfile
|
|
13456
|
+
if (config?.blockProfile === void 0) {
|
|
13477
13457
|
throw new Error(
|
|
13478
|
-
`[FECManager] packetId=${packetId} has no iso-ssbg1-v1 block
|
|
13458
|
+
`[FECManager] packetId=${packetId} has no configured iso-ssbg1-v1 block profile`
|
|
13479
13459
|
);
|
|
13480
13460
|
}
|
|
13481
|
-
|
|
13461
|
+
if (!_FECManager.#hasCatalogTiming(config)) return LOCAL_FEC_RETENTION_BLOCKS;
|
|
13462
|
+
const terminalRetentionMs = config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
|
|
13463
|
+
const blocks = Math.ceil(terminalRetentionMs / config.interleaveDepthMs) + 1;
|
|
13482
13464
|
if (!Number.isSafeInteger(blocks) || blocks <= 0) {
|
|
13483
13465
|
throw new Error(`[FECManager] packetId=${packetId} iso-ssbg1-v1 retention of ${blocks} blocks is not derivable`);
|
|
13484
13466
|
}
|
|
@@ -13486,7 +13468,8 @@ var FECManager = class _FECManager {
|
|
|
13486
13468
|
}
|
|
13487
13469
|
/** Whether an iso-ssbg1-v1 protected packet fits one ssbg_mode1 symbol (len + 2 <= T). */
|
|
13488
13470
|
isoSourceFits(packetId, packetLength) {
|
|
13489
|
-
|
|
13471
|
+
const symbolSize = this.#trackConfigs.get(packetId)?.symbolSize;
|
|
13472
|
+
return symbolSize !== void 0 && ssbg1FitsSymbol(packetLength, symbolSize);
|
|
13490
13473
|
}
|
|
13491
13474
|
/**
|
|
13492
13475
|
* Locate an iso-ssbg1-v1 source by range membership in a block a repair
|
|
@@ -13547,11 +13530,11 @@ var FECManager = class _FECManager {
|
|
|
13547
13530
|
* that could hold them is pending or not yet stated by a repair, else the
|
|
13548
13531
|
* starts of the blocks that failed or vanished.
|
|
13549
13532
|
*
|
|
13550
|
-
* `untracked` reads
|
|
13551
|
-
*
|
|
13552
|
-
*
|
|
13553
|
-
*
|
|
13554
|
-
* an SS_Start
|
|
13533
|
+
* `untracked` reads as the release gate reads it (MmtpRouter
|
|
13534
|
+
* #createFecReleaseGate): a block holding either released frame's SS_ID
|
|
13535
|
+
* (`fromSsId` or `toSsId`) delivered that frame, so untracked there means it
|
|
13536
|
+
* resolved and retired; a block starting after the stream's frontier
|
|
13537
|
+
* (`highestSbn`, an SS_Start) may still be heard from; a block at or behind the
|
|
13555
13538
|
* frontier that no symbol of this manager ever reached vanished, and its
|
|
13556
13539
|
* media is lost.
|
|
13557
13540
|
*/
|
|
@@ -13619,6 +13602,7 @@ var FECManager = class _FECManager {
|
|
|
13619
13602
|
}
|
|
13620
13603
|
/** Current classifier mode of a stream. */
|
|
13621
13604
|
classifierModeFor(packetId, catalogTrackName) {
|
|
13605
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return { mode: "all-protected", reason: null };
|
|
13622
13606
|
const classifier = this.#classifierFor(packetId, catalogTrackName);
|
|
13623
13607
|
return { mode: classifier.mode, reason: classifier.reason };
|
|
13624
13608
|
}
|
|
@@ -13628,10 +13612,12 @@ var FECManager = class _FECManager {
|
|
|
13628
13612
|
* it does not. A stream with no classifier yet has shown nothing against it.
|
|
13629
13613
|
*/
|
|
13630
13614
|
mpuBoundsRapsFor(packetId, catalogTrackName) {
|
|
13615
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return false;
|
|
13631
13616
|
return this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))?.mpuBoundsRaps ?? true;
|
|
13632
13617
|
}
|
|
13633
13618
|
/** A block's class now, or frozen at its resolution. */
|
|
13634
13619
|
blockClassFor(sbn, packetId, catalogTrackName) {
|
|
13620
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return "unknown";
|
|
13635
13621
|
const frozen = this.#resolvedBlockClasses.get(this.#blockKey(sbn, packetId, catalogTrackName));
|
|
13636
13622
|
if (frozen !== void 0) return frozen;
|
|
13637
13623
|
return this.#peekClass(sbn, packetId, catalogTrackName);
|
|
@@ -13653,7 +13639,7 @@ var FECManager = class _FECManager {
|
|
|
13653
13639
|
* invariant (consecutive MPUs exactly one keyframe interval apart).
|
|
13654
13640
|
*/
|
|
13655
13641
|
noteReleasedRap(packetId, catalogTrackName, mpu, presentationTime, mediaTimescale) {
|
|
13656
|
-
if (!this.#trackConfigs.has(packetId)) return;
|
|
13642
|
+
if (!this.#trackConfigs.has(packetId) || !this.#hasCatalogTimingForPacket(packetId)) return;
|
|
13657
13643
|
this.#applyClassifierChanges(
|
|
13658
13644
|
packetId,
|
|
13659
13645
|
catalogTrackName,
|
|
@@ -13669,27 +13655,34 @@ var FECManager = class _FECManager {
|
|
|
13669
13655
|
// -----------------------------------------------------------------------
|
|
13670
13656
|
/** Configure FEC parameters. */
|
|
13671
13657
|
configure(config) {
|
|
13672
|
-
if (config.
|
|
13658
|
+
if (config.interleaveDepthMs != null && (!Number.isSafeInteger(config.interleaveDepthMs) || config.interleaveDepthMs < 0)) {
|
|
13659
|
+
throw new Error(
|
|
13660
|
+
`[FECManager] interleaveDepthMs=${config.interleaveDepthMs} must be a non-negative safe integer in milliseconds`
|
|
13661
|
+
);
|
|
13662
|
+
}
|
|
13663
|
+
if (config.timeoutMs != null && (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)) {
|
|
13664
|
+
throw new Error(
|
|
13665
|
+
`[FECManager] timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`
|
|
13666
|
+
);
|
|
13667
|
+
}
|
|
13668
|
+
if (config.k != null && (!Number.isSafeInteger(config.k) || config.k <= 0)) {
|
|
13669
|
+
throw new Error(`[FECManager] k=${config.k} must be a positive integer`);
|
|
13670
|
+
}
|
|
13671
|
+
if (config.k != null) this.#streamK = config.k;
|
|
13673
13672
|
if (config.symbolSize != null && config.symbolSize > 0) this.#fecSymbolSize = config.symbolSize;
|
|
13674
13673
|
if (config.interleaveDepthMs != null) this.#fecInterleaveDepthMs = config.interleaveDepthMs;
|
|
13675
13674
|
if (config.fps != null && Number.isFinite(config.fps) && config.fps > 0) {
|
|
13676
13675
|
this.#fecFps = config.fps;
|
|
13677
13676
|
}
|
|
13678
|
-
if (config.interleaveDepthMs != null && config.interleaveDepthMs > 0 && config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
|
|
13679
|
-
console.warn(`[FECManager] interleaveDepthMs=${config.interleaveDepthMs} looks like frame count, not milliseconds (\xA75.1 requires ms). Expected range: ${MIN_INTERLEAVE_DEPTH_MS}-10000ms.`);
|
|
13680
|
-
}
|
|
13681
13677
|
if (config.timeoutMs != null && config.timeoutMs > 0) {
|
|
13682
13678
|
this.#fecTimeoutMs = config.timeoutMs;
|
|
13683
|
-
} else if (config.interleaveDepthMs
|
|
13684
|
-
this.#fecTimeoutMs =
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
symbolSize: this.#fecSymbolSize,
|
|
13688
|
-
mediaUnitsPerSecond: config.fps
|
|
13689
|
-
});
|
|
13679
|
+
} else if (config.interleaveDepthMs === 0) {
|
|
13680
|
+
this.#fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS;
|
|
13681
|
+
} else if (config.interleaveDepthMs != null && Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
|
|
13682
|
+
this.#fecTimeoutMs = fecSpanDeliveryWindowMs(config.interleaveDepthMs);
|
|
13690
13683
|
}
|
|
13691
13684
|
for (const [packetId, track] of this.#trackConfigs) {
|
|
13692
|
-
if (track.timeoutMs < this.#fecTimeoutMs) {
|
|
13685
|
+
if (_FECManager.#hasCatalogTiming(track) && track.timeoutMs < this.#fecTimeoutMs) {
|
|
13693
13686
|
this.#trackConfigs.set(packetId, { ...track, timeoutMs: this.#fecTimeoutMs });
|
|
13694
13687
|
}
|
|
13695
13688
|
}
|
|
@@ -13748,7 +13741,7 @@ var FECManager = class _FECManager {
|
|
|
13748
13741
|
);
|
|
13749
13742
|
}
|
|
13750
13743
|
if (config) this.#upsertTrackConfig(packetId, config, catalogTrackName);
|
|
13751
|
-
const workerK = this.#
|
|
13744
|
+
const workerK = this.#trackConfig(packetId).k;
|
|
13752
13745
|
this.#workerClients.set(streamKey, client);
|
|
13753
13746
|
this.#releasedWorkerClients.delete(client);
|
|
13754
13747
|
client.onFrame((data, meta) => {
|
|
@@ -13928,7 +13921,7 @@ var FECManager = class _FECManager {
|
|
|
13928
13921
|
return;
|
|
13929
13922
|
}
|
|
13930
13923
|
this.#settleOwedCleanup(key, "cleanup-ack-timeout");
|
|
13931
|
-
}, Math.ceil(this.#liveBlockLifetimeMs(this.#
|
|
13924
|
+
}, Math.ceil(this.#liveBlockLifetimeMs(this.#trackConfig(packetId))))
|
|
13932
13925
|
};
|
|
13933
13926
|
this.#pendingWorkerCleanupBounds.set(key, bound);
|
|
13934
13927
|
}
|
|
@@ -13969,16 +13962,11 @@ var FECManager = class _FECManager {
|
|
|
13969
13962
|
if (this.#fpsDetected || fps <= 0) return;
|
|
13970
13963
|
this.#fpsDetected = true;
|
|
13971
13964
|
this.#fecFps = fps;
|
|
13972
|
-
if (this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
|
|
13973
|
-
this.#fecTimeoutMs =
|
|
13974
|
-
interleaveDepthMs: this.#fecInterleaveDepthMs,
|
|
13975
|
-
sourceSymbols: this.#fecK,
|
|
13976
|
-
symbolSize: this.#fecSymbolSize,
|
|
13977
|
-
mediaUnitsPerSecond: fps
|
|
13978
|
-
});
|
|
13965
|
+
if (Number.isSafeInteger(this.#fecInterleaveDepthMs) && this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
|
|
13966
|
+
this.#fecTimeoutMs = fecSpanDeliveryWindowMs(this.#fecInterleaveDepthMs);
|
|
13979
13967
|
}
|
|
13980
13968
|
for (const [packetId, config] of this.#trackConfigs) {
|
|
13981
|
-
if (config.fps !== void 0 || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue;
|
|
13969
|
+
if (config.fps !== void 0 || !Number.isSafeInteger(config.interleaveDepthMs) || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue;
|
|
13982
13970
|
this.#trackConfigs.set(packetId, {
|
|
13983
13971
|
...config,
|
|
13984
13972
|
fps,
|
|
@@ -14010,35 +13998,28 @@ var FECManager = class _FECManager {
|
|
|
14010
13998
|
feedSourceSymbol(packetId, ssId, symbolData, alta, catalogTrackName) {
|
|
14011
13999
|
this.#fecStats.sourcePackets++;
|
|
14012
14000
|
this.#statsForPacket(packetId).sourcePackets++;
|
|
14013
|
-
|
|
14014
|
-
|
|
14015
|
-
|
|
14016
|
-
|
|
14017
|
-
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
`[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; hold it until a repair places it (placeIsoRepair)`
|
|
14021
|
-
);
|
|
14022
|
-
}
|
|
14023
|
-
locator.observe(ssId);
|
|
14024
|
-
if (!ssbg1FitsSymbol(symbolData.byteLength, this.symbolSizeForPacket(packetId))) {
|
|
14025
|
-
this.#isoSourceOversizeCount++;
|
|
14026
|
-
return null;
|
|
14027
|
-
}
|
|
14028
|
-
sbn = located.block.start;
|
|
14029
|
-
fecData = symbolData;
|
|
14030
|
-
} else {
|
|
14031
|
-
fecData = this.#normalizeSymbolData(packetId, symbolData);
|
|
14032
|
-
sbn = Math.floor(ssId / this.kForPacket(packetId));
|
|
14001
|
+
if (!this.#hasBlockProfile(packetId)) return null;
|
|
14002
|
+
const locator = this.#isoLocatorFor(packetId, catalogTrackName);
|
|
14003
|
+
const located = locator.locate(ssId);
|
|
14004
|
+
if (located === null) {
|
|
14005
|
+
throw new Error(
|
|
14006
|
+
`[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; hold it until a repair places it (placeIsoRepair)`
|
|
14007
|
+
);
|
|
14033
14008
|
}
|
|
14009
|
+
locator.observe(ssId);
|
|
14010
|
+
if (!ssbg1FitsSymbol(symbolData.byteLength, this.#trackConfig(packetId).symbolSize)) {
|
|
14011
|
+
this.#isoSourceOversizeCount++;
|
|
14012
|
+
return null;
|
|
14013
|
+
}
|
|
14014
|
+
const sbn = located.block.start;
|
|
14034
14015
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
14035
14016
|
const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey);
|
|
14036
14017
|
const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName);
|
|
14037
14018
|
if (workerClient && !syncBlockOwner) {
|
|
14038
14019
|
if (!this.#claimDecoderBlock(workerClient, sbn, catalogTrackName)) return null;
|
|
14039
|
-
const buf =
|
|
14040
|
-
|
|
14041
|
-
|
|
14020
|
+
const buf = symbolData.buffer.slice(
|
|
14021
|
+
symbolData.byteOffset,
|
|
14022
|
+
symbolData.byteOffset + symbolData.byteLength
|
|
14042
14023
|
);
|
|
14043
14024
|
if (alta) {
|
|
14044
14025
|
const authBuf = alta.auth.buffer.slice(
|
|
@@ -14061,7 +14042,7 @@ var FECManager = class _FECManager {
|
|
|
14061
14042
|
this.#syncDecoderBlockOwners.set(blockKey, client);
|
|
14062
14043
|
let recovered = null;
|
|
14063
14044
|
try {
|
|
14064
|
-
recovered = client.add_source(ssId,
|
|
14045
|
+
recovered = client.add_source(ssId, symbolData, performance.now());
|
|
14065
14046
|
} catch {
|
|
14066
14047
|
}
|
|
14067
14048
|
this.#retireIsoUnplaced(client, packetId, catalogTrackName);
|
|
@@ -14080,7 +14061,8 @@ var FECManager = class _FECManager {
|
|
|
14080
14061
|
feedRepairSymbol(packetId, ssStart, ssbLength, rsId, repairData, catalogTrackName) {
|
|
14081
14062
|
this.#fecStats.repairPackets++;
|
|
14082
14063
|
this.#statsForPacket(packetId).repairPackets++;
|
|
14083
|
-
|
|
14064
|
+
if (!this.#hasBlockProfile(packetId)) return null;
|
|
14065
|
+
const symbolSize = this.#trackConfig(packetId).symbolSize;
|
|
14084
14066
|
if (symbolSize > 0 && repairData.byteLength !== symbolSize) {
|
|
14085
14067
|
this.#repairSymbolSizeMismatchCount++;
|
|
14086
14068
|
if (this.#repairSymbolSizeMismatchCount <= 5 || this.#repairSymbolSizeMismatchCount % 100 === 0) {
|
|
@@ -14091,18 +14073,12 @@ var FECManager = class _FECManager {
|
|
|
14091
14073
|
return null;
|
|
14092
14074
|
}
|
|
14093
14075
|
const fecData = repairData;
|
|
14094
|
-
const k = this
|
|
14095
|
-
|
|
14096
|
-
if (
|
|
14097
|
-
|
|
14098
|
-
if (ssbLength <= 0 || ssbLength > k || block === void 0 || block.span || block.length !== ssbLength) {
|
|
14099
|
-
return null;
|
|
14100
|
-
}
|
|
14101
|
-
sbn = ssStart;
|
|
14102
|
-
} else {
|
|
14103
|
-
if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null;
|
|
14104
|
-
sbn = Math.floor(ssStart / k);
|
|
14076
|
+
const k = this.#trackConfig(packetId).k;
|
|
14077
|
+
const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart);
|
|
14078
|
+
if (ssbLength <= 0 || ssbLength > k || block === void 0 || block.span || block.length !== ssbLength) {
|
|
14079
|
+
return null;
|
|
14105
14080
|
}
|
|
14081
|
+
const sbn = ssStart;
|
|
14106
14082
|
this.#noteSbnSeen(sbn, packetId, catalogTrackName, "repair");
|
|
14107
14083
|
this.noteBlockActivity(sbn, packetId, catalogTrackName);
|
|
14108
14084
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
@@ -14140,8 +14116,9 @@ var FECManager = class _FECManager {
|
|
|
14140
14116
|
* Record a block's authored SSB_length from a repair symbol. Returns false,
|
|
14141
14117
|
* leaving the block unchanged, when the symbol contradicts the block: a
|
|
14142
14118
|
* different SSB_length than an earlier repair symbol declared is dropped,
|
|
14143
|
-
* and an SSB_length that places an already-received source ESI
|
|
14144
|
-
*
|
|
14119
|
+
* and an SSB_length that places an already-received source ESI at or past
|
|
14120
|
+
* the block's end fails the block, since its geometry can no longer be
|
|
14121
|
+
* trusted.
|
|
14145
14122
|
*/
|
|
14146
14123
|
#noteAuthoredLength(blockKey, sbn, packetId, ssbLength, catalogTrackName) {
|
|
14147
14124
|
const known = this.#blockAuthoredLengths.get(blockKey);
|
|
@@ -14208,7 +14185,7 @@ var FECManager = class _FECManager {
|
|
|
14208
14185
|
* a sweep tied to expiry alone would never run there.
|
|
14209
14186
|
*/
|
|
14210
14187
|
#retireIsoUnplaced(decoder, packetId, catalogTrackName) {
|
|
14211
|
-
if (!this.#
|
|
14188
|
+
if (!this.#hasBlockProfile(packetId)) return;
|
|
14212
14189
|
const newest = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newest;
|
|
14213
14190
|
if (newest === void 0) return;
|
|
14214
14191
|
try {
|
|
@@ -14253,11 +14230,13 @@ var FECManager = class _FECManager {
|
|
|
14253
14230
|
this.#failBlock(sbn, packetId, catalogTrackName);
|
|
14254
14231
|
return;
|
|
14255
14232
|
}
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14233
|
+
if (_FECManager.#hasCatalogTiming(trackConfig)) {
|
|
14234
|
+
this.#applyClassifierChanges(
|
|
14235
|
+
packetId,
|
|
14236
|
+
catalogTrackName,
|
|
14237
|
+
this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence)
|
|
14238
|
+
);
|
|
14239
|
+
}
|
|
14261
14240
|
const count = (this.#blockSourceCounts.get(key) ?? 0) + 1;
|
|
14262
14241
|
this.#blockSourceCounts.set(key, count);
|
|
14263
14242
|
if (count === 1) this.#indexBlockKey(key, sbn, packetId);
|
|
@@ -14301,6 +14280,7 @@ var FECManager = class _FECManager {
|
|
|
14301
14280
|
}
|
|
14302
14281
|
/** Refresh deadlines after accepted block activity. */
|
|
14303
14282
|
noteBlockActivity(sbn, packetId, catalogTrackName, sourceProgress = false) {
|
|
14283
|
+
if (!this.#trackConfigs.has(packetId)) return;
|
|
14304
14284
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
14305
14285
|
if (this.#completedBlocks.has(key)) return;
|
|
14306
14286
|
if (this.#pendingWorkerCleanups.has(key)) return;
|
|
@@ -14310,13 +14290,13 @@ var FECManager = class _FECManager {
|
|
|
14310
14290
|
this.#holdForOrderedSettlement(key, sbn, packetId, catalogTrackName);
|
|
14311
14291
|
return;
|
|
14312
14292
|
}
|
|
14313
|
-
const trackConfig = this.#
|
|
14293
|
+
const trackConfig = this.#trackConfig(packetId);
|
|
14314
14294
|
const timeoutMs = this.#multicastWaitMs(trackConfig);
|
|
14315
14295
|
if (!(timeoutMs > 0)) {
|
|
14316
14296
|
throw new Error(`[FECManager] packetId=${packetId} has no positive fail bound (timeoutMs=${timeoutMs})`);
|
|
14317
14297
|
}
|
|
14318
14298
|
const repairDeadline = this.#repairDeadlines.get(key);
|
|
14319
|
-
if (this.onRepairNeeded && !repairRequested && (repairDeadline === void 0 || sourceProgress)) {
|
|
14299
|
+
if (this.#hasCatalogTimingForPacket(packetId) && this.onRepairNeeded && !repairRequested && (repairDeadline === void 0 || sourceProgress)) {
|
|
14320
14300
|
this.#armRepairDeadline(
|
|
14321
14301
|
key,
|
|
14322
14302
|
sbn,
|
|
@@ -14386,6 +14366,7 @@ var FECManager = class _FECManager {
|
|
|
14386
14366
|
}
|
|
14387
14367
|
/** Mark a block as completed externally (e.g., after routing recovered data). */
|
|
14388
14368
|
markBlockCompleted(sbn, packetId, catalogTrackName) {
|
|
14369
|
+
if (packetId != null && !this.#trackConfigs.has(packetId)) return;
|
|
14389
14370
|
for (const key of this.#blockKeysFor(sbn, packetId, catalogTrackName)) {
|
|
14390
14371
|
this.#resolveCompleted(key);
|
|
14391
14372
|
}
|
|
@@ -15422,6 +15403,21 @@ var FecReleaseSequencer = class {
|
|
|
15422
15403
|
this.#streams.delete(key);
|
|
15423
15404
|
}
|
|
15424
15405
|
}
|
|
15406
|
+
/**
|
|
15407
|
+
* The packet's FEC ended, so no block will resolve a held gap: release its
|
|
15408
|
+
* frames in order, each gap stalled as a discontinuity, and forget its
|
|
15409
|
+
* streams, so FEC configured on it again starts from a fresh one.
|
|
15410
|
+
*/
|
|
15411
|
+
endPacket(packetId) {
|
|
15412
|
+
for (const [key, stream] of this.#streams) {
|
|
15413
|
+
if (stream.packetId !== packetId) continue;
|
|
15414
|
+
while (stream.buf.length > 0 && stream.last !== null) {
|
|
15415
|
+
stream.discontinuity = true;
|
|
15416
|
+
this.#drain(stream);
|
|
15417
|
+
}
|
|
15418
|
+
this.#streams.delete(key);
|
|
15419
|
+
}
|
|
15420
|
+
}
|
|
15425
15421
|
discardPacket(packetId) {
|
|
15426
15422
|
for (const [key, stream] of this.#streams) {
|
|
15427
15423
|
if (stream.packetId !== packetId) continue;
|
|
@@ -15804,6 +15800,13 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
15804
15800
|
markFecDiscontinuity(packetId, catalogTrackName) {
|
|
15805
15801
|
this.#fecSequencer?.markDiscontinuity(this.#streamKey(packetId, catalogTrackName));
|
|
15806
15802
|
}
|
|
15803
|
+
/**
|
|
15804
|
+
* The FEC of a packet ID ended: release the frames the FEC release gate
|
|
15805
|
+
* holds for it in order, each gap stalled, and forget its gated streams.
|
|
15806
|
+
*/
|
|
15807
|
+
endFecRelease(packetId) {
|
|
15808
|
+
this.#fecSequencer?.endPacket(packetId);
|
|
15809
|
+
}
|
|
15807
15810
|
/**
|
|
15808
15811
|
* MPU and data-unit identity of one MMTP packet, parsed in place (no copy,
|
|
15809
15812
|
* no reassembly). Null for non-MPU packets and packets that do not parse.
|
|
@@ -17137,6 +17140,8 @@ var MmtpRouter = class {
|
|
|
17137
17140
|
*/
|
|
17138
17141
|
#onPublisherRestart;
|
|
17139
17142
|
#publisherRestarts = 0;
|
|
17143
|
+
/** Inits delivered with no onTrackInit handler for their packetId (MOQT-01). */
|
|
17144
|
+
#unmappedInitDeliveries = 0;
|
|
17140
17145
|
// FEC Payload IDs left unread because their (owner) packetId had no
|
|
17141
17146
|
// configured fec.blockProfile (see #noteUnreadableFecPayloadId).
|
|
17142
17147
|
#fecPayloadIdsWithoutProfile = 0;
|
|
@@ -17333,15 +17338,24 @@ var MmtpRouter = class {
|
|
|
17333
17338
|
return NO_FEC_GEOMETRY_HORIZON;
|
|
17334
17339
|
}
|
|
17335
17340
|
const blockSpanMs = fec.interleaveDepthMsForPacket(packetId);
|
|
17341
|
+
const blockLifetimeMs = fec.blockLifetimeMsForPacket(packetId);
|
|
17342
|
+
if (blockSpanMs === void 0 || blockLifetimeMs === void 0) return NO_FEC_GEOMETRY_HORIZON;
|
|
17336
17343
|
if (blockSpanMs === 0) return NO_BLOCK_SPAN_HORIZON;
|
|
17337
17344
|
return {
|
|
17338
17345
|
kind: "fec-horizon",
|
|
17339
|
-
retentionMs: duplicateRetentionMs({
|
|
17340
|
-
blockSpanMs,
|
|
17341
|
-
blockLifetimeMs: fec.blockLifetimeMsForPacket(packetId)
|
|
17342
|
-
})
|
|
17346
|
+
retentionMs: duplicateRetentionMs({ blockSpanMs, blockLifetimeMs })
|
|
17343
17347
|
};
|
|
17344
17348
|
}
|
|
17349
|
+
/**
|
|
17350
|
+
* Init segments delivered for a packetId with no `onTrackInit` handler
|
|
17351
|
+
* since construction, whether the deprecated aggregate `onInitSegment`
|
|
17352
|
+
* callback then received them or nothing did. A supported deployment reads
|
|
17353
|
+
* 0 here; a non-zero value is the evidence that blocks removing the
|
|
17354
|
+
* aggregate callback (MOQT-01, BLO-21181).
|
|
17355
|
+
*/
|
|
17356
|
+
get unmappedInitDeliveryCount() {
|
|
17357
|
+
return this.#unmappedInitDeliveries;
|
|
17358
|
+
}
|
|
17345
17359
|
/** Publisher restarts detected on the multicast leg since construction. */
|
|
17346
17360
|
get publisherRestartCount() {
|
|
17347
17361
|
return this.#publisherRestarts;
|
|
@@ -17429,7 +17443,7 @@ var MmtpRouter = class {
|
|
|
17429
17443
|
/** Update MFU reassembly timeout. Source and FEC-recovery paths share the
|
|
17430
17444
|
* parser's single reassembler (BLO-8151), so one update covers both. */
|
|
17431
17445
|
setMfuTimeoutMs(ms) {
|
|
17432
|
-
if (ms <= 0) throw new Error("[MmtpRouter] mfuTimeoutMs must be > 0");
|
|
17446
|
+
if (ms !== null && ms <= 0) throw new Error("[MmtpRouter] mfuTimeoutMs must be > 0");
|
|
17433
17447
|
this.#parser.setMfuTimeoutMs(ms);
|
|
17434
17448
|
}
|
|
17435
17449
|
/** Update parser frame reordering for unordered multicast delivery.
|
|
@@ -17499,7 +17513,15 @@ var MmtpRouter = class {
|
|
|
17499
17513
|
onTrackPacket(packetId, cb) {
|
|
17500
17514
|
this.#trackPacketCallbacks.set(packetId, cb);
|
|
17501
17515
|
}
|
|
17502
|
-
/**
|
|
17516
|
+
/**
|
|
17517
|
+
* Register an init segment callback (ftyp+moov from MMTP MPU metadata).
|
|
17518
|
+
* Receives an init only for a packetId with no `onTrackInit` handler.
|
|
17519
|
+
*
|
|
17520
|
+
* @deprecated Use onTrackInit(packetId, cb). Still delivered as a fallback;
|
|
17521
|
+
* removed in the 1.0.0 major, no earlier than one minor release after this
|
|
17522
|
+
* deprecation reaches the `latest` dist-tag (BLO-21181). Count fallback
|
|
17523
|
+
* deliveries with `unmappedInitDeliveryCount`.
|
|
17524
|
+
*/
|
|
17503
17525
|
onInitSegment(cb) {
|
|
17504
17526
|
this.#onInitSegment = cb;
|
|
17505
17527
|
}
|
|
@@ -17513,17 +17535,16 @@ var MmtpRouter = class {
|
|
|
17513
17535
|
* When active, source frames are buffered per FEC block and emitted as
|
|
17514
17536
|
* multi-sample CMAF segments instead of per-frame callbacks. */
|
|
17515
17537
|
enableMseAssembler(packetId, config, catalogTrackName) {
|
|
17516
|
-
const k = this.#kForPacket(packetId);
|
|
17517
17538
|
const assemblerKey = this.#mseAssemblerKey(packetId, catalogTrackName);
|
|
17518
17539
|
const previous = this.#mseAssemblers.get(assemblerKey);
|
|
17519
17540
|
if (previous) {
|
|
17520
17541
|
previous.flush();
|
|
17521
17542
|
previous.dispose();
|
|
17522
17543
|
}
|
|
17523
|
-
const assembler = new CmafBlockAssembler(
|
|
17544
|
+
const assembler = new CmafBlockAssembler(config);
|
|
17524
17545
|
this.#mseAssemblers.set(assemblerKey, assembler);
|
|
17525
17546
|
console.log(
|
|
17526
|
-
`[MmtpRouter] MSE assembler enabled for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (
|
|
17547
|
+
`[MmtpRouter] MSE assembler enabled for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${config.trackType})`
|
|
17527
17548
|
);
|
|
17528
17549
|
return assembler;
|
|
17529
17550
|
}
|
|
@@ -17632,6 +17653,21 @@ var MmtpRouter = class {
|
|
|
17632
17653
|
}
|
|
17633
17654
|
this.#fecMappersByPacketId.set(packetId, mapper);
|
|
17634
17655
|
}
|
|
17656
|
+
/**
|
|
17657
|
+
* Release the router side of a packetId whose FEC is ending: drop its block
|
|
17658
|
+
* mapper and end its FEC release gating. Frames the gate holds are released
|
|
17659
|
+
* in order, each gap stalled (no block will resolve it), not stranded. Call
|
|
17660
|
+
* before FECManager.releasePacket, while the gate can still read its blocks.
|
|
17661
|
+
*/
|
|
17662
|
+
releaseFecPacket(packetId) {
|
|
17663
|
+
this.#fecMappersByPacketId.delete(packetId);
|
|
17664
|
+
this.#withDeferredFecPokes(() => {
|
|
17665
|
+
this.#parser.endFecRelease(packetId);
|
|
17666
|
+
for (const [key, poke] of this.#pendingFecPokes) {
|
|
17667
|
+
if (poke.packetId === packetId) this.#pendingFecPokes.delete(key);
|
|
17668
|
+
}
|
|
17669
|
+
});
|
|
17670
|
+
}
|
|
17635
17671
|
registerManifestTrack(context) {
|
|
17636
17672
|
if (!this.#manifestVerifier) throw new Error("manifest verifier is not configured");
|
|
17637
17673
|
this.#manifestVerifier.registerTrack(context);
|
|
@@ -17751,87 +17787,21 @@ var MmtpRouter = class {
|
|
|
17751
17787
|
this.#noteUnreadableFecPayloadId(ownerPacketId, "repair");
|
|
17752
17788
|
return;
|
|
17753
17789
|
}
|
|
17754
|
-
|
|
17755
|
-
this.#handleIsoRepair(ownerPacketId, catalogTrackName, parsed, "moq");
|
|
17756
|
-
return;
|
|
17757
|
-
}
|
|
17758
|
-
const repairSbn = this.#deriveRepairSbn(parsed.ssStart, parsed.ssbLength, ownerPacketId);
|
|
17759
|
-
if (repairSbn === null) return;
|
|
17760
|
-
const repairEsi = this.#kForPacket(ownerPacketId) + parsed.rsId;
|
|
17761
|
-
if (!this.#feedFecTransportSymbol(
|
|
17762
|
-
ownerPacketId,
|
|
17763
|
-
repairSbn,
|
|
17764
|
-
repairEsi,
|
|
17765
|
-
parsed.symbolData,
|
|
17766
|
-
"moq",
|
|
17767
|
-
catalogTrackName
|
|
17768
|
-
)) return;
|
|
17769
|
-
const recovered = catalogTrackName === void 0 ? this.#fecManager.feedRepairSymbol(
|
|
17770
|
-
ownerPacketId,
|
|
17771
|
-
parsed.ssStart,
|
|
17772
|
-
parsed.ssbLength,
|
|
17773
|
-
parsed.rsId,
|
|
17774
|
-
parsed.symbolData
|
|
17775
|
-
) : this.#fecManager.feedRepairSymbol(
|
|
17776
|
-
ownerPacketId,
|
|
17777
|
-
parsed.ssStart,
|
|
17778
|
-
parsed.ssbLength,
|
|
17779
|
-
parsed.rsId,
|
|
17780
|
-
parsed.symbolData,
|
|
17781
|
-
catalogTrackName
|
|
17782
|
-
);
|
|
17783
|
-
if (recovered) {
|
|
17784
|
-
const alreadyCompleted = catalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId) : this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId, catalogTrackName);
|
|
17785
|
-
if (catalogTrackName === void 0) {
|
|
17786
|
-
this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId);
|
|
17787
|
-
} else {
|
|
17788
|
-
this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId, catalogTrackName);
|
|
17789
|
-
}
|
|
17790
|
-
if (!alreadyCompleted) {
|
|
17791
|
-
this.#fecManager.countRecovery(ownerPacketId, repairSbn, catalogTrackName);
|
|
17792
|
-
if (catalogTrackName === void 0) {
|
|
17793
|
-
this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId);
|
|
17794
|
-
} else {
|
|
17795
|
-
this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId, catalogTrackName);
|
|
17796
|
-
}
|
|
17797
|
-
this.#routeRecoveredBlock(
|
|
17798
|
-
repairSbn,
|
|
17799
|
-
recovered,
|
|
17800
|
-
ownerPacketId,
|
|
17801
|
-
catalogTrackName
|
|
17802
|
-
);
|
|
17803
|
-
}
|
|
17804
|
-
}
|
|
17790
|
+
this.#handleIsoRepair(ownerPacketId, catalogTrackName, parsed, "moq");
|
|
17805
17791
|
}
|
|
17806
17792
|
}
|
|
17807
|
-
// ──
|
|
17808
|
-
#kForPacket(packetId) {
|
|
17809
|
-
if (packetId != null)
|
|
17810
|
-
return this.#fecManager?.kForPacket?.(packetId) ?? this.#fecMappersByPacketId.get(packetId)?.k ?? this.#fecManager?.k ?? 4;
|
|
17811
|
-
return this.#defaultFecMapper?.k ?? this.#fecManager?.k ?? 4;
|
|
17812
|
-
}
|
|
17793
|
+
// ── Packet FEC geometry and block profile ───────────────────────
|
|
17813
17794
|
#symbolSizeForPacket(packetId) {
|
|
17814
|
-
if (packetId != null) return this.#fecManager?.symbolSizeForPacket?.(packetId) ??
|
|
17795
|
+
if (packetId != null) return this.#fecManager?.symbolSizeForPacket?.(packetId) ?? 0;
|
|
17815
17796
|
return this.#fecManager?.symbolSize ?? 0;
|
|
17816
17797
|
}
|
|
17817
|
-
/** Derive SBN/ESI from SS_ID using packet-scoped FEC geometry. */
|
|
17818
|
-
#deriveSbnEsi(ssId, packetId) {
|
|
17819
|
-
const mapper = packetId == null ? this.#defaultFecMapper : this.#fecMappersByPacketId.get(packetId) ?? null;
|
|
17820
|
-
if (mapper) return mapper.fromSsId(ssId);
|
|
17821
|
-
const k = this.#kForPacket(packetId);
|
|
17822
|
-
return { sbn: Math.floor(ssId / k), esi: ssId % k };
|
|
17823
|
-
}
|
|
17824
|
-
#deriveRepairSbn(ssStart, ssbLength, packetId) {
|
|
17825
|
-
const k = this.#kForPacket(packetId);
|
|
17826
|
-
if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null;
|
|
17827
|
-
return Math.floor(ssStart / k);
|
|
17828
|
-
}
|
|
17829
17798
|
/**
|
|
17830
17799
|
* The configured block profile of a packetId, or undefined while it is
|
|
17831
17800
|
* unconfigured. With an FEC manager its per-packet configuration is the
|
|
17832
17801
|
* only source. A router without one still recovers symbols through its
|
|
17833
17802
|
* registered mapper, built with the catalog's explicit profile: the
|
|
17834
|
-
* packet's own mapper, else the default one (
|
|
17803
|
+
* packet's own mapper, else the default one. A NullBlockMapper (a stream
|
|
17804
|
+
* without an FEC instance) states none.
|
|
17835
17805
|
*/
|
|
17836
17806
|
#configuredBlockProfile(packetId) {
|
|
17837
17807
|
if (this.#fecManager) return this.#fecManager.blockProfileForPacket(packetId);
|
|
@@ -17839,9 +17809,10 @@ var MmtpRouter = class {
|
|
|
17839
17809
|
return mapper?.blockProfile ?? void 0;
|
|
17840
17810
|
}
|
|
17841
17811
|
/**
|
|
17842
|
-
* The block profile an FEC coordinate of `packetId` is read under
|
|
17843
|
-
*
|
|
17844
|
-
*
|
|
17812
|
+
* The block profile an FEC coordinate of `packetId` is read under:
|
|
17813
|
+
* `iso-ssbg1-v1`, the only one (`k-padded-v1` is retired and refused).
|
|
17814
|
+
* Throws while none is configured: such a packet has no FEC geometry, and
|
|
17815
|
+
* nothing is inferred from the wire or defaulted.
|
|
17845
17816
|
* Packet ingress leaves such a packet's FEC Payload ID unread first
|
|
17846
17817
|
* (#noteUnreadableFecPayloadId); a throw here is a wiring fault on a path
|
|
17847
17818
|
* that cannot hold an unconfigured packet.
|
|
@@ -17855,8 +17826,16 @@ var MmtpRouter = class {
|
|
|
17855
17826
|
}
|
|
17856
17827
|
return profile;
|
|
17857
17828
|
}
|
|
17858
|
-
|
|
17859
|
-
|
|
17829
|
+
/**
|
|
17830
|
+
* The block coordinates (SS_Start, ESI) of an FEC source: range membership
|
|
17831
|
+
* in a block a repair already stated (ISO/IEC 23008-1 C.5.3). Null while no
|
|
17832
|
+
* repair has stated its block, while its packetId has no configured block
|
|
17833
|
+
* profile, and without an FEC manager: SS_ID alone locates no block, so
|
|
17834
|
+
* nothing is derived from it.
|
|
17835
|
+
*/
|
|
17836
|
+
#locateFecSource(packetId, catalogTrackName, ssId) {
|
|
17837
|
+
if (this.#configuredBlockProfile(packetId) === void 0) return null;
|
|
17838
|
+
return this.#fecManager?.locateIsoSource?.(packetId, catalogTrackName, ssId) ?? null;
|
|
17860
17839
|
}
|
|
17861
17840
|
/**
|
|
17862
17841
|
* Explicit refusal to read an FEC Payload ID: its (owner) packetId has no
|
|
@@ -17875,21 +17854,20 @@ var MmtpRouter = class {
|
|
|
17875
17854
|
}
|
|
17876
17855
|
}
|
|
17877
17856
|
/**
|
|
17878
|
-
* The release-gate anchor of a recovered symbol: its
|
|
17879
|
-
*
|
|
17880
|
-
*
|
|
17857
|
+
* The release-gate anchor of a recovered symbol: its own SS_ID, SS_Start +
|
|
17858
|
+
* ESI modulo 2^32 (C.5.2), where a frame is gated by the SS_IDs between it
|
|
17859
|
+
* and the last released frame.
|
|
17881
17860
|
*/
|
|
17882
|
-
#fecAnchorForRecovered(
|
|
17883
|
-
return
|
|
17861
|
+
#fecAnchorForRecovered(blockStart, index) {
|
|
17862
|
+
return serial32Add(blockStart, index);
|
|
17884
17863
|
}
|
|
17885
17864
|
/**
|
|
17886
17865
|
* One iso-ssbg1-v1 repair symbol (ISO/IEC 23008-1 C.5.3). It states the
|
|
17887
17866
|
* block `[SS_Start, SS_Start + SSB_length)`; repair ESI = SSB_length +
|
|
17888
17867
|
* RS_ID (K' = SSB_length). The held sources it placed (and those of a
|
|
17889
17868
|
* repair-less span in front of it) are fed as first-arrival sources, then
|
|
17890
|
-
* the repair, the order they had on the wire, so a loss-free block
|
|
17891
|
-
* on its repair
|
|
17892
|
-
* block is dropped.
|
|
17869
|
+
* the repair, the order they had on the wire, so a loss-free block completes
|
|
17870
|
+
* clean on its repair. A repair contradicting a known block is dropped.
|
|
17893
17871
|
*/
|
|
17894
17872
|
#handleIsoRepair(ownerPacketId, catalogTrackName, repair, source) {
|
|
17895
17873
|
const fec = this.#fecManager;
|
|
@@ -17981,7 +17959,8 @@ var MmtpRouter = class {
|
|
|
17981
17959
|
return null;
|
|
17982
17960
|
}
|
|
17983
17961
|
/** Route a recovered FEC block through the parser + dedup pipeline.
|
|
17984
|
-
* Sync WASM decoders return K fixed-width
|
|
17962
|
+
* Sync WASM decoders return the block's K' = SSB_length fixed-width
|
|
17963
|
+
* ssbg_mode1 source symbols concatenated. */
|
|
17985
17964
|
routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
|
|
17986
17965
|
this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
|
|
17987
17966
|
}
|
|
@@ -18449,29 +18428,19 @@ var MmtpRouter = class {
|
|
|
18449
18428
|
resolvedLoss: (packetId, catalogTrackName, fromSbn, toSbn, releasedSbn) => {
|
|
18450
18429
|
const fec = this.#fecManager;
|
|
18451
18430
|
if (!fec?.blockResolution) return [];
|
|
18452
|
-
|
|
18453
|
-
|
|
18454
|
-
return fec.isoSpanResolution?.(packetId, catalogTrackName, releasedSbn, head) ?? null;
|
|
18455
|
-
}
|
|
18456
|
-
const highest = fec.highestSbn?.(packetId, catalogTrackName);
|
|
18457
|
-
const lost = [];
|
|
18458
|
-
for (let sbn = fromSbn; sbn <= toSbn; sbn++) {
|
|
18459
|
-
const resolution = fec.blockResolution(sbn, packetId, catalogTrackName);
|
|
18460
|
-
if (resolution === "pending") return null;
|
|
18461
|
-
if (resolution === "failed") {
|
|
18462
|
-
lost.push(sbn);
|
|
18463
|
-
} else if (resolution === "untracked") {
|
|
18464
|
-
if (sbn === releasedSbn) continue;
|
|
18465
|
-
if (highest === void 0 || sbn > highest) return null;
|
|
18466
|
-
lost.push(sbn);
|
|
18467
|
-
}
|
|
18468
|
-
}
|
|
18469
|
-
return lost;
|
|
18431
|
+
const head = fromSbn === releasedSbn ? toSbn : fromSbn;
|
|
18432
|
+
return fec.isoSpanResolution?.(packetId, catalogTrackName, releasedSbn, head) ?? null;
|
|
18470
18433
|
},
|
|
18471
18434
|
// The retention window itself: a frame is never still held when a block
|
|
18472
18435
|
// it waits on retires. Only consulted while resolvedLoss holds a gap,
|
|
18473
18436
|
// which needs blockResolution, so the check above guarantees the method.
|
|
18474
|
-
|
|
18437
|
+
// A packetId the manager no longer has configured (released) has no
|
|
18438
|
+
// block left to wait on: release its frames now (BLO-45953).
|
|
18439
|
+
holdLimitMs: (packetId) => {
|
|
18440
|
+
const fec = this.#fecManager;
|
|
18441
|
+
if (!fec?.terminalRetentionMsForPacket) return Infinity;
|
|
18442
|
+
return fec.terminalRetentionMsForPacket(packetId) ?? 0;
|
|
18443
|
+
},
|
|
18475
18444
|
decide: (before, after, lostBlocks, discontinuity) => {
|
|
18476
18445
|
const decision = this.#decideGap(before, after, lostBlocks, discontinuity);
|
|
18477
18446
|
this.#onFrameGap?.(decision);
|
|
@@ -18533,9 +18502,10 @@ var MmtpRouter = class {
|
|
|
18533
18502
|
}
|
|
18534
18503
|
/**
|
|
18535
18504
|
* The most media units a gap between two released frames can hold: the
|
|
18536
|
-
* FEC blocks
|
|
18537
|
-
* block touches (ceil(interleaveDepthMs / unit duration), §5.1).
|
|
18538
|
-
* either
|
|
18505
|
+
* FEC blocks between `before`'s anchor and `after`'s, times D, the media
|
|
18506
|
+
* units one block touches (ceil(interleaveDepthMs / unit duration), §5.1).
|
|
18507
|
+
* Null when either anchor, the unit duration or the catalog interleave is
|
|
18508
|
+
* unknown.
|
|
18539
18509
|
*/
|
|
18540
18510
|
#maxMissingUnits(before, after) {
|
|
18541
18511
|
const from = before.fecSbn;
|
|
@@ -18548,7 +18518,7 @@ var MmtpRouter = class {
|
|
|
18548
18518
|
const denominator = BigInt(timing.durationTicks) * 1000n;
|
|
18549
18519
|
const unitsPerBlock = (numerator + denominator - 1n) / denominator;
|
|
18550
18520
|
const blocks = BigInt(
|
|
18551
|
-
(
|
|
18521
|
+
(serial32Precedes(from, to) ? serial32Distance(from, to) : serial32Distance(to, from)) + 1
|
|
18552
18522
|
);
|
|
18553
18523
|
const bound = unitsPerBlock * blocks;
|
|
18554
18524
|
return bound > BigInt(Number.MAX_SAFE_INTEGER) ? null : Number(bound);
|
|
@@ -18598,8 +18568,9 @@ var MmtpRouter = class {
|
|
|
18598
18568
|
const cb = this.#trackInitCallbacks.get(packetId);
|
|
18599
18569
|
if (cb) {
|
|
18600
18570
|
cb(data);
|
|
18601
|
-
} else
|
|
18602
|
-
this.#
|
|
18571
|
+
} else {
|
|
18572
|
+
this.#unmappedInitDeliveries++;
|
|
18573
|
+
this.#onInitSegment?.(data);
|
|
18603
18574
|
}
|
|
18604
18575
|
console.log(
|
|
18605
18576
|
`[MmtpRouter] Init delivered for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${data.byteLength}B)`
|
|
@@ -18641,9 +18612,9 @@ var MmtpRouter = class {
|
|
|
18641
18612
|
if (!info.isRepair && this.#manifestVerifier && !manifestAdmitted) {
|
|
18642
18613
|
let identity = manifestIdentity;
|
|
18643
18614
|
if (!identity && info.fecPayloadId && !fecUnreadable && (this.#resolveManifestIdentityForRoute || this.#resolveManifestIdentity)) {
|
|
18644
|
-
const coordinates = this.#deriveSbnEsi(info.fecPayloadId.ssId, info.packetId);
|
|
18645
18615
|
const catalogTrackName = catalogTrackNameForPacket(ingress, info.packetId);
|
|
18646
|
-
|
|
18616
|
+
const coordinates = this.#locateFecSource(info.packetId, catalogTrackName, info.fecPayloadId.ssId);
|
|
18617
|
+
identity = coordinates === null ? void 0 : (this.#resolveManifestIdentityForRoute ? this.#resolveManifestIdentityForRoute({
|
|
18647
18618
|
packetId: info.packetId,
|
|
18648
18619
|
sbn: coordinates.sbn,
|
|
18649
18620
|
esi: coordinates.esi,
|
|
@@ -18695,79 +18666,7 @@ var MmtpRouter = class {
|
|
|
18695
18666
|
this.#noteUnreadableFecPayloadId(ownerPacketId, "repair");
|
|
18696
18667
|
return;
|
|
18697
18668
|
}
|
|
18698
|
-
|
|
18699
|
-
this.#handleIsoRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair, source);
|
|
18700
|
-
return;
|
|
18701
|
-
}
|
|
18702
|
-
const repairSbn = this.#deriveRepairSbn(repair.ssStart, repair.ssbLength, ownerPacketId);
|
|
18703
|
-
if (repairSbn === null) return;
|
|
18704
|
-
const repairEsi = this.#kForPacket(ownerPacketId) + repair.rsId;
|
|
18705
|
-
if (!this.#feedFecTransportSymbol(
|
|
18706
|
-
ownerPacketId,
|
|
18707
|
-
repairSbn,
|
|
18708
|
-
repairEsi,
|
|
18709
|
-
repair.symbolData,
|
|
18710
|
-
source,
|
|
18711
|
-
catalogTrackNameForPacket(ingress, ownerPacketId)
|
|
18712
|
-
)) return;
|
|
18713
|
-
const repairCatalogTrackName = catalogTrackNameForPacket(ingress, ownerPacketId);
|
|
18714
|
-
if (repairCatalogTrackName === void 0) {
|
|
18715
|
-
this.#fecManager.noteBlockActivity?.(repairSbn, ownerPacketId);
|
|
18716
|
-
} else {
|
|
18717
|
-
this.#fecManager.noteBlockActivity?.(
|
|
18718
|
-
repairSbn,
|
|
18719
|
-
ownerPacketId,
|
|
18720
|
-
repairCatalogTrackName
|
|
18721
|
-
);
|
|
18722
|
-
}
|
|
18723
|
-
const recovered = repairCatalogTrackName === void 0 ? this.#fecManager.feedRepairSymbol(
|
|
18724
|
-
ownerPacketId,
|
|
18725
|
-
repair.ssStart,
|
|
18726
|
-
repair.ssbLength,
|
|
18727
|
-
repair.rsId,
|
|
18728
|
-
repair.symbolData
|
|
18729
|
-
) : this.#fecManager.feedRepairSymbol(
|
|
18730
|
-
ownerPacketId,
|
|
18731
|
-
repair.ssStart,
|
|
18732
|
-
repair.ssbLength,
|
|
18733
|
-
repair.rsId,
|
|
18734
|
-
repair.symbolData,
|
|
18735
|
-
repairCatalogTrackName
|
|
18736
|
-
);
|
|
18737
|
-
if (recovered) {
|
|
18738
|
-
const alreadyCompleted = repairCatalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId) : this.#fecManager.isBlockCompleted(
|
|
18739
|
-
repairSbn,
|
|
18740
|
-
ownerPacketId,
|
|
18741
|
-
repairCatalogTrackName
|
|
18742
|
-
);
|
|
18743
|
-
if (repairCatalogTrackName === void 0) {
|
|
18744
|
-
this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId);
|
|
18745
|
-
} else {
|
|
18746
|
-
this.#fecManager.cancelBlockTimeout(
|
|
18747
|
-
repairSbn,
|
|
18748
|
-
ownerPacketId,
|
|
18749
|
-
repairCatalogTrackName
|
|
18750
|
-
);
|
|
18751
|
-
}
|
|
18752
|
-
if (!alreadyCompleted) {
|
|
18753
|
-
this.#fecManager.countRecovery(ownerPacketId, repairSbn, repairCatalogTrackName);
|
|
18754
|
-
if (repairCatalogTrackName === void 0) {
|
|
18755
|
-
this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId);
|
|
18756
|
-
} else {
|
|
18757
|
-
this.#fecManager.markBlockCompleted(
|
|
18758
|
-
repairSbn,
|
|
18759
|
-
ownerPacketId,
|
|
18760
|
-
repairCatalogTrackName
|
|
18761
|
-
);
|
|
18762
|
-
}
|
|
18763
|
-
this.#routeRecoveredBlock(
|
|
18764
|
-
repairSbn,
|
|
18765
|
-
recovered,
|
|
18766
|
-
ownerPacketId,
|
|
18767
|
-
catalogTrackNameForPacket(ingress, ownerPacketId)
|
|
18768
|
-
);
|
|
18769
|
-
}
|
|
18770
|
-
}
|
|
18669
|
+
this.#handleIsoRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair, source);
|
|
18771
18670
|
}
|
|
18772
18671
|
} else {
|
|
18773
18672
|
if (this.#classifyPacketId(info.packetId) === null) return;
|
|
@@ -18791,8 +18690,8 @@ var MmtpRouter = class {
|
|
|
18791
18690
|
}
|
|
18792
18691
|
const duplicates = this.#stream(info.packetId, catalogTrackName).duplicates;
|
|
18793
18692
|
const fecPayloadId = fecUnreadable ? null : info.fecPayloadId;
|
|
18794
|
-
const
|
|
18795
|
-
const coords =
|
|
18693
|
+
const fecSource = this.#fecManager !== null && fecPayloadId != null;
|
|
18694
|
+
const coords = fecSource && fecPayloadId ? this.#locateFecSource(info.packetId, catalogTrackName, fecPayloadId.ssId) ?? void 0 : void 0;
|
|
18796
18695
|
duplicates.advance(coords?.sbn);
|
|
18797
18696
|
if (source === "multicast" && !overlapCopy && !releasedFromQuarantine && catalogTrackName !== void 0 && duplicates.predatesHorizon(info.sequenceNumber, fecPayloadId?.ssId)) {
|
|
18798
18697
|
this.#publisherRestarts++;
|
|
@@ -18807,9 +18706,8 @@ var MmtpRouter = class {
|
|
|
18807
18706
|
}
|
|
18808
18707
|
if (this.#fecManager && fecPayloadId && coords) {
|
|
18809
18708
|
const fecId = fecPayloadId;
|
|
18810
|
-
const symbolSize = this.#symbolSizeForPacket(info.packetId);
|
|
18811
18709
|
const rawSymbol = packet.subarray(0, packet.length - 4);
|
|
18812
|
-
const oversized =
|
|
18710
|
+
const oversized = !(this.#fecManager.isoSourceFits?.(info.packetId, rawSymbol.length) ?? false);
|
|
18813
18711
|
let overlapBlockCompleted = false;
|
|
18814
18712
|
if (overlapCopy) {
|
|
18815
18713
|
overlapBlockCompleted = catalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(coords.sbn, info.packetId) : this.#fecManager.isBlockCompleted(
|
|
@@ -18844,7 +18742,7 @@ var MmtpRouter = class {
|
|
|
18844
18742
|
}
|
|
18845
18743
|
parseSourceSymbol = (overlapCopy ? !overlapBlockCompleted : sourceSymbolAccepted) && !duplicates.parsedSymbols.has(fecId.ssId);
|
|
18846
18744
|
fecInfo = { sbn: coords.sbn, esi: coords.esi, oversized };
|
|
18847
|
-
} else if (
|
|
18745
|
+
} else if (fecSource && this.#fecManager && fecPayloadId) {
|
|
18848
18746
|
const ssId = fecPayloadId.ssId;
|
|
18849
18747
|
if (overlapCopy) {
|
|
18850
18748
|
parseSourceSymbol = !duplicates.parsedSymbols.has(ssId);
|
|
@@ -18878,12 +18776,13 @@ var MmtpRouter = class {
|
|
|
18878
18776
|
this.#feedViaParser(packet, info, fecInfo, {
|
|
18879
18777
|
source,
|
|
18880
18778
|
catalogTrackName,
|
|
18881
|
-
|
|
18779
|
+
// An FEC source frame is anchored by its own SS_ID (C.5.2).
|
|
18780
|
+
...fecSource && fecPayloadId ? { fecSbn: fecPayloadId.ssId } : {}
|
|
18882
18781
|
}, parsed);
|
|
18883
18782
|
}
|
|
18884
18783
|
if (this.#classifyPacketId(info.packetId) === null) return;
|
|
18885
|
-
if (this.#fecManager && fecPayloadId && sourceSymbolAccepted && sourceSymbolTrackable) {
|
|
18886
|
-
const { sbn, esi } =
|
|
18784
|
+
if (this.#fecManager && fecPayloadId && coords && sourceSymbolAccepted && sourceSymbolTrackable) {
|
|
18785
|
+
const { sbn, esi } = coords;
|
|
18887
18786
|
if (this.#classifyPacketId(info.packetId) !== null && this.#fecManager.isTrackConfigured?.(info.packetId) !== false) {
|
|
18888
18787
|
this.#fecManager.trackBlock(
|
|
18889
18788
|
sbn,
|
|
@@ -19317,7 +19216,6 @@ var MmtpRouter = class {
|
|
|
19317
19216
|
// ── Private: FEC recovery routing ───────────────────────────────
|
|
19318
19217
|
/** Route WASM-recovered block through appropriate path based on container kind. */
|
|
19319
19218
|
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName) {
|
|
19320
|
-
const k = this.#kForPacket(ownerPacketId);
|
|
19321
19219
|
const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
|
|
19322
19220
|
if (ownerPacketId === void 0) {
|
|
19323
19221
|
throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
|
|
@@ -19330,7 +19228,6 @@ var MmtpRouter = class {
|
|
|
19330
19228
|
}
|
|
19331
19229
|
const symbolSize = packetSymbolSize;
|
|
19332
19230
|
const result = processFecBlock(recovered, {
|
|
19333
|
-
...blockProfile === "k-padded-v1" ? { k } : {},
|
|
19334
19231
|
symbolSize,
|
|
19335
19232
|
ownerPacketId,
|
|
19336
19233
|
blockProfile
|
|
@@ -19410,8 +19307,7 @@ var MmtpRouter = class {
|
|
|
19410
19307
|
const admission = verifier.admitObject(
|
|
19411
19308
|
{
|
|
19412
19309
|
...identity,
|
|
19413
|
-
packetId: ownerPacketId
|
|
19414
|
-
...ownerPacketId !== void 0 && this.#isIsoPacket(ownerPacketId) ? {} : { ssId: blockId * this.#kForPacket(ownerPacketId) + symbol.index }
|
|
19310
|
+
packetId: ownerPacketId
|
|
19415
19311
|
},
|
|
19416
19312
|
symbol.rawSymbol,
|
|
19417
19313
|
() => {
|
|
@@ -19514,7 +19410,7 @@ var MmtpRouter = class {
|
|
|
19514
19410
|
rapFlag: sym.fragment.rapFlag,
|
|
19515
19411
|
recovered: true,
|
|
19516
19412
|
deliveryProvenance,
|
|
19517
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
19413
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
19518
19414
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
19519
19415
|
});
|
|
19520
19416
|
continue;
|
|
@@ -19626,7 +19522,7 @@ var MmtpRouter = class {
|
|
|
19626
19522
|
rapFlag: assembled.rapFlag,
|
|
19627
19523
|
recovered: true,
|
|
19628
19524
|
...assembled.deliveryProvenance === void 0 ? {} : { deliveryProvenance: assembled.deliveryProvenance },
|
|
19629
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
19525
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
19630
19526
|
...recoveredCatalogTrackName === void 0 ? {} : { catalogTrackName: recoveredCatalogTrackName }
|
|
19631
19527
|
});
|
|
19632
19528
|
continue;
|
|
@@ -19763,7 +19659,7 @@ var MmtpRouter = class {
|
|
|
19763
19659
|
rapFlag: sym.fragment.rapFlag,
|
|
19764
19660
|
recovered: true,
|
|
19765
19661
|
deliveryProvenance,
|
|
19766
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
19662
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
19767
19663
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
19768
19664
|
});
|
|
19769
19665
|
} else {
|
|
@@ -19842,7 +19738,7 @@ var MmtpRouter = class {
|
|
|
19842
19738
|
rapFlag: assembled.rapFlag,
|
|
19843
19739
|
recovered: true,
|
|
19844
19740
|
deliveryProvenance,
|
|
19845
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
19741
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
19846
19742
|
...assembled.catalogTrackName === void 0 ? {} : { catalogTrackName: assembled.catalogTrackName }
|
|
19847
19743
|
});
|
|
19848
19744
|
}
|
|
@@ -19877,7 +19773,7 @@ var MmtpRouter = class {
|
|
|
19877
19773
|
...sym.fragment.sampleNumber === void 0 ? {} : { sampleNumber: sym.fragment.sampleNumber },
|
|
19878
19774
|
recovered: true,
|
|
19879
19775
|
deliveryProvenance,
|
|
19880
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
19776
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
19881
19777
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
19882
19778
|
});
|
|
19883
19779
|
continue;
|
|
@@ -20225,22 +20121,22 @@ var MoqFecAlgorithm;
|
|
|
20225
20121
|
MoqFecAlgorithm2[MoqFecAlgorithm2["RaptorQ"] = 2] = "RaptorQ";
|
|
20226
20122
|
})(MoqFecAlgorithm || (MoqFecAlgorithm = {}));
|
|
20227
20123
|
|
|
20228
|
-
// node_modules/@blockcast/mmt-
|
|
20124
|
+
// node_modules/@blockcast/mmt-alta-parse/dist/index.js
|
|
20229
20125
|
var MPU_HEADER_SIZE3 = 8;
|
|
20230
20126
|
var MPU_LENGTH_FIELD_SIZE3 = 2;
|
|
20231
20127
|
var MPU_HEADER_REMAINDER_SIZE3 = MPU_HEADER_SIZE3 - MPU_LENGTH_FIELD_SIZE3;
|
|
20232
20128
|
|
|
20233
|
-
// node_modules/@blockcast/mmt-
|
|
20129
|
+
// node_modules/@blockcast/mmt-base/dist/mmtp.js
|
|
20234
20130
|
var MPU_HEADER_SIZE4 = 8;
|
|
20235
20131
|
var MPU_LENGTH_FIELD_SIZE4 = 2;
|
|
20236
20132
|
var MPU_HEADER_REMAINDER_SIZE4 = MPU_HEADER_SIZE4 - MPU_LENGTH_FIELD_SIZE4;
|
|
20237
20133
|
var MMTP_SIGNALING_PACKET_ID2 = 0;
|
|
20238
20134
|
var MMTP_PACKET_ID_MAX2 = 65535;
|
|
20239
20135
|
|
|
20240
|
-
// node_modules/@blockcast/mmt-
|
|
20136
|
+
// node_modules/@blockcast/mmt-base/dist/net-constants.js
|
|
20241
20137
|
var DEFAULT_AMT_RELAY_PORT2 = 2268;
|
|
20242
20138
|
|
|
20243
|
-
// node_modules/@blockcast/mmt-
|
|
20139
|
+
// node_modules/@blockcast/mmt-base/dist/ntp-short.js
|
|
20244
20140
|
var NTP_SHORT_BITS2 = 32;
|
|
20245
20141
|
var NTP_SHORT_MODULUS2 = 2 ** NTP_SHORT_BITS2;
|
|
20246
20142
|
var NTP_SHORT_HALF_RANGE2 = 2 ** (NTP_SHORT_BITS2 - 1);
|
|
@@ -20261,9 +20157,8 @@ function compareNtpShort(a, b) {
|
|
|
20261
20157
|
return "unordered";
|
|
20262
20158
|
}
|
|
20263
20159
|
|
|
20264
|
-
// node_modules/@blockcast/mmt-
|
|
20160
|
+
// node_modules/@blockcast/mmt-base/dist/fec-block-profile.js
|
|
20265
20161
|
var FEC_BLOCK_PROFILES2 = Object.freeze([
|
|
20266
|
-
"k-padded-v1",
|
|
20267
20162
|
"iso-ssbg1-v1"
|
|
20268
20163
|
]);
|
|
20269
20164
|
|
|
@@ -20655,10 +20550,12 @@ var MulticastOrchestrator = class {
|
|
|
20655
20550
|
}
|
|
20656
20551
|
onGoodput(sample) {
|
|
20657
20552
|
const bitrate = sample.bitrate;
|
|
20658
|
-
if (!this.#current
|
|
20553
|
+
if (!this.#current)
|
|
20659
20554
|
return void 0;
|
|
20660
20555
|
if (this.#state === "joining")
|
|
20661
20556
|
return this.poll();
|
|
20557
|
+
if (this.options.adaptive === false)
|
|
20558
|
+
return void 0;
|
|
20662
20559
|
if (bitrate === void 0 || !Number.isFinite(bitrate))
|
|
20663
20560
|
return void 0;
|
|
20664
20561
|
this.#estimate = bitrate;
|
|
@@ -20725,6 +20622,27 @@ var MulticastOrchestrator = class {
|
|
|
20725
20622
|
this.#favored = void 0;
|
|
20726
20623
|
return { kind: "join-target", current: this.#current, target: desired };
|
|
20727
20624
|
}
|
|
20625
|
+
/**
|
|
20626
|
+
* Join `target` because the viewer selected it, not because goodput or
|
|
20627
|
+
* headroom ranked it. It runs whether or not the orchestrator adapts, and
|
|
20628
|
+
* takes the path an adaptive join takes: commit on the target's RAP at or
|
|
20629
|
+
* beyond the high-water mark, abandon at the join deadline. While it is
|
|
20630
|
+
* pending nothing re-ranks the ladder; goodput only advances the deadline.
|
|
20631
|
+
*
|
|
20632
|
+
* Returns undefined, changing nothing, while another join is pending (cancel
|
|
20633
|
+
* it first), when `target` is the current tier, or when it carries no media.
|
|
20634
|
+
*/
|
|
20635
|
+
select(target) {
|
|
20636
|
+
if (!this.#current || this.#state === "joining" || !carriesMedia(target))
|
|
20637
|
+
return void 0;
|
|
20638
|
+
if (endpointKey(target) === endpointKey(this.#current))
|
|
20639
|
+
return void 0;
|
|
20640
|
+
this.#state = "joining";
|
|
20641
|
+
this.#target = target;
|
|
20642
|
+
this.#joinStartedAt = this.#now();
|
|
20643
|
+
this.#favored = void 0;
|
|
20644
|
+
return { kind: "join-target", current: this.#current, target };
|
|
20645
|
+
}
|
|
20728
20646
|
/**
|
|
20729
20647
|
* Feed a packet from the active tier (to advance the high-water mark) or
|
|
20730
20648
|
* from the in-flight target (to find a committable RAP).
|
|
@@ -21764,7 +21682,7 @@ var MulticastTransport = class _MulticastTransport {
|
|
|
21764
21682
|
// inside the window, instead of a clear+set pair on every packet.
|
|
21765
21683
|
#watchdog = null;
|
|
21766
21684
|
#lastPacketAtMs = 0;
|
|
21767
|
-
#failbackWindowMs
|
|
21685
|
+
#failbackWindowMs;
|
|
21768
21686
|
#onTakeover = null;
|
|
21769
21687
|
#onFailback = null;
|
|
21770
21688
|
// Data-plane-live edge: the first packet this join accepted. Everything
|
|
@@ -22069,14 +21987,20 @@ var MulticastTransport = class _MulticastTransport {
|
|
|
22069
21987
|
return this.#dataPlaneLive;
|
|
22070
21988
|
}
|
|
22071
21989
|
setFailbackWindowMs(windowMs) {
|
|
22072
|
-
if (!Number.isFinite(windowMs) || windowMs <= 0) {
|
|
21990
|
+
if (windowMs !== null && (!Number.isFinite(windowMs) || windowMs <= 0)) {
|
|
22073
21991
|
throw new Error(
|
|
22074
21992
|
`[MulticastTransport] failbackWindowMs=${windowMs}. Derive a positive failback window from the stream catalog.`
|
|
22075
21993
|
);
|
|
22076
21994
|
}
|
|
22077
21995
|
this.#failbackWindowMs = windowMs;
|
|
22078
|
-
if (
|
|
22079
|
-
clearTimeout(this.#watchdog);
|
|
21996
|
+
if (windowMs === null) {
|
|
21997
|
+
if (this.#watchdog !== null) clearTimeout(this.#watchdog);
|
|
21998
|
+
this.#watchdog = null;
|
|
21999
|
+
return;
|
|
22000
|
+
}
|
|
22001
|
+
if (this.#watchdog !== null) clearTimeout(this.#watchdog);
|
|
22002
|
+
this.#watchdog = null;
|
|
22003
|
+
if (this.#dataPlaneLive && this.#onFailback) {
|
|
22080
22004
|
const silentMs = performance.now() - this.#lastPacketAtMs;
|
|
22081
22005
|
this.#armWatchdog(Math.max(0, Math.ceil(windowMs - silentMs)));
|
|
22082
22006
|
}
|
|
@@ -22402,7 +22326,8 @@ var MulticastTransport = class _MulticastTransport {
|
|
|
22402
22326
|
}
|
|
22403
22327
|
if (this.#watchdog !== null) return;
|
|
22404
22328
|
if (!this.#onFailback) return;
|
|
22405
|
-
if (this.#failbackWindowMs === null)
|
|
22329
|
+
if (this.#failbackWindowMs === null) return;
|
|
22330
|
+
if (this.#failbackWindowMs === void 0) {
|
|
22406
22331
|
throw new Error(
|
|
22407
22332
|
"[MulticastTransport] Failback watchdog requires a catalog-derived window"
|
|
22408
22333
|
);
|
|
@@ -22412,7 +22337,7 @@ var MulticastTransport = class _MulticastTransport {
|
|
|
22412
22337
|
#armWatchdog(delayMs) {
|
|
22413
22338
|
this.#watchdog = setTimeout(() => {
|
|
22414
22339
|
this.#watchdog = null;
|
|
22415
|
-
if (this.#disposed || !this.#onFailback || this.#failbackWindowMs
|
|
22340
|
+
if (this.#disposed || !this.#onFailback || this.#failbackWindowMs == null) return;
|
|
22416
22341
|
const silentMs = performance.now() - this.#lastPacketAtMs;
|
|
22417
22342
|
if (silentMs < this.#failbackWindowMs) {
|
|
22418
22343
|
this.#armWatchdog(Math.ceil(this.#failbackWindowMs - silentMs));
|
|
@@ -22452,18 +22377,320 @@ function catalogAmtNetworkSource(networkSource) {
|
|
|
22452
22377
|
return void 0;
|
|
22453
22378
|
}
|
|
22454
22379
|
|
|
22455
|
-
// src/
|
|
22456
|
-
|
|
22457
|
-
|
|
22458
|
-
|
|
22459
|
-
[...tracks].map(({ name, packetId }) => Object.freeze({ name, packetId }))
|
|
22460
|
-
)
|
|
22461
|
-
});
|
|
22380
|
+
// src/catalog-fec-params.ts
|
|
22381
|
+
var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
|
|
22382
|
+
function timescaleHz(value) {
|
|
22383
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
22462
22384
|
}
|
|
22463
|
-
function
|
|
22464
|
-
if (
|
|
22465
|
-
|
|
22466
|
-
|
|
22385
|
+
function catalogGroupDurationMs(rendition) {
|
|
22386
|
+
if (!rendition) return void 0;
|
|
22387
|
+
const ticks = numericField(rendition.groupDurationTicks);
|
|
22388
|
+
const ms = numericField(rendition.groupDurationMs);
|
|
22389
|
+
const timescale = timescaleHz(rendition.container?.kind === "mmtp" ? rendition.container.timescale : void 0) ?? timescaleHz(rendition.timescale);
|
|
22390
|
+
if (ticks !== void 0 && timescale !== void 0 && Number.isSafeInteger(ticks) && ticks <= timescale * MAX_SAFE_TICKS_PER_HZ) {
|
|
22391
|
+
return resolveGroupDurationMs({ groupDurationTicks: ticks }, timescale);
|
|
22392
|
+
}
|
|
22393
|
+
return ms;
|
|
22394
|
+
}
|
|
22395
|
+
function resolveCatalogTrackGroupDurationMs(catalog, track) {
|
|
22396
|
+
const renditions = track.type === "audio" ? catalog?.audio?.renditions : catalog?.video?.renditions;
|
|
22397
|
+
return catalogGroupDurationMs(renditions?.[track.name]);
|
|
22398
|
+
}
|
|
22399
|
+
function positiveNumber(value) {
|
|
22400
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
22401
|
+
}
|
|
22402
|
+
function catalogKeyframeCadenceMs(rendition) {
|
|
22403
|
+
if (!rendition) return void 0;
|
|
22404
|
+
const timescale = positiveNumber(rendition.timescale);
|
|
22405
|
+
const ticksToMs = (ticks) => {
|
|
22406
|
+
const value = positiveNumber(ticks);
|
|
22407
|
+
return value !== void 0 && timescale !== void 0 ? value * 1e3 / timescale : void 0;
|
|
22408
|
+
};
|
|
22409
|
+
return ticksToMs(rendition.keyframeIntervalTicks) ?? positiveNumber(rendition.keyframeIntervalMs) ?? ticksToMs(rendition.keyframeDurationTicks) ?? positiveNumber(rendition.keyframeDurationMs) ?? ticksToMs(rendition.gopDurationTicks) ?? positiveNumber(rendition.gopDurationMs);
|
|
22410
|
+
}
|
|
22411
|
+
function catalogFecMediaUnitsPerSecond(type, rendition, endpointSampleRate) {
|
|
22412
|
+
if (type === "video") return positiveNumber(rendition?.framerate);
|
|
22413
|
+
const sampleRate = positiveNumber(endpointSampleRate ?? rendition?.sampleRate ?? rendition?.timescale);
|
|
22414
|
+
return sampleRate === void 0 ? void 0 : sampleRate / 1024;
|
|
22415
|
+
}
|
|
22416
|
+
var NO_FEC = {
|
|
22417
|
+
fecK: 0,
|
|
22418
|
+
fecRepairCount: 0,
|
|
22419
|
+
fecTotalRepairCount: 0,
|
|
22420
|
+
fecSymbolSize: 0,
|
|
22421
|
+
fecInterleaveMs: 0
|
|
22422
|
+
};
|
|
22423
|
+
function joinedRepairSymbolCount(params, joinedRepairTrackNames) {
|
|
22424
|
+
const layers = params.repairLayers;
|
|
22425
|
+
if (layers === void 0 || layers.length === 0) return params.fecRepairCount;
|
|
22426
|
+
const joined = new Set(joinedRepairTrackNames);
|
|
22427
|
+
let total = 0;
|
|
22428
|
+
for (const layer of layers) {
|
|
22429
|
+
if (joined.has(layer.repairTrackName)) total += layer.repairSymbols;
|
|
22430
|
+
}
|
|
22431
|
+
return total;
|
|
22432
|
+
}
|
|
22433
|
+
function recordOf2(value) {
|
|
22434
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
22435
|
+
}
|
|
22436
|
+
function msfTracks(catalog) {
|
|
22437
|
+
const raw = catalog.tracks;
|
|
22438
|
+
if (!Array.isArray(raw)) return void 0;
|
|
22439
|
+
return { tracks: raw.map((t) => recordOf2(t) ?? {}) };
|
|
22440
|
+
}
|
|
22441
|
+
function resolveRepairLayers(catalog, source) {
|
|
22442
|
+
if (!catalog) return void 0;
|
|
22443
|
+
const sourceName = typeof source === "string" ? source : source.name;
|
|
22444
|
+
const hang = catalog;
|
|
22445
|
+
const roles = typeof source === "string" ? ["video", "audio"] : source.type === "audio" ? ["audio"] : source.type === "video" ? ["video"] : ["video", "audio"];
|
|
22446
|
+
const role = roles.find((r) => recordOf2(hang[r]?.renditions?.[sourceName]) !== void 0);
|
|
22447
|
+
if (role !== void 0) {
|
|
22448
|
+
const renditionPath = `$.${role}.renditions[${JSON.stringify(sourceName)}]`;
|
|
22449
|
+
const rendition = recordOf2(hang[role].renditions[sourceName]);
|
|
22450
|
+
rejectRepairFieldsOnMediaTrack(renditionPath, rendition, throwRepairContractError);
|
|
22451
|
+
const repairRenditions = recordOf2(hang.repair?.renditions) ?? {};
|
|
22452
|
+
const descriptors2 = Object.entries(repairRenditions).map(([name, entry]) => {
|
|
22453
|
+
const path = `$.repair.renditions[${JSON.stringify(name)}]`;
|
|
22454
|
+
const fields = recordOf2(entry);
|
|
22455
|
+
if (fields === void 0) {
|
|
22456
|
+
throwRepairContractError("track_repair_layer", path, "a repair rendition must be an object");
|
|
22457
|
+
}
|
|
22458
|
+
rejectRepairTrackGeometry(path, fields, throwRepairContractError);
|
|
22459
|
+
return describeRepairTrack(name, path, fields);
|
|
22460
|
+
});
|
|
22461
|
+
const fecOfRendition = (entry) => {
|
|
22462
|
+
const fec2 = recordOf2(recordOf2(entry)?.["fec"]);
|
|
22463
|
+
return fec2 !== void 0 && fec2["algorithm"] !== "none" ? fec2 : void 0;
|
|
22464
|
+
};
|
|
22465
|
+
const mediaRenditions = [
|
|
22466
|
+
...Object.values(recordOf2(hang.video?.renditions) ?? {}),
|
|
22467
|
+
...Object.values(recordOf2(hang.audio?.renditions) ?? {})
|
|
22468
|
+
];
|
|
22469
|
+
const reachability2 = repairReachability(mediaRenditions.map((entry) => recordOf2(entry)?.["fec"]));
|
|
22470
|
+
for (const d of descriptors2) {
|
|
22471
|
+
validateRepairTrackFields(d, throwRepairContractError);
|
|
22472
|
+
assertRepairTrackReachable(d, reachability2, throwRepairContractError);
|
|
22473
|
+
const target = dependsTarget(d);
|
|
22474
|
+
if (target === void 0) continue;
|
|
22475
|
+
const targetFec = fecOfRendition(hang.video?.renditions?.[target]) ?? fecOfRendition(hang.audio?.renditions?.[target]);
|
|
22476
|
+
if (targetFec === void 0) {
|
|
22477
|
+
throwRepairContractError(
|
|
22478
|
+
"track_repair_depends",
|
|
22479
|
+
`${d.path}.depends`,
|
|
22480
|
+
`${JSON.stringify(target)} is not a video or audio rendition with FEC`
|
|
22481
|
+
);
|
|
22482
|
+
}
|
|
22483
|
+
}
|
|
22484
|
+
const layers2 = assembleRepairLayers(
|
|
22485
|
+
geometryOf(sourceName, `${renditionPath}.fec`, recordOf2(rendition["fec"])),
|
|
22486
|
+
descriptors2,
|
|
22487
|
+
throwRepairContractError
|
|
22488
|
+
);
|
|
22489
|
+
return catalogFecUnavailable(rendition["fec"], `${renditionPath}.fec`) === void 0 ? layers2 : void 0;
|
|
22490
|
+
}
|
|
22491
|
+
const msf = msfTracks(catalog);
|
|
22492
|
+
const msfIndex = msf ? msf.tracks.findIndex((t) => t["name"] === sourceName) : -1;
|
|
22493
|
+
if (!msf || msfIndex < 0) return void 0;
|
|
22494
|
+
const track = msf.tracks[msfIndex];
|
|
22495
|
+
const fec = recordOf2(track["fec"]);
|
|
22496
|
+
const packagingOf = (t) => t["packaging"];
|
|
22497
|
+
const fecOfTrack = (t) => {
|
|
22498
|
+
const trackFec = recordOf2(t?.["fec"]);
|
|
22499
|
+
return trackFec !== void 0 && trackFec["algorithm"] !== "none" ? trackFec : void 0;
|
|
22500
|
+
};
|
|
22501
|
+
const descriptors = [];
|
|
22502
|
+
const mediaTracks = [];
|
|
22503
|
+
msf.tracks.forEach((t, i) => {
|
|
22504
|
+
const path = `$.tracks[${i}]`;
|
|
22505
|
+
if (packagingOf(t) === "fec-repair") {
|
|
22506
|
+
rejectRepairTrackGeometry(path, t, throwRepairContractError);
|
|
22507
|
+
if (typeof t["name"] === "string") descriptors.push(describeRepairTrack(t["name"], path, t));
|
|
22508
|
+
return;
|
|
22509
|
+
}
|
|
22510
|
+
rejectRepairFieldsOnMediaTrack(path, t, throwRepairContractError);
|
|
22511
|
+
mediaTracks.push(t);
|
|
22512
|
+
});
|
|
22513
|
+
const reachability = repairReachability(mediaTracks.map((t) => t["fec"]));
|
|
22514
|
+
for (const d of descriptors) {
|
|
22515
|
+
validateRepairTrackFields(d, throwRepairContractError);
|
|
22516
|
+
assertRepairTrackReachable(d, reachability, throwRepairContractError);
|
|
22517
|
+
const target = dependsTarget(d);
|
|
22518
|
+
if (target === void 0) continue;
|
|
22519
|
+
const targetTrack = msf.tracks.find((t) => t["name"] === target);
|
|
22520
|
+
if (targetTrack === void 0 || packagingOf(targetTrack) !== "mmtp" || fecOfTrack(targetTrack) === void 0) {
|
|
22521
|
+
throwRepairContractError(
|
|
22522
|
+
"track_repair_depends",
|
|
22523
|
+
`${d.path}.depends`,
|
|
22524
|
+
`${JSON.stringify(target)} is not an mmtp track with FEC`
|
|
22525
|
+
);
|
|
22526
|
+
}
|
|
22527
|
+
}
|
|
22528
|
+
const layers = assembleRepairLayers(
|
|
22529
|
+
geometryOf(sourceName, `$.tracks[${msfIndex}].fec`, fec),
|
|
22530
|
+
descriptors,
|
|
22531
|
+
throwRepairContractError,
|
|
22532
|
+
{ layerZeroMustBeDeclared: true }
|
|
22533
|
+
);
|
|
22534
|
+
return catalogFecUnavailable(fec, `$.tracks[${msfIndex}].fec`) === void 0 ? layers : void 0;
|
|
22535
|
+
}
|
|
22536
|
+
function moqtPriority2(value) {
|
|
22537
|
+
return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
|
|
22538
|
+
}
|
|
22539
|
+
function catalogRepairTarget(catalog, source) {
|
|
22540
|
+
const repair = resolveRepairLayers(catalog, source);
|
|
22541
|
+
const layerZero = repair?.baseLayers[0];
|
|
22542
|
+
if (!catalog || !layerZero) return null;
|
|
22543
|
+
const sourceName = typeof source === "string" ? source : source.name;
|
|
22544
|
+
const msf = msfTracks(catalog);
|
|
22545
|
+
const entry = msf?.tracks.find((t) => t["name"] === sourceName);
|
|
22546
|
+
const hang = catalog;
|
|
22547
|
+
const roles = typeof source === "string" ? ["video", "audio"] : source.type === "video" ? ["video"] : source.type === "audio" ? ["audio"] : ["video", "audio"];
|
|
22548
|
+
const rendition = roles.map((role) => hang[role]?.renditions?.[sourceName]).find((r) => recordOf2(r) !== void 0);
|
|
22549
|
+
const fec = rendition === void 0 ? recordOf2(entry?.["fec"]) : recordOf2(rendition.fec);
|
|
22550
|
+
if (!fec || !(catalogFecInterleaveMs(fec) ?? 0)) return null;
|
|
22551
|
+
const sourcePriority = entry === void 0 ? null : moqtPriority2(entry["priority"]);
|
|
22552
|
+
return { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };
|
|
22553
|
+
}
|
|
22554
|
+
function geometryOf(name, path, fec) {
|
|
22555
|
+
return {
|
|
22556
|
+
name,
|
|
22557
|
+
path,
|
|
22558
|
+
algorithm: fec?.["algorithm"],
|
|
22559
|
+
sourceSymbols: fec?.["sourceSymbols"],
|
|
22560
|
+
repairSymbols: fec?.["repairSymbols"],
|
|
22561
|
+
repairTrack: fec?.["repairTrack"]
|
|
22562
|
+
};
|
|
22563
|
+
}
|
|
22564
|
+
function numericField(value) {
|
|
22565
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
22566
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
22567
|
+
}
|
|
22568
|
+
function nonNegativeField(value) {
|
|
22569
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
22570
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
22571
|
+
}
|
|
22572
|
+
function blockProfileUnavailable(value, path, declaresFecInstance) {
|
|
22573
|
+
try {
|
|
22574
|
+
readFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);
|
|
22575
|
+
return void 0;
|
|
22576
|
+
} catch (error) {
|
|
22577
|
+
if (!(error instanceof RepairContractError) || error.code !== "track_fec_block_profile" && error.code !== "track_fec_block_profile_unimplemented") {
|
|
22578
|
+
throw error;
|
|
22579
|
+
}
|
|
22580
|
+
return Object.freeze({ code: error.code, path: error.path, message: error.message });
|
|
22581
|
+
}
|
|
22582
|
+
}
|
|
22583
|
+
function isoSpanUnavailable(fec, path, declaresFecInstance) {
|
|
22584
|
+
if (!declaresFecInstance || isoBlockSpanUsable(fec)) return void 0;
|
|
22585
|
+
return Object.freeze({
|
|
22586
|
+
code: "track_fec",
|
|
22587
|
+
path,
|
|
22588
|
+
message: `[repair-layers:track_fec] ${path}: must be a non-negative safe integer in milliseconds`
|
|
22589
|
+
});
|
|
22590
|
+
}
|
|
22591
|
+
function catalogFecUnavailable(fec, fecPath) {
|
|
22592
|
+
const block = recordOf2(fec);
|
|
22593
|
+
if (block === void 0) return void 0;
|
|
22594
|
+
const declaresFecInstance = block["algorithm"] !== "none";
|
|
22595
|
+
return blockProfileUnavailable(block["blockProfile"], `${fecPath}.blockProfile`, declaresFecInstance) ?? isoSpanUnavailable(block, `${fecPath}.interleaveDepthMs`, declaresFecInstance);
|
|
22596
|
+
}
|
|
22597
|
+
function fecParams(catalog, track, rendition, fps, sampleRate) {
|
|
22598
|
+
const mediaRate = sampleRate === void 0 ? { fps } : { fps, sampleRate };
|
|
22599
|
+
const fec = rendition?.fec;
|
|
22600
|
+
if (!fec || !track) return { ...NO_FEC, ...mediaRate };
|
|
22601
|
+
const repair = resolveRepairLayers(catalog, track);
|
|
22602
|
+
const fecPath = `$.${track.type}.renditions[${JSON.stringify(track.name)}].fec`;
|
|
22603
|
+
if (!catalogDeclaresFec(fec)) {
|
|
22604
|
+
const declaresFecInstance = fec.algorithm !== "none";
|
|
22605
|
+
const kValid = Number.isSafeInteger(fec.sourceSymbols) && (fec.sourceSymbols ?? 0) > 0;
|
|
22606
|
+
const tValid = Number.isSafeInteger(fec.symbolSize) && (fec.symbolSize ?? 0) > 0;
|
|
22607
|
+
if (declaresFecInstance && !(kValid && tValid)) {
|
|
22608
|
+
throwRepairContractError(
|
|
22609
|
+
"track_fec",
|
|
22610
|
+
`${fecPath}.${kValid ? "symbolSize" : "sourceSymbols"}`,
|
|
22611
|
+
"must be a positive integer"
|
|
22612
|
+
);
|
|
22613
|
+
}
|
|
22614
|
+
const fecUnavailable = catalogFecUnavailable(fec, fecPath);
|
|
22615
|
+
return fecUnavailable === void 0 ? { ...NO_FEC, ...mediaRate } : { ...NO_FEC, ...mediaRate, fecUnavailable };
|
|
22616
|
+
}
|
|
22617
|
+
const fecBlockProfile = readFecBlockProfile(fec.blockProfile, `${fecPath}.blockProfile`, true, throwRepairContractError);
|
|
22618
|
+
return {
|
|
22619
|
+
fecK: fec.sourceSymbols ?? 0,
|
|
22620
|
+
fecBlockProfile,
|
|
22621
|
+
fecRepairCount: fec.repairSymbols ?? 0,
|
|
22622
|
+
// The wire RSB_length: every base layer's packets carry the base total (§7.1).
|
|
22623
|
+
fecTotalRepairCount: repair?.totalBaseRepairSymbols ?? fec.repairSymbols ?? 0,
|
|
22624
|
+
fecSymbolSize: fec.symbolSize ?? 0,
|
|
22625
|
+
// Zero/absence leaves the elapsed deadline unknown; geometry stays in-band.
|
|
22626
|
+
fecInterleaveMs: catalogFecInterleaveMs(fec) ?? 0,
|
|
22627
|
+
// Additive, never a multiplier on the span: jitter belongs to the network
|
|
22628
|
+
// path and the span belongs to the encoder, so scaling one by the other
|
|
22629
|
+
// hands one network different slack per track. `numericField` rejects 0,
|
|
22630
|
+
// so it cannot be reused here — a declared 0 is a legitimate value.
|
|
22631
|
+
fecReorderToleranceMs: nonNegativeField(fec.reorderToleranceMs),
|
|
22632
|
+
repairTrackName: fec.repairTrack,
|
|
22633
|
+
repairLayers: repair?.baseLayers,
|
|
22634
|
+
repairOverlays: repair?.overlays,
|
|
22635
|
+
...mediaRate
|
|
22636
|
+
};
|
|
22637
|
+
}
|
|
22638
|
+
function resolveCatalogTrackFec(catalog, tracks) {
|
|
22639
|
+
const videoTrackName = tracks.find((t) => t.type === "video")?.name;
|
|
22640
|
+
const audioTrackName = tracks.find((t) => t.type === "audio")?.name;
|
|
22641
|
+
const videoRenditions = catalog?.video?.renditions;
|
|
22642
|
+
const audioRenditions = catalog?.audio?.renditions;
|
|
22643
|
+
const video = videoTrackName ? videoRenditions?.[videoTrackName] : void 0;
|
|
22644
|
+
const audio = audioTrackName ? audioRenditions?.[audioTrackName] : void 0;
|
|
22645
|
+
const catalogFps = numericField(video?.framerate);
|
|
22646
|
+
const sampleRate = numericField(audio?.sampleRate ?? audio?.samplerate);
|
|
22647
|
+
const audioFps = sampleRate ? sampleRate / 1024 : void 0;
|
|
22648
|
+
const videoParams = fecParams(catalog, videoTrackName ? { name: videoTrackName, type: "video" } : void 0, video, catalogFps);
|
|
22649
|
+
const audioParams = fecParams(
|
|
22650
|
+
catalog,
|
|
22651
|
+
audioTrackName ? { name: audioTrackName, type: "audio" } : void 0,
|
|
22652
|
+
audio,
|
|
22653
|
+
audioFps,
|
|
22654
|
+
sampleRate
|
|
22655
|
+
);
|
|
22656
|
+
const forTrack = (track) => {
|
|
22657
|
+
if (track.type === "audio") {
|
|
22658
|
+
if (track.name === audioTrackName) return audioParams;
|
|
22659
|
+
const rendition2 = audioRenditions?.[track.name];
|
|
22660
|
+
if (!rendition2) return { ...NO_FEC };
|
|
22661
|
+
const rate = numericField(rendition2.sampleRate ?? rendition2.samplerate);
|
|
22662
|
+
return fecParams(catalog, { name: track.name, type: "audio" }, rendition2, rate ? rate / 1024 : void 0, rate);
|
|
22663
|
+
}
|
|
22664
|
+
if (track.name === videoTrackName) return videoParams;
|
|
22665
|
+
const rendition = videoRenditions?.[track.name];
|
|
22666
|
+
if (!rendition) return { ...NO_FEC };
|
|
22667
|
+
return fecParams(catalog, { name: track.name, type: "video" }, rendition, numericField(rendition.framerate));
|
|
22668
|
+
};
|
|
22669
|
+
return {
|
|
22670
|
+
videoTrackName,
|
|
22671
|
+
audioTrackName,
|
|
22672
|
+
missingVideoRendition: videoTrackName && videoRenditions && !video ? videoTrackName : void 0,
|
|
22673
|
+
missingAudioRendition: audioTrackName && audioRenditions && !audio ? audioTrackName : void 0,
|
|
22674
|
+
videoForKeyframeRepair: video,
|
|
22675
|
+
catalogFps,
|
|
22676
|
+
video: videoParams,
|
|
22677
|
+
audio: audioParams,
|
|
22678
|
+
forTrack
|
|
22679
|
+
};
|
|
22680
|
+
}
|
|
22681
|
+
|
|
22682
|
+
// src/multicast-manager.ts
|
|
22683
|
+
function freezeIngress(tracks) {
|
|
22684
|
+
return Object.freeze({
|
|
22685
|
+
tracks: Object.freeze(
|
|
22686
|
+
[...tracks].map(({ name, packetId }) => Object.freeze({ name, packetId }))
|
|
22687
|
+
)
|
|
22688
|
+
});
|
|
22689
|
+
}
|
|
22690
|
+
function sameIdSet(a, b) {
|
|
22691
|
+
if (a.size !== b.size) return false;
|
|
22692
|
+
for (const id of a) if (!b.has(id)) return false;
|
|
22693
|
+
return true;
|
|
22467
22694
|
}
|
|
22468
22695
|
function newBlockPolicyStats(packetId, catalogTrackName, epoch) {
|
|
22469
22696
|
return {
|
|
@@ -22762,9 +22989,8 @@ function repairTrackNamesBySource(tracks) {
|
|
|
22762
22989
|
bySource.set(source, names);
|
|
22763
22990
|
};
|
|
22764
22991
|
for (const track of tracks) {
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
}
|
|
22992
|
+
const repairTrack = typeof track.fec === "object" && track.fec !== null ? track.fec.repairTrack : void 0;
|
|
22993
|
+
if (typeof repairTrack === "string") add(track.name, repairTrack);
|
|
22768
22994
|
if (track.packaging === "fec-repair" && Array.isArray(track.depends) && track.depends.length === 1) {
|
|
22769
22995
|
add(track.depends[0], track.name);
|
|
22770
22996
|
}
|
|
@@ -22774,6 +23000,7 @@ function repairTrackNamesBySource(tracks) {
|
|
|
22774
23000
|
var MulticastManager = class {
|
|
22775
23001
|
#fec;
|
|
22776
23002
|
#router;
|
|
23003
|
+
#hostMfuTimeoutMs;
|
|
22777
23004
|
#transport;
|
|
22778
23005
|
#reportedFecDiagnosticsFailure = false;
|
|
22779
23006
|
// ── Stream-global FEC timing reduction ──
|
|
@@ -22872,6 +23099,7 @@ var MulticastManager = class {
|
|
|
22872
23099
|
this.#emitRepairLifecycle(decision);
|
|
22873
23100
|
}
|
|
22874
23101
|
});
|
|
23102
|
+
this.#hostMfuTimeoutMs = this.#router.reassembler.timeoutMs;
|
|
22875
23103
|
this.#transport = new MulticastTransport((packet, ingress) => {
|
|
22876
23104
|
this.#router.feedPacket(packet, "multicast", void 0, ingress);
|
|
22877
23105
|
});
|
|
@@ -23088,7 +23316,6 @@ var MulticastManager = class {
|
|
|
23088
23316
|
...this.#streamFecBaseline ? [this.#streamFecBaseline] : [],
|
|
23089
23317
|
...this.#trackFecTiming.values()
|
|
23090
23318
|
];
|
|
23091
|
-
if (contributions.length === 0) return;
|
|
23092
23319
|
let deliveryWindowMs;
|
|
23093
23320
|
let minimumFailbackWindowMs;
|
|
23094
23321
|
let mfuTimeoutMs;
|
|
@@ -23120,15 +23347,9 @@ var MulticastManager = class {
|
|
|
23120
23347
|
}
|
|
23121
23348
|
}
|
|
23122
23349
|
const failbackWindowMs = deliveryWindowMs === void 0 ? minimumFailbackWindowMs : Math.max(deliveryWindowMs, minimumFailbackWindowMs ?? 0);
|
|
23123
|
-
|
|
23124
|
-
|
|
23125
|
-
|
|
23126
|
-
if (mfuTimeoutMs !== void 0) {
|
|
23127
|
-
this.#router.setMfuTimeoutMs(mfuTimeoutMs);
|
|
23128
|
-
}
|
|
23129
|
-
if (reorderWindowFrames !== void 0) {
|
|
23130
|
-
this.#router.setReorderWindow(reorderWindowFrames);
|
|
23131
|
-
}
|
|
23350
|
+
this.#transport.setFailbackWindowMs(failbackWindowMs ?? null);
|
|
23351
|
+
this.#router.setMfuTimeoutMs(mfuTimeoutMs ?? this.#hostMfuTimeoutMs);
|
|
23352
|
+
this.#router.setReorderWindow(reorderWindowFrames ?? 0);
|
|
23132
23353
|
}
|
|
23133
23354
|
/**
|
|
23134
23355
|
* Configure one track's FEC geometry, block mapper, and block-inactivity
|
|
@@ -23155,9 +23376,9 @@ var MulticastManager = class {
|
|
|
23155
23376
|
`[MulticastManager] configureTrackFec(${packetId}) catalogTrackName must be non-empty when given`
|
|
23156
23377
|
);
|
|
23157
23378
|
}
|
|
23158
|
-
if (config.fecK === void 0
|
|
23379
|
+
if (config.fecK === void 0) {
|
|
23159
23380
|
throw new Error(
|
|
23160
|
-
`[MulticastManager] configureTrackFec(${packetId}) requires fecK
|
|
23381
|
+
`[MulticastManager] configureTrackFec(${packetId}) requires fecK. Derive the per-track FEC geometry from the stream catalog.`
|
|
23161
23382
|
);
|
|
23162
23383
|
}
|
|
23163
23384
|
const blockProfile = parseFecBlockProfile(
|
|
@@ -23165,7 +23386,10 @@ var MulticastManager = class {
|
|
|
23165
23386
|
`[MulticastManager] configureTrackFec(${packetId}) fecBlockProfile`
|
|
23166
23387
|
);
|
|
23167
23388
|
assertPositiveGeometry("fecK", config.fecK);
|
|
23168
|
-
|
|
23389
|
+
const interleaveDepthMs = config.fecInterleaveDepthMs ?? 0;
|
|
23390
|
+
if (!Number.isSafeInteger(interleaveDepthMs) || interleaveDepthMs < 0) {
|
|
23391
|
+
throw new Error("[MulticastManager] fecInterleaveDepthMs must be a non-negative safe integer");
|
|
23392
|
+
}
|
|
23169
23393
|
assertPositiveGeometry("fecSymbolSize", config.fecSymbolSize);
|
|
23170
23394
|
assertPositiveGeometry("fps", config.fps);
|
|
23171
23395
|
assertPositiveGeometry("bandwidth", config.bandwidth);
|
|
@@ -23181,8 +23405,8 @@ var MulticastManager = class {
|
|
|
23181
23405
|
"fecReorderToleranceMs",
|
|
23182
23406
|
config.fecReorderToleranceMs
|
|
23183
23407
|
);
|
|
23184
|
-
const deliveryWindowMs =
|
|
23185
|
-
interleaveDepthMs
|
|
23408
|
+
const deliveryWindowMs = interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
|
|
23409
|
+
interleaveDepthMs,
|
|
23186
23410
|
sourceSymbols: config.fecK,
|
|
23187
23411
|
repairSymbols: config.fecRepairSymbols,
|
|
23188
23412
|
symbolSize: config.fecSymbolSize,
|
|
@@ -23191,10 +23415,10 @@ var MulticastManager = class {
|
|
|
23191
23415
|
jitterBufferMs: config.fecReorderToleranceMs
|
|
23192
23416
|
}) : void 0;
|
|
23193
23417
|
const trackKind = config.trackKind ?? this.#router.trackKindFor(packetId);
|
|
23194
|
-
const geometry = trackKind === void 0 ? void 0 : deriveBlockClassifierGeometry({
|
|
23418
|
+
const geometry = trackKind === void 0 || interleaveDepthMs === 0 ? void 0 : deriveBlockClassifierGeometry({
|
|
23195
23419
|
trackKind,
|
|
23196
23420
|
framerate: config.fps,
|
|
23197
|
-
interleaveDepthMs
|
|
23421
|
+
interleaveDepthMs,
|
|
23198
23422
|
timescale: config.timescale,
|
|
23199
23423
|
keyframeIntervalTicks: config.keyframeIntervalTicks,
|
|
23200
23424
|
keyframeIntervalMs: config.keyframeIntervalMs
|
|
@@ -23203,7 +23427,7 @@ var MulticastManager = class {
|
|
|
23203
23427
|
blockProfile,
|
|
23204
23428
|
k: config.fecK,
|
|
23205
23429
|
symbolSize: config.fecSymbolSize,
|
|
23206
|
-
interleaveDepthMs
|
|
23430
|
+
interleaveDepthMs,
|
|
23207
23431
|
fps: config.fps,
|
|
23208
23432
|
timeoutMs: deliveryWindowMs,
|
|
23209
23433
|
...trackKind === void 0 ? {} : { trackKind },
|
|
@@ -23214,7 +23438,7 @@ var MulticastManager = class {
|
|
|
23214
23438
|
this.#configureTrackMapper(packetId, fecConfig);
|
|
23215
23439
|
this.#trackFecTiming.set(packetId, {
|
|
23216
23440
|
deliveryWindowMs,
|
|
23217
|
-
interleaveDepthMs
|
|
23441
|
+
interleaveDepthMs,
|
|
23218
23442
|
fps: config.fps,
|
|
23219
23443
|
k: config.fecK,
|
|
23220
23444
|
repairSymbols: config.fecRepairSymbols,
|
|
@@ -23223,6 +23447,10 @@ var MulticastManager = class {
|
|
|
23223
23447
|
this.#applyStreamGlobalFecTiming();
|
|
23224
23448
|
}
|
|
23225
23449
|
configureFec(config) {
|
|
23450
|
+
const interleaveDepthMs = config.fecInterleaveDepthMs ?? 0;
|
|
23451
|
+
if (!Number.isSafeInteger(interleaveDepthMs) || interleaveDepthMs < 0) {
|
|
23452
|
+
throw new Error("[MulticastManager] fecInterleaveDepthMs must be a non-negative safe integer");
|
|
23453
|
+
}
|
|
23226
23454
|
const minimumFailbackWindowMs = config.minimumFailbackWindowMs;
|
|
23227
23455
|
if (minimumFailbackWindowMs !== void 0 && (!Number.isFinite(minimumFailbackWindowMs) || minimumFailbackWindowMs <= 0)) {
|
|
23228
23456
|
throw new Error(
|
|
@@ -23234,7 +23462,7 @@ var MulticastManager = class {
|
|
|
23234
23462
|
config.fecReorderToleranceMs
|
|
23235
23463
|
);
|
|
23236
23464
|
const deliveryWindowMs = config.fecK && config.fecK > 0 && config.fecInterleaveDepthMs && config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
|
|
23237
|
-
interleaveDepthMs
|
|
23465
|
+
interleaveDepthMs,
|
|
23238
23466
|
sourceSymbols: config.fecK,
|
|
23239
23467
|
repairSymbols: config.fecRepairSymbols,
|
|
23240
23468
|
symbolSize: config.fecSymbolSize,
|
|
@@ -23245,13 +23473,13 @@ var MulticastManager = class {
|
|
|
23245
23473
|
this.#fec.configure({
|
|
23246
23474
|
k: config.fecK,
|
|
23247
23475
|
symbolSize: config.fecSymbolSize,
|
|
23248
|
-
interleaveDepthMs
|
|
23476
|
+
interleaveDepthMs,
|
|
23249
23477
|
fps: config.fps,
|
|
23250
23478
|
timeoutMs: deliveryWindowMs
|
|
23251
23479
|
});
|
|
23252
23480
|
this.#streamFecBaseline = {
|
|
23253
23481
|
deliveryWindowMs,
|
|
23254
|
-
interleaveDepthMs
|
|
23482
|
+
interleaveDepthMs,
|
|
23255
23483
|
fps: config.fps,
|
|
23256
23484
|
k: config.fecK,
|
|
23257
23485
|
repairSymbols: config.fecRepairSymbols,
|
|
@@ -23300,6 +23528,7 @@ var MulticastManager = class {
|
|
|
23300
23528
|
}
|
|
23301
23529
|
setMfuTimeoutMs(ms) {
|
|
23302
23530
|
this.#router.setMfuTimeoutMs(ms);
|
|
23531
|
+
this.#hostMfuTimeoutMs = this.#router.reassembler.timeoutMs;
|
|
23303
23532
|
}
|
|
23304
23533
|
setTrackCodec(packetId, codec) {
|
|
23305
23534
|
this.#router.setTrackCodec(packetId, codec);
|
|
@@ -23322,6 +23551,7 @@ var MulticastManager = class {
|
|
|
23322
23551
|
onTrackPacket(packetId, cb) {
|
|
23323
23552
|
this.#router.onTrackPacket(packetId, cb);
|
|
23324
23553
|
}
|
|
23554
|
+
/** @deprecated Use onTrackInit(packetId, cb); see MmtpRouter.onInitSegment. */
|
|
23325
23555
|
onInitSegment(cb) {
|
|
23326
23556
|
this.#router.onInitSegment(cb);
|
|
23327
23557
|
}
|
|
@@ -23633,7 +23863,10 @@ var MulticastManager = class {
|
|
|
23633
23863
|
console.warn("[MulticastManager] rendition switch onCancel failed", error);
|
|
23634
23864
|
}
|
|
23635
23865
|
}
|
|
23636
|
-
/**
|
|
23866
|
+
/**
|
|
23867
|
+
* Release what the manager and FEC hold for a stream whose packet ID the
|
|
23868
|
+
* router retired, or whose FEC a catalog reconfiguration no longer configures.
|
|
23869
|
+
*/
|
|
23637
23870
|
#releaseRetiredStream(stream) {
|
|
23638
23871
|
for (const observer of this.#trackResetObservers) {
|
|
23639
23872
|
try {
|
|
@@ -23652,7 +23885,10 @@ var MulticastManager = class {
|
|
|
23652
23885
|
for (const [key, stats] of [...this.#blockPolicyStats]) {
|
|
23653
23886
|
if (stats.packetId === stream.packetId) this.#blockPolicyStats.delete(key);
|
|
23654
23887
|
}
|
|
23655
|
-
this.#trackFecTiming.delete(stream.packetId)
|
|
23888
|
+
if (this.#trackFecTiming.delete(stream.packetId)) {
|
|
23889
|
+
this.#applyStreamGlobalFecTiming();
|
|
23890
|
+
}
|
|
23891
|
+
this.#multiTrackFecTracks.delete(stream.packetId);
|
|
23656
23892
|
this.#fec.releasePacket(stream.packetId);
|
|
23657
23893
|
}
|
|
23658
23894
|
/** Feed a raw packet from an external source (AMT tunnel, ASM subscription).
|
|
@@ -23697,12 +23933,53 @@ var MulticastManager = class {
|
|
|
23697
23933
|
* D-17: Builds altGroup → track-name index for getSwitchingSet().
|
|
23698
23934
|
* D-26: Registers packetId → TrackRole on MmtpRouter for block-complete fanout.
|
|
23699
23935
|
*
|
|
23936
|
+
* FEC availability is per track (draft-ramadan-moq-fec §5.1). A media track
|
|
23937
|
+
* whose `fec` names no implemented `blockProfile` (only `iso-ssbg1-v1` is
|
|
23938
|
+
* implemented; the retired `k-padded-v1` is not), or an `iso-ssbg1-v1` span
|
|
23939
|
+
* it cannot use, is consumed without FEC: it gets no decoder, and none of
|
|
23940
|
+
* its repair tracks (layer 0 by `fec.repairTrack`, further layers and the
|
|
23941
|
+
* overlay by `depends`) is given an owner, so the router drops their packets
|
|
23942
|
+
* before any decoder. Those legs keep the `repair` role and their owner's
|
|
23943
|
+
* switching set. Neither the call nor any other track's FEC fails on that
|
|
23944
|
+
* account; `trackFecUnavailable(name)` reports the reason. FEC a previous
|
|
23945
|
+
* call configured on a packetId this call does not is released
|
|
23946
|
+
* (FECManager.releasePacket); frames its release gate held are delivered in
|
|
23947
|
+
* order, each gap stalled. FEC wired by hand (setFecDecoder) is left alone.
|
|
23948
|
+
* Geometry a decoder could not configure (K, P, T, `repairTrack`,
|
|
23949
|
+
* algorithm) still throws.
|
|
23950
|
+
*
|
|
23700
23951
|
* @param catalog - Multi-track catalog (tracks array with name, altGroup, fec, packetId).
|
|
23701
23952
|
* @param wasmDecoderSource - One WASM FEC decoder for a single FEC stream, or
|
|
23702
23953
|
* a factory returning a distinct decoder per stream.
|
|
23703
23954
|
*/
|
|
23704
23955
|
configureMultiTrack(catalog, wasmDecoderSource) {
|
|
23705
23956
|
const repairNamesBySource = repairTrackNamesBySource(catalog.tracks);
|
|
23957
|
+
const declaredRepairNames = /* @__PURE__ */ new Set();
|
|
23958
|
+
for (const names of repairNamesBySource.values()) {
|
|
23959
|
+
for (const name of names) declaredRepairNames.add(name);
|
|
23960
|
+
}
|
|
23961
|
+
const fecUnavailableByTrack = /* @__PURE__ */ new Map();
|
|
23962
|
+
for (let i = 0; i < catalog.tracks.length; i++) {
|
|
23963
|
+
const track = catalog.tracks[i];
|
|
23964
|
+
if (track.fec === void 0 || track.packaging === "fec-repair" || declaredRepairNames.has(track.name)) {
|
|
23965
|
+
continue;
|
|
23966
|
+
}
|
|
23967
|
+
const fecUnavailable = catalogFecUnavailable(track.fec, `$.tracks[${i}].fec`);
|
|
23968
|
+
if (fecUnavailable === void 0) continue;
|
|
23969
|
+
fecUnavailableByTrack.set(track.name, fecUnavailable);
|
|
23970
|
+
const layerZero = track.fec.repairTrack;
|
|
23971
|
+
if (typeof layerZero === "string" && layerZero.length > 0) {
|
|
23972
|
+
const names = repairNamesBySource.get(track.name) ?? /* @__PURE__ */ new Set();
|
|
23973
|
+
names.add(layerZero);
|
|
23974
|
+
repairNamesBySource.set(track.name, names);
|
|
23975
|
+
}
|
|
23976
|
+
}
|
|
23977
|
+
const unownedRepairNames = /* @__PURE__ */ new Set();
|
|
23978
|
+
for (const sourceName of fecUnavailableByTrack.keys()) {
|
|
23979
|
+
for (const repairName of repairNamesBySource.get(sourceName) ?? []) {
|
|
23980
|
+
unownedRepairNames.add(repairName);
|
|
23981
|
+
}
|
|
23982
|
+
}
|
|
23706
23983
|
const repairOwnerGroup = /* @__PURE__ */ new Map();
|
|
23707
23984
|
const repairOwnerAltGroup = /* @__PURE__ */ new Map();
|
|
23708
23985
|
for (const track of catalog.tracks) {
|
|
@@ -23755,11 +24032,13 @@ var MulticastManager = class {
|
|
|
23755
24032
|
for (let i = 0; i < catalog.tracks.length; i++) {
|
|
23756
24033
|
const track = catalog.tracks[i];
|
|
23757
24034
|
if (resolvedRoles[i] === "repair" || track.fec === void 0) continue;
|
|
23758
|
-
|
|
24035
|
+
const unavailable = fecUnavailableByTrack.has(track.name);
|
|
24036
|
+
if (!(unavailable ? isFecCatalogGeometry(track.fec) : isFecCatalogConfig(track.fec))) {
|
|
23759
24037
|
throw new Error(
|
|
23760
24038
|
`[MulticastManager] invalid fec config for track '${track.name}'`
|
|
23761
24039
|
);
|
|
23762
24040
|
}
|
|
24041
|
+
if (unavailable) continue;
|
|
23763
24042
|
if (track.packetId !== void 0) fecTrackIndexes.push(i);
|
|
23764
24043
|
}
|
|
23765
24044
|
const fecDecoders = /* @__PURE__ */ new Map();
|
|
@@ -23788,6 +24067,20 @@ var MulticastManager = class {
|
|
|
23788
24067
|
this.#switchingSets.clear();
|
|
23789
24068
|
this.#repairPacketIds.clear();
|
|
23790
24069
|
this.#router.clearRepairPacketIdOwners();
|
|
24070
|
+
const fecPacketIds = new Set([...fecDecoders.keys()].map((i) => catalog.tracks[i].packetId));
|
|
24071
|
+
for (const [packetId, catalogTrackName] of this.#multiTrackFecTracks) {
|
|
24072
|
+
if (fecPacketIds.has(packetId)) continue;
|
|
24073
|
+
if (!this.#fec.isTrackConfigured(packetId)) {
|
|
24074
|
+
this.#multiTrackFecTracks.delete(packetId);
|
|
24075
|
+
continue;
|
|
24076
|
+
}
|
|
24077
|
+
this.#router.releaseFecPacket(packetId);
|
|
24078
|
+
this.#releaseRetiredStream({ packetId, catalogTrackName });
|
|
24079
|
+
}
|
|
24080
|
+
this.#fecUnavailableByTrack.clear();
|
|
24081
|
+
for (const [name, reason] of fecUnavailableByTrack) {
|
|
24082
|
+
this.#fecUnavailableByTrack.set(name, reason);
|
|
24083
|
+
}
|
|
23791
24084
|
const catalogTracksByName = new Map(
|
|
23792
24085
|
catalog.tracks.map((track) => [track.name, track])
|
|
23793
24086
|
);
|
|
@@ -23809,10 +24102,11 @@ var MulticastManager = class {
|
|
|
23809
24102
|
}
|
|
23810
24103
|
continue;
|
|
23811
24104
|
}
|
|
23812
|
-
if (track.fec !== void 0) {
|
|
24105
|
+
if (track.fec !== void 0 && !fecUnavailableByTrack.has(track.name)) {
|
|
23813
24106
|
const fecCfg = track.fec;
|
|
23814
24107
|
if (track.packetId !== void 0) {
|
|
23815
24108
|
for (const repairName of repairNamesBySource.get(track.name) ?? []) {
|
|
24109
|
+
if (unownedRepairNames.has(repairName)) continue;
|
|
23816
24110
|
const repairTrack = catalogTracksByName.get(repairName);
|
|
23817
24111
|
if (repairTrack?.packetId !== void 0) {
|
|
23818
24112
|
this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);
|
|
@@ -23833,17 +24127,18 @@ var MulticastManager = class {
|
|
|
23833
24127
|
blockProfile,
|
|
23834
24128
|
k: fecCfg.sourceSymbols,
|
|
23835
24129
|
symbolSize: fecCfg.symbolSize,
|
|
23836
|
-
interleaveDepthMs: fecCfg.interleaveDepthMs,
|
|
24130
|
+
interleaveDepthMs: fecCfg.interleaveDepthMs ?? 0,
|
|
23837
24131
|
fps: track.framerate,
|
|
23838
24132
|
trackKind: "video"
|
|
23839
24133
|
},
|
|
23840
24134
|
track.name
|
|
23841
24135
|
);
|
|
24136
|
+
this.#multiTrackFecTracks.set(track.packetId, track.name);
|
|
23842
24137
|
this.#configureTrackMapper(track.packetId, {
|
|
23843
24138
|
blockProfile,
|
|
23844
24139
|
k: fecCfg.sourceSymbols,
|
|
23845
24140
|
symbolSize: fecCfg.symbolSize,
|
|
23846
|
-
interleaveDepthMs: fecCfg.interleaveDepthMs,
|
|
24141
|
+
interleaveDepthMs: fecCfg.interleaveDepthMs ?? 0,
|
|
23847
24142
|
fps: track.framerate
|
|
23848
24143
|
});
|
|
23849
24144
|
this.#router.setPacketIdType(track.packetId, "video");
|
|
@@ -23851,6 +24146,24 @@ var MulticastManager = class {
|
|
|
23851
24146
|
}
|
|
23852
24147
|
}
|
|
23853
24148
|
}
|
|
24149
|
+
/**
|
|
24150
|
+
* Why `configureMultiTrack` consumes the named catalog track without FEC
|
|
24151
|
+
* although its `fec` block declares one (draft-ramadan-moq-fec §5.1): no
|
|
24152
|
+
* implemented `blockProfile` (`track_fec_block_profile_unimplemented`, e.g.
|
|
24153
|
+
* the retired `k-padded-v1`; `track_fec_block_profile` when absent), or an
|
|
24154
|
+
* invalid `iso-ssbg1-v1` span (`track_fec`). Absent/zero spans keep in-band
|
|
24155
|
+
* decoding enabled. The same reason the hang
|
|
24156
|
+
* resolver reports (`catalogFecUnavailable`). Undefined for every other
|
|
24157
|
+
* track, and for every track before the first call; each call replaces the
|
|
24158
|
+
* previous call's answers.
|
|
24159
|
+
*/
|
|
24160
|
+
trackFecUnavailable(trackName) {
|
|
24161
|
+
return this.#fecUnavailableByTrack.get(trackName);
|
|
24162
|
+
}
|
|
24163
|
+
/** Per-track FEC availability from the last `configureMultiTrack` call. */
|
|
24164
|
+
#fecUnavailableByTrack = /* @__PURE__ */ new Map();
|
|
24165
|
+
/** packetId → catalog track name of the FEC the last `configureMultiTrack` installed. */
|
|
24166
|
+
#multiTrackFecTracks = /* @__PURE__ */ new Map();
|
|
23854
24167
|
/**
|
|
23855
24168
|
* Return the track names belonging to the given altGroup switching set.
|
|
23856
24169
|
* D-17: altGroup groups base+delta+repair into one switching set.
|
|
@@ -24061,31 +24374,62 @@ var MulticastManager = class {
|
|
|
24061
24374
|
|
|
24062
24375
|
// src/fec-worker-setup.ts
|
|
24063
24376
|
async function setupFecWorkerForTrack(opts) {
|
|
24064
|
-
if (!Number.isFinite(opts.fecInterleaveMs) || opts.fecInterleaveMs
|
|
24377
|
+
if (!Number.isFinite(opts.fecInterleaveMs) || !Number.isSafeInteger(opts.fecInterleaveMs) || opts.fecInterleaveMs < 0) {
|
|
24065
24378
|
throw new Error(
|
|
24066
|
-
`[FEC] interleaveDepthMs (opts.fecInterleaveMs) must be finite
|
|
24379
|
+
`[FEC] interleaveDepthMs (opts.fecInterleaveMs) must be a finite non-negative integer (got ${opts.fecInterleaveMs})`
|
|
24067
24380
|
);
|
|
24068
24381
|
}
|
|
24069
|
-
if (!Number.isFinite(opts.
|
|
24382
|
+
if (opts.fecReorderToleranceMs !== void 0 && (!Number.isFinite(opts.fecReorderToleranceMs) || opts.fecReorderToleranceMs < 0)) {
|
|
24070
24383
|
throw new Error(
|
|
24071
|
-
`[FEC]
|
|
24384
|
+
`[FEC] fecReorderToleranceMs must be finite and >= 0 (got ${opts.fecReorderToleranceMs})`
|
|
24385
|
+
);
|
|
24386
|
+
}
|
|
24387
|
+
const hasCatalogTiming = opts.fecInterleaveMs > 0;
|
|
24388
|
+
const validateWorkerGeometry = () => {
|
|
24389
|
+
if (!Number.isSafeInteger(opts.fecK) || opts.fecK <= 0) {
|
|
24390
|
+
throw new Error(`[FEC] fecK must be a finite positive integer (got ${opts.fecK})`);
|
|
24391
|
+
}
|
|
24392
|
+
if (!Number.isSafeInteger(opts.fecSymbolSize) || opts.fecSymbolSize <= 0 || opts.fecSymbolSize % 8 !== 0) {
|
|
24393
|
+
throw new Error(
|
|
24394
|
+
`[FEC] fecSymbolSize must be a positive 8-byte-aligned integer (got ${opts.fecSymbolSize})`
|
|
24395
|
+
);
|
|
24396
|
+
}
|
|
24397
|
+
if (!Number.isSafeInteger(opts.fecRepairCount) || opts.fecRepairCount <= 0) {
|
|
24398
|
+
throw new Error(
|
|
24399
|
+
`[FEC] fecRepairCount must be a finite positive integer (got ${opts.fecRepairCount})`
|
|
24400
|
+
);
|
|
24401
|
+
}
|
|
24402
|
+
};
|
|
24403
|
+
let deliveryWindowMs;
|
|
24404
|
+
let interleaveDepthBlocks;
|
|
24405
|
+
if (!hasCatalogTiming) {
|
|
24406
|
+
validateWorkerGeometry();
|
|
24407
|
+
deliveryWindowMs = LOCAL_FEC_TIMEOUT_MS;
|
|
24408
|
+
interleaveDepthBlocks = LOCAL_FEC_RETENTION_BLOCKS;
|
|
24409
|
+
} else {
|
|
24410
|
+
const mediaUnitDurationMs = opts.mediaUnitDurationMs;
|
|
24411
|
+
if (typeof mediaUnitDurationMs !== "number" || !Number.isFinite(mediaUnitDurationMs) || mediaUnitDurationMs <= 0) {
|
|
24412
|
+
throw new Error(
|
|
24413
|
+
`[FEC] mediaUnitDurationMs must be finite and > 0 (got ${mediaUnitDurationMs})`
|
|
24414
|
+
);
|
|
24415
|
+
}
|
|
24416
|
+
validateWorkerGeometry();
|
|
24417
|
+
deliveryWindowMs = computeFecDeliveryWindowMs({
|
|
24418
|
+
interleaveDepthMs: opts.fecInterleaveMs,
|
|
24419
|
+
sourceSymbols: opts.fecK,
|
|
24420
|
+
repairSymbols: opts.fecRepairCount,
|
|
24421
|
+
symbolSize: opts.fecSymbolSize,
|
|
24422
|
+
mediaUnitsPerSecond: 1e3 / mediaUnitDurationMs,
|
|
24423
|
+
jitterBufferMs: opts.fecReorderToleranceMs
|
|
24424
|
+
});
|
|
24425
|
+
interleaveDepthBlocks = deriveFecRetentionBlocks(
|
|
24426
|
+
deliveryWindowMs,
|
|
24427
|
+
opts.fecInterleaveMs
|
|
24072
24428
|
);
|
|
24073
24429
|
}
|
|
24074
24430
|
const blockProfile = parseFecBlockProfile(
|
|
24075
24431
|
opts.fecBlockProfile,
|
|
24076
|
-
`[FEC] ${opts.trackName} fec.blockProfile`
|
|
24077
|
-
);
|
|
24078
|
-
const deliveryWindowMs = computeFecDeliveryWindowMs({
|
|
24079
|
-
interleaveDepthMs: opts.fecInterleaveMs,
|
|
24080
|
-
sourceSymbols: opts.fecK,
|
|
24081
|
-
repairSymbols: opts.fecRepairCount,
|
|
24082
|
-
symbolSize: opts.fecSymbolSize,
|
|
24083
|
-
mediaUnitsPerSecond: 1e3 / opts.mediaUnitDurationMs,
|
|
24084
|
-
jitterBufferMs: opts.fecReorderToleranceMs
|
|
24085
|
-
});
|
|
24086
|
-
const interleaveDepthBlocks = deriveFecRetentionBlocks(
|
|
24087
|
-
deliveryWindowMs,
|
|
24088
|
-
opts.fecInterleaveMs
|
|
24432
|
+
`[FEC] ${opts.trackName} fec.blockProfile`
|
|
24089
24433
|
);
|
|
24090
24434
|
const origin = typeof location !== "undefined" && location.origin ? location.origin : "http://localhost";
|
|
24091
24435
|
const workerUrl = new URL(opts.assetUrls.fecWorker, origin);
|
|
@@ -24140,9 +24484,10 @@ async function setupFecWorkerForTrack(opts) {
|
|
|
24140
24484
|
k: opts.fecK,
|
|
24141
24485
|
symbolSize: opts.fecSymbolSize,
|
|
24142
24486
|
interleaveDepthMs: opts.fecInterleaveMs,
|
|
24143
|
-
//
|
|
24144
|
-
//
|
|
24145
|
-
|
|
24487
|
+
// No-span tracks have no catalog media cadence. The manager still gets
|
|
24488
|
+
// the local cleanup budget, but it must not use a guessed fps for timing
|
|
24489
|
+
// or classification.
|
|
24490
|
+
...hasCatalogTiming ? { fps: 1e3 / opts.mediaUnitDurationMs } : {},
|
|
24146
24491
|
timeoutMs: deliveryWindowMs
|
|
24147
24492
|
}, opts.trackName);
|
|
24148
24493
|
} catch (e) {
|
|
@@ -24182,6 +24527,28 @@ var TrackSourceConflictError = class extends Error {
|
|
|
24182
24527
|
};
|
|
24183
24528
|
var DEFAULT_CROSSFADE_FRAMES = 10;
|
|
24184
24529
|
var DEFAULT_MAX_OVERLAY_BUFFER = 128;
|
|
24530
|
+
function assertDeltaFallbackConfig(cfg, prefix) {
|
|
24531
|
+
if (!isPositiveFinite2(cfg.framerate)) throw new Error(`${prefix}: framerate required and must be > 0`);
|
|
24532
|
+
if (!isPositiveFinite2(cfg.sourceSymbols))
|
|
24533
|
+
throw new Error(`${prefix}: sourceSymbols (K_delta) required and must be > 0`);
|
|
24534
|
+
if (!isPositiveFinite2(cfg.repairSymbols))
|
|
24535
|
+
throw new Error(`${prefix}: repairSymbols (P_delta) required and must be > 0`);
|
|
24536
|
+
if (cfg.deltaFailWindowBlocks !== void 0 && !(Number.isInteger(cfg.deltaFailWindowBlocks) && cfg.deltaFailWindowBlocks > 0)) {
|
|
24537
|
+
throw new Error(`${prefix}: deltaFailWindowBlocks must be a positive integer when present`);
|
|
24538
|
+
}
|
|
24539
|
+
if (cfg.deltaProbeIntervalMs !== void 0 && !isPositiveFinite2(cfg.deltaProbeIntervalMs))
|
|
24540
|
+
throw new Error(`${prefix}: deltaProbeIntervalMs must be > 0 when present`);
|
|
24541
|
+
if (cfg.interleaveDepthMs !== void 0 && !isPositiveFinite2(cfg.interleaveDepthMs))
|
|
24542
|
+
throw new Error(`${prefix}: interleaveDepthMs must be > 0 when present`);
|
|
24543
|
+
if (cfg.deltaProbeIntervalMs === void 0 && cfg.interleaveDepthMs === void 0) {
|
|
24544
|
+
throw new Error(
|
|
24545
|
+
`${prefix}: either deltaProbeIntervalMs or interleaveDepthMs required for probe interval derivation`
|
|
24546
|
+
);
|
|
24547
|
+
}
|
|
24548
|
+
}
|
|
24549
|
+
function isPositiveFinite2(value) {
|
|
24550
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
24551
|
+
}
|
|
24185
24552
|
var MediaRouter = class {
|
|
24186
24553
|
#active = "moq";
|
|
24187
24554
|
#multicastVideoKeyed = false;
|
|
@@ -24341,22 +24708,21 @@ var MediaRouter = class {
|
|
|
24341
24708
|
* CLAUDE.md: error on bad init — throws if required inputs are absent.
|
|
24342
24709
|
*
|
|
24343
24710
|
* Calling again resets deltaActive=true and clears the window buffer (stream restart).
|
|
24711
|
+
* Pass null on teardown or when a replacement catalog cannot configure this policy.
|
|
24344
24712
|
*/
|
|
24345
24713
|
configureDelta(cfg) {
|
|
24346
|
-
if (
|
|
24347
|
-
|
|
24348
|
-
|
|
24349
|
-
|
|
24350
|
-
|
|
24351
|
-
|
|
24352
|
-
|
|
24353
|
-
|
|
24354
|
-
|
|
24355
|
-
|
|
24356
|
-
throw new Error(
|
|
24357
|
-
"[MediaRouter] configureDelta: either deltaProbeIntervalMs or interleaveDepthMs required for probe interval derivation"
|
|
24358
|
-
);
|
|
24714
|
+
if (cfg === null) {
|
|
24715
|
+
this.#deltaConfigured = false;
|
|
24716
|
+
this.#deltaActive = true;
|
|
24717
|
+
this.#deltaWindow = [];
|
|
24718
|
+
this.#deltaWindowSize = 0;
|
|
24719
|
+
this.#deltaFailThresholdRatio = 0;
|
|
24720
|
+
this.#deltaRecoverThresholdRatio = 0;
|
|
24721
|
+
this.#deltaProbeIntervalMs = 0;
|
|
24722
|
+
this.#lastProbeAt = 0;
|
|
24723
|
+
return;
|
|
24359
24724
|
}
|
|
24725
|
+
assertDeltaFallbackConfig(cfg, "[MediaRouter] configureDelta");
|
|
24360
24726
|
this.#deltaWindowSize = cfg.deltaFailWindowBlocks ?? Math.ceil(cfg.framerate);
|
|
24361
24727
|
this.#deltaFailThresholdRatio = cfg.repairSymbols / cfg.sourceSymbols;
|
|
24362
24728
|
this.#deltaRecoverThresholdRatio = this.#deltaFailThresholdRatio / 2;
|
|
@@ -24692,6 +25058,7 @@ var MediaRouter = class {
|
|
|
24692
25058
|
/** Dispose timers and per-track sources. Call on teardown to prevent
|
|
24693
25059
|
* stale timer callbacks firing into garbage-collected closures (WR-03). */
|
|
24694
25060
|
dispose() {
|
|
25061
|
+
this.configureDelta(null);
|
|
24695
25062
|
if (this.#audioFallbackTimer) {
|
|
24696
25063
|
clearTimeout(this.#audioFallbackTimer);
|
|
24697
25064
|
this.#audioFallbackTimer = null;
|
|
@@ -24733,6 +25100,35 @@ var MediaRouter = class {
|
|
|
24733
25100
|
}
|
|
24734
25101
|
};
|
|
24735
25102
|
|
|
25103
|
+
// src/delta-fallback-config.ts
|
|
25104
|
+
function resolveDeltaFallback(base, delta) {
|
|
25105
|
+
const repair = base.fec?.repairTrack;
|
|
25106
|
+
if (!repair) throw new Error(`[delta-fallback] base track "${base.name}" missing fec.repairTrack`);
|
|
25107
|
+
const deltaFec = delta.fec;
|
|
25108
|
+
if (!deltaFec?.repairTrack) throw new Error(`[delta-fallback] delta track "${delta.name}" missing fec.repairTrack`);
|
|
25109
|
+
if (deltaFec.repairTrack !== repair)
|
|
25110
|
+
throw new Error(
|
|
25111
|
+
`[delta-fallback] inconsistent repair tracks: ${base.name} references "${repair}" but ${delta.name} references "${deltaFec.repairTrack}"`
|
|
25112
|
+
);
|
|
25113
|
+
if (!(typeof base.framerate === "number" && Number.isFinite(base.framerate) && base.framerate > 0))
|
|
25114
|
+
throw new Error(`[delta-fallback] base track "${base.name}" missing positive framerate`);
|
|
25115
|
+
if (!(isPositive(deltaFec.sourceSymbols) && isPositive(deltaFec.repairSymbols)))
|
|
25116
|
+
throw new Error(`[delta-fallback] delta track "${delta.name}" missing positive fec.sourceSymbols/repairSymbols`);
|
|
25117
|
+
const config = {
|
|
25118
|
+
framerate: base.framerate,
|
|
25119
|
+
sourceSymbols: deltaFec.sourceSymbols,
|
|
25120
|
+
repairSymbols: deltaFec.repairSymbols,
|
|
25121
|
+
deltaFailWindowBlocks: base.fec?.deltaFailWindowBlocks ?? deltaFec.deltaFailWindowBlocks,
|
|
25122
|
+
deltaProbeIntervalMs: base.fec?.deltaProbeIntervalMs ?? deltaFec.deltaProbeIntervalMs,
|
|
25123
|
+
interleaveDepthMs: base.fec?.interleaveDepthMs ?? deltaFec.interleaveDepthMs
|
|
25124
|
+
};
|
|
25125
|
+
assertDeltaFallbackConfig(config, `[delta-fallback] group ${base.name}/${delta.name}`);
|
|
25126
|
+
return { repairTrackName: repair, config };
|
|
25127
|
+
}
|
|
25128
|
+
function isPositive(value) {
|
|
25129
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
25130
|
+
}
|
|
25131
|
+
|
|
24736
25132
|
// src/align.ts
|
|
24737
25133
|
function secondsFromTicks(ticks, timescale) {
|
|
24738
25134
|
if (!Number.isFinite(ticks)) {
|
|
@@ -25625,400 +26021,131 @@ var CmcdProducer = class {
|
|
|
25625
26021
|
this.#onCmcdParams = options.onCmcdParams;
|
|
25626
26022
|
this.#onCmcdPost = options.onCmcdPost;
|
|
25627
26023
|
}
|
|
25628
|
-
/** Update the stored transport state. Called by player wiring layer. */
|
|
25629
|
-
updateTransport(snapshot) {
|
|
25630
|
-
this.#transport = snapshot;
|
|
25631
|
-
}
|
|
25632
|
-
/**
|
|
25633
|
-
* Set the current play-mode. Emitted as `x-bc-play-mode` on subsequent
|
|
25634
|
-
* snapshots. Throws on any value outside {'full','base-only','single-track'}
|
|
25635
|
-
* (CLAUDE.md: error on bad init).
|
|
25636
|
-
*/
|
|
25637
|
-
updatePlayMode(mode) {
|
|
25638
|
-
if (typeof mode !== "string" || !PLAY_MODES.includes(mode)) {
|
|
25639
|
-
throw new Error(
|
|
25640
|
-
`[CmcdProducer] invalid play-mode "${String(mode)}"; expected one of: ${PLAY_MODES.join(", ")}`
|
|
25641
|
-
);
|
|
25642
|
-
}
|
|
25643
|
-
this.#playMode = mode;
|
|
25644
|
-
}
|
|
25645
|
-
/**
|
|
25646
|
-
* Set the delta-track FEC block-failure rate (0..1 inclusive). Emitted as
|
|
25647
|
-
* `x-bc-delta-fail-rate` on snapshots when play-mode is 'full' or 'base-only'.
|
|
25648
|
-
* Omitted on single-track streams.
|
|
25649
|
-
*/
|
|
25650
|
-
updateDeltaFailRate(rate) {
|
|
25651
|
-
if (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0 || rate > 1) {
|
|
25652
|
-
throw new Error(
|
|
25653
|
-
`[CmcdProducer] invalid delta-fail-rate "${String(rate)}"; expected a finite number in [0, 1]`
|
|
25654
|
-
);
|
|
25655
|
-
}
|
|
25656
|
-
this.#deltaFailRate = rate;
|
|
25657
|
-
}
|
|
25658
|
-
/**
|
|
25659
|
-
* Bump the cumulative switching-set misalignment counter. Emitted as
|
|
25660
|
-
* `x-bc-switch-miscount` on snapshots when the counter is > 0.
|
|
25661
|
-
*/
|
|
25662
|
-
incrementSwitchMiscount() {
|
|
25663
|
-
this.#switchMiscount += 1;
|
|
25664
|
-
}
|
|
25665
|
-
updateManifestVerification(stats) {
|
|
25666
|
-
this.#manifestStats = stats;
|
|
25667
|
-
}
|
|
25668
|
-
/**
|
|
25669
|
-
* One-shot emit of `x-bc-switch-drift-us` for a single misalignment event.
|
|
25670
|
-
* Does NOT mutate snapshot state — subsequent onSnapshot() calls will not
|
|
25671
|
-
* include the drift key until the next emitSwitchDrift(). Mirrors the
|
|
25672
|
-
* existing onTransition() pattern.
|
|
25673
|
-
*/
|
|
25674
|
-
emitSwitchDrift(driftUs) {
|
|
25675
|
-
if (typeof driftUs !== "number" || !Number.isFinite(driftUs) || driftUs < 0) {
|
|
25676
|
-
throw new Error(
|
|
25677
|
-
`[CmcdProducer] invalid drift-us "${String(driftUs)}"; expected a non-negative finite number`
|
|
25678
|
-
);
|
|
25679
|
-
}
|
|
25680
|
-
const params = {};
|
|
25681
|
-
if (this.#lastStats) Object.assign(params, buildFecParams(this.#lastStats));
|
|
25682
|
-
Object.assign(params, buildTransportParams(this.#transport));
|
|
25683
|
-
if (this.#switchMiscount > 0) params["x-bc-switch-miscount"] = this.#switchMiscount;
|
|
25684
|
-
params["x-bc-switch-drift-us"] = driftUs;
|
|
25685
|
-
this.#emit(params);
|
|
25686
|
-
}
|
|
25687
|
-
/**
|
|
25688
|
-
* Process a FecTrackStats snapshot. Finds the video track, builds
|
|
25689
|
-
* x-bc-* vendor keys, and fires both delivery callbacks.
|
|
25690
|
-
*/
|
|
25691
|
-
onSnapshot(data) {
|
|
25692
|
-
const videoTrack = data.tracks.find((t) => t.config.trackType === "video");
|
|
25693
|
-
if (!videoTrack) return;
|
|
25694
|
-
this.#lastStats = videoTrack;
|
|
25695
|
-
const params = {
|
|
25696
|
-
...buildFecParams(videoTrack),
|
|
25697
|
-
...buildTransportParams(this.#transport)
|
|
25698
|
-
};
|
|
25699
|
-
if (this.#playMode !== null) {
|
|
25700
|
-
params["x-bc-play-mode"] = this.#playMode;
|
|
25701
|
-
}
|
|
25702
|
-
if (this.#playMode !== null && this.#playMode !== "single-track" && this.#deltaFailRate !== null) {
|
|
25703
|
-
params["x-bc-delta-fail-rate"] = parseFloat(this.#deltaFailRate.toFixed(4));
|
|
25704
|
-
}
|
|
25705
|
-
if (this.#switchMiscount > 0) {
|
|
25706
|
-
params["x-bc-switch-miscount"] = this.#switchMiscount;
|
|
25707
|
-
}
|
|
25708
|
-
if (this.#manifestStats) {
|
|
25709
|
-
params["x-bc-manifest-verified"] = this.#manifestStats.verified > 0;
|
|
25710
|
-
params["x-bc-manifest-failures"] = Object.values(this.#manifestStats.failures).reduce((total, count) => total + count, 0);
|
|
25711
|
-
}
|
|
25712
|
-
this.#emit(params);
|
|
25713
|
-
}
|
|
25714
|
-
/**
|
|
25715
|
-
* Emit transport state without inventing FEC metrics. Cadence is owned by
|
|
25716
|
-
* the caller because only the transport integration knows when state changed.
|
|
25717
|
-
*/
|
|
25718
|
-
onTransportSnapshot(data) {
|
|
25719
|
-
if (!Number.isFinite(data.timestampMs) || data.timestampMs < 0) {
|
|
25720
|
-
throw new Error("[CmcdProducer] transport snapshot timestamp must be a non-negative finite number");
|
|
25721
|
-
}
|
|
25722
|
-
if (data.playMode !== void 0) this.updatePlayMode(data.playMode);
|
|
25723
|
-
if (data.switchMiscount !== void 0 && (!Number.isSafeInteger(data.switchMiscount) || data.switchMiscount < 0)) {
|
|
25724
|
-
throw new Error("[CmcdProducer] transport snapshot switch-miscount must be a non-negative safe integer");
|
|
25725
|
-
}
|
|
25726
|
-
this.#transport = data.transport;
|
|
25727
|
-
const params = buildTransportParams(data.transport);
|
|
25728
|
-
if (this.#playMode !== null) params["x-bc-play-mode"] = this.#playMode;
|
|
25729
|
-
const switchMiscount = data.switchMiscount ?? this.#switchMiscount;
|
|
25730
|
-
if (switchMiscount > 0) params["x-bc-switch-miscount"] = switchMiscount;
|
|
25731
|
-
this.#emit(params);
|
|
25732
|
-
}
|
|
25733
|
-
/**
|
|
25734
|
-
* Signal a transport transition event. Fires an immediate out-of-band
|
|
25735
|
-
* emit with x-bc-ct set. The subsequent onSnapshot() will NOT include
|
|
25736
|
-
* x-bc-ct (one-shot).
|
|
25737
|
-
*/
|
|
25738
|
-
onTransition(event) {
|
|
25739
|
-
const params = {};
|
|
25740
|
-
if (this.#lastStats) {
|
|
25741
|
-
Object.assign(params, buildFecParams(this.#lastStats));
|
|
25742
|
-
}
|
|
25743
|
-
Object.assign(params, buildTransportParams(this.#transport));
|
|
25744
|
-
params["x-bc-ct"] = event;
|
|
25745
|
-
this.#emit(params);
|
|
25746
|
-
}
|
|
25747
|
-
/** Fire both delivery callbacks with the given params. */
|
|
25748
|
-
#emit(params) {
|
|
25749
|
-
this.#onCmcdParams?.(params);
|
|
25750
|
-
this.#onCmcdPost?.(formatQueryString(params));
|
|
25751
|
-
}
|
|
25752
|
-
};
|
|
25753
|
-
|
|
25754
|
-
// src/catalog-fec-params.ts
|
|
25755
|
-
var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
|
|
25756
|
-
function timescaleHz(value) {
|
|
25757
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
25758
|
-
}
|
|
25759
|
-
function catalogGroupDurationMs(rendition) {
|
|
25760
|
-
if (!rendition) return void 0;
|
|
25761
|
-
const ticks = numericField(rendition.groupDurationTicks);
|
|
25762
|
-
const ms = numericField(rendition.groupDurationMs);
|
|
25763
|
-
const timescale = timescaleHz(rendition.container?.kind === "mmtp" ? rendition.container.timescale : void 0) ?? timescaleHz(rendition.timescale);
|
|
25764
|
-
if (ticks !== void 0 && timescale !== void 0 && Number.isSafeInteger(ticks) && ticks <= timescale * MAX_SAFE_TICKS_PER_HZ) {
|
|
25765
|
-
return resolveGroupDurationMs({ groupDurationTicks: ticks }, timescale);
|
|
25766
|
-
}
|
|
25767
|
-
return ms;
|
|
25768
|
-
}
|
|
25769
|
-
function resolveCatalogTrackGroupDurationMs(catalog, track) {
|
|
25770
|
-
const renditions = track.type === "audio" ? catalog?.audio?.renditions : catalog?.video?.renditions;
|
|
25771
|
-
return catalogGroupDurationMs(renditions?.[track.name]);
|
|
25772
|
-
}
|
|
25773
|
-
var NO_FEC = {
|
|
25774
|
-
fecK: 0,
|
|
25775
|
-
fecRepairCount: 0,
|
|
25776
|
-
fecTotalRepairCount: 0,
|
|
25777
|
-
fecSymbolSize: 0,
|
|
25778
|
-
fecInterleaveMs: 0
|
|
25779
|
-
};
|
|
25780
|
-
function joinedRepairSymbolCount(params, joinedRepairTrackNames) {
|
|
25781
|
-
const layers = params.repairLayers;
|
|
25782
|
-
if (layers === void 0 || layers.length === 0) return params.fecRepairCount;
|
|
25783
|
-
const joined = new Set(joinedRepairTrackNames);
|
|
25784
|
-
let total = 0;
|
|
25785
|
-
for (const layer of layers) {
|
|
25786
|
-
if (joined.has(layer.repairTrackName)) total += layer.repairSymbols;
|
|
25787
|
-
}
|
|
25788
|
-
return total;
|
|
25789
|
-
}
|
|
25790
|
-
function recordOf2(value) {
|
|
25791
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
25792
|
-
}
|
|
25793
|
-
function msfTracks(catalog) {
|
|
25794
|
-
const raw = catalog.tracks;
|
|
25795
|
-
if (!Array.isArray(raw)) return void 0;
|
|
25796
|
-
return { tracks: raw.map((t) => recordOf2(t) ?? {}) };
|
|
25797
|
-
}
|
|
25798
|
-
function resolveRepairLayers(catalog, source) {
|
|
25799
|
-
if (!catalog) return void 0;
|
|
25800
|
-
const sourceName = typeof source === "string" ? source : source.name;
|
|
25801
|
-
const hang = catalog;
|
|
25802
|
-
const roles = typeof source === "string" ? ["video", "audio"] : source.type === "audio" ? ["audio"] : source.type === "video" ? ["video"] : ["video", "audio"];
|
|
25803
|
-
const role = roles.find((r) => recordOf2(hang[r]?.renditions?.[sourceName]) !== void 0);
|
|
25804
|
-
if (role !== void 0) {
|
|
25805
|
-
const renditionPath = `$.${role}.renditions[${JSON.stringify(sourceName)}]`;
|
|
25806
|
-
const rendition = recordOf2(hang[role].renditions[sourceName]);
|
|
25807
|
-
rejectRepairFieldsOnMediaTrack(renditionPath, rendition, throwRepairContractError);
|
|
25808
|
-
const repairRenditions = recordOf2(hang.repair?.renditions) ?? {};
|
|
25809
|
-
const descriptors2 = Object.entries(repairRenditions).map(([name, entry]) => {
|
|
25810
|
-
const path = `$.repair.renditions[${JSON.stringify(name)}]`;
|
|
25811
|
-
const fields = recordOf2(entry);
|
|
25812
|
-
if (fields === void 0) {
|
|
25813
|
-
throwRepairContractError("track_repair_layer", path, "a repair rendition must be an object");
|
|
25814
|
-
}
|
|
25815
|
-
rejectRepairTrackGeometry(path, fields, throwRepairContractError);
|
|
25816
|
-
return describeRepairTrack(name, path, fields);
|
|
25817
|
-
});
|
|
25818
|
-
const fecOfRendition = (entry) => {
|
|
25819
|
-
const fec2 = recordOf2(recordOf2(entry)?.["fec"]);
|
|
25820
|
-
return fec2 !== void 0 && fec2["algorithm"] !== "none" ? fec2 : void 0;
|
|
25821
|
-
};
|
|
25822
|
-
const mediaRenditions = [
|
|
25823
|
-
...Object.values(recordOf2(hang.video?.renditions) ?? {}),
|
|
25824
|
-
...Object.values(recordOf2(hang.audio?.renditions) ?? {})
|
|
25825
|
-
];
|
|
25826
|
-
const reachability2 = repairReachability(mediaRenditions.map((entry) => recordOf2(entry)?.["fec"]));
|
|
25827
|
-
for (const d of descriptors2) {
|
|
25828
|
-
validateRepairTrackFields(d, throwRepairContractError);
|
|
25829
|
-
assertRepairTrackReachable(d, reachability2, throwRepairContractError);
|
|
25830
|
-
const target = dependsTarget(d);
|
|
25831
|
-
if (target === void 0) continue;
|
|
25832
|
-
const targetFec = fecOfRendition(hang.video?.renditions?.[target]) ?? fecOfRendition(hang.audio?.renditions?.[target]);
|
|
25833
|
-
if (targetFec === void 0) {
|
|
25834
|
-
throwRepairContractError(
|
|
25835
|
-
"track_repair_depends",
|
|
25836
|
-
`${d.path}.depends`,
|
|
25837
|
-
`${JSON.stringify(target)} is not a video or audio rendition with FEC`
|
|
25838
|
-
);
|
|
25839
|
-
}
|
|
25840
|
-
}
|
|
25841
|
-
return assembleRepairLayers(
|
|
25842
|
-
geometryOf(sourceName, `${renditionPath}.fec`, recordOf2(rendition["fec"])),
|
|
25843
|
-
descriptors2,
|
|
25844
|
-
throwRepairContractError
|
|
25845
|
-
);
|
|
25846
|
-
}
|
|
25847
|
-
const msf = msfTracks(catalog);
|
|
25848
|
-
const msfIndex = msf ? msf.tracks.findIndex((t) => t["name"] === sourceName) : -1;
|
|
25849
|
-
if (!msf || msfIndex < 0) return void 0;
|
|
25850
|
-
const track = msf.tracks[msfIndex];
|
|
25851
|
-
const fec = recordOf2(track["fec"]);
|
|
25852
|
-
const packagingOf = (t) => t["packaging"];
|
|
25853
|
-
const fecOfTrack = (t) => {
|
|
25854
|
-
const trackFec = recordOf2(t?.["fec"]);
|
|
25855
|
-
return trackFec !== void 0 && trackFec["algorithm"] !== "none" ? trackFec : void 0;
|
|
25856
|
-
};
|
|
25857
|
-
const descriptors = [];
|
|
25858
|
-
const mediaTracks = [];
|
|
25859
|
-
msf.tracks.forEach((t, i) => {
|
|
25860
|
-
const path = `$.tracks[${i}]`;
|
|
25861
|
-
if (packagingOf(t) === "fec-repair") {
|
|
25862
|
-
rejectRepairTrackGeometry(path, t, throwRepairContractError);
|
|
25863
|
-
if (typeof t["name"] === "string") descriptors.push(describeRepairTrack(t["name"], path, t));
|
|
25864
|
-
return;
|
|
26024
|
+
/** Update the stored transport state. Called by player wiring layer. */
|
|
26025
|
+
updateTransport(snapshot) {
|
|
26026
|
+
this.#transport = snapshot;
|
|
26027
|
+
}
|
|
26028
|
+
/**
|
|
26029
|
+
* Set the current play-mode. Emitted as `x-bc-play-mode` on subsequent
|
|
26030
|
+
* snapshots. Throws on any value outside {'full','base-only','single-track'}
|
|
26031
|
+
* (CLAUDE.md: error on bad init).
|
|
26032
|
+
*/
|
|
26033
|
+
updatePlayMode(mode) {
|
|
26034
|
+
if (typeof mode !== "string" || !PLAY_MODES.includes(mode)) {
|
|
26035
|
+
throw new Error(
|
|
26036
|
+
`[CmcdProducer] invalid play-mode "${String(mode)}"; expected one of: ${PLAY_MODES.join(", ")}`
|
|
26037
|
+
);
|
|
25865
26038
|
}
|
|
25866
|
-
|
|
25867
|
-
|
|
25868
|
-
|
|
25869
|
-
|
|
25870
|
-
|
|
25871
|
-
|
|
25872
|
-
|
|
25873
|
-
|
|
25874
|
-
if (
|
|
25875
|
-
|
|
25876
|
-
|
|
25877
|
-
throwRepairContractError(
|
|
25878
|
-
"track_repair_depends",
|
|
25879
|
-
`${d.path}.depends`,
|
|
25880
|
-
`${JSON.stringify(target)} is not an mmtp track with FEC`
|
|
26039
|
+
this.#playMode = mode;
|
|
26040
|
+
}
|
|
26041
|
+
/**
|
|
26042
|
+
* Set the delta-track FEC block-failure rate (0..1 inclusive). Emitted as
|
|
26043
|
+
* `x-bc-delta-fail-rate` on snapshots when play-mode is 'full' or 'base-only'.
|
|
26044
|
+
* Omitted on single-track streams.
|
|
26045
|
+
*/
|
|
26046
|
+
updateDeltaFailRate(rate) {
|
|
26047
|
+
if (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0 || rate > 1) {
|
|
26048
|
+
throw new Error(
|
|
26049
|
+
`[CmcdProducer] invalid delta-fail-rate "${String(rate)}"; expected a finite number in [0, 1]`
|
|
25881
26050
|
);
|
|
25882
26051
|
}
|
|
26052
|
+
this.#deltaFailRate = rate;
|
|
25883
26053
|
}
|
|
25884
|
-
|
|
25885
|
-
|
|
25886
|
-
|
|
25887
|
-
|
|
25888
|
-
|
|
25889
|
-
|
|
25890
|
-
}
|
|
25891
|
-
function catalogRepairTarget(catalog, source) {
|
|
25892
|
-
const repair = resolveRepairLayers(catalog, source);
|
|
25893
|
-
const layerZero = repair?.baseLayers[0];
|
|
25894
|
-
if (!catalog || !layerZero) return null;
|
|
25895
|
-
const sourceName = typeof source === "string" ? source : source.name;
|
|
25896
|
-
const msf = msfTracks(catalog);
|
|
25897
|
-
const entry = msf?.tracks.find((t) => t["name"] === sourceName);
|
|
25898
|
-
const sourcePriority = entry === void 0 ? null : moqtPriority2(entry["priority"]);
|
|
25899
|
-
return { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };
|
|
25900
|
-
}
|
|
25901
|
-
function geometryOf(name, path, fec) {
|
|
25902
|
-
return {
|
|
25903
|
-
name,
|
|
25904
|
-
path,
|
|
25905
|
-
algorithm: fec?.["algorithm"],
|
|
25906
|
-
sourceSymbols: fec?.["sourceSymbols"],
|
|
25907
|
-
repairSymbols: fec?.["repairSymbols"],
|
|
25908
|
-
repairTrack: fec?.["repairTrack"]
|
|
25909
|
-
};
|
|
25910
|
-
}
|
|
25911
|
-
function numericField(value) {
|
|
25912
|
-
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
25913
|
-
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
25914
|
-
}
|
|
25915
|
-
function nonNegativeField(value) {
|
|
25916
|
-
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
25917
|
-
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
25918
|
-
}
|
|
25919
|
-
function blockProfileUnavailable(value, path, declaresFecInstance) {
|
|
25920
|
-
try {
|
|
25921
|
-
readFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);
|
|
25922
|
-
return void 0;
|
|
25923
|
-
} catch (error) {
|
|
25924
|
-
if (!(error instanceof RepairContractError) || error.code !== "track_fec_block_profile") throw error;
|
|
25925
|
-
return Object.freeze({ code: "track_fec_block_profile", path: error.path, message: error.message });
|
|
26054
|
+
/**
|
|
26055
|
+
* Bump the cumulative switching-set misalignment counter. Emitted as
|
|
26056
|
+
* `x-bc-switch-miscount` on snapshots when the counter is > 0.
|
|
26057
|
+
*/
|
|
26058
|
+
incrementSwitchMiscount() {
|
|
26059
|
+
this.#switchMiscount += 1;
|
|
25926
26060
|
}
|
|
25927
|
-
|
|
25928
|
-
|
|
25929
|
-
|
|
25930
|
-
|
|
25931
|
-
|
|
25932
|
-
|
|
25933
|
-
|
|
25934
|
-
|
|
25935
|
-
|
|
25936
|
-
|
|
25937
|
-
|
|
25938
|
-
|
|
25939
|
-
|
|
25940
|
-
const repair = resolveRepairLayers(catalog, track);
|
|
25941
|
-
const fecPath = `$.${track.type}.renditions[${JSON.stringify(track.name)}].fec`;
|
|
25942
|
-
if (!catalogDeclaresFec(fec)) {
|
|
25943
|
-
const declaresFecInstance = fec.algorithm !== "none";
|
|
25944
|
-
const kValid = Number.isSafeInteger(fec.sourceSymbols) && (fec.sourceSymbols ?? 0) > 0;
|
|
25945
|
-
const tValid = Number.isSafeInteger(fec.symbolSize) && (fec.symbolSize ?? 0) > 0;
|
|
25946
|
-
if (declaresFecInstance && !(kValid && tValid)) {
|
|
25947
|
-
throwRepairContractError(
|
|
25948
|
-
"track_fec",
|
|
25949
|
-
`${fecPath}.${kValid ? "symbolSize" : "sourceSymbols"}`,
|
|
25950
|
-
"must be a positive integer"
|
|
26061
|
+
updateManifestVerification(stats) {
|
|
26062
|
+
this.#manifestStats = stats;
|
|
26063
|
+
}
|
|
26064
|
+
/**
|
|
26065
|
+
* One-shot emit of `x-bc-switch-drift-us` for a single misalignment event.
|
|
26066
|
+
* Does NOT mutate snapshot state — subsequent onSnapshot() calls will not
|
|
26067
|
+
* include the drift key until the next emitSwitchDrift(). Mirrors the
|
|
26068
|
+
* existing onTransition() pattern.
|
|
26069
|
+
*/
|
|
26070
|
+
emitSwitchDrift(driftUs) {
|
|
26071
|
+
if (typeof driftUs !== "number" || !Number.isFinite(driftUs) || driftUs < 0) {
|
|
26072
|
+
throw new Error(
|
|
26073
|
+
`[CmcdProducer] invalid drift-us "${String(driftUs)}"; expected a non-negative finite number`
|
|
25951
26074
|
);
|
|
25952
26075
|
}
|
|
25953
|
-
const
|
|
25954
|
-
|
|
26076
|
+
const params = {};
|
|
26077
|
+
if (this.#lastStats) Object.assign(params, buildFecParams(this.#lastStats));
|
|
26078
|
+
Object.assign(params, buildTransportParams(this.#transport));
|
|
26079
|
+
if (this.#switchMiscount > 0) params["x-bc-switch-miscount"] = this.#switchMiscount;
|
|
26080
|
+
params["x-bc-switch-drift-us"] = driftUs;
|
|
26081
|
+
this.#emit(params);
|
|
25955
26082
|
}
|
|
25956
|
-
|
|
25957
|
-
|
|
25958
|
-
|
|
25959
|
-
|
|
25960
|
-
|
|
25961
|
-
|
|
25962
|
-
|
|
25963
|
-
|
|
25964
|
-
|
|
25965
|
-
|
|
25966
|
-
|
|
25967
|
-
|
|
25968
|
-
|
|
25969
|
-
|
|
25970
|
-
// hands one network different slack per track. `numericField` rejects 0,
|
|
25971
|
-
// so it cannot be reused here — a declared 0 is a legitimate value.
|
|
25972
|
-
fecReorderToleranceMs: nonNegativeField(fec.reorderToleranceMs),
|
|
25973
|
-
repairTrackName: fec.repairTrack,
|
|
25974
|
-
repairLayers: repair?.baseLayers,
|
|
25975
|
-
repairOverlays: repair?.overlays,
|
|
25976
|
-
...mediaRate
|
|
25977
|
-
};
|
|
25978
|
-
}
|
|
25979
|
-
function resolveCatalogTrackFec(catalog, tracks) {
|
|
25980
|
-
const videoTrackName = tracks.find((t) => t.type === "video")?.name;
|
|
25981
|
-
const audioTrackName = tracks.find((t) => t.type === "audio")?.name;
|
|
25982
|
-
const videoRenditions = catalog?.video?.renditions;
|
|
25983
|
-
const audioRenditions = catalog?.audio?.renditions;
|
|
25984
|
-
const video = videoTrackName ? videoRenditions?.[videoTrackName] : void 0;
|
|
25985
|
-
const audio = audioTrackName ? audioRenditions?.[audioTrackName] : void 0;
|
|
25986
|
-
const catalogFps = numericField(video?.framerate);
|
|
25987
|
-
const sampleRate = numericField(audio?.sampleRate ?? audio?.samplerate);
|
|
25988
|
-
const audioFps = sampleRate ? sampleRate / 1024 : void 0;
|
|
25989
|
-
const videoParams = fecParams(catalog, videoTrackName ? { name: videoTrackName, type: "video" } : void 0, video, catalogFps);
|
|
25990
|
-
const audioParams = fecParams(
|
|
25991
|
-
catalog,
|
|
25992
|
-
audioTrackName ? { name: audioTrackName, type: "audio" } : void 0,
|
|
25993
|
-
audio,
|
|
25994
|
-
audioFps,
|
|
25995
|
-
sampleRate
|
|
25996
|
-
);
|
|
25997
|
-
const forTrack = (track) => {
|
|
25998
|
-
if (track.type === "audio") {
|
|
25999
|
-
if (track.name === audioTrackName) return audioParams;
|
|
26000
|
-
const rendition2 = audioRenditions?.[track.name];
|
|
26001
|
-
if (!rendition2) return { ...NO_FEC };
|
|
26002
|
-
const rate = numericField(rendition2.sampleRate ?? rendition2.samplerate);
|
|
26003
|
-
return fecParams(catalog, { name: track.name, type: "audio" }, rendition2, rate ? rate / 1024 : void 0, rate);
|
|
26083
|
+
/**
|
|
26084
|
+
* Process a FecTrackStats snapshot. Finds the video track, builds
|
|
26085
|
+
* x-bc-* vendor keys, and fires both delivery callbacks.
|
|
26086
|
+
*/
|
|
26087
|
+
onSnapshot(data) {
|
|
26088
|
+
const videoTrack = data.tracks.find((t) => t.config.trackType === "video");
|
|
26089
|
+
if (!videoTrack) return;
|
|
26090
|
+
this.#lastStats = videoTrack;
|
|
26091
|
+
const params = {
|
|
26092
|
+
...buildFecParams(videoTrack),
|
|
26093
|
+
...buildTransportParams(this.#transport)
|
|
26094
|
+
};
|
|
26095
|
+
if (this.#playMode !== null) {
|
|
26096
|
+
params["x-bc-play-mode"] = this.#playMode;
|
|
26004
26097
|
}
|
|
26005
|
-
if (track
|
|
26006
|
-
|
|
26007
|
-
|
|
26008
|
-
|
|
26009
|
-
|
|
26010
|
-
|
|
26011
|
-
|
|
26012
|
-
|
|
26013
|
-
|
|
26014
|
-
|
|
26015
|
-
|
|
26016
|
-
|
|
26017
|
-
|
|
26018
|
-
|
|
26019
|
-
|
|
26020
|
-
|
|
26021
|
-
|
|
26098
|
+
if (this.#playMode !== null && this.#playMode !== "single-track" && this.#deltaFailRate !== null) {
|
|
26099
|
+
params["x-bc-delta-fail-rate"] = parseFloat(this.#deltaFailRate.toFixed(4));
|
|
26100
|
+
}
|
|
26101
|
+
if (this.#switchMiscount > 0) {
|
|
26102
|
+
params["x-bc-switch-miscount"] = this.#switchMiscount;
|
|
26103
|
+
}
|
|
26104
|
+
if (this.#manifestStats) {
|
|
26105
|
+
params["x-bc-manifest-verified"] = this.#manifestStats.verified > 0;
|
|
26106
|
+
params["x-bc-manifest-failures"] = Object.values(this.#manifestStats.failures).reduce((total, count) => total + count, 0);
|
|
26107
|
+
}
|
|
26108
|
+
this.#emit(params);
|
|
26109
|
+
}
|
|
26110
|
+
/**
|
|
26111
|
+
* Emit transport state without inventing FEC metrics. Cadence is owned by
|
|
26112
|
+
* the caller because only the transport integration knows when state changed.
|
|
26113
|
+
*/
|
|
26114
|
+
onTransportSnapshot(data) {
|
|
26115
|
+
if (!Number.isFinite(data.timestampMs) || data.timestampMs < 0) {
|
|
26116
|
+
throw new Error("[CmcdProducer] transport snapshot timestamp must be a non-negative finite number");
|
|
26117
|
+
}
|
|
26118
|
+
if (data.playMode !== void 0) this.updatePlayMode(data.playMode);
|
|
26119
|
+
if (data.switchMiscount !== void 0 && (!Number.isSafeInteger(data.switchMiscount) || data.switchMiscount < 0)) {
|
|
26120
|
+
throw new Error("[CmcdProducer] transport snapshot switch-miscount must be a non-negative safe integer");
|
|
26121
|
+
}
|
|
26122
|
+
this.#transport = data.transport;
|
|
26123
|
+
const params = buildTransportParams(data.transport);
|
|
26124
|
+
if (this.#playMode !== null) params["x-bc-play-mode"] = this.#playMode;
|
|
26125
|
+
const switchMiscount = data.switchMiscount ?? this.#switchMiscount;
|
|
26126
|
+
if (switchMiscount > 0) params["x-bc-switch-miscount"] = switchMiscount;
|
|
26127
|
+
this.#emit(params);
|
|
26128
|
+
}
|
|
26129
|
+
/**
|
|
26130
|
+
* Signal a transport transition event. Fires an immediate out-of-band
|
|
26131
|
+
* emit with x-bc-ct set. The subsequent onSnapshot() will NOT include
|
|
26132
|
+
* x-bc-ct (one-shot).
|
|
26133
|
+
*/
|
|
26134
|
+
onTransition(event) {
|
|
26135
|
+
const params = {};
|
|
26136
|
+
if (this.#lastStats) {
|
|
26137
|
+
Object.assign(params, buildFecParams(this.#lastStats));
|
|
26138
|
+
}
|
|
26139
|
+
Object.assign(params, buildTransportParams(this.#transport));
|
|
26140
|
+
params["x-bc-ct"] = event;
|
|
26141
|
+
this.#emit(params);
|
|
26142
|
+
}
|
|
26143
|
+
/** Fire both delivery callbacks with the given params. */
|
|
26144
|
+
#emit(params) {
|
|
26145
|
+
this.#onCmcdParams?.(params);
|
|
26146
|
+
this.#onCmcdPost?.(formatQueryString(params));
|
|
26147
|
+
}
|
|
26148
|
+
};
|
|
26022
26149
|
|
|
26023
26150
|
// src/repair-fetch.ts
|
|
26024
26151
|
var MOQT_PRIORITY_MAX = 255;
|
|
@@ -26201,7 +26328,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
26201
26328
|
if (profile === void 0) {
|
|
26202
26329
|
throw new Error(`[RepairFetch] packetId=${packetId} has no configured fec.blockProfile`);
|
|
26203
26330
|
}
|
|
26204
|
-
return profile;
|
|
26331
|
+
return parseFecBlockProfile(profile, `[RepairFetch] packetId=${packetId} fec.blockProfile`);
|
|
26205
26332
|
};
|
|
26206
26333
|
const execute = async (entry, run) => {
|
|
26207
26334
|
let fed = 0;
|
|
@@ -26268,7 +26395,9 @@ function wireRepairFetch(mc, opts) {
|
|
|
26268
26395
|
dispatch();
|
|
26269
26396
|
});
|
|
26270
26397
|
const presentationDeadlineAt = (sbn, packetId, catalogTrackName, now) => {
|
|
26271
|
-
const
|
|
26398
|
+
const waitMs = mc.fecManager.repairFetchTimeoutMsForBlock(sbn, packetId, catalogTrackName);
|
|
26399
|
+
if (waitMs === void 0) return null;
|
|
26400
|
+
const waitDeadlineAt = now + waitMs;
|
|
26272
26401
|
const anchor = mc.fecManager.blockAnchorAtMs(sbn, packetId, catalogTrackName);
|
|
26273
26402
|
if (anchor === void 0) return waitDeadlineAt;
|
|
26274
26403
|
let delayMs;
|
|
@@ -26278,7 +26407,9 @@ function wireRepairFetch(mc, opts) {
|
|
|
26278
26407
|
return null;
|
|
26279
26408
|
}
|
|
26280
26409
|
if (!(Number.isFinite(delayMs) && delayMs > 0)) return null;
|
|
26281
|
-
const
|
|
26410
|
+
const retentionMs = mc.fecManager.terminalRetentionMsForPacket(packetId);
|
|
26411
|
+
if (retentionMs === void 0) return null;
|
|
26412
|
+
const ceilingAt = anchor + retentionMs;
|
|
26282
26413
|
return Math.max(waitDeadlineAt, Math.min(anchor + delayMs, ceilingAt));
|
|
26283
26414
|
};
|
|
26284
26415
|
mc.onRepairSubscribe((sbn, packetId, catalogTrackName, blockClass) => {
|
|
@@ -26427,7 +26558,10 @@ export {
|
|
|
26427
26558
|
catalogAmtNetworkSource,
|
|
26428
26559
|
catalogDeclaresFec,
|
|
26429
26560
|
catalogFecInterleaveMs,
|
|
26561
|
+
catalogFecMediaUnitsPerSecond,
|
|
26562
|
+
catalogFecUnavailable,
|
|
26430
26563
|
catalogGroupDurationMs,
|
|
26564
|
+
catalogKeyframeCadenceMs,
|
|
26431
26565
|
catalogRepairTarget,
|
|
26432
26566
|
classifyH264Keyframe,
|
|
26433
26567
|
classifyHevcKeyframe,
|
|
@@ -26483,6 +26617,7 @@ export {
|
|
|
26483
26617
|
isCompleteFmp4DataUnit,
|
|
26484
26618
|
isFecBlockProfile,
|
|
26485
26619
|
isFecCatalogConfig,
|
|
26620
|
+
isFecCatalogGeometry,
|
|
26486
26621
|
isFmp4InitSegment,
|
|
26487
26622
|
isHevcCodec,
|
|
26488
26623
|
isHevcIrapNalUnitType,
|
|
@@ -26524,6 +26659,7 @@ export {
|
|
|
26524
26659
|
repairReachability,
|
|
26525
26660
|
resolveCatalogTrackFec,
|
|
26526
26661
|
resolveCatalogTrackGroupDurationMs,
|
|
26662
|
+
resolveDeltaFallback,
|
|
26527
26663
|
resolveFecState,
|
|
26528
26664
|
resolveGlobalMmtWasmBindings,
|
|
26529
26665
|
resolveGroupDurationMs,
|