@blockcast/mmt-container 0.3.0 → 0.4.0-main.1e94b11199ea
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 +33 -16
- package/dist/browser-runtime.js +14816 -14254
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.d.ts +24 -19
- package/dist/carp-catalog.d.ts.map +1 -1
- package/dist/carp-catalog.js +32 -14
- package/dist/carp-catalog.js.map +4 -4
- 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 +60 -23
- package/dist/catalog-fec-params.js.map +4 -4
- package/dist/catalog-network-source.js +1 -7
- package/dist/catalog-network-source.js.map +4 -4
- 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/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js +0 -4
- package/dist/cmcd-producer.js.map +2 -2
- package/dist/delivery-catalog.d.ts +124 -0
- package/dist/delivery-catalog.d.ts.map +1 -0
- package/dist/delivery-catalog.js +1857 -0
- package/dist/delivery-catalog.js.map +7 -0
- 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 +52 -30
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +338 -293
- package/dist/fec-manager.js.map +4 -4
- package/dist/fec-state.d.ts +19 -31
- package/dist/fec-state.d.ts.map +1 -1
- package/dist/fec-state.js +4 -10
- package/dist/fec-state.js.map +4 -4
- package/dist/fec-worker-setup.d.ts +18 -14
- package/dist/fec-worker-setup.d.ts.map +1 -1
- package/dist/fec-worker-setup.js +81 -96
- package/dist/fec-worker-setup.js.map +4 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.js +1 -7
- package/dist/iso-block-locator.js.map +4 -4
- 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 +24 -30
- package/dist/mfu-reassembler.js.map +4 -4
- package/dist/mmtp-container-parser.d.ts +14 -11
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +126 -99
- package/dist/mmtp-container-parser.js.map +4 -4
- package/dist/mmtp-container-parser.no-modules.js +154 -108
- package/dist/mmtp-container-parser.no-modules.js.map +4 -4
- package/dist/mmtp-decode-pipeline.js +126 -99
- package/dist/mmtp-decode-pipeline.js.map +4 -4
- package/dist/mmtp-router.d.ts +51 -29
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +260 -326
- package/dist/mmtp-router.js.map +4 -4
- package/dist/multicast-input.js +1 -7
- package/dist/multicast-input.js.map +4 -4
- package/dist/multicast-manager.d.ts +72 -6
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +882 -673
- 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 +44 -26
- package/dist/multicast-transport.js.map +4 -4
- package/dist/multicast-unwrap.js +29 -35
- package/dist/multicast-unwrap.js.map +4 -4
- package/dist/no-modules.js +150 -106
- package/dist/no-modules.js.map +4 -4
- package/dist/repair-fetch.d.ts +116 -21
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +114 -34
- package/dist/repair-fetch.js.map +4 -4
- package/dist/repair-layers.d.ts +18 -7
- package/dist/repair-layers.d.ts.map +1 -1
- package/dist/repair-layers.js +12 -10
- package/dist/repair-layers.js.map +4 -4
- package/dist/signaling.js +2 -11
- package/dist/signaling.js.map +4 -4
- package/dist/timestamp-codec.js +24 -30
- package/dist/timestamp-codec.js.map +4 -4
- 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 +1 -7
- package/dist/wasm-mfu-reassembler.js.map +4 -4
- package/dist/wasm-mmtp-container-parser.js +126 -99
- package/dist/wasm-mmtp-container-parser.js.map +4 -4
- package/package.json +7 -14
- package/schemas/catalog.schema.json +3 -3
- package/src/carp-catalog.ts +51 -24
- package/src/catalog-fec-params.ts +173 -64
- package/src/cmaf-block-assembler.ts +1 -4
- package/src/cmcd-producer.ts +0 -6
- package/src/delivery-catalog.ts +498 -0
- package/src/delta-fallback-config.ts +72 -0
- package/src/fec-manager.ts +457 -334
- package/src/fec-state.ts +20 -32
- package/src/fec-worker-setup.ts +109 -90
- package/src/index.ts +32 -0
- package/src/media-router.ts +59 -21
- package/src/mmtp-container-parser.ts +106 -20
- package/src/mmtp-router.ts +189 -330
- package/src/multicast-manager.ts +265 -86
- package/src/multicast-transport.ts +16 -9
- package/src/repair-fetch.ts +273 -43
- package/src/repair-layers.ts +28 -8
- package/src/track-session.ts +28 -9
- package/test-vectors/catalog/README.md +44 -18
- 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-endpoint-relay-address.json +39 -0
- 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 +2 -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-amt-manual-relay.json +38 -0
- package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +2 -1
- package/test-vectors/catalog/section-codes.json +33 -0
- package/test-vectors/catalog-timing.json +356 -0
- package/test-vectors/catalog-validation.json +40 -5
- package/test-vectors/delivery-catalog/README.md +11 -0
- package/test-vectors/delivery-catalog/negative/missing-block-profile.json +58 -0
- package/test-vectors/delivery-catalog/negative/packet-id-signaling.json +59 -0
- package/test-vectors/delivery-catalog/negative/retired-k-padded-block-profile.json +59 -0
- package/test-vectors/delivery-catalog/positive/dash.json +56 -0
- package/test-vectors/delivery-catalog/positive/mmtp-source-only.json +43 -0
- 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
|
@@ -1,78 +1,17 @@
|
|
|
1
|
-
// ../mmt-
|
|
1
|
+
// ../mmt-base/dist/mmtp.js
|
|
2
2
|
var MMTP_HEADER_SIZE = 12;
|
|
3
3
|
var MPU_HEADER_SIZE = 8;
|
|
4
|
-
var MPU_LENGTH_FIELD_SIZE = 2;
|
|
5
|
-
var MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE;
|
|
6
|
-
var TIMED_MFU_DU_HEADER_SIZE = 14;
|
|
7
|
-
var NONTIMED_MFU_DU_HEADER_SIZE = 4;
|
|
8
|
-
var PACKET_TYPE_MPU = 0;
|
|
9
|
-
var MMTP_EXT_TYPE_ALTA_AUTH = 61601;
|
|
10
|
-
var MMTP_EXT_TRAILER_HEADER_SIZE = 4;
|
|
11
|
-
function extractAltaTrailer(mmtpPacket, options) {
|
|
12
|
-
if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
|
|
13
|
-
return null;
|
|
14
|
-
const packetType = mmtpPacket[1] & 63;
|
|
15
|
-
if (packetType !== PACKET_TYPE_MPU)
|
|
16
|
-
return null;
|
|
17
|
-
const fecType = mmtpPacket[0] >> 3 & 3;
|
|
18
|
-
let headerLength = MMTP_HEADER_SIZE;
|
|
19
|
-
if (fecType < 2 && (mmtpPacket[0] & 32) !== 0)
|
|
20
|
-
headerLength += 4;
|
|
21
|
-
if (fecType < 2 && (mmtpPacket[0] & 2) !== 0) {
|
|
22
|
-
if (headerLength + 4 > mmtpPacket.length)
|
|
23
|
-
return null;
|
|
24
|
-
const extensionLength = new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + headerLength + 2, 2).getUint16(0, false);
|
|
25
|
-
headerLength += 4 + extensionLength;
|
|
26
|
-
}
|
|
27
|
-
if (headerLength + MPU_HEADER_SIZE > mmtpPacket.length)
|
|
28
|
-
return null;
|
|
29
|
-
const payload = mmtpPacket.subarray(headerLength);
|
|
30
|
-
const payloadLength = payload[0] << 8 | payload[1];
|
|
31
|
-
const flagsByte = payload[2];
|
|
32
|
-
const fragmentType = flagsByte >> 4 & 15;
|
|
33
|
-
const timed = (flagsByte >> 3 & 1) === 1;
|
|
34
|
-
const fragmentationIndicator = flagsByte >> 1 & 3;
|
|
35
|
-
if (fragmentType !== 2)
|
|
36
|
-
return null;
|
|
37
|
-
const dataUnitHeaderSize = fragmentationIndicator <= 1 ? timed ? TIMED_MFU_DU_HEADER_SIZE : NONTIMED_MFU_DU_HEADER_SIZE : 0;
|
|
38
|
-
if (payloadLength <= MPU_HEADER_REMAINDER_SIZE + dataUnitHeaderSize)
|
|
39
|
-
return null;
|
|
40
|
-
if (fecType > 1)
|
|
41
|
-
return null;
|
|
42
|
-
const hasFecId = !options?.skipFecId && fecType === 1;
|
|
43
|
-
const payloadEnd = hasFecId ? payload.length - 4 : payload.length;
|
|
44
|
-
const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength;
|
|
45
|
-
if (declaredEnd > payloadEnd)
|
|
46
|
-
return null;
|
|
47
|
-
if (declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE > payloadEnd)
|
|
48
|
-
return null;
|
|
49
|
-
const extType = payload[declaredEnd] << 8 | payload[declaredEnd + 1];
|
|
50
|
-
if (extType !== MMTP_EXT_TYPE_ALTA_AUTH)
|
|
51
|
-
return null;
|
|
52
|
-
const extLen = payload[declaredEnd + 2] << 8 | payload[declaredEnd + 3];
|
|
53
|
-
const authStart = declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE;
|
|
54
|
-
if (authStart + extLen !== payloadEnd)
|
|
55
|
-
return null;
|
|
56
|
-
return {
|
|
57
|
-
auth: payload.subarray(authStart, payloadEnd),
|
|
58
|
-
signedEnd: headerLength + declaredEnd
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ../mmt-base/dist/mmtp.js
|
|
63
|
-
var MMTP_HEADER_SIZE2 = 12;
|
|
64
|
-
var MPU_HEADER_SIZE2 = 8;
|
|
65
4
|
var MFU_DU_HEADER_SIZE_TIMED = 14;
|
|
66
5
|
var MFU_DU_HEADER_SIZE_NONTIMED = 4;
|
|
67
|
-
var
|
|
68
|
-
var
|
|
6
|
+
var MPU_LENGTH_FIELD_SIZE = 2;
|
|
7
|
+
var MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE;
|
|
69
8
|
var SOURCE_FEC_PAYLOAD_ID_SIZE = 4;
|
|
70
|
-
var
|
|
9
|
+
var PACKET_TYPE_MPU = 0;
|
|
71
10
|
var PACKET_TYPE_REPAIR = 3;
|
|
72
11
|
var MMTP_SIGNALING_PACKET_ID = 0;
|
|
73
12
|
var MMTP_PACKET_ID_MAX = 65535;
|
|
74
13
|
function parseMMTPHeader(buffer) {
|
|
75
|
-
if (buffer.length <
|
|
14
|
+
if (buffer.length < MMTP_HEADER_SIZE) {
|
|
76
15
|
throw new Error(`MMTP packet too small: ${buffer.length} bytes`);
|
|
77
16
|
}
|
|
78
17
|
const view2 = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
@@ -128,12 +67,12 @@ function parseMMTPHeader(buffer) {
|
|
|
128
67
|
};
|
|
129
68
|
}
|
|
130
69
|
function readMMTPPacketId(buffer) {
|
|
131
|
-
if (buffer.length <
|
|
70
|
+
if (buffer.length < MMTP_HEADER_SIZE)
|
|
132
71
|
return null;
|
|
133
72
|
const flags = buffer[0];
|
|
134
73
|
if ((flags >> 6 & 3) !== 0)
|
|
135
74
|
return null;
|
|
136
|
-
let offset =
|
|
75
|
+
let offset = MMTP_HEADER_SIZE;
|
|
137
76
|
if ((flags & 32) !== 0) {
|
|
138
77
|
if (offset + 4 > buffer.length)
|
|
139
78
|
return null;
|
|
@@ -149,7 +88,7 @@ function readMMTPPacketId(buffer) {
|
|
|
149
88
|
return buffer[2] << 8 | buffer[3];
|
|
150
89
|
}
|
|
151
90
|
function parseMPUHeader(payload) {
|
|
152
|
-
if (payload.length <
|
|
91
|
+
if (payload.length < MPU_HEADER_SIZE) {
|
|
153
92
|
throw new Error(`Payload too small for MPU header: ${payload.length} bytes`);
|
|
154
93
|
}
|
|
155
94
|
const view2 = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
@@ -182,15 +121,15 @@ function parseTimedMfuDuHeader(payload, offset) {
|
|
|
182
121
|
};
|
|
183
122
|
}
|
|
184
123
|
function resolveMfuPayloadLayout(mpu, fragmentType, payload) {
|
|
185
|
-
if (mpu.payloadLength <
|
|
124
|
+
if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE) {
|
|
186
125
|
return {
|
|
187
126
|
rejection: {
|
|
188
127
|
gate: "mpu-payload-length-below-header-remainder",
|
|
189
|
-
detail: `payload_length=${mpu.payloadLength} < ${
|
|
128
|
+
detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE}`
|
|
190
129
|
}
|
|
191
130
|
};
|
|
192
131
|
}
|
|
193
|
-
const declaredEnd =
|
|
132
|
+
const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength;
|
|
194
133
|
if (declaredEnd > payload.length) {
|
|
195
134
|
return {
|
|
196
135
|
rejection: {
|
|
@@ -201,7 +140,7 @@ function resolveMfuPayloadLayout(mpu, fragmentType, payload) {
|
|
|
201
140
|
}
|
|
202
141
|
const hasHeader = fragmentType === 2 && mpu.fragmentationIndicator <= 1;
|
|
203
142
|
const mfuHeaderSize = mpu.timedFlag ? MFU_DU_HEADER_SIZE_TIMED : MFU_DU_HEADER_SIZE_NONTIMED;
|
|
204
|
-
const mfuOffset =
|
|
143
|
+
const mfuOffset = MPU_HEADER_SIZE + (hasHeader ? mfuHeaderSize : 0);
|
|
205
144
|
if (mfuOffset >= declaredEnd) {
|
|
206
145
|
return {
|
|
207
146
|
rejection: {
|
|
@@ -218,44 +157,46 @@ function resolveMfuPayloadLayout(mpu, fragmentType, payload) {
|
|
|
218
157
|
}
|
|
219
158
|
};
|
|
220
159
|
}
|
|
221
|
-
|
|
160
|
+
var LEGACY_TRAILER_EXT_TYPE = 61601;
|
|
161
|
+
var LEGACY_TRAILER_HEADER_SIZE = 4;
|
|
162
|
+
function isEmptyOrExactLegacyTrailer(payload, start, end) {
|
|
222
163
|
if (start === end)
|
|
223
164
|
return true;
|
|
224
|
-
if (start < 0 || end > payload.length || start +
|
|
165
|
+
if (start < 0 || end > payload.length || start + LEGACY_TRAILER_HEADER_SIZE > end) {
|
|
225
166
|
return false;
|
|
226
167
|
}
|
|
227
168
|
const view2 = new DataView(payload.buffer, payload.byteOffset + start, end - start);
|
|
228
|
-
if (view2.getUint16(0, false) !==
|
|
169
|
+
if (view2.getUint16(0, false) !== LEGACY_TRAILER_EXT_TYPE)
|
|
229
170
|
return false;
|
|
230
171
|
const authLength = view2.getUint16(2, false);
|
|
231
|
-
return start +
|
|
172
|
+
return start + LEGACY_TRAILER_HEADER_SIZE + authLength === end;
|
|
232
173
|
}
|
|
233
174
|
function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
|
|
234
175
|
if (declaredEnd > payload.length)
|
|
235
176
|
return null;
|
|
236
177
|
if (options?.skipFecId) {
|
|
237
|
-
return
|
|
178
|
+
return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
|
|
238
179
|
}
|
|
239
180
|
if (fecType === 1) {
|
|
240
181
|
const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
241
|
-
if (fecStart < declaredEnd || !
|
|
182
|
+
if (fecStart < declaredEnd || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {
|
|
242
183
|
return null;
|
|
243
184
|
}
|
|
244
185
|
return { hasFecId: true };
|
|
245
186
|
}
|
|
246
187
|
if (fecType !== 0)
|
|
247
188
|
return null;
|
|
248
|
-
if (
|
|
189
|
+
if (isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length)) {
|
|
249
190
|
return { hasFecId: false };
|
|
250
191
|
}
|
|
251
192
|
return null;
|
|
252
193
|
}
|
|
253
194
|
function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
254
|
-
if (mmtpPacket.length <
|
|
195
|
+
if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {
|
|
255
196
|
return {
|
|
256
197
|
rejection: {
|
|
257
198
|
gate: "packet-shorter-than-mmtp-plus-mpu-header",
|
|
258
|
-
detail: `${mmtpPacket.length} bytes < ${
|
|
199
|
+
detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE + MPU_HEADER_SIZE}`
|
|
259
200
|
}
|
|
260
201
|
};
|
|
261
202
|
}
|
|
@@ -274,7 +215,7 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
|
274
215
|
};
|
|
275
216
|
}
|
|
276
217
|
}
|
|
277
|
-
if (header.packetType !==
|
|
218
|
+
if (header.packetType !== PACKET_TYPE_MPU) {
|
|
278
219
|
return {
|
|
279
220
|
rejection: {
|
|
280
221
|
gate: "not-an-mpu-packet",
|
|
@@ -283,11 +224,11 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
|
283
224
|
};
|
|
284
225
|
}
|
|
285
226
|
const payload = mmtpPacket.subarray(header.headerLength);
|
|
286
|
-
if (payload.length <
|
|
227
|
+
if (payload.length < MPU_HEADER_SIZE) {
|
|
287
228
|
return {
|
|
288
229
|
rejection: {
|
|
289
230
|
gate: "payload-shorter-than-mpu-header",
|
|
290
|
-
detail: `${payload.length} bytes < ${
|
|
231
|
+
detail: `${payload.length} bytes < ${MPU_HEADER_SIZE}`
|
|
291
232
|
}
|
|
292
233
|
};
|
|
293
234
|
}
|
|
@@ -318,7 +259,7 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
|
318
259
|
return {
|
|
319
260
|
rejection: {
|
|
320
261
|
gate: "declared-trailers-unresolved",
|
|
321
|
-
detail: `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty nor an exact
|
|
262
|
+
detail: `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty nor an exact legacy authenticator trailer (fecType=${header.fecType})`
|
|
322
263
|
}
|
|
323
264
|
};
|
|
324
265
|
}
|
|
@@ -334,8 +275,8 @@ function extractPlannedMfu(mmtpPacket, result) {
|
|
|
334
275
|
const mfuDuHeaderPresent = layout.hasMfuDuHeader;
|
|
335
276
|
const mfuOffset = layout.mfuOffset;
|
|
336
277
|
const dataEnd = layout.dataEnd;
|
|
337
|
-
const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload,
|
|
338
|
-
const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset +
|
|
278
|
+
const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE) : null;
|
|
279
|
+
const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false) : null;
|
|
339
280
|
const data = payload.subarray(mfuOffset, dataEnd);
|
|
340
281
|
const fecPayloadId = trailerLayout.hasFecId ? {
|
|
341
282
|
ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, SOURCE_FEC_PAYLOAD_ID_SIZE).getUint32(0, false)
|
|
@@ -343,17 +284,17 @@ function extractPlannedMfu(mmtpPacket, result) {
|
|
|
343
284
|
return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
|
|
344
285
|
}
|
|
345
286
|
function sourceFecPayloadIdOf(mmtpPacket, header) {
|
|
346
|
-
if (mmtpPacket.length <
|
|
287
|
+
if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
|
|
347
288
|
return null;
|
|
348
|
-
if (header.packetType !==
|
|
289
|
+
if (header.packetType !== PACKET_TYPE_MPU)
|
|
349
290
|
return null;
|
|
350
291
|
const payload = mmtpPacket.subarray(header.headerLength);
|
|
351
|
-
if (payload.length <
|
|
292
|
+
if (payload.length < MPU_HEADER_SIZE)
|
|
352
293
|
return null;
|
|
353
294
|
const payloadLength = payload[0] << 8 | payload[1];
|
|
354
|
-
if (payloadLength <
|
|
295
|
+
if (payloadLength < MPU_HEADER_REMAINDER_SIZE)
|
|
355
296
|
return null;
|
|
356
|
-
const declaredEnd =
|
|
297
|
+
const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength;
|
|
357
298
|
if (!resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId)
|
|
358
299
|
return null;
|
|
359
300
|
const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
@@ -361,7 +302,6 @@ function sourceFecPayloadIdOf(mmtpPacket, header) {
|
|
|
361
302
|
ssId: (mmtpPacket[off] << 24 | mmtpPacket[off + 1] << 16 | mmtpPacket[off + 2] << 8 | mmtpPacket[off + 3]) >>> 0
|
|
362
303
|
};
|
|
363
304
|
}
|
|
364
|
-
var extractAltaTrailer2 = extractAltaTrailer;
|
|
365
305
|
var REPAIR_FEC_PID_SIZE = 13;
|
|
366
306
|
function readRepairFecPayloadIdFields(mmtpPacket, parsedHeader) {
|
|
367
307
|
let header;
|
|
@@ -441,49 +381,21 @@ function serial32Precedes(a, b) {
|
|
|
441
381
|
const distance = serial32Distance(a, b);
|
|
442
382
|
return distance !== 0 && distance < SERIAL32_HALF;
|
|
443
383
|
}
|
|
444
|
-
function kStrideBlockStart(sbn, k) {
|
|
445
|
-
assertUint32(sbn, "sbn");
|
|
446
|
-
if (!Number.isInteger(k) || k < 1 || k >= SERIAL32_HALF) {
|
|
447
|
-
throw new RangeError(`k must be a positive integer below 2^31 (got ${k})`);
|
|
448
|
-
}
|
|
449
|
-
const high = Math.floor(sbn / 65536);
|
|
450
|
-
const low = sbn % 65536;
|
|
451
|
-
return (high * k % SERIAL32_SPAN * 65536 + low * k) % SERIAL32_SPAN;
|
|
452
|
-
}
|
|
453
|
-
function kStrideBlockPrecedes(a, b, k) {
|
|
454
|
-
return serial32Precedes(kStrideBlockStart(a, k), kStrideBlockStart(b, k));
|
|
455
|
-
}
|
|
456
|
-
function kStrideBlocksBehind(a, b, k) {
|
|
457
|
-
const from = kStrideBlockStart(a, k);
|
|
458
|
-
const to = kStrideBlockStart(b, k);
|
|
459
|
-
if (!serial32Precedes(from, to))
|
|
460
|
-
return 0;
|
|
461
|
-
return Math.ceil(serial32Distance(from, to) / k);
|
|
462
|
-
}
|
|
463
|
-
function isKStrideBlock(sbn, k) {
|
|
464
|
-
return Number.isInteger(sbn) && sbn >= 0 && sbn < SERIAL32_SPAN && Number.isInteger(k) && k >= 1 && k < SERIAL32_HALF;
|
|
465
|
-
}
|
|
466
|
-
function kStrideNextBlock(sbn, k) {
|
|
467
|
-
assertUint32(sbn, "sbn");
|
|
468
|
-
kStrideBlockStart(sbn, k);
|
|
469
|
-
return (sbn + 1) * k >= SERIAL32_SPAN ? 0 : sbn + 1;
|
|
470
|
-
}
|
|
471
384
|
|
|
472
385
|
// ../mmt-base/dist/fec-block-profile.js
|
|
473
386
|
var FEC_BLOCK_PROFILES = Object.freeze([
|
|
474
|
-
"k-padded-v1",
|
|
475
387
|
"iso-ssbg1-v1"
|
|
476
388
|
]);
|
|
477
389
|
function isFecBlockProfile(value) {
|
|
478
|
-
return value === "
|
|
390
|
+
return value === "iso-ssbg1-v1";
|
|
479
391
|
}
|
|
480
392
|
function parseFecBlockProfile(value, where) {
|
|
481
393
|
if (isFecBlockProfile(value))
|
|
482
394
|
return value;
|
|
483
395
|
if (value === void 0) {
|
|
484
|
-
throw new Error(`${where} is required: name the FEC block profile ("
|
|
396
|
+
throw new Error(`${where} is required: name the FEC block profile ("iso-ssbg1-v1")`);
|
|
485
397
|
}
|
|
486
|
-
throw new Error(`${where} must be "
|
|
398
|
+
throw new Error(`${where} must be "iso-ssbg1-v1" (got ${JSON.stringify(value)})`);
|
|
487
399
|
}
|
|
488
400
|
var SSBG1_LENGTH_FIELD_BYTES = 2;
|
|
489
401
|
function ssbg1MaxPacketLength(symbolSize) {
|
|
@@ -508,20 +420,22 @@ function isoBlocksBehindLowerBound(a, b, kMax) {
|
|
|
508
420
|
// src/track-session.ts
|
|
509
421
|
import { fecBlockBudgetMs } from "@blockcast/mmt-fec";
|
|
510
422
|
function computeFecDeliveryWindowMs(config) {
|
|
511
|
-
const fecInterleaveMs = config.interleaveDepthMs;
|
|
512
423
|
const fecK = config.sourceSymbols;
|
|
513
|
-
if (!Number.isFinite(
|
|
424
|
+
if (!Number.isFinite(fecK) || fecK <= 0) {
|
|
514
425
|
throw new Error(
|
|
515
|
-
`[TrackSession]
|
|
426
|
+
`[TrackSession] fecK=${fecK}. Derive a positive FEC source-symbol count from the stream catalog.`
|
|
516
427
|
);
|
|
517
428
|
}
|
|
518
|
-
|
|
429
|
+
return fecSpanDeliveryWindowMs(config.interleaveDepthMs, config.jitterBufferMs);
|
|
430
|
+
}
|
|
431
|
+
function fecSpanDeliveryWindowMs(interleaveDepthMs, jitterBufferMs) {
|
|
432
|
+
if (!Number.isFinite(interleaveDepthMs) || interleaveDepthMs <= 0) {
|
|
519
433
|
throw new Error(
|
|
520
|
-
`[TrackSession]
|
|
434
|
+
`[TrackSession] fecInterleaveMs=${interleaveDepthMs}. Derive a positive FEC interleave window from the stream catalog.`
|
|
521
435
|
);
|
|
522
436
|
}
|
|
523
|
-
if (
|
|
524
|
-
return fecBlockBudgetMs(
|
|
437
|
+
if (jitterBufferMs === void 0) return Math.ceil(interleaveDepthMs);
|
|
438
|
+
return fecBlockBudgetMs(interleaveDepthMs, jitterBufferMs);
|
|
525
439
|
}
|
|
526
440
|
|
|
527
441
|
// src/delivery-gap.ts
|
|
@@ -613,7 +527,9 @@ var DeliveryGapEstimator = class {
|
|
|
613
527
|
import {
|
|
614
528
|
deriveFecInterleaveDepth as deriveFecInterleaveDepth2,
|
|
615
529
|
deriveFecRetentionBlocks,
|
|
616
|
-
tryDeriveFecRetentionBlocks
|
|
530
|
+
tryDeriveFecRetentionBlocks,
|
|
531
|
+
LOCAL_FEC_RETENTION_BLOCKS,
|
|
532
|
+
LOCAL_FEC_TIMEOUT_MS
|
|
617
533
|
} from "@blockcast/mmt-fec";
|
|
618
534
|
|
|
619
535
|
// src/iso-block-locator.ts
|
|
@@ -847,7 +763,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
847
763
|
};
|
|
848
764
|
|
|
849
765
|
// src/fec-state.ts
|
|
850
|
-
var MIN_INTERLEAVE_DEPTH_MS =
|
|
766
|
+
var MIN_INTERLEAVE_DEPTH_MS = 1;
|
|
851
767
|
var FEC_INACTIVE_REASONS = Object.freeze([
|
|
852
768
|
"no-catalog-fec",
|
|
853
769
|
"reliable-moq",
|
|
@@ -858,6 +774,15 @@ var INACTIVE = Object.freeze({
|
|
|
858
774
|
"reliable-moq": Object.freeze({ state: "inactive", reason: "reliable-moq" }),
|
|
859
775
|
"no-multicast-bridge": Object.freeze({ state: "inactive", reason: "no-multicast-bridge" })
|
|
860
776
|
});
|
|
777
|
+
function recordOf(value) {
|
|
778
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
779
|
+
}
|
|
780
|
+
function isoBlockSpanUsable(fec) {
|
|
781
|
+
const block = recordOf(fec);
|
|
782
|
+
if (block === void 0 || block["blockProfile"] !== "iso-ssbg1-v1") return true;
|
|
783
|
+
const ms = block["interleaveDepthMs"];
|
|
784
|
+
return ms === void 0 || typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0;
|
|
785
|
+
}
|
|
861
786
|
|
|
862
787
|
// src/block-policy.ts
|
|
863
788
|
import { deriveFecInterleaveDepth } from "@blockcast/mmt-fec";
|
|
@@ -1366,7 +1291,6 @@ var FECManager = class _FECManager {
|
|
|
1366
1291
|
constructor() {
|
|
1367
1292
|
// Per-track WASM RaptorQ decoders, keyed by physical stream identity.
|
|
1368
1293
|
this.#fecDecoders = /* @__PURE__ */ new Map();
|
|
1369
|
-
/** @deprecated Use setFecDecoder(packetId, ...). Backwards-compat shim. */
|
|
1370
1294
|
// Per-track Worker-backed FEC decoders, keyed by physical stream identity.
|
|
1371
1295
|
// Worker-backed tracks use async decode — recovered frames arrive via onFrame callback,
|
|
1372
1296
|
// not synchronous return from feedSourceSymbol/feedRepairSymbol.
|
|
@@ -1381,10 +1305,9 @@ var FECManager = class _FECManager {
|
|
|
1381
1305
|
// source/repair symbols do not split across two RaptorQ states.
|
|
1382
1306
|
this.#syncDecoderBlockOwners = /* @__PURE__ */ new Map();
|
|
1383
1307
|
this.#decoderBlockOwners = /* @__PURE__ */ new WeakMap();
|
|
1384
|
-
this.#fecK = 4;
|
|
1385
1308
|
this.#fecSymbolSize = 0;
|
|
1386
|
-
this.#fecTimeoutMs =
|
|
1387
|
-
// default
|
|
1309
|
+
this.#fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS;
|
|
1310
|
+
// local default; refined by setFramerate() when fps known
|
|
1388
1311
|
this.#trackConfigs = /* @__PURE__ */ new Map();
|
|
1389
1312
|
this.#fecStats = newFecCounters();
|
|
1390
1313
|
this.#fecTrackStats = /* @__PURE__ */ new Map();
|
|
@@ -1424,14 +1347,17 @@ var FECManager = class _FECManager {
|
|
|
1424
1347
|
this.#blockSourceEsis = /* @__PURE__ */ new Map();
|
|
1425
1348
|
// Authored SSB_length per block (ISO/IEC 23008-1 §C.5.3), learned from the
|
|
1426
1349
|
// first repair symbol. The FFmpeg moq_mmt publisher closes a block every D
|
|
1427
|
-
// media units, usually before it holds K symbols
|
|
1428
|
-
//
|
|
1429
|
-
// SSB_length
|
|
1430
|
-
//
|
|
1350
|
+
// media units, usually before it holds K symbols; under iso-ssbg1-v1
|
|
1351
|
+
// (ssbg_mode1) that block is K' = SSB_length symbols, its repair ESIs start
|
|
1352
|
+
// at SSB_length, and K is only the maximum. Such a block is clean at
|
|
1353
|
+
// SSB_length source arrivals and recoverable from SSB_length received
|
|
1431
1354
|
// symbols; waiting for K fails every one of them.
|
|
1432
1355
|
this.#blockAuthoredLengths = /* @__PURE__ */ new Map();
|
|
1433
1356
|
this.#ssbLengthConflictCount = 0;
|
|
1434
1357
|
this.#blockRepairRsIds = /* @__PURE__ */ new Map();
|
|
1358
|
+
// RSB_length per block (C.5.3 Repair FEC Payload ID): how many repair
|
|
1359
|
+
// symbols its repair group carries. Same lifecycle as #blockRepairRsIds.
|
|
1360
|
+
this.#blockRsbLengths = /* @__PURE__ */ new Map();
|
|
1435
1361
|
// Blocks already recovered — prevents late source symbols from restarting timeouts.
|
|
1436
1362
|
// WASM removes blocks after recovery; late symbols create empty blocks returning null,
|
|
1437
1363
|
// which would falsely start new timeouts without this guard.
|
|
@@ -1544,14 +1470,16 @@ var FECManager = class _FECManager {
|
|
|
1544
1470
|
this.#resolvedBlockClasses = /* @__PURE__ */ new Map();
|
|
1545
1471
|
// Blocks already reported through onBlockRepaired (once per block).
|
|
1546
1472
|
this.#repairedBlocks = /* @__PURE__ */ new Set();
|
|
1547
|
-
// Blocks created
|
|
1473
|
+
// Blocks created before any symbol of theirs was tracked: the repair-less
|
|
1474
|
+
// spans placeIsoRepair materializes (onBlockVanished).
|
|
1548
1475
|
this.#materializedBlocks = /* @__PURE__ */ new Set();
|
|
1549
1476
|
// packetIds whose ordered-relay horizon became underivable and fell back to
|
|
1550
1477
|
// the datagram deadline. Bounds the warn in #derivableOrderedHorizon to once
|
|
1551
1478
|
// per stream. Not only throws: the ordinary out-of-range case is detected by
|
|
1552
1479
|
// arithmetic and never throws at all.
|
|
1553
1480
|
this.#orderedHorizonWarnedFor = /* @__PURE__ */ new Set();
|
|
1554
|
-
//
|
|
1481
|
+
// Newest block start (SS_Start) seen per stream and when: the frontier for
|
|
1482
|
+
// source-discontinuity detection and isoSpanResolution.
|
|
1555
1483
|
this.#highestSbn = /* @__PURE__ */ new Map();
|
|
1556
1484
|
// iso-ssbg1-v1 block location per stream (streamKey). A source belongs to a
|
|
1557
1485
|
// block only once a repair symbol stated the block's (SS_Start, SSB_length).
|
|
@@ -1645,7 +1573,10 @@ var FECManager = class _FECManager {
|
|
|
1645
1573
|
#releasedWorkerClients;
|
|
1646
1574
|
#syncDecoderBlockOwners;
|
|
1647
1575
|
#decoderBlockOwners;
|
|
1648
|
-
|
|
1576
|
+
// The stream-global K a configure() call stated, reported as fecStats.k.
|
|
1577
|
+
// It never resolves a packetId: a packetId has only its own track config
|
|
1578
|
+
// (BLO-45953), and K does not feed the delivery-window math.
|
|
1579
|
+
#streamK;
|
|
1649
1580
|
#fecSymbolSize;
|
|
1650
1581
|
#fecTimeoutMs;
|
|
1651
1582
|
#trackConfigs;
|
|
@@ -1667,6 +1598,7 @@ var FECManager = class _FECManager {
|
|
|
1667
1598
|
#blockAuthoredLengths;
|
|
1668
1599
|
#ssbLengthConflictCount;
|
|
1669
1600
|
#blockRepairRsIds;
|
|
1601
|
+
#blockRsbLengths;
|
|
1670
1602
|
#completedBlocks;
|
|
1671
1603
|
#terminalExpiryMs;
|
|
1672
1604
|
#terminalQueues;
|
|
@@ -1807,7 +1739,8 @@ var FECManager = class _FECManager {
|
|
|
1807
1739
|
this.#completedBlocks.add(key);
|
|
1808
1740
|
if (clean) this.#cleanlyCompletedBlocks.add(key);
|
|
1809
1741
|
this.#indexBlockKey(key, sbn, packetId);
|
|
1810
|
-
const
|
|
1742
|
+
const config = this.#trackConfig(packetId);
|
|
1743
|
+
const expiresAtMs = nowMs + config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
|
|
1811
1744
|
this.#terminalExpiryMs.set(key, expiresAtMs);
|
|
1812
1745
|
let queue = this.#terminalQueues.get(packetId);
|
|
1813
1746
|
if (!queue) {
|
|
@@ -1843,8 +1776,9 @@ var FECManager = class _FECManager {
|
|
|
1843
1776
|
* retention the Worker and the WASM decoder are configured with, because it
|
|
1844
1777
|
* comes from the same function they do (`deriveFecRetentionBlocks`, over the
|
|
1845
1778
|
* block's delivery window and its span). The decoder retires every block
|
|
1846
|
-
* more than this many
|
|
1847
|
-
*
|
|
1779
|
+
* more than this many blocks behind the newest SS_Start it saw, counted
|
|
1780
|
+
* from below by `isoBlocksBehindLowerBound` (`cleanup_by_ss_start`), so
|
|
1781
|
+
* settling earlier than this would decide a block the decoder would still
|
|
1848
1782
|
* have recovered.
|
|
1849
1783
|
*
|
|
1850
1784
|
* This used to call `deriveFecInterleaveDepth`, which returns D -- MEDIA
|
|
@@ -1857,6 +1791,14 @@ var FECManager = class _FECManager {
|
|
|
1857
1791
|
* derivation; do not re-derive a horizon at either call site.
|
|
1858
1792
|
*/
|
|
1859
1793
|
#orderedHorizonBlocksFor(packetId, config) {
|
|
1794
|
+
if (config.interleaveDepthMs === 0) {
|
|
1795
|
+
if (Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
|
|
1796
|
+
return LOCAL_FEC_RETENTION_BLOCKS;
|
|
1797
|
+
}
|
|
1798
|
+
throw new Error(
|
|
1799
|
+
`[FECManager] packetId=${packetId}: local FEC cleanup requires a positive timeout (got ${config.timeoutMs})`
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1860
1802
|
if (!_FECManager.#hasOrderedGeometry(config)) {
|
|
1861
1803
|
throw new Error(
|
|
1862
1804
|
`[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}.`
|
|
@@ -1870,7 +1812,7 @@ var FECManager = class _FECManager {
|
|
|
1870
1812
|
* two can never disagree about which streams settle by stream order.
|
|
1871
1813
|
*/
|
|
1872
1814
|
static #hasOrderedGeometry(config) {
|
|
1873
|
-
return config
|
|
1815
|
+
return _FECManager.#hasCatalogTiming(config) && // `fps` is no longer an input to the horizon, but it still gates WHICH
|
|
1874
1816
|
// streams settle by stream order at all: a track with no usable
|
|
1875
1817
|
// media-unit rate fell back to datagram semantics before this and must
|
|
1876
1818
|
// keep doing so. Removing this widens ordered-relay settlement to streams
|
|
@@ -1910,6 +1852,9 @@ var FECManager = class _FECManager {
|
|
|
1910
1852
|
* argument: narrower IS the failing direction.
|
|
1911
1853
|
*/
|
|
1912
1854
|
#derivableOrderedHorizon(config, packetId) {
|
|
1855
|
+
if (config.interleaveDepthMs === 0) {
|
|
1856
|
+
return Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? LOCAL_FEC_RETENTION_BLOCKS : void 0;
|
|
1857
|
+
}
|
|
1913
1858
|
if (!_FECManager.#hasOrderedGeometry(config)) return void 0;
|
|
1914
1859
|
try {
|
|
1915
1860
|
const blocks = tryDeriveFecRetentionBlocks(config.timeoutMs, config.interleaveDepthMs);
|
|
@@ -1942,19 +1887,20 @@ var FECManager = class _FECManager {
|
|
|
1942
1887
|
/**
|
|
1943
1888
|
* The stream's interleave horizon, or undefined when its geometry cannot
|
|
1944
1889
|
* define one. Two different streams reach that case, with different causes:
|
|
1945
|
-
* a packetId that was never configured,
|
|
1946
|
-
*
|
|
1890
|
+
* a packetId that was never configured, which has no geometry at all
|
|
1891
|
+
* (explicitly configured tracks are refused an underivable geometry
|
|
1947
1892
|
* at config time — #upsertTrackConfig, setMulticastSymbolDelivery); or a
|
|
1948
1893
|
* configured track whose timeoutMs was later widened past the 255-block
|
|
1949
1894
|
* ceiling without re-validating, which #derivableOrderedHorizon catches
|
|
1950
1895
|
* rather than throwing from the packet-feed path. Either way the stream
|
|
1951
|
-
* keeps datagram semantics instead of throwing.
|
|
1896
|
+
* keeps datagram semantics instead of throwing. A track's configuration can
|
|
1952
1897
|
* also change under a stream that already holds blocks, so a caller cannot
|
|
1953
1898
|
* assume a horizon that was defined when a block was held is still defined
|
|
1954
1899
|
* (see #advanceOrderedFrontier).
|
|
1955
1900
|
*/
|
|
1956
1901
|
#orderedHorizonBlocks(packetId) {
|
|
1957
|
-
|
|
1902
|
+
const config = this.#trackConfigs.get(packetId);
|
|
1903
|
+
return config === void 0 ? void 0 : this.#derivableOrderedHorizon(config, packetId);
|
|
1958
1904
|
}
|
|
1959
1905
|
/** True while a block of an ordered-relay stream can still receive symbols. */
|
|
1960
1906
|
#withinOrderedHorizon(key) {
|
|
@@ -1988,7 +1934,7 @@ var FECManager = class _FECManager {
|
|
|
1988
1934
|
*/
|
|
1989
1935
|
#advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName) {
|
|
1990
1936
|
const previous = this.#orderedFrontiers.get(streamKey);
|
|
1991
|
-
const frontier = previous === void 0 || this.#blockPrecedes(previous, sbn
|
|
1937
|
+
const frontier = previous === void 0 || this.#blockPrecedes(previous, sbn) ? sbn : previous;
|
|
1992
1938
|
if (frontier !== previous) this.#orderedFrontiers.set(streamKey, frontier);
|
|
1993
1939
|
const pending = this.#orderedPendingBlocks.get(streamKey);
|
|
1994
1940
|
if (!pending) return;
|
|
@@ -2023,7 +1969,7 @@ var FECManager = class _FECManager {
|
|
|
2023
1969
|
if (this.#completedBlocks.has(key) || this.#pendingWorkerCleanups.has(key) || this.#repairRequestedBlocks.has(key)) return;
|
|
2024
1970
|
const authoredLength = this.#blockAuthoredLengths.get(key);
|
|
2025
1971
|
const received = (this.#blockSourceCounts.get(key) ?? 0) + (this.#blockRepairRsIds.get(key)?.size ?? 0);
|
|
2026
|
-
if (this.onRepairNeeded && (authoredLength === void 0 || received < authoredLength)) {
|
|
1972
|
+
if (this.#hasCatalogTimingForPacket(packetId) && this.onRepairNeeded && (authoredLength === void 0 || received < authoredLength)) {
|
|
2027
1973
|
this.#requestRepair(sbn, packetId, catalogTrackName, true);
|
|
2028
1974
|
return;
|
|
2029
1975
|
}
|
|
@@ -2179,7 +2125,7 @@ var FECManager = class _FECManager {
|
|
|
2179
2125
|
}
|
|
2180
2126
|
#requestRepair(sbn, packetId, catalogTrackName, settledByOrder = false) {
|
|
2181
2127
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
2182
|
-
if (this.#completedBlocks.has(key) || !this.onRepairNeeded || this.#repairRequestedBlocks.has(key)) return;
|
|
2128
|
+
if (this.#completedBlocks.has(key) || !this.#hasCatalogTimingForPacket(packetId) || !this.onRepairNeeded || this.#repairRequestedBlocks.has(key)) return;
|
|
2183
2129
|
const deadline = this.#repairDeadlines.get(key);
|
|
2184
2130
|
if (deadline?.handle != null) clearTimeout(deadline.handle);
|
|
2185
2131
|
this.#repairDeadlines.set(key, null);
|
|
@@ -2259,6 +2205,7 @@ var FECManager = class _FECManager {
|
|
|
2259
2205
|
}
|
|
2260
2206
|
/** A block's live class without creating classifier state for the stream. */
|
|
2261
2207
|
#peekClass(sbn, packetId, catalogTrackName) {
|
|
2208
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return "unknown";
|
|
2262
2209
|
const classifier = this.#classifiers.get(this.#streamKey(packetId, catalogTrackName));
|
|
2263
2210
|
if (classifier) return classifier.classOf(sbn);
|
|
2264
2211
|
return this.#geometryFor(packetId, catalogTrackName).trackKind === "audio" ? "audio" : "unknown";
|
|
@@ -2280,8 +2227,8 @@ var FECManager = class _FECManager {
|
|
|
2280
2227
|
// Deliberately one block tighter than deriveFecRetentionBlocks over the same
|
|
2281
2228
|
// inputs (that function's `+ 1`). Different question: the retention horizon
|
|
2282
2229
|
// asks how long the decoder still ACCEPTS symbols for a block, this asks how
|
|
2283
|
-
//
|
|
2284
|
-
// #noteSbnSeen).
|
|
2230
|
+
// many blocks a stream can live behind (or ahead of) its frontier before a
|
|
2231
|
+
// source symbol there is a discontinuity rather than loss (see #noteSbnSeen).
|
|
2285
2232
|
// The extra block of retention is slack for a block still being fed; a block
|
|
2286
2233
|
// nothing has been heard from does not get it. Not the units defect — but it
|
|
2287
2234
|
// is the next place the two can drift, so change both or neither.
|
|
@@ -2290,27 +2237,28 @@ var FECManager = class _FECManager {
|
|
|
2290
2237
|
return Math.ceil(config.timeoutMs / config.interleaveDepthMs);
|
|
2291
2238
|
}
|
|
2292
2239
|
/**
|
|
2293
|
-
* Record that a symbol for block `sbn`
|
|
2294
|
-
*
|
|
2240
|
+
* Record that a symbol for block `sbn` (the block's SS_Start) arrived and
|
|
2241
|
+
* advance the stream's frontier, the newest block start seen.
|
|
2295
2242
|
*
|
|
2296
|
-
*
|
|
2297
|
-
*
|
|
2298
|
-
*
|
|
2299
|
-
*
|
|
2300
|
-
*
|
|
2301
|
-
* its last symbol, is a source discontinuity, not loss. A repair symbol can
|
|
2302
|
-
* only advance the frontier: a D-28 fetch legitimately answers for an old
|
|
2303
|
-
* block, so it never signals a discontinuity.
|
|
2243
|
+
* A SOURCE symbol whose block goes back further than one live window, or
|
|
2244
|
+
* jumps further than the stream could have produced since its last symbol,
|
|
2245
|
+
* is a source discontinuity, not loss. A repair symbol can only advance the
|
|
2246
|
+
* frontier: a D-28 fetch legitimately answers for an old block, so it never
|
|
2247
|
+
* signals a discontinuity.
|
|
2304
2248
|
*
|
|
2305
2249
|
* The producer closes a block at its Dth media-unit boundary OR when K
|
|
2306
|
-
* source symbols filled it, so one interleave span can start several
|
|
2307
|
-
*
|
|
2308
|
-
*
|
|
2309
|
-
*
|
|
2310
|
-
*
|
|
2311
|
-
*
|
|
2312
|
-
*
|
|
2313
|
-
*
|
|
2250
|
+
* source symbols filled it, so one interleave span can start several blocks
|
|
2251
|
+
* (every large keyframe does). What bounds a forward jump is therefore
|
|
2252
|
+
* packet evidence, not a block rate: SS_ID advances by one per FEC source
|
|
2253
|
+
* packet (ISO/IEC 23008-1 C.5.2) and the MMTP packet_sequence_number by one
|
|
2254
|
+
* per packet of the packet_id, so with both source symbols'
|
|
2255
|
+
* packet_sequence_number a block start can move at most the packets sent
|
|
2256
|
+
* between them plus the anchor's own offset into its block (< K). Without
|
|
2257
|
+
* it, only the D-close rate is known.
|
|
2258
|
+
*
|
|
2259
|
+
* Vanished blocks are not enumerated here: their starts are unknown until a
|
|
2260
|
+
* repair states them. The SS_IDs between two stated blocks become one
|
|
2261
|
+
* repair-less span when the later block's repair arrives (placeIsoRepair).
|
|
2314
2262
|
*/
|
|
2315
2263
|
#noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber) {
|
|
2316
2264
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
@@ -2319,7 +2267,7 @@ var FECManager = class _FECManager {
|
|
|
2319
2267
|
const anchor = this.#sourceAnchors.get(streamKey);
|
|
2320
2268
|
const psn = symbol === "source" && packetSequenceNumber !== void 0 && Number.isSafeInteger(packetSequenceNumber) && packetSequenceNumber >= 0 && packetSequenceNumber <= 4294967295 ? packetSequenceNumber : void 0;
|
|
2321
2269
|
const anchorHere = () => {
|
|
2322
|
-
if (psn !== void 0 && (anchor === void 0 || !this.#blockPrecedes(sbn, anchor.sbn
|
|
2270
|
+
if (psn !== void 0 && (anchor === void 0 || !this.#blockPrecedes(sbn, anchor.sbn))) {
|
|
2323
2271
|
this.#sourceAnchors.set(streamKey, { sbn, psn, atMs: now });
|
|
2324
2272
|
}
|
|
2325
2273
|
};
|
|
@@ -2335,9 +2283,9 @@ var FECManager = class _FECManager {
|
|
|
2335
2283
|
anchorHere();
|
|
2336
2284
|
return;
|
|
2337
2285
|
}
|
|
2338
|
-
const config = this.#
|
|
2286
|
+
const config = this.#trackConfig(packetId);
|
|
2339
2287
|
const window2 = this.#liveWindowBlocks(config);
|
|
2340
|
-
if (!this.#blockPrecedes(previous.sbn, sbn
|
|
2288
|
+
if (!this.#blockPrecedes(previous.sbn, sbn)) {
|
|
2341
2289
|
if (symbol === "source" && window2 !== null && this.#blocksBehind(sbn, previous.sbn, packetId) > window2) {
|
|
2342
2290
|
discontinuity();
|
|
2343
2291
|
return;
|
|
@@ -2348,15 +2296,9 @@ var FECManager = class _FECManager {
|
|
|
2348
2296
|
const missing = this.#blocksBehind(previous.sbn, sbn, packetId) - 1;
|
|
2349
2297
|
let plausible = true;
|
|
2350
2298
|
if (window2 !== null) {
|
|
2351
|
-
if (
|
|
2299
|
+
if (psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn) && psn !== anchor.psn) {
|
|
2352
2300
|
const sent = psn - anchor.psn >>> 0;
|
|
2353
2301
|
plausible = sent <= 2147483647 && serial32Distance(anchor.sbn, sbn) <= sent + config.k;
|
|
2354
|
-
} else if (psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {
|
|
2355
|
-
const sent = psn - anchor.psn >>> 0;
|
|
2356
|
-
const between = this.#blocksBehind(anchor.sbn, sbn, packetId) - 1;
|
|
2357
|
-
const dCloses = window2 + Math.ceil((now - anchor.atMs) / config.interleaveDepthMs);
|
|
2358
|
-
const kCloses = config.k > 0 ? Math.ceil(sent / config.k) : sent;
|
|
2359
|
-
plausible = sent <= 2147483647 && between <= sent - 1 && between <= dCloses + kCloses;
|
|
2360
2302
|
} else {
|
|
2361
2303
|
plausible = missing <= window2 + Math.ceil((now - previous.atMs) / config.interleaveDepthMs);
|
|
2362
2304
|
}
|
|
@@ -2368,48 +2310,31 @@ var FECManager = class _FECManager {
|
|
|
2368
2310
|
}
|
|
2369
2311
|
this.#highestSbn.set(streamKey, { sbn, atMs: now });
|
|
2370
2312
|
anchorHere();
|
|
2371
|
-
if (this.#isIso(packetId)) return;
|
|
2372
|
-
if (missing <= 0 || window2 === null) return;
|
|
2373
|
-
let vanished = previous.sbn;
|
|
2374
|
-
for (let i = 0; i < missing; i++) {
|
|
2375
|
-
vanished = this.#nextBlock(vanished, packetId);
|
|
2376
|
-
this.#materializeBlock(
|
|
2377
|
-
vanished,
|
|
2378
|
-
packetId,
|
|
2379
|
-
catalogTrackName,
|
|
2380
|
-
this.#blocksBehind(vanished, sbn, packetId) <= window2
|
|
2381
|
-
);
|
|
2382
|
-
}
|
|
2383
2313
|
}
|
|
2384
2314
|
/*
|
|
2385
|
-
* Block order for one stream (BLO-35597 iso:5).
|
|
2386
|
-
* number (ISO/IEC 23008-1 C.5.2), so
|
|
2387
|
-
*
|
|
2388
|
-
*
|
|
2389
|
-
* the order across the wrap changes. A block outside K-stride geometry has
|
|
2390
|
-
* no SS_ID position, so it keeps the SBN order it always had.
|
|
2315
|
+
* Block order for one stream (BLO-35597 iso:5). A block is keyed by its
|
|
2316
|
+
* SS_Start and SS_ID is a 32-bit serial number (ISO/IEC 23008-1 C.5.2), so
|
|
2317
|
+
* blocks are ordered by RFC 1982 comparison of their starts. Only configured
|
|
2318
|
+
* streams hold blocks: every entry point refuses an unconfigured packetId.
|
|
2391
2319
|
*/
|
|
2392
|
-
#blockPrecedes(a, b
|
|
2393
|
-
|
|
2394
|
-
const k = this.kForPacket(packetId);
|
|
2395
|
-
return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlockPrecedes(a, b, k) : a < b;
|
|
2320
|
+
#blockPrecedes(a, b) {
|
|
2321
|
+
return serial32Precedes(a, b);
|
|
2396
2322
|
}
|
|
2397
|
-
/**
|
|
2323
|
+
/**
|
|
2324
|
+
* Blocks `a` lies behind `b` (0 when it does not precede it). Blocks vary
|
|
2325
|
+
* in length (K' = SSB_length <= K), so only a lower bound is known: the one
|
|
2326
|
+
* metric the decoder and the worker retire by too.
|
|
2327
|
+
*/
|
|
2398
2328
|
#blocksBehind(a, b, packetId) {
|
|
2399
|
-
|
|
2400
|
-
const k = this.kForPacket(packetId);
|
|
2401
|
-
return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlocksBehind(a, b, k) : Math.max(0, b - a);
|
|
2402
|
-
}
|
|
2403
|
-
/** The block after `sbn`, stepping to block 0 where SS_ID wraps. */
|
|
2404
|
-
#nextBlock(sbn, packetId) {
|
|
2405
|
-
const k = this.kForPacket(packetId);
|
|
2406
|
-
return isKStrideBlock(sbn, k) ? kStrideNextBlock(sbn, k) : sbn + 1;
|
|
2329
|
+
return isoBlocksBehindLowerBound(a, b, this.#trackConfig(packetId).k);
|
|
2407
2330
|
}
|
|
2408
2331
|
#materializeBlock(sbn, packetId, catalogTrackName, repairable) {
|
|
2409
2332
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
2410
2333
|
if (this.#blockResolutionFor(key) !== "untracked") return;
|
|
2411
2334
|
this.#materializedBlocks.add(key);
|
|
2412
|
-
this.#
|
|
2335
|
+
if (this.#hasCatalogTimingForPacket(packetId)) {
|
|
2336
|
+
this.#classifierFor(packetId, catalogTrackName).touch(sbn);
|
|
2337
|
+
}
|
|
2413
2338
|
this.onBlockVanished?.(sbn, packetId, catalogTrackName);
|
|
2414
2339
|
if (repairable) {
|
|
2415
2340
|
this.noteBlockActivity(sbn, packetId, catalogTrackName, true);
|
|
@@ -2448,21 +2373,39 @@ var FECManager = class _FECManager {
|
|
|
2448
2373
|
});
|
|
2449
2374
|
if (this.#lifecycleTrace.length > 64) this.#lifecycleTrace.splice(0, 32);
|
|
2450
2375
|
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2376
|
+
/**
|
|
2377
|
+
* A configured packetId's track config. There is no manager-wide default
|
|
2378
|
+
* (BLO-45953): an unconfigured packetId does no FEC work, so every caller
|
|
2379
|
+
* here is reached only behind a configuration gate and this throw marks a
|
|
2380
|
+
* broken invariant, never a packet-path outcome.
|
|
2381
|
+
*/
|
|
2382
|
+
#trackConfig(packetId) {
|
|
2383
|
+
const config = this.#trackConfigs.get(packetId);
|
|
2384
|
+
if (config === void 0) {
|
|
2385
|
+
throw new Error(
|
|
2386
|
+
`[FECManager] packetId=${packetId} was never configured; configure the track from its catalog (configureTrack/setFecWorker/setFecDecoder) first`
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
return config;
|
|
2459
2390
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2391
|
+
/**
|
|
2392
|
+
* Whether this track has the catalog's elapsed-time FEC policy. A zero span
|
|
2393
|
+
* is a deliberate no-deadline signal; it still leaves the wire geometry
|
|
2394
|
+
* usable for in-band decode, but it cannot drive classification, repair
|
|
2395
|
+
* fetches or layer adaptation.
|
|
2396
|
+
*/
|
|
2397
|
+
static #hasCatalogTiming(config) {
|
|
2398
|
+
return Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS;
|
|
2399
|
+
}
|
|
2400
|
+
#hasCatalogTimingForPacket(packetId) {
|
|
2401
|
+
const config = this.#trackConfigs.get(packetId);
|
|
2402
|
+
return config !== void 0 && _FECManager.#hasCatalogTiming(config);
|
|
2462
2403
|
}
|
|
2463
2404
|
#computeTimeout(config) {
|
|
2464
|
-
if (config.timeoutMs && config.timeoutMs > 0)
|
|
2465
|
-
|
|
2405
|
+
if (typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
|
|
2406
|
+
return config.timeoutMs;
|
|
2407
|
+
}
|
|
2408
|
+
if (Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
|
|
2466
2409
|
return Math.max(
|
|
2467
2410
|
this.#fecTimeoutMs,
|
|
2468
2411
|
computeFecDeliveryWindowMs({
|
|
@@ -2487,14 +2430,17 @@ var FECManager = class _FECManager {
|
|
|
2487
2430
|
*/
|
|
2488
2431
|
#refreshDeliveryGapWindow() {
|
|
2489
2432
|
let windowMs;
|
|
2490
|
-
const consider = (geometry) => {
|
|
2433
|
+
const consider = (geometry, packetId) => {
|
|
2434
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return;
|
|
2491
2435
|
if (geometry?.trackKind !== "video" || geometry.mode !== "classifying") return;
|
|
2492
2436
|
const { ticks, timescale } = geometry.keyframeInterval;
|
|
2493
2437
|
const ms = ticks / timescale * 1e3;
|
|
2494
2438
|
if (Number.isFinite(ms) && ms > 0 && (windowMs === void 0 || ms > windowMs)) windowMs = ms;
|
|
2495
2439
|
};
|
|
2496
|
-
for (const geometry of this.#streamGeometries
|
|
2497
|
-
|
|
2440
|
+
for (const [streamKey, geometry] of this.#streamGeometries) {
|
|
2441
|
+
consider(geometry, this.#parseStreamKey(streamKey).packetId);
|
|
2442
|
+
}
|
|
2443
|
+
for (const [packetId, config] of this.#trackConfigs) consider(config.geometry, packetId);
|
|
2498
2444
|
this.#deliveryGaps.setWindowMs(windowMs);
|
|
2499
2445
|
}
|
|
2500
2446
|
/**
|
|
@@ -2522,6 +2468,7 @@ var FECManager = class _FECManager {
|
|
|
2522
2468
|
return Math.max(config.timeoutMs, Math.ceil(config.interleaveDepthMs + gapMs));
|
|
2523
2469
|
}
|
|
2524
2470
|
#repairDeadlineMs(config, waitMs = this.#multicastWaitMs(config)) {
|
|
2471
|
+
if (!_FECManager.#hasCatalogTiming(config)) return waitMs;
|
|
2525
2472
|
const halfFailBoundMs = Math.ceil(waitMs / 2);
|
|
2526
2473
|
if (config.k > 0 && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS && config.fps !== void 0 && Number.isFinite(config.fps) && config.fps > 0) {
|
|
2527
2474
|
const mediaUnitMs = 1e3 / config.fps;
|
|
@@ -2536,10 +2483,12 @@ var FECManager = class _FECManager {
|
|
|
2536
2483
|
}
|
|
2537
2484
|
/**
|
|
2538
2485
|
* One block lifetime: the inactivity fail bound plus the D-28 repair grace,
|
|
2539
|
-
* at the widest wait the catalog allows (#maxMulticastWaitMs).
|
|
2540
|
-
*
|
|
2541
|
-
*
|
|
2542
|
-
*
|
|
2486
|
+
* at the widest wait the catalog allows (#maxMulticastWaitMs). For a track
|
|
2487
|
+
* without a catalog span this is receiver-local cleanup time only; no repair
|
|
2488
|
+
* grace is added. Everything derived from it (terminal retention, the
|
|
2489
|
+
* release gate's hold ceiling, the duplicate ledgers, the owed-cleanup
|
|
2490
|
+
* bound) is compared across time, so it must not follow the live
|
|
2491
|
+
* measurement.
|
|
2543
2492
|
*/
|
|
2544
2493
|
#blockLifetimeMs(config) {
|
|
2545
2494
|
const waitMs = this.#maxMulticastWaitMs(config);
|
|
@@ -2555,6 +2504,7 @@ var FECManager = class _FECManager {
|
|
|
2555
2504
|
return Math.min(waitMs + this.#repairGraceMs(config, waitMs), this.#blockLifetimeMs(config));
|
|
2556
2505
|
}
|
|
2557
2506
|
#repairGraceMs(config, waitMs) {
|
|
2507
|
+
if (!_FECManager.#hasCatalogTiming(config)) return 0;
|
|
2558
2508
|
return Math.max(1, waitMs - this.#repairDeadlineMs(config, waitMs));
|
|
2559
2509
|
}
|
|
2560
2510
|
#armBlockTimeout(sbn, packetId, catalogTrackName, delayMs) {
|
|
@@ -2641,51 +2591,72 @@ var FECManager = class _FECManager {
|
|
|
2641
2591
|
* stream; every other field stays per packetId.
|
|
2642
2592
|
*/
|
|
2643
2593
|
#upsertTrackConfig(packetId, config, catalogTrackName) {
|
|
2644
|
-
const
|
|
2594
|
+
const previousTrack = this.#trackConfigs.get(packetId);
|
|
2595
|
+
const k = config.k ?? previousTrack?.k;
|
|
2596
|
+
if (k === void 0 || !Number.isSafeInteger(k) || k <= 0) {
|
|
2597
|
+
throw new Error(
|
|
2598
|
+
`[FECManager] packetId=${packetId}: fec.k=${String(k)} must be a positive integer; configure the track from its catalog`
|
|
2599
|
+
);
|
|
2600
|
+
}
|
|
2601
|
+
const previous = previousTrack ?? {};
|
|
2645
2602
|
const streamScopedGeometry = catalogTrackName !== void 0 && config.geometry !== void 0 ? config.geometry : void 0;
|
|
2646
2603
|
if (streamScopedGeometry !== void 0) {
|
|
2647
2604
|
const { geometry: _streamGeometry, ...packetLevel } = config;
|
|
2648
2605
|
config = packetLevel;
|
|
2649
2606
|
}
|
|
2650
2607
|
const timingChanged = config.k !== void 0 || config.interleaveDepthMs !== void 0 || config.fps !== void 0;
|
|
2608
|
+
if (config.timeoutMs !== void 0 && (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)) {
|
|
2609
|
+
throw new Error(
|
|
2610
|
+
`[FECManager] packetId=${packetId}: timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2651
2613
|
if (config.blockProfile !== void 0) {
|
|
2652
2614
|
parseFecBlockProfile(config.blockProfile, `packetId=${packetId} fec.blockProfile`);
|
|
2653
2615
|
}
|
|
2654
2616
|
const blockProfile = config.blockProfile ?? previous.blockProfile;
|
|
2655
2617
|
if (blockProfile === void 0) {
|
|
2656
2618
|
throw new Error(
|
|
2657
|
-
`[FECManager] packetId=${packetId}: fec.blockProfile is required ("
|
|
2619
|
+
`[FECManager] packetId=${packetId}: fec.blockProfile is required ("iso-ssbg1-v1"); configure the track from its catalog`
|
|
2658
2620
|
);
|
|
2659
2621
|
}
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
);
|
|
2666
|
-
}
|
|
2622
|
+
const ownInterleaveDepthMs = config.interleaveDepthMs ?? previousTrack?.interleaveDepthMs ?? 0;
|
|
2623
|
+
if (!Number.isSafeInteger(ownInterleaveDepthMs) || ownInterleaveDepthMs !== 0 && ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
|
|
2624
|
+
throw new Error(
|
|
2625
|
+
`[FECManager] packetId=${packetId}: fec.interleaveDepthMs must be absent, 0, or a finite value >= ${MIN_INTERLEAVE_DEPTH_MS} ms (got ${String(ownInterleaveDepthMs)})`
|
|
2626
|
+
);
|
|
2667
2627
|
}
|
|
2628
|
+
const hasCatalogTiming = _FECManager.#hasCatalogTiming({ interleaveDepthMs: ownInterleaveDepthMs });
|
|
2668
2629
|
const next = {
|
|
2669
2630
|
blockProfile,
|
|
2670
|
-
k
|
|
2671
|
-
symbolSize: config.symbolSize != null && config.symbolSize > 0 ? config.symbolSize :
|
|
2672
|
-
interleaveDepthMs:
|
|
2673
|
-
fps: config.fps ?? previous.fps,
|
|
2674
|
-
timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs),
|
|
2631
|
+
k,
|
|
2632
|
+
symbolSize: config.symbolSize != null && config.symbolSize > 0 ? config.symbolSize : previousTrack?.symbolSize ?? 0,
|
|
2633
|
+
interleaveDepthMs: ownInterleaveDepthMs,
|
|
2634
|
+
fps: config.fps ?? previous.fps ?? this.#fecFps,
|
|
2635
|
+
timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs ?? 0),
|
|
2675
2636
|
...(config.trackKind ?? previous.trackKind) === void 0 ? {} : { trackKind: config.trackKind ?? previous.trackKind },
|
|
2676
2637
|
...(config.codec ?? previous.codec) === void 0 ? {} : { codec: config.codec ?? previous.codec },
|
|
2677
2638
|
...(config.geometry ?? previous.geometry) === void 0 ? {} : { geometry: config.geometry ?? previous.geometry }
|
|
2678
2639
|
};
|
|
2679
|
-
|
|
2640
|
+
if (!hasCatalogTiming) {
|
|
2641
|
+
next.timeoutMs = config.timeoutMs !== void 0 && config.timeoutMs > 0 ? config.timeoutMs : previousTrack !== void 0 && previousTrack.interleaveDepthMs === 0 ? previousTrack.timeoutMs : LOCAL_FEC_TIMEOUT_MS;
|
|
2642
|
+
} else {
|
|
2643
|
+
next.timeoutMs = this.#computeTimeout(next);
|
|
2644
|
+
}
|
|
2680
2645
|
if (this.#multicastSymbolDelivery === "ordered-relay") {
|
|
2681
2646
|
this.#orderedHorizonBlocksFor(packetId, next);
|
|
2682
2647
|
}
|
|
2683
|
-
|
|
2648
|
+
const timingPolicyChanged = _FECManager.#hasCatalogTiming({ interleaveDepthMs: previous.interleaveDepthMs ?? 0 }) !== hasCatalogTiming;
|
|
2649
|
+
if (timingPolicyChanged && !hasCatalogTiming) {
|
|
2650
|
+
for (const key of [...this.#repairDeadlines.keys()]) {
|
|
2651
|
+
if (this.#belongsToPacket(key, packetId)) this.#clearRepairDeadline(key);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
if (previous.blockProfile !== void 0 && previous.k !== next.k) {
|
|
2684
2655
|
this.#dropIsoLocators(packetId);
|
|
2685
2656
|
}
|
|
2686
2657
|
this.#trackConfigs.set(packetId, next);
|
|
2687
2658
|
this.#refreshDeliveryGapWindow();
|
|
2688
|
-
let policyChanged = config.geometry !== void 0 && JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry) || config.trackKind !== void 0 && config.trackKind !== previous.trackKind;
|
|
2659
|
+
let policyChanged = timingPolicyChanged || config.geometry !== void 0 && JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry) || config.trackKind !== void 0 && config.trackKind !== previous.trackKind;
|
|
2689
2660
|
if (streamScopedGeometry !== void 0) {
|
|
2690
2661
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
2691
2662
|
if (JSON.stringify(streamScopedGeometry) !== JSON.stringify(this.#streamGeometries.get(streamKey))) {
|
|
@@ -2700,20 +2671,14 @@ var FECManager = class _FECManager {
|
|
|
2700
2671
|
if (!this.#belongsToPacket(key, packetId)) continue;
|
|
2701
2672
|
const stream = this.#parseStreamKey(key);
|
|
2702
2673
|
const geometry = this.#geometryFor(stream.packetId, stream.catalogTrackName);
|
|
2703
|
-
if (JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
|
|
2674
|
+
if (hasCatalogTiming && JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
|
|
2704
2675
|
this.#classifiers.delete(key);
|
|
2676
|
+
if (!hasCatalogTiming) continue;
|
|
2705
2677
|
this.#applyClassifierChanges(stream.packetId, stream.catalogTrackName, classifier.retire());
|
|
2706
2678
|
}
|
|
2707
2679
|
}
|
|
2708
2680
|
return next;
|
|
2709
2681
|
}
|
|
2710
|
-
#normalizeSymbolData(packetId, data) {
|
|
2711
|
-
const symbolSize = this.symbolSizeForPacket(packetId);
|
|
2712
|
-
if (symbolSize <= 0 || data.byteLength === symbolSize) return data;
|
|
2713
|
-
const normalized = new Uint8Array(symbolSize);
|
|
2714
|
-
normalized.set(data.subarray(0, symbolSize));
|
|
2715
|
-
return normalized;
|
|
2716
|
-
}
|
|
2717
2682
|
#blockKeysFor(sbn, packetId, catalogTrackName) {
|
|
2718
2683
|
if (packetId != null && catalogTrackName !== void 0) {
|
|
2719
2684
|
return [this.#blockKey(sbn, packetId, catalogTrackName)];
|
|
@@ -2774,7 +2739,7 @@ var FECManager = class _FECManager {
|
|
|
2774
2739
|
...stats,
|
|
2775
2740
|
packetId,
|
|
2776
2741
|
blocksComplete: trackTotal,
|
|
2777
|
-
k: this.#
|
|
2742
|
+
k: this.#trackConfigs.get(packetId)?.k,
|
|
2778
2743
|
recoveryRate: trackAffected > 0 ? stats.blocksRecovered / trackAffected * 100 : 100,
|
|
2779
2744
|
packetLossRate: trackTotal > 0 ? trackAffected / trackTotal * 100 : 0
|
|
2780
2745
|
};
|
|
@@ -2786,7 +2751,7 @@ var FECManager = class _FECManager {
|
|
|
2786
2751
|
(block) => block.multicastSymbols > 0 && block.moqSymbols > 0
|
|
2787
2752
|
).length,
|
|
2788
2753
|
recentBlockPathCounts: Array.from(this.#blockPathCounts.values()).slice(-24),
|
|
2789
|
-
k: this.#
|
|
2754
|
+
k: this.#streamK,
|
|
2790
2755
|
// True recovery rate: of blocks that HAD loss, how many were recovered?
|
|
2791
2756
|
recoveryRate: affected > 0 ? s.blocksRecovered / affected * 100 : 100,
|
|
2792
2757
|
packetLossRate: total > 0 ? affected / total * 100 : 0,
|
|
@@ -2799,7 +2764,6 @@ var FECManager = class _FECManager {
|
|
|
2799
2764
|
},
|
|
2800
2765
|
blocks: stats.blocks.map((block) => ({ ...block })),
|
|
2801
2766
|
decoder: stats.decoder ? { ...stats.decoder } : void 0,
|
|
2802
|
-
altaFailedDetails: stats.altaFailedDetails?.map((detail) => ({ ...detail })),
|
|
2803
2767
|
packetId
|
|
2804
2768
|
})),
|
|
2805
2769
|
repairSymbolSizeMismatches: this.#repairSymbolSizeMismatchCount,
|
|
@@ -2906,6 +2870,7 @@ var FECManager = class _FECManager {
|
|
|
2906
2870
|
resetMap(this.#blockSourceEsis);
|
|
2907
2871
|
resetMap(this.#blockAuthoredLengths);
|
|
2908
2872
|
resetMap(this.#blockRepairRsIds);
|
|
2873
|
+
resetMap(this.#blockRsbLengths);
|
|
2909
2874
|
resetMap(this.#blockPathCounts);
|
|
2910
2875
|
resetMap(this.#blockAnchorsMs);
|
|
2911
2876
|
resetMap(this.#blockCatalogTrackNames);
|
|
@@ -3023,6 +2988,7 @@ var FECManager = class _FECManager {
|
|
|
3023
2988
|
dropMap(this.#blockSourceEsis);
|
|
3024
2989
|
dropMap(this.#blockAuthoredLengths);
|
|
3025
2990
|
dropMap(this.#blockRepairRsIds);
|
|
2991
|
+
dropMap(this.#blockRsbLengths);
|
|
3026
2992
|
dropMap(this.#blockPathCounts);
|
|
3027
2993
|
dropMap(this.#blockAnchorsMs);
|
|
3028
2994
|
dropMap(this.#blockCatalogTrackNames);
|
|
@@ -3081,15 +3047,20 @@ var FECManager = class _FECManager {
|
|
|
3081
3047
|
}
|
|
3082
3048
|
}
|
|
3083
3049
|
}
|
|
3084
|
-
/**
|
|
3050
|
+
/**
|
|
3051
|
+
* Record a transport symbol and reject later duplicates of the same
|
|
3052
|
+
* packetId/SBN/ESI. `sbn` is the block's SS_Start. A packetId with no
|
|
3053
|
+
* configured block profile has no block geometry: its symbol is refused
|
|
3054
|
+
* (false) and no block state is created for it.
|
|
3055
|
+
*/
|
|
3085
3056
|
feedSymbolFromSource(packetId, sbn, esi, _data, source, catalogTrackName) {
|
|
3057
|
+
if (!this.#hasBlockProfile(packetId)) return false;
|
|
3086
3058
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
3087
3059
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
3088
3060
|
if (source === "multicast" && this.#multicastSymbolDelivery === "ordered-relay") {
|
|
3089
3061
|
this.#advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName);
|
|
3090
3062
|
}
|
|
3091
|
-
const
|
|
3092
|
-
const sourceSymbols = this.#isIso(packetId) ? this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? k : k;
|
|
3063
|
+
const sourceSymbols = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? this.#trackConfig(packetId).k;
|
|
3093
3064
|
const isSourceSymbol = esi >= 0 && esi < sourceSymbols;
|
|
3094
3065
|
const isStartupPartial = this.#startupPartialBlocks.has(blockKey);
|
|
3095
3066
|
const isReadyMulticastRepair = this.#startupRequireRepairReady && source === "multicast" && !isSourceSymbol && this.#startupRepairReadyPackets.has(streamKey);
|
|
@@ -3207,14 +3178,14 @@ var FECManager = class _FECManager {
|
|
|
3207
3178
|
for (const [key] of this.#activeBlocks) {
|
|
3208
3179
|
const { packetId, sbn } = this.#parseBlockKey(key);
|
|
3209
3180
|
const count = this.#blockSourceCounts.get(key) ?? 0;
|
|
3210
|
-
const k = this.#blockAuthoredLengths.get(key) ?? this
|
|
3181
|
+
const k = this.#blockAuthoredLengths.get(key) ?? this.#trackConfig(packetId).k;
|
|
3211
3182
|
blocks.push({ sbn, packetId, state: "collecting", sourceCount: count, k, progress: k > 0 ? Math.min(count / k, 1) : 0 });
|
|
3212
3183
|
}
|
|
3213
3184
|
const completed = [...this.#completedBlocks].slice(-16);
|
|
3214
3185
|
for (const key of completed) {
|
|
3215
3186
|
const { packetId, sbn } = this.#parseBlockKey(key);
|
|
3216
3187
|
if (!this.#activeBlocks.has(key)) {
|
|
3217
|
-
const k = this
|
|
3188
|
+
const k = this.#trackConfig(packetId).k;
|
|
3218
3189
|
blocks.push({ sbn, packetId, state: "complete", sourceCount: k, k, progress: 1 });
|
|
3219
3190
|
}
|
|
3220
3191
|
}
|
|
@@ -3225,25 +3196,23 @@ var FECManager = class _FECManager {
|
|
|
3225
3196
|
get wasmDecoder() {
|
|
3226
3197
|
return this.#fecDecoders.values().next().value?.client ?? null;
|
|
3227
3198
|
}
|
|
3228
|
-
|
|
3229
|
-
return this.#fecK;
|
|
3230
|
-
}
|
|
3199
|
+
/** A configured packetId's K; undefined for an unconfigured one (no FEC work). */
|
|
3231
3200
|
kForPacket(packetId) {
|
|
3232
|
-
return this.#
|
|
3201
|
+
return this.#trackConfigs.get(packetId)?.k;
|
|
3233
3202
|
}
|
|
3234
3203
|
/** FEC symbol size in bytes. */
|
|
3235
3204
|
get symbolSize() {
|
|
3236
3205
|
return this.#fecSymbolSize;
|
|
3237
3206
|
}
|
|
3238
3207
|
symbolSizeForPacket(packetId) {
|
|
3239
|
-
return this.#
|
|
3208
|
+
return this.#trackConfigs.get(packetId)?.symbolSize;
|
|
3240
3209
|
}
|
|
3241
3210
|
/** FEC block span in milliseconds (0 = no interleaving). */
|
|
3242
3211
|
get interleaveDepthMs() {
|
|
3243
3212
|
return this.#fecInterleaveDepthMs;
|
|
3244
3213
|
}
|
|
3245
3214
|
interleaveDepthMsForPacket(packetId) {
|
|
3246
|
-
return this.#
|
|
3215
|
+
return this.#trackConfigs.get(packetId)?.interleaveDepthMs;
|
|
3247
3216
|
}
|
|
3248
3217
|
/**
|
|
3249
3218
|
* Record one multicast MMTP packet arrival (every packet of the leg, FEC
|
|
@@ -3261,7 +3230,8 @@ var FECManager = class _FECManager {
|
|
|
3261
3230
|
* plus the larger of the catalog tolerance and the measured delivery gap.
|
|
3262
3231
|
*/
|
|
3263
3232
|
timeoutMsForPacket(packetId) {
|
|
3264
|
-
|
|
3233
|
+
const config = this.#trackConfigs.get(packetId);
|
|
3234
|
+
return config === void 0 ? void 0 : this.#multicastWaitMs(config);
|
|
3265
3235
|
}
|
|
3266
3236
|
/**
|
|
3267
3237
|
* Largest multicast delivery gap measured over the current window (ms), and
|
|
@@ -3273,6 +3243,31 @@ var FECManager = class _FECManager {
|
|
|
3273
3243
|
windowMs: this.#deliveryGaps.windowMs
|
|
3274
3244
|
};
|
|
3275
3245
|
}
|
|
3246
|
+
/**
|
|
3247
|
+
* An open block's accepted sources, distinct repair RS_IDs, authored K'
|
|
3248
|
+
* (SSB_length) and RSB_length (each undefined until a repair stated it).
|
|
3249
|
+
* Undefined for a block that is not open.
|
|
3250
|
+
*/
|
|
3251
|
+
blockSymbolCounts(sbn, packetId, catalogTrackName) {
|
|
3252
|
+
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
3253
|
+
if (!this.#activeBlocks.has(key)) return void 0;
|
|
3254
|
+
return {
|
|
3255
|
+
sources: this.#blockSourceCounts.get(key) ?? 0,
|
|
3256
|
+
repairs: this.#blockRepairRsIds.get(key)?.size ?? 0,
|
|
3257
|
+
authoredLength: this.#blockAuthoredLengths.get(key),
|
|
3258
|
+
repairGroupLength: this.#blockRsbLengths.get(key)
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
/**
|
|
3262
|
+
* Record a block's RSB_length from one of its repair symbols (the router
|
|
3263
|
+
* parses it from the Repair FEC Payload ID). The largest stated value wins.
|
|
3264
|
+
*/
|
|
3265
|
+
noteRepairGroupLength(sbn, packetId, rsbLength, catalogTrackName) {
|
|
3266
|
+
if (!Number.isSafeInteger(rsbLength) || rsbLength <= 0) return;
|
|
3267
|
+
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
3268
|
+
if (!this.#activeBlocks.has(key)) return;
|
|
3269
|
+
if ((this.#blockRsbLengths.get(key) ?? 0) < rsbLength) this.#blockRsbLengths.set(key, rsbLength);
|
|
3270
|
+
}
|
|
3276
3271
|
/**
|
|
3277
3272
|
* Receiver-local time of a block's first accepted symbol, or undefined for
|
|
3278
3273
|
* a block no symbol reached (one materialized because a later SBN arrived).
|
|
@@ -3317,27 +3312,31 @@ var FECManager = class _FECManager {
|
|
|
3317
3312
|
}
|
|
3318
3313
|
/** Resolved per-track D-28 repair request deadline in milliseconds. */
|
|
3319
3314
|
repairDeadlineMsForPacket(packetId) {
|
|
3320
|
-
|
|
3315
|
+
const config = this.#trackConfigs.get(packetId);
|
|
3316
|
+
return config === void 0 ? void 0 : this.#repairDeadlineMs(config);
|
|
3321
3317
|
}
|
|
3322
3318
|
/**
|
|
3323
3319
|
* One block lifetime: the longest a block stays open after its last symbol
|
|
3324
3320
|
* (inactivity fail bound plus D-28 repair grace).
|
|
3325
3321
|
*/
|
|
3326
3322
|
blockLifetimeMsForPacket(packetId) {
|
|
3327
|
-
|
|
3323
|
+
const config = this.#trackConfigs.get(packetId);
|
|
3324
|
+
return config === void 0 ? void 0 : this.#blockLifetimeMs(config);
|
|
3328
3325
|
}
|
|
3329
3326
|
/**
|
|
3330
3327
|
* How long a terminal (clean, recovered or failed) block stays authoritative,
|
|
3331
3328
|
* for late symbols and for the in-order release gate. It is also the gate's
|
|
3332
3329
|
* hold ceiling (MmtpRouter's holdLimitMs): a retired block reads back as
|
|
3333
3330
|
* untracked, which the gate counts as lost, so no frame may still be waiting
|
|
3334
|
-
* on a block that can retire.
|
|
3335
|
-
*
|
|
3336
|
-
*
|
|
3337
|
-
*
|
|
3331
|
+
* on a block that can retire. With a positive catalog span the window is one
|
|
3332
|
+
* interleave depth for a gap's last block to finish arriving, one block
|
|
3333
|
+
* lifetime to fail it, and one more for an owed Worker cleanup. With an
|
|
3334
|
+
* absent/zero span it is a receiver-local cleanup bound only; it creates no
|
|
3335
|
+
* catalog FEC deadline.
|
|
3338
3336
|
*/
|
|
3339
3337
|
terminalRetentionMsForPacket(packetId) {
|
|
3340
|
-
const config = this.#
|
|
3338
|
+
const config = this.#trackConfigs.get(packetId);
|
|
3339
|
+
if (config === void 0) return void 0;
|
|
3341
3340
|
return config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
|
|
3342
3341
|
}
|
|
3343
3342
|
/** Remaining fetch budget for a specific repair-requested block. */
|
|
@@ -3348,7 +3347,8 @@ var FECManager = class _FECManager {
|
|
|
3348
3347
|
if (deadlineMs !== void 0) {
|
|
3349
3348
|
return Math.max(1, Math.ceil(deadlineMs - performance.now()));
|
|
3350
3349
|
}
|
|
3351
|
-
const trackConfig = this.#
|
|
3350
|
+
const trackConfig = this.#trackConfigs.get(packetId);
|
|
3351
|
+
if (trackConfig === void 0) return void 0;
|
|
3352
3352
|
return Math.max(1, trackConfig.timeoutMs - this.#repairDeadlineMs(trackConfig));
|
|
3353
3353
|
}
|
|
3354
3354
|
/** True once configureTrack/setFecDecoder/setFecWorker configured this packetId. */
|
|
@@ -3359,14 +3359,19 @@ var FECManager = class _FECManager {
|
|
|
3359
3359
|
blockProfileForPacket(packetId) {
|
|
3360
3360
|
return this.#trackConfigs.get(packetId)?.blockProfile;
|
|
3361
3361
|
}
|
|
3362
|
-
|
|
3363
|
-
|
|
3362
|
+
/**
|
|
3363
|
+
* Whether a packetId has a configured block geometry. iso-ssbg1-v1 is the
|
|
3364
|
+
* only profile, so every configured track has it; a packetId never
|
|
3365
|
+
* configured (or released) has none and gets no FEC block tracking.
|
|
3366
|
+
*/
|
|
3367
|
+
#hasBlockProfile(packetId) {
|
|
3368
|
+
return this.#trackConfigs.get(packetId)?.blockProfile !== void 0;
|
|
3364
3369
|
}
|
|
3365
3370
|
#isoLocatorFor(packetId, catalogTrackName) {
|
|
3366
3371
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
3367
3372
|
let locator = this.#isoLocators.get(streamKey);
|
|
3368
3373
|
if (!locator) {
|
|
3369
|
-
locator = new IsoBlockLocator(this
|
|
3374
|
+
locator = new IsoBlockLocator(this.#trackConfig(packetId).k, this.#isoRetentionBlocks(packetId));
|
|
3370
3375
|
this.#isoLocators.set(streamKey, locator);
|
|
3371
3376
|
}
|
|
3372
3377
|
return locator;
|
|
@@ -3397,12 +3402,14 @@ var FECManager = class _FECManager {
|
|
|
3397
3402
|
*/
|
|
3398
3403
|
#isoRetentionBlocks(packetId) {
|
|
3399
3404
|
const config = this.#trackConfigs.get(packetId);
|
|
3400
|
-
if (config?.blockProfile
|
|
3405
|
+
if (config?.blockProfile === void 0) {
|
|
3401
3406
|
throw new Error(
|
|
3402
|
-
`[FECManager] packetId=${packetId} has no iso-ssbg1-v1 block
|
|
3407
|
+
`[FECManager] packetId=${packetId} has no configured iso-ssbg1-v1 block profile`
|
|
3403
3408
|
);
|
|
3404
3409
|
}
|
|
3405
|
-
|
|
3410
|
+
if (!_FECManager.#hasCatalogTiming(config)) return LOCAL_FEC_RETENTION_BLOCKS;
|
|
3411
|
+
const terminalRetentionMs = config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
|
|
3412
|
+
const blocks = Math.ceil(terminalRetentionMs / config.interleaveDepthMs) + 1;
|
|
3406
3413
|
if (!Number.isSafeInteger(blocks) || blocks <= 0) {
|
|
3407
3414
|
throw new Error(`[FECManager] packetId=${packetId} iso-ssbg1-v1 retention of ${blocks} blocks is not derivable`);
|
|
3408
3415
|
}
|
|
@@ -3410,7 +3417,8 @@ var FECManager = class _FECManager {
|
|
|
3410
3417
|
}
|
|
3411
3418
|
/** Whether an iso-ssbg1-v1 protected packet fits one ssbg_mode1 symbol (len + 2 <= T). */
|
|
3412
3419
|
isoSourceFits(packetId, packetLength) {
|
|
3413
|
-
|
|
3420
|
+
const symbolSize = this.#trackConfigs.get(packetId)?.symbolSize;
|
|
3421
|
+
return symbolSize !== void 0 && ssbg1FitsSymbol(packetLength, symbolSize);
|
|
3414
3422
|
}
|
|
3415
3423
|
/**
|
|
3416
3424
|
* Locate an iso-ssbg1-v1 source by range membership in a block a repair
|
|
@@ -3471,11 +3479,11 @@ var FECManager = class _FECManager {
|
|
|
3471
3479
|
* that could hold them is pending or not yet stated by a repair, else the
|
|
3472
3480
|
* starts of the blocks that failed or vanished.
|
|
3473
3481
|
*
|
|
3474
|
-
* `untracked` reads
|
|
3475
|
-
*
|
|
3476
|
-
*
|
|
3477
|
-
*
|
|
3478
|
-
* an SS_Start
|
|
3482
|
+
* `untracked` reads as the release gate reads it (MmtpRouter
|
|
3483
|
+
* #createFecReleaseGate): a block holding either released frame's SS_ID
|
|
3484
|
+
* (`fromSsId` or `toSsId`) delivered that frame, so untracked there means it
|
|
3485
|
+
* resolved and retired; a block starting after the stream's frontier
|
|
3486
|
+
* (`highestSbn`, an SS_Start) may still be heard from; a block at or behind the
|
|
3479
3487
|
* frontier that no symbol of this manager ever reached vanished, and its
|
|
3480
3488
|
* media is lost.
|
|
3481
3489
|
*/
|
|
@@ -3543,6 +3551,7 @@ var FECManager = class _FECManager {
|
|
|
3543
3551
|
}
|
|
3544
3552
|
/** Current classifier mode of a stream. */
|
|
3545
3553
|
classifierModeFor(packetId, catalogTrackName) {
|
|
3554
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return { mode: "all-protected", reason: null };
|
|
3546
3555
|
const classifier = this.#classifierFor(packetId, catalogTrackName);
|
|
3547
3556
|
return { mode: classifier.mode, reason: classifier.reason };
|
|
3548
3557
|
}
|
|
@@ -3552,10 +3561,12 @@ var FECManager = class _FECManager {
|
|
|
3552
3561
|
* it does not. A stream with no classifier yet has shown nothing against it.
|
|
3553
3562
|
*/
|
|
3554
3563
|
mpuBoundsRapsFor(packetId, catalogTrackName) {
|
|
3564
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return false;
|
|
3555
3565
|
return this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))?.mpuBoundsRaps ?? true;
|
|
3556
3566
|
}
|
|
3557
3567
|
/** A block's class now, or frozen at its resolution. */
|
|
3558
3568
|
blockClassFor(sbn, packetId, catalogTrackName) {
|
|
3569
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return "unknown";
|
|
3559
3570
|
const frozen = this.#resolvedBlockClasses.get(this.#blockKey(sbn, packetId, catalogTrackName));
|
|
3560
3571
|
if (frozen !== void 0) return frozen;
|
|
3561
3572
|
return this.#peekClass(sbn, packetId, catalogTrackName);
|
|
@@ -3577,7 +3588,7 @@ var FECManager = class _FECManager {
|
|
|
3577
3588
|
* invariant (consecutive MPUs exactly one keyframe interval apart).
|
|
3578
3589
|
*/
|
|
3579
3590
|
noteReleasedRap(packetId, catalogTrackName, mpu, presentationTime, mediaTimescale) {
|
|
3580
|
-
if (!this.#trackConfigs.has(packetId)) return;
|
|
3591
|
+
if (!this.#trackConfigs.has(packetId) || !this.#hasCatalogTimingForPacket(packetId)) return;
|
|
3581
3592
|
this.#applyClassifierChanges(
|
|
3582
3593
|
packetId,
|
|
3583
3594
|
catalogTrackName,
|
|
@@ -3593,27 +3604,34 @@ var FECManager = class _FECManager {
|
|
|
3593
3604
|
// -----------------------------------------------------------------------
|
|
3594
3605
|
/** Configure FEC parameters. */
|
|
3595
3606
|
configure(config) {
|
|
3596
|
-
if (config.
|
|
3607
|
+
if (config.interleaveDepthMs != null && (!Number.isSafeInteger(config.interleaveDepthMs) || config.interleaveDepthMs < 0)) {
|
|
3608
|
+
throw new Error(
|
|
3609
|
+
`[FECManager] interleaveDepthMs=${config.interleaveDepthMs} must be a non-negative safe integer in milliseconds`
|
|
3610
|
+
);
|
|
3611
|
+
}
|
|
3612
|
+
if (config.timeoutMs != null && (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)) {
|
|
3613
|
+
throw new Error(
|
|
3614
|
+
`[FECManager] timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3617
|
+
if (config.k != null && (!Number.isSafeInteger(config.k) || config.k <= 0)) {
|
|
3618
|
+
throw new Error(`[FECManager] k=${config.k} must be a positive integer`);
|
|
3619
|
+
}
|
|
3620
|
+
if (config.k != null) this.#streamK = config.k;
|
|
3597
3621
|
if (config.symbolSize != null && config.symbolSize > 0) this.#fecSymbolSize = config.symbolSize;
|
|
3598
3622
|
if (config.interleaveDepthMs != null) this.#fecInterleaveDepthMs = config.interleaveDepthMs;
|
|
3599
3623
|
if (config.fps != null && Number.isFinite(config.fps) && config.fps > 0) {
|
|
3600
3624
|
this.#fecFps = config.fps;
|
|
3601
3625
|
}
|
|
3602
|
-
if (config.interleaveDepthMs != null && config.interleaveDepthMs > 0 && config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
|
|
3603
|
-
console.warn(`[FECManager] interleaveDepthMs=${config.interleaveDepthMs} looks like frame count, not milliseconds (\xA75.1 requires ms). Expected range: ${MIN_INTERLEAVE_DEPTH_MS}-10000ms.`);
|
|
3604
|
-
}
|
|
3605
3626
|
if (config.timeoutMs != null && config.timeoutMs > 0) {
|
|
3606
3627
|
this.#fecTimeoutMs = config.timeoutMs;
|
|
3607
|
-
} else if (config.interleaveDepthMs
|
|
3608
|
-
this.#fecTimeoutMs =
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
symbolSize: this.#fecSymbolSize,
|
|
3612
|
-
mediaUnitsPerSecond: config.fps
|
|
3613
|
-
});
|
|
3628
|
+
} else if (config.interleaveDepthMs === 0) {
|
|
3629
|
+
this.#fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS;
|
|
3630
|
+
} else if (config.interleaveDepthMs != null && Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
|
|
3631
|
+
this.#fecTimeoutMs = fecSpanDeliveryWindowMs(config.interleaveDepthMs);
|
|
3614
3632
|
}
|
|
3615
3633
|
for (const [packetId, track] of this.#trackConfigs) {
|
|
3616
|
-
if (track.timeoutMs < this.#fecTimeoutMs) {
|
|
3634
|
+
if (_FECManager.#hasCatalogTiming(track) && track.timeoutMs < this.#fecTimeoutMs) {
|
|
3617
3635
|
this.#trackConfigs.set(packetId, { ...track, timeoutMs: this.#fecTimeoutMs });
|
|
3618
3636
|
}
|
|
3619
3637
|
}
|
|
@@ -3672,7 +3690,7 @@ var FECManager = class _FECManager {
|
|
|
3672
3690
|
);
|
|
3673
3691
|
}
|
|
3674
3692
|
if (config) this.#upsertTrackConfig(packetId, config, catalogTrackName);
|
|
3675
|
-
const workerK = this.#
|
|
3693
|
+
const workerK = this.#trackConfig(packetId).k;
|
|
3676
3694
|
this.#workerClients.set(streamKey, client);
|
|
3677
3695
|
this.#releasedWorkerClients.delete(client);
|
|
3678
3696
|
client.onFrame((data, meta) => {
|
|
@@ -3852,7 +3870,7 @@ var FECManager = class _FECManager {
|
|
|
3852
3870
|
return;
|
|
3853
3871
|
}
|
|
3854
3872
|
this.#settleOwedCleanup(key, "cleanup-ack-timeout");
|
|
3855
|
-
}, Math.ceil(this.#liveBlockLifetimeMs(this.#
|
|
3873
|
+
}, Math.ceil(this.#liveBlockLifetimeMs(this.#trackConfig(packetId))))
|
|
3856
3874
|
};
|
|
3857
3875
|
this.#pendingWorkerCleanupBounds.set(key, bound);
|
|
3858
3876
|
}
|
|
@@ -3893,16 +3911,11 @@ var FECManager = class _FECManager {
|
|
|
3893
3911
|
if (this.#fpsDetected || fps <= 0) return;
|
|
3894
3912
|
this.#fpsDetected = true;
|
|
3895
3913
|
this.#fecFps = fps;
|
|
3896
|
-
if (this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
|
|
3897
|
-
this.#fecTimeoutMs =
|
|
3898
|
-
interleaveDepthMs: this.#fecInterleaveDepthMs,
|
|
3899
|
-
sourceSymbols: this.#fecK,
|
|
3900
|
-
symbolSize: this.#fecSymbolSize,
|
|
3901
|
-
mediaUnitsPerSecond: fps
|
|
3902
|
-
});
|
|
3914
|
+
if (Number.isSafeInteger(this.#fecInterleaveDepthMs) && this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
|
|
3915
|
+
this.#fecTimeoutMs = fecSpanDeliveryWindowMs(this.#fecInterleaveDepthMs);
|
|
3903
3916
|
}
|
|
3904
3917
|
for (const [packetId, config] of this.#trackConfigs) {
|
|
3905
|
-
if (config.fps !== void 0 || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue;
|
|
3918
|
+
if (config.fps !== void 0 || !Number.isSafeInteger(config.interleaveDepthMs) || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue;
|
|
3906
3919
|
this.#trackConfigs.set(packetId, {
|
|
3907
3920
|
...config,
|
|
3908
3921
|
fps,
|
|
@@ -3931,51 +3944,33 @@ var FECManager = class _FECManager {
|
|
|
3931
3944
|
* Caller is responsible for routing recovered data and updating
|
|
3932
3945
|
* block completion state via `markBlockCompleted()`.
|
|
3933
3946
|
*/
|
|
3934
|
-
feedSourceSymbol(packetId, ssId, symbolData,
|
|
3947
|
+
feedSourceSymbol(packetId, ssId, symbolData, catalogTrackName) {
|
|
3935
3948
|
this.#fecStats.sourcePackets++;
|
|
3936
3949
|
this.#statsForPacket(packetId).sourcePackets++;
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
`[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; hold it until a repair places it (placeIsoRepair)`
|
|
3945
|
-
);
|
|
3946
|
-
}
|
|
3947
|
-
locator.observe(ssId);
|
|
3948
|
-
if (!ssbg1FitsSymbol(symbolData.byteLength, this.symbolSizeForPacket(packetId))) {
|
|
3949
|
-
this.#isoSourceOversizeCount++;
|
|
3950
|
-
return null;
|
|
3951
|
-
}
|
|
3952
|
-
sbn = located.block.start;
|
|
3953
|
-
fecData = symbolData;
|
|
3954
|
-
} else {
|
|
3955
|
-
fecData = this.#normalizeSymbolData(packetId, symbolData);
|
|
3956
|
-
sbn = Math.floor(ssId / this.kForPacket(packetId));
|
|
3950
|
+
if (!this.#hasBlockProfile(packetId)) return null;
|
|
3951
|
+
const locator = this.#isoLocatorFor(packetId, catalogTrackName);
|
|
3952
|
+
const located = locator.locate(ssId);
|
|
3953
|
+
if (located === null) {
|
|
3954
|
+
throw new Error(
|
|
3955
|
+
`[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; hold it until a repair places it (placeIsoRepair)`
|
|
3956
|
+
);
|
|
3957
3957
|
}
|
|
3958
|
+
locator.observe(ssId);
|
|
3959
|
+
if (!ssbg1FitsSymbol(symbolData.byteLength, this.#trackConfig(packetId).symbolSize)) {
|
|
3960
|
+
this.#isoSourceOversizeCount++;
|
|
3961
|
+
return null;
|
|
3962
|
+
}
|
|
3963
|
+
const sbn = located.block.start;
|
|
3958
3964
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
3959
3965
|
const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey);
|
|
3960
3966
|
const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName);
|
|
3961
3967
|
if (workerClient && !syncBlockOwner) {
|
|
3962
3968
|
if (!this.#claimDecoderBlock(workerClient, sbn, catalogTrackName)) return null;
|
|
3963
|
-
const buf =
|
|
3964
|
-
|
|
3965
|
-
|
|
3969
|
+
const buf = symbolData.buffer.slice(
|
|
3970
|
+
symbolData.byteOffset,
|
|
3971
|
+
symbolData.byteOffset + symbolData.byteLength
|
|
3966
3972
|
);
|
|
3967
|
-
|
|
3968
|
-
const authBuf = alta.auth.buffer.slice(
|
|
3969
|
-
alta.auth.byteOffset,
|
|
3970
|
-
alta.auth.byteOffset + alta.auth.byteLength
|
|
3971
|
-
);
|
|
3972
|
-
workerClient.feedSource(ssId, buf, performance.now(), {
|
|
3973
|
-
auth: authBuf,
|
|
3974
|
-
payloadEnd: alta.payloadEnd
|
|
3975
|
-
});
|
|
3976
|
-
} else {
|
|
3977
|
-
workerClient.feedSource(ssId, buf, performance.now());
|
|
3978
|
-
}
|
|
3973
|
+
workerClient.feedSource(ssId, buf, performance.now());
|
|
3979
3974
|
return null;
|
|
3980
3975
|
}
|
|
3981
3976
|
const entry = this.#decoderForPacket(packetId, catalogTrackName);
|
|
@@ -3985,7 +3980,7 @@ var FECManager = class _FECManager {
|
|
|
3985
3980
|
this.#syncDecoderBlockOwners.set(blockKey, client);
|
|
3986
3981
|
let recovered = null;
|
|
3987
3982
|
try {
|
|
3988
|
-
recovered = client.add_source(ssId,
|
|
3983
|
+
recovered = client.add_source(ssId, symbolData, performance.now());
|
|
3989
3984
|
} catch {
|
|
3990
3985
|
}
|
|
3991
3986
|
this.#retireIsoUnplaced(client, packetId, catalogTrackName);
|
|
@@ -4004,7 +3999,8 @@ var FECManager = class _FECManager {
|
|
|
4004
3999
|
feedRepairSymbol(packetId, ssStart, ssbLength, rsId, repairData, catalogTrackName) {
|
|
4005
4000
|
this.#fecStats.repairPackets++;
|
|
4006
4001
|
this.#statsForPacket(packetId).repairPackets++;
|
|
4007
|
-
|
|
4002
|
+
if (!this.#hasBlockProfile(packetId)) return null;
|
|
4003
|
+
const symbolSize = this.#trackConfig(packetId).symbolSize;
|
|
4008
4004
|
if (symbolSize > 0 && repairData.byteLength !== symbolSize) {
|
|
4009
4005
|
this.#repairSymbolSizeMismatchCount++;
|
|
4010
4006
|
if (this.#repairSymbolSizeMismatchCount <= 5 || this.#repairSymbolSizeMismatchCount % 100 === 0) {
|
|
@@ -4015,18 +4011,12 @@ var FECManager = class _FECManager {
|
|
|
4015
4011
|
return null;
|
|
4016
4012
|
}
|
|
4017
4013
|
const fecData = repairData;
|
|
4018
|
-
const k = this
|
|
4019
|
-
|
|
4020
|
-
if (
|
|
4021
|
-
|
|
4022
|
-
if (ssbLength <= 0 || ssbLength > k || block === void 0 || block.span || block.length !== ssbLength) {
|
|
4023
|
-
return null;
|
|
4024
|
-
}
|
|
4025
|
-
sbn = ssStart;
|
|
4026
|
-
} else {
|
|
4027
|
-
if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null;
|
|
4028
|
-
sbn = Math.floor(ssStart / k);
|
|
4014
|
+
const k = this.#trackConfig(packetId).k;
|
|
4015
|
+
const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart);
|
|
4016
|
+
if (ssbLength <= 0 || ssbLength > k || block === void 0 || block.span || block.length !== ssbLength) {
|
|
4017
|
+
return null;
|
|
4029
4018
|
}
|
|
4019
|
+
const sbn = ssStart;
|
|
4030
4020
|
this.#noteSbnSeen(sbn, packetId, catalogTrackName, "repair");
|
|
4031
4021
|
this.noteBlockActivity(sbn, packetId, catalogTrackName);
|
|
4032
4022
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
@@ -4064,8 +4054,9 @@ var FECManager = class _FECManager {
|
|
|
4064
4054
|
* Record a block's authored SSB_length from a repair symbol. Returns false,
|
|
4065
4055
|
* leaving the block unchanged, when the symbol contradicts the block: a
|
|
4066
4056
|
* different SSB_length than an earlier repair symbol declared is dropped,
|
|
4067
|
-
* and an SSB_length that places an already-received source ESI
|
|
4068
|
-
*
|
|
4057
|
+
* and an SSB_length that places an already-received source ESI at or past
|
|
4058
|
+
* the block's end fails the block, since its geometry can no longer be
|
|
4059
|
+
* trusted.
|
|
4069
4060
|
*/
|
|
4070
4061
|
#noteAuthoredLength(blockKey, sbn, packetId, ssbLength, catalogTrackName) {
|
|
4071
4062
|
const known = this.#blockAuthoredLengths.get(blockKey);
|
|
@@ -4132,7 +4123,7 @@ var FECManager = class _FECManager {
|
|
|
4132
4123
|
* a sweep tied to expiry alone would never run there.
|
|
4133
4124
|
*/
|
|
4134
4125
|
#retireIsoUnplaced(decoder, packetId, catalogTrackName) {
|
|
4135
|
-
if (!this.#
|
|
4126
|
+
if (!this.#hasBlockProfile(packetId)) return;
|
|
4136
4127
|
const newest = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newest;
|
|
4137
4128
|
if (newest === void 0) return;
|
|
4138
4129
|
try {
|
|
@@ -4177,11 +4168,13 @@ var FECManager = class _FECManager {
|
|
|
4177
4168
|
this.#failBlock(sbn, packetId, catalogTrackName);
|
|
4178
4169
|
return;
|
|
4179
4170
|
}
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4171
|
+
if (_FECManager.#hasCatalogTiming(trackConfig)) {
|
|
4172
|
+
this.#applyClassifierChanges(
|
|
4173
|
+
packetId,
|
|
4174
|
+
catalogTrackName,
|
|
4175
|
+
this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence)
|
|
4176
|
+
);
|
|
4177
|
+
}
|
|
4185
4178
|
const count = (this.#blockSourceCounts.get(key) ?? 0) + 1;
|
|
4186
4179
|
this.#blockSourceCounts.set(key, count);
|
|
4187
4180
|
if (count === 1) this.#indexBlockKey(key, sbn, packetId);
|
|
@@ -4214,6 +4207,7 @@ var FECManager = class _FECManager {
|
|
|
4214
4207
|
this.#blockSourceEsis.delete(key);
|
|
4215
4208
|
this.#blockAuthoredLengths.delete(key);
|
|
4216
4209
|
this.#blockRepairRsIds.delete(key);
|
|
4210
|
+
this.#blockRsbLengths.delete(key);
|
|
4217
4211
|
this.#markTerminal(key, sbn, packetId, true);
|
|
4218
4212
|
this.#markPathBlockComplete(sbn, packetId, catalogTrackName);
|
|
4219
4213
|
if (catalogTrackName === void 0) {
|
|
@@ -4225,6 +4219,7 @@ var FECManager = class _FECManager {
|
|
|
4225
4219
|
}
|
|
4226
4220
|
/** Refresh deadlines after accepted block activity. */
|
|
4227
4221
|
noteBlockActivity(sbn, packetId, catalogTrackName, sourceProgress = false) {
|
|
4222
|
+
if (!this.#trackConfigs.has(packetId)) return;
|
|
4228
4223
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
4229
4224
|
if (this.#completedBlocks.has(key)) return;
|
|
4230
4225
|
if (this.#pendingWorkerCleanups.has(key)) return;
|
|
@@ -4234,13 +4229,13 @@ var FECManager = class _FECManager {
|
|
|
4234
4229
|
this.#holdForOrderedSettlement(key, sbn, packetId, catalogTrackName);
|
|
4235
4230
|
return;
|
|
4236
4231
|
}
|
|
4237
|
-
const trackConfig = this.#
|
|
4232
|
+
const trackConfig = this.#trackConfig(packetId);
|
|
4238
4233
|
const timeoutMs = this.#multicastWaitMs(trackConfig);
|
|
4239
4234
|
if (!(timeoutMs > 0)) {
|
|
4240
4235
|
throw new Error(`[FECManager] packetId=${packetId} has no positive fail bound (timeoutMs=${timeoutMs})`);
|
|
4241
4236
|
}
|
|
4242
4237
|
const repairDeadline = this.#repairDeadlines.get(key);
|
|
4243
|
-
if (this.onRepairNeeded && !repairRequested && (repairDeadline === void 0 || sourceProgress)) {
|
|
4238
|
+
if (this.#hasCatalogTimingForPacket(packetId) && this.onRepairNeeded && !repairRequested && (repairDeadline === void 0 || sourceProgress)) {
|
|
4244
4239
|
this.#armRepairDeadline(
|
|
4245
4240
|
key,
|
|
4246
4241
|
sbn,
|
|
@@ -4264,6 +4259,7 @@ var FECManager = class _FECManager {
|
|
|
4264
4259
|
this.#blockSourceEsis.delete(key);
|
|
4265
4260
|
this.#blockAuthoredLengths.delete(key);
|
|
4266
4261
|
this.#blockRepairRsIds.delete(key);
|
|
4262
|
+
this.#blockRsbLengths.delete(key);
|
|
4267
4263
|
this.#syncDecoderBlockOwners.delete(key);
|
|
4268
4264
|
this.#markTerminal(key, sbn, packetId, false);
|
|
4269
4265
|
this.#failedBlocks.add(key);
|
|
@@ -4301,6 +4297,7 @@ var FECManager = class _FECManager {
|
|
|
4301
4297
|
this.#blockSourceEsis.delete(key);
|
|
4302
4298
|
this.#blockAuthoredLengths.delete(key);
|
|
4303
4299
|
this.#blockRepairRsIds.delete(key);
|
|
4300
|
+
this.#blockRsbLengths.delete(key);
|
|
4304
4301
|
this.#clearBlockTimer(key);
|
|
4305
4302
|
this.onBlockResolved?.(block.sbn, block.packetId, block.catalogTrackName);
|
|
4306
4303
|
}
|
|
@@ -4310,6 +4307,7 @@ var FECManager = class _FECManager {
|
|
|
4310
4307
|
}
|
|
4311
4308
|
/** Mark a block as completed externally (e.g., after routing recovered data). */
|
|
4312
4309
|
markBlockCompleted(sbn, packetId, catalogTrackName) {
|
|
4310
|
+
if (packetId != null && !this.#trackConfigs.has(packetId)) return;
|
|
4313
4311
|
for (const key of this.#blockKeysFor(sbn, packetId, catalogTrackName)) {
|
|
4314
4312
|
this.#resolveCompleted(key);
|
|
4315
4313
|
}
|
|
@@ -4390,6 +4388,7 @@ var FECManager = class _FECManager {
|
|
|
4390
4388
|
this.#blockSourceEsis.clear();
|
|
4391
4389
|
this.#blockAuthoredLengths.clear();
|
|
4392
4390
|
this.#blockRepairRsIds.clear();
|
|
4391
|
+
this.#blockRsbLengths.clear();
|
|
4393
4392
|
this.#completedBlocks.clear();
|
|
4394
4393
|
this.#cleanlyCompletedBlocks.clear();
|
|
4395
4394
|
this.#repairRequestedBlocks.clear();
|
|
@@ -8526,7 +8525,6 @@ var CmafBlockAssembler = class {
|
|
|
8526
8525
|
failedBlocks: 0
|
|
8527
8526
|
};
|
|
8528
8527
|
this.#config = {
|
|
8529
|
-
k: config.k,
|
|
8530
8528
|
timescale: config.timescale ?? 1e6,
|
|
8531
8529
|
trackId: config.trackId ?? 1,
|
|
8532
8530
|
trackType: config.trackType,
|
|
@@ -10627,6 +10625,42 @@ var FecReleaseSequencer = class {
|
|
|
10627
10625
|
for (const stream of this.#streams.values()) total += stream.buf.length;
|
|
10628
10626
|
return total;
|
|
10629
10627
|
}
|
|
10628
|
+
#deferDepth = 0;
|
|
10629
|
+
#deferredStreams = /* @__PURE__ */ new Set();
|
|
10630
|
+
/**
|
|
10631
|
+
* Run `work` with the drains its pushes would run deferred to its end. A
|
|
10632
|
+
* recovered FEC block is routed one frame at a time, and its block is
|
|
10633
|
+
* already resolved when the first frame is pushed: draining on that push
|
|
10634
|
+
* would release past the gap before the block's other recovered frames
|
|
10635
|
+
* arrive (deciding a conceal or stall for media that is about to land),
|
|
10636
|
+
* and those frames would then be dropped as stale. Nests.
|
|
10637
|
+
*/
|
|
10638
|
+
defer(work) {
|
|
10639
|
+
this.#deferDepth++;
|
|
10640
|
+
let threw = true;
|
|
10641
|
+
try {
|
|
10642
|
+
work();
|
|
10643
|
+
threw = false;
|
|
10644
|
+
} finally {
|
|
10645
|
+
this.#deferDepth--;
|
|
10646
|
+
if (this.#deferDepth === 0 && this.#deferredStreams.size > 0) {
|
|
10647
|
+
const streams = [...this.#deferredStreams];
|
|
10648
|
+
this.#deferredStreams.clear();
|
|
10649
|
+
if (!threw) {
|
|
10650
|
+
for (const stream of streams) this.#drain(stream);
|
|
10651
|
+
} else {
|
|
10652
|
+
try {
|
|
10653
|
+
for (const stream of streams) this.#drain(stream);
|
|
10654
|
+
} catch {
|
|
10655
|
+
}
|
|
10656
|
+
}
|
|
10657
|
+
}
|
|
10658
|
+
}
|
|
10659
|
+
}
|
|
10660
|
+
#drainUnlessDeferred(stream) {
|
|
10661
|
+
if (this.#deferDepth > 0) this.#deferredStreams.add(stream);
|
|
10662
|
+
else this.#drain(stream);
|
|
10663
|
+
}
|
|
10630
10664
|
push(streamKey, data, meta, seq) {
|
|
10631
10665
|
const frame = { data, meta: { ...meta }, seq, arrivedAtMs: performance.now() };
|
|
10632
10666
|
const stream = this.#streamFor(streamKey, meta);
|
|
@@ -10648,7 +10682,7 @@ var FecReleaseSequencer = class {
|
|
|
10648
10682
|
return;
|
|
10649
10683
|
}
|
|
10650
10684
|
this.#release(stream, frame, void 0);
|
|
10651
|
-
this.#
|
|
10685
|
+
this.#drainUnlessDeferred(stream);
|
|
10652
10686
|
return;
|
|
10653
10687
|
}
|
|
10654
10688
|
let lo = 0;
|
|
@@ -10659,7 +10693,23 @@ var FecReleaseSequencer = class {
|
|
|
10659
10693
|
else hi = mid;
|
|
10660
10694
|
}
|
|
10661
10695
|
stream.buf.splice(lo, 0, frame);
|
|
10662
|
-
this.#
|
|
10696
|
+
this.#drainUnlessDeferred(stream);
|
|
10697
|
+
}
|
|
10698
|
+
/**
|
|
10699
|
+
* Arrival time of the earliest frame a stream of `packetId` holds behind a
|
|
10700
|
+
* gap (the gate forces it out `holdLimitMs` after that), or undefined when
|
|
10701
|
+
* none is held. Without `catalogTrackName`, every stream of the packetId.
|
|
10702
|
+
*/
|
|
10703
|
+
earliestHeldArrivalMs(packetId, catalogTrackName) {
|
|
10704
|
+
let earliest;
|
|
10705
|
+
for (const stream of this.#streams.values()) {
|
|
10706
|
+
if (stream.packetId !== packetId) continue;
|
|
10707
|
+
if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
|
|
10708
|
+
for (const frame of stream.buf) {
|
|
10709
|
+
if (earliest === void 0 || frame.arrivedAtMs < earliest) earliest = frame.arrivedAtMs;
|
|
10710
|
+
}
|
|
10711
|
+
}
|
|
10712
|
+
return earliest;
|
|
10663
10713
|
}
|
|
10664
10714
|
/** Re-evaluate held gaps after FEC state changed. */
|
|
10665
10715
|
poke(streamKey) {
|
|
@@ -10712,6 +10762,21 @@ var FecReleaseSequencer = class {
|
|
|
10712
10762
|
this.#streams.delete(key);
|
|
10713
10763
|
}
|
|
10714
10764
|
}
|
|
10765
|
+
/**
|
|
10766
|
+
* The packet's FEC ended, so no block will resolve a held gap: release its
|
|
10767
|
+
* frames in order, each gap stalled as a discontinuity, and forget its
|
|
10768
|
+
* streams, so FEC configured on it again starts from a fresh one.
|
|
10769
|
+
*/
|
|
10770
|
+
endPacket(packetId) {
|
|
10771
|
+
for (const [key, stream] of this.#streams) {
|
|
10772
|
+
if (stream.packetId !== packetId) continue;
|
|
10773
|
+
while (stream.buf.length > 0 && stream.last !== null) {
|
|
10774
|
+
stream.discontinuity = true;
|
|
10775
|
+
this.#drain(stream);
|
|
10776
|
+
}
|
|
10777
|
+
this.#streams.delete(key);
|
|
10778
|
+
}
|
|
10779
|
+
}
|
|
10715
10780
|
discardPacket(packetId) {
|
|
10716
10781
|
for (const [key, stream] of this.#streams) {
|
|
10717
10782
|
if (stream.packetId !== packetId) continue;
|
|
@@ -11080,6 +11145,22 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11080
11145
|
}
|
|
11081
11146
|
) : null;
|
|
11082
11147
|
}
|
|
11148
|
+
/**
|
|
11149
|
+
* Run `work` with the FEC release gate's drains deferred to its end, so
|
|
11150
|
+
* every frame it pushes is in place before any held gap is decided (see
|
|
11151
|
+
* FecReleaseSequencer.defer).
|
|
11152
|
+
*/
|
|
11153
|
+
deferFecRelease(work) {
|
|
11154
|
+
if (!this.#fecSequencer) {
|
|
11155
|
+
work();
|
|
11156
|
+
return;
|
|
11157
|
+
}
|
|
11158
|
+
this.#fecSequencer.defer(work);
|
|
11159
|
+
}
|
|
11160
|
+
/** See FecReleaseSequencer.earliestHeldArrivalMs. */
|
|
11161
|
+
fecReleaseEarliestHeldAtMs(packetId, catalogTrackName) {
|
|
11162
|
+
return this.#fecSequencer?.earliestHeldArrivalMs(packetId, catalogTrackName);
|
|
11163
|
+
}
|
|
11083
11164
|
/** Re-evaluate held gaps after FEC block state changed. */
|
|
11084
11165
|
pokeFecRelease(packetId, catalogTrackName) {
|
|
11085
11166
|
if (!this.#fecSequencer) return;
|
|
@@ -11094,6 +11175,13 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11094
11175
|
markFecDiscontinuity(packetId, catalogTrackName) {
|
|
11095
11176
|
this.#fecSequencer?.markDiscontinuity(this.#streamKey(packetId, catalogTrackName));
|
|
11096
11177
|
}
|
|
11178
|
+
/**
|
|
11179
|
+
* The FEC of a packet ID ended: release the frames the FEC release gate
|
|
11180
|
+
* holds for it in order, each gap stalled, and forget its gated streams.
|
|
11181
|
+
*/
|
|
11182
|
+
endFecRelease(packetId) {
|
|
11183
|
+
this.#fecSequencer?.endPacket(packetId);
|
|
11184
|
+
}
|
|
11097
11185
|
/**
|
|
11098
11186
|
* MPU and data-unit identity of one MMTP packet, parsed in place (no copy,
|
|
11099
11187
|
* no reassembly). Null for non-MPU packets and packets that do not parse.
|
|
@@ -11274,7 +11362,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11274
11362
|
const parsed = preParsed !== void 0 && preParsed.skipFecPayloadId === (options?.skipFecPayloadId === true) ? preParsed : this.parsePacket(packet, options);
|
|
11275
11363
|
if (!parsed) return this.#drop("header-parse-failed");
|
|
11276
11364
|
const info = parsed.info;
|
|
11277
|
-
if (info.packetType !==
|
|
11365
|
+
if (info.packetType !== PACKET_TYPE_MPU) {
|
|
11278
11366
|
return {
|
|
11279
11367
|
kind: info.isRepair ? "repair" : "non-media",
|
|
11280
11368
|
packet: info,
|
|
@@ -11283,10 +11371,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11283
11371
|
}
|
|
11284
11372
|
if (info.fecPayloadId !== null && this.#sourceSymbolCb) {
|
|
11285
11373
|
const rawSymbol = packet.subarray(0, packet.length - 4);
|
|
11286
|
-
const altaTrailer = extractAltaTrailer2(packet);
|
|
11287
|
-
const alta = altaTrailer ? { auth: altaTrailer.auth, payloadEnd: altaTrailer.signedEnd } : void 0;
|
|
11288
11374
|
try {
|
|
11289
|
-
this.#sourceSymbolCb(info.packetId, info.fecPayloadId.ssId, rawSymbol
|
|
11375
|
+
this.#sourceSymbolCb(info.packetId, info.fecPayloadId.ssId, rawSymbol);
|
|
11290
11376
|
} catch {
|
|
11291
11377
|
}
|
|
11292
11378
|
}
|
|
@@ -12411,6 +12497,8 @@ var MmtpRouter = class {
|
|
|
12411
12497
|
// ── Keyframe loss detection state ───────────────────────────────
|
|
12412
12498
|
/** Catalog-derived keyframe loss timeout (ms), or a reader evaluated at each arm. null = not configured (warns on video track registration; Phase-13 keyframe repair stays off). */
|
|
12413
12499
|
#keyframeLostTimeoutMs;
|
|
12500
|
+
/** packetIds already warned for running without Phase 13 (non-FEC video only). */
|
|
12501
|
+
#warnedNoKeyframeLostTimeout = /* @__PURE__ */ new Set();
|
|
12414
12502
|
/** Why the last reader evaluation left the timer unarmed; null while arming. Dedups the warning. */
|
|
12415
12503
|
#keyframeLostUnarmedReason = null;
|
|
12416
12504
|
/** Active keyframe loss timer handle. Single timer per video track — cleared on each rapFlag and on dispose(). */
|
|
@@ -12426,6 +12514,8 @@ var MmtpRouter = class {
|
|
|
12426
12514
|
*/
|
|
12427
12515
|
#onPublisherRestart;
|
|
12428
12516
|
#publisherRestarts = 0;
|
|
12517
|
+
/** Inits delivered with no onTrackInit handler for their packetId (MOQT-01). */
|
|
12518
|
+
#unmappedInitDeliveries = 0;
|
|
12429
12519
|
// FEC Payload IDs left unread because their (owner) packetId had no
|
|
12430
12520
|
// configured fec.blockProfile (see #noteUnreadableFecPayloadId).
|
|
12431
12521
|
#fecPayloadIdsWithoutProfile = 0;
|
|
@@ -12622,15 +12712,24 @@ var MmtpRouter = class {
|
|
|
12622
12712
|
return NO_FEC_GEOMETRY_HORIZON;
|
|
12623
12713
|
}
|
|
12624
12714
|
const blockSpanMs = fec.interleaveDepthMsForPacket(packetId);
|
|
12715
|
+
const blockLifetimeMs = fec.blockLifetimeMsForPacket(packetId);
|
|
12716
|
+
if (blockSpanMs === void 0 || blockLifetimeMs === void 0) return NO_FEC_GEOMETRY_HORIZON;
|
|
12625
12717
|
if (blockSpanMs === 0) return NO_BLOCK_SPAN_HORIZON;
|
|
12626
12718
|
return {
|
|
12627
12719
|
kind: "fec-horizon",
|
|
12628
|
-
retentionMs: duplicateRetentionMs({
|
|
12629
|
-
blockSpanMs,
|
|
12630
|
-
blockLifetimeMs: fec.blockLifetimeMsForPacket(packetId)
|
|
12631
|
-
})
|
|
12720
|
+
retentionMs: duplicateRetentionMs({ blockSpanMs, blockLifetimeMs })
|
|
12632
12721
|
};
|
|
12633
12722
|
}
|
|
12723
|
+
/**
|
|
12724
|
+
* Init segments delivered for a packetId with no `onTrackInit` handler
|
|
12725
|
+
* since construction, whether the deprecated aggregate `onInitSegment`
|
|
12726
|
+
* callback then received them or nothing did. A supported deployment reads
|
|
12727
|
+
* 0 here; a non-zero value is the evidence that blocks removing the
|
|
12728
|
+
* aggregate callback (MOQT-01, BLO-21181).
|
|
12729
|
+
*/
|
|
12730
|
+
get unmappedInitDeliveryCount() {
|
|
12731
|
+
return this.#unmappedInitDeliveries;
|
|
12732
|
+
}
|
|
12634
12733
|
/** Publisher restarts detected on the multicast leg since construction. */
|
|
12635
12734
|
get publisherRestartCount() {
|
|
12636
12735
|
return this.#publisherRestarts;
|
|
@@ -12718,7 +12817,7 @@ var MmtpRouter = class {
|
|
|
12718
12817
|
/** Update MFU reassembly timeout. Source and FEC-recovery paths share the
|
|
12719
12818
|
* parser's single reassembler (BLO-8151), so one update covers both. */
|
|
12720
12819
|
setMfuTimeoutMs(ms) {
|
|
12721
|
-
if (ms <= 0) throw new Error("[MmtpRouter] mfuTimeoutMs must be > 0");
|
|
12820
|
+
if (ms !== null && ms <= 0) throw new Error("[MmtpRouter] mfuTimeoutMs must be > 0");
|
|
12722
12821
|
this.#parser.setMfuTimeoutMs(ms);
|
|
12723
12822
|
}
|
|
12724
12823
|
/** Update parser frame reordering for unordered multicast delivery.
|
|
@@ -12729,15 +12828,14 @@ var MmtpRouter = class {
|
|
|
12729
12828
|
}
|
|
12730
12829
|
/** Register MMTP packetId → track type mapping.
|
|
12731
12830
|
* Call BEFORE packets arrive to ensure correct routing from the first packet.
|
|
12732
|
-
*
|
|
12733
|
-
*
|
|
12734
|
-
*
|
|
12831
|
+
* Without keyframeLostTimeoutMs a track WITHOUT FEC has no Phase 13
|
|
12832
|
+
* keyframe repair (video still routes/decodes normally); that is warned at
|
|
12833
|
+
* its first keyframe, once the FEC configuration is known (see
|
|
12834
|
+
* #noteEmittedKeyframe). Nothing is warned here: an FEC track never uses
|
|
12835
|
+
* the timer, and at registration the FEC configuration may not be applied
|
|
12836
|
+
* yet. framerate is OPTIONAL per draft-ietf-moq-catalogformat-01 §3.2.23,
|
|
12837
|
+
* so we can't require the timeout. */
|
|
12735
12838
|
setPacketIdType(packetId, trackType) {
|
|
12736
|
-
if (trackType === "video" && this.#keyframeLostTimeoutMs === null) {
|
|
12737
|
-
console.warn(
|
|
12738
|
-
"[MmtpRouter] keyframeLostTimeoutMs not configured \u2014 Phase 13 keyframe repair disabled for this track. Set it via setKeyframeLostTimeoutMs() or provide in constructor config to enable: Math.ceil(groupDurationMs * KEYFRAME_LOST_TIMEOUT_SLACK)."
|
|
12739
|
-
);
|
|
12740
|
-
}
|
|
12741
12839
|
if (trackType === "video") this.#videoPacketIds.add(packetId);
|
|
12742
12840
|
else this.#audioPacketIds.add(packetId);
|
|
12743
12841
|
const codec = this.#trackCodecs.get(packetId) ?? "";
|
|
@@ -12788,7 +12886,15 @@ var MmtpRouter = class {
|
|
|
12788
12886
|
onTrackPacket(packetId, cb) {
|
|
12789
12887
|
this.#trackPacketCallbacks.set(packetId, cb);
|
|
12790
12888
|
}
|
|
12791
|
-
/**
|
|
12889
|
+
/**
|
|
12890
|
+
* Register an init segment callback (ftyp+moov from MMTP MPU metadata).
|
|
12891
|
+
* Receives an init only for a packetId with no `onTrackInit` handler.
|
|
12892
|
+
*
|
|
12893
|
+
* @deprecated Use onTrackInit(packetId, cb). Still delivered as a fallback;
|
|
12894
|
+
* removed in the 1.0.0 major, no earlier than one minor release after this
|
|
12895
|
+
* deprecation reaches the `latest` dist-tag (BLO-21181). Count fallback
|
|
12896
|
+
* deliveries with `unmappedInitDeliveryCount`.
|
|
12897
|
+
*/
|
|
12792
12898
|
onInitSegment(cb) {
|
|
12793
12899
|
this.#onInitSegment = cb;
|
|
12794
12900
|
}
|
|
@@ -12802,17 +12908,16 @@ var MmtpRouter = class {
|
|
|
12802
12908
|
* When active, source frames are buffered per FEC block and emitted as
|
|
12803
12909
|
* multi-sample CMAF segments instead of per-frame callbacks. */
|
|
12804
12910
|
enableMseAssembler(packetId, config, catalogTrackName) {
|
|
12805
|
-
const k = this.#kForPacket(packetId);
|
|
12806
12911
|
const assemblerKey = this.#mseAssemblerKey(packetId, catalogTrackName);
|
|
12807
12912
|
const previous = this.#mseAssemblers.get(assemblerKey);
|
|
12808
12913
|
if (previous) {
|
|
12809
12914
|
previous.flush();
|
|
12810
12915
|
previous.dispose();
|
|
12811
12916
|
}
|
|
12812
|
-
const assembler = new CmafBlockAssembler(
|
|
12917
|
+
const assembler = new CmafBlockAssembler(config);
|
|
12813
12918
|
this.#mseAssemblers.set(assemblerKey, assembler);
|
|
12814
12919
|
console.log(
|
|
12815
|
-
`[MmtpRouter] MSE assembler enabled for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (
|
|
12920
|
+
`[MmtpRouter] MSE assembler enabled for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${config.trackType})`
|
|
12816
12921
|
);
|
|
12817
12922
|
return assembler;
|
|
12818
12923
|
}
|
|
@@ -12921,6 +13026,21 @@ var MmtpRouter = class {
|
|
|
12921
13026
|
}
|
|
12922
13027
|
this.#fecMappersByPacketId.set(packetId, mapper);
|
|
12923
13028
|
}
|
|
13029
|
+
/**
|
|
13030
|
+
* Release the router side of a packetId whose FEC is ending: drop its block
|
|
13031
|
+
* mapper and end its FEC release gating. Frames the gate holds are released
|
|
13032
|
+
* in order, each gap stalled (no block will resolve it), not stranded. Call
|
|
13033
|
+
* before FECManager.releasePacket, while the gate can still read its blocks.
|
|
13034
|
+
*/
|
|
13035
|
+
releaseFecPacket(packetId) {
|
|
13036
|
+
this.#fecMappersByPacketId.delete(packetId);
|
|
13037
|
+
this.#withDeferredFecPokes(() => {
|
|
13038
|
+
this.#parser.endFecRelease(packetId);
|
|
13039
|
+
for (const [key, poke] of this.#pendingFecPokes) {
|
|
13040
|
+
if (poke.packetId === packetId) this.#pendingFecPokes.delete(key);
|
|
13041
|
+
}
|
|
13042
|
+
});
|
|
13043
|
+
}
|
|
12924
13044
|
registerManifestTrack(context) {
|
|
12925
13045
|
if (!this.#manifestVerifier) throw new Error("manifest verifier is not configured");
|
|
12926
13046
|
this.#manifestVerifier.registerTrack(context);
|
|
@@ -13040,87 +13160,21 @@ var MmtpRouter = class {
|
|
|
13040
13160
|
this.#noteUnreadableFecPayloadId(ownerPacketId, "repair");
|
|
13041
13161
|
return;
|
|
13042
13162
|
}
|
|
13043
|
-
|
|
13044
|
-
this.#handleIsoRepair(ownerPacketId, catalogTrackName, parsed, "moq");
|
|
13045
|
-
return;
|
|
13046
|
-
}
|
|
13047
|
-
const repairSbn = this.#deriveRepairSbn(parsed.ssStart, parsed.ssbLength, ownerPacketId);
|
|
13048
|
-
if (repairSbn === null) return;
|
|
13049
|
-
const repairEsi = this.#kForPacket(ownerPacketId) + parsed.rsId;
|
|
13050
|
-
if (!this.#feedFecTransportSymbol(
|
|
13051
|
-
ownerPacketId,
|
|
13052
|
-
repairSbn,
|
|
13053
|
-
repairEsi,
|
|
13054
|
-
parsed.symbolData,
|
|
13055
|
-
"moq",
|
|
13056
|
-
catalogTrackName
|
|
13057
|
-
)) return;
|
|
13058
|
-
const recovered = catalogTrackName === void 0 ? this.#fecManager.feedRepairSymbol(
|
|
13059
|
-
ownerPacketId,
|
|
13060
|
-
parsed.ssStart,
|
|
13061
|
-
parsed.ssbLength,
|
|
13062
|
-
parsed.rsId,
|
|
13063
|
-
parsed.symbolData
|
|
13064
|
-
) : this.#fecManager.feedRepairSymbol(
|
|
13065
|
-
ownerPacketId,
|
|
13066
|
-
parsed.ssStart,
|
|
13067
|
-
parsed.ssbLength,
|
|
13068
|
-
parsed.rsId,
|
|
13069
|
-
parsed.symbolData,
|
|
13070
|
-
catalogTrackName
|
|
13071
|
-
);
|
|
13072
|
-
if (recovered) {
|
|
13073
|
-
const alreadyCompleted = catalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId) : this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId, catalogTrackName);
|
|
13074
|
-
if (catalogTrackName === void 0) {
|
|
13075
|
-
this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId);
|
|
13076
|
-
} else {
|
|
13077
|
-
this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId, catalogTrackName);
|
|
13078
|
-
}
|
|
13079
|
-
if (!alreadyCompleted) {
|
|
13080
|
-
this.#fecManager.countRecovery(ownerPacketId, repairSbn, catalogTrackName);
|
|
13081
|
-
if (catalogTrackName === void 0) {
|
|
13082
|
-
this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId);
|
|
13083
|
-
} else {
|
|
13084
|
-
this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId, catalogTrackName);
|
|
13085
|
-
}
|
|
13086
|
-
this.#routeRecoveredBlock(
|
|
13087
|
-
repairSbn,
|
|
13088
|
-
recovered,
|
|
13089
|
-
ownerPacketId,
|
|
13090
|
-
catalogTrackName
|
|
13091
|
-
);
|
|
13092
|
-
}
|
|
13093
|
-
}
|
|
13163
|
+
this.#handleIsoRepair(ownerPacketId, catalogTrackName, parsed, "moq");
|
|
13094
13164
|
}
|
|
13095
13165
|
}
|
|
13096
|
-
// ──
|
|
13097
|
-
#kForPacket(packetId) {
|
|
13098
|
-
if (packetId != null)
|
|
13099
|
-
return this.#fecManager?.kForPacket?.(packetId) ?? this.#fecMappersByPacketId.get(packetId)?.k ?? this.#fecManager?.k ?? 4;
|
|
13100
|
-
return this.#defaultFecMapper?.k ?? this.#fecManager?.k ?? 4;
|
|
13101
|
-
}
|
|
13166
|
+
// ── Packet FEC geometry and block profile ───────────────────────
|
|
13102
13167
|
#symbolSizeForPacket(packetId) {
|
|
13103
|
-
if (packetId != null) return this.#fecManager?.symbolSizeForPacket?.(packetId) ??
|
|
13168
|
+
if (packetId != null) return this.#fecManager?.symbolSizeForPacket?.(packetId) ?? 0;
|
|
13104
13169
|
return this.#fecManager?.symbolSize ?? 0;
|
|
13105
13170
|
}
|
|
13106
|
-
/** Derive SBN/ESI from SS_ID using packet-scoped FEC geometry. */
|
|
13107
|
-
#deriveSbnEsi(ssId, packetId) {
|
|
13108
|
-
const mapper = packetId == null ? this.#defaultFecMapper : this.#fecMappersByPacketId.get(packetId) ?? null;
|
|
13109
|
-
if (mapper) return mapper.fromSsId(ssId);
|
|
13110
|
-
const k = this.#kForPacket(packetId);
|
|
13111
|
-
return { sbn: Math.floor(ssId / k), esi: ssId % k };
|
|
13112
|
-
}
|
|
13113
|
-
#deriveRepairSbn(ssStart, ssbLength, packetId) {
|
|
13114
|
-
const k = this.#kForPacket(packetId);
|
|
13115
|
-
if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null;
|
|
13116
|
-
return Math.floor(ssStart / k);
|
|
13117
|
-
}
|
|
13118
13171
|
/**
|
|
13119
13172
|
* The configured block profile of a packetId, or undefined while it is
|
|
13120
13173
|
* unconfigured. With an FEC manager its per-packet configuration is the
|
|
13121
13174
|
* only source. A router without one still recovers symbols through its
|
|
13122
13175
|
* registered mapper, built with the catalog's explicit profile: the
|
|
13123
|
-
* packet's own mapper, else the default one (
|
|
13176
|
+
* packet's own mapper, else the default one. A NullBlockMapper (a stream
|
|
13177
|
+
* without an FEC instance) states none.
|
|
13124
13178
|
*/
|
|
13125
13179
|
#configuredBlockProfile(packetId) {
|
|
13126
13180
|
if (this.#fecManager) return this.#fecManager.blockProfileForPacket(packetId);
|
|
@@ -13128,9 +13182,10 @@ var MmtpRouter = class {
|
|
|
13128
13182
|
return mapper?.blockProfile ?? void 0;
|
|
13129
13183
|
}
|
|
13130
13184
|
/**
|
|
13131
|
-
* The block profile an FEC coordinate of `packetId` is read under
|
|
13132
|
-
*
|
|
13133
|
-
*
|
|
13185
|
+
* The block profile an FEC coordinate of `packetId` is read under:
|
|
13186
|
+
* `iso-ssbg1-v1`, the only one (`k-padded-v1` is retired and refused).
|
|
13187
|
+
* Throws while none is configured: such a packet has no FEC geometry, and
|
|
13188
|
+
* nothing is inferred from the wire or defaulted.
|
|
13134
13189
|
* Packet ingress leaves such a packet's FEC Payload ID unread first
|
|
13135
13190
|
* (#noteUnreadableFecPayloadId); a throw here is a wiring fault on a path
|
|
13136
13191
|
* that cannot hold an unconfigured packet.
|
|
@@ -13144,8 +13199,16 @@ var MmtpRouter = class {
|
|
|
13144
13199
|
}
|
|
13145
13200
|
return profile;
|
|
13146
13201
|
}
|
|
13147
|
-
|
|
13148
|
-
|
|
13202
|
+
/**
|
|
13203
|
+
* The block coordinates (SS_Start, ESI) of an FEC source: range membership
|
|
13204
|
+
* in a block a repair already stated (ISO/IEC 23008-1 C.5.3). Null while no
|
|
13205
|
+
* repair has stated its block, while its packetId has no configured block
|
|
13206
|
+
* profile, and without an FEC manager: SS_ID alone locates no block, so
|
|
13207
|
+
* nothing is derived from it.
|
|
13208
|
+
*/
|
|
13209
|
+
#locateFecSource(packetId, catalogTrackName, ssId) {
|
|
13210
|
+
if (this.#configuredBlockProfile(packetId) === void 0) return null;
|
|
13211
|
+
return this.#fecManager?.locateIsoSource?.(packetId, catalogTrackName, ssId) ?? null;
|
|
13149
13212
|
}
|
|
13150
13213
|
/**
|
|
13151
13214
|
* Explicit refusal to read an FEC Payload ID: its (owner) packetId has no
|
|
@@ -13164,21 +13227,20 @@ var MmtpRouter = class {
|
|
|
13164
13227
|
}
|
|
13165
13228
|
}
|
|
13166
13229
|
/**
|
|
13167
|
-
* The release-gate anchor of a recovered symbol: its
|
|
13168
|
-
*
|
|
13169
|
-
*
|
|
13230
|
+
* The release-gate anchor of a recovered symbol: its own SS_ID, SS_Start +
|
|
13231
|
+
* ESI modulo 2^32 (C.5.2), where a frame is gated by the SS_IDs between it
|
|
13232
|
+
* and the last released frame.
|
|
13170
13233
|
*/
|
|
13171
|
-
#fecAnchorForRecovered(
|
|
13172
|
-
return
|
|
13234
|
+
#fecAnchorForRecovered(blockStart, index) {
|
|
13235
|
+
return serial32Add(blockStart, index);
|
|
13173
13236
|
}
|
|
13174
13237
|
/**
|
|
13175
13238
|
* One iso-ssbg1-v1 repair symbol (ISO/IEC 23008-1 C.5.3). It states the
|
|
13176
13239
|
* block `[SS_Start, SS_Start + SSB_length)`; repair ESI = SSB_length +
|
|
13177
13240
|
* RS_ID (K' = SSB_length). The held sources it placed (and those of a
|
|
13178
13241
|
* repair-less span in front of it) are fed as first-arrival sources, then
|
|
13179
|
-
* the repair, the order they had on the wire, so a loss-free block
|
|
13180
|
-
* on its repair
|
|
13181
|
-
* block is dropped.
|
|
13242
|
+
* the repair, the order they had on the wire, so a loss-free block completes
|
|
13243
|
+
* clean on its repair. A repair contradicting a known block is dropped.
|
|
13182
13244
|
*/
|
|
13183
13245
|
#handleIsoRepair(ownerPacketId, catalogTrackName, repair, source) {
|
|
13184
13246
|
const fec = this.#fecManager;
|
|
@@ -13204,6 +13266,7 @@ var MmtpRouter = class {
|
|
|
13204
13266
|
catalogTrackName
|
|
13205
13267
|
)) return;
|
|
13206
13268
|
fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);
|
|
13269
|
+
fec.noteRepairGroupLength?.(blockStart, ownerPacketId, repair.rsbLength, catalogTrackName);
|
|
13207
13270
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
13208
13271
|
const recovered = fec.feedRepairSymbol(
|
|
13209
13272
|
ownerPacketId,
|
|
@@ -13245,7 +13308,7 @@ var MmtpRouter = class {
|
|
|
13245
13308
|
}
|
|
13246
13309
|
if (payload.oversized) continue;
|
|
13247
13310
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
13248
|
-
const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData,
|
|
13311
|
+
const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, catalogTrackName);
|
|
13249
13312
|
if (!recovered || alreadyCompleted) continue;
|
|
13250
13313
|
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
13251
13314
|
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
@@ -13270,9 +13333,10 @@ var MmtpRouter = class {
|
|
|
13270
13333
|
return null;
|
|
13271
13334
|
}
|
|
13272
13335
|
/** Route a recovered FEC block through the parser + dedup pipeline.
|
|
13273
|
-
* Sync WASM decoders return K fixed-width
|
|
13336
|
+
* Sync WASM decoders return the block's K' = SSB_length fixed-width
|
|
13337
|
+
* ssbg_mode1 source symbols concatenated. */
|
|
13274
13338
|
routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
|
|
13275
|
-
this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
|
|
13339
|
+
this.#withDeferredFecPokes(() => this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName));
|
|
13276
13340
|
}
|
|
13277
13341
|
/** Route a single recovered source symbol through the parser + dedup pipeline.
|
|
13278
13342
|
* Worker-backed decoders emit one padded source symbol per frame event. */
|
|
@@ -13686,8 +13750,16 @@ var MmtpRouter = class {
|
|
|
13686
13750
|
#noteEmittedKeyframe(packetId, keyframe) {
|
|
13687
13751
|
if (!keyframe) return;
|
|
13688
13752
|
if (!this.#videoPacketIds.has(packetId)) return;
|
|
13689
|
-
if (this.#keyframeLostTimeoutMs === null) return;
|
|
13690
13753
|
if (this.#isFecTrack(packetId)) return;
|
|
13754
|
+
if (this.#keyframeLostTimeoutMs === null) {
|
|
13755
|
+
if (!this.#warnedNoKeyframeLostTimeout.has(packetId)) {
|
|
13756
|
+
this.#warnedNoKeyframeLostTimeout.add(packetId);
|
|
13757
|
+
console.warn(
|
|
13758
|
+
`[MmtpRouter] keyframeLostTimeoutMs not configured \u2014 Phase 13 keyframe repair disabled for packetId=${packetId}, a video track without FEC. Set it via setKeyframeLostTimeoutMs() or provide in constructor config to enable.`
|
|
13759
|
+
);
|
|
13760
|
+
}
|
|
13761
|
+
return;
|
|
13762
|
+
}
|
|
13691
13763
|
this.#lastKeyframeGroupId++;
|
|
13692
13764
|
this.#armKeyframeLostTimer();
|
|
13693
13765
|
}
|
|
@@ -13699,7 +13771,7 @@ var MmtpRouter = class {
|
|
|
13699
13771
|
#withDeferredFecPokes(work) {
|
|
13700
13772
|
this.#fecPokeDepth++;
|
|
13701
13773
|
try {
|
|
13702
|
-
work
|
|
13774
|
+
this.#parser.deferFecRelease(work);
|
|
13703
13775
|
} finally {
|
|
13704
13776
|
this.#fecPokeDepth--;
|
|
13705
13777
|
if (this.#fecPokeDepth === 0 && this.#pendingFecPokes.size > 0 && !this.#disposed) {
|
|
@@ -13711,6 +13783,13 @@ var MmtpRouter = class {
|
|
|
13711
13783
|
}
|
|
13712
13784
|
}
|
|
13713
13785
|
}
|
|
13786
|
+
/**
|
|
13787
|
+
* Arrival time of the earliest frame the FEC release gate holds for this
|
|
13788
|
+
* stream (see FecReleaseSequencer.earliestHeldArrivalMs), or undefined.
|
|
13789
|
+
*/
|
|
13790
|
+
fecReleaseEarliestHeldAtMs(packetId, catalogTrackName) {
|
|
13791
|
+
return this.#parser.fecReleaseEarliestHeldAtMs(packetId, catalogTrackName);
|
|
13792
|
+
}
|
|
13714
13793
|
#requestFecPoke(packetId, catalogTrackName) {
|
|
13715
13794
|
if (this.#disposed) return;
|
|
13716
13795
|
if (this.#fecPokeDepth > 0) {
|
|
@@ -13738,29 +13817,19 @@ var MmtpRouter = class {
|
|
|
13738
13817
|
resolvedLoss: (packetId, catalogTrackName, fromSbn, toSbn, releasedSbn) => {
|
|
13739
13818
|
const fec = this.#fecManager;
|
|
13740
13819
|
if (!fec?.blockResolution) return [];
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
return fec.isoSpanResolution?.(packetId, catalogTrackName, releasedSbn, head) ?? null;
|
|
13744
|
-
}
|
|
13745
|
-
const highest = fec.highestSbn?.(packetId, catalogTrackName);
|
|
13746
|
-
const lost = [];
|
|
13747
|
-
for (let sbn = fromSbn; sbn <= toSbn; sbn++) {
|
|
13748
|
-
const resolution = fec.blockResolution(sbn, packetId, catalogTrackName);
|
|
13749
|
-
if (resolution === "pending") return null;
|
|
13750
|
-
if (resolution === "failed") {
|
|
13751
|
-
lost.push(sbn);
|
|
13752
|
-
} else if (resolution === "untracked") {
|
|
13753
|
-
if (sbn === releasedSbn) continue;
|
|
13754
|
-
if (highest === void 0 || sbn > highest) return null;
|
|
13755
|
-
lost.push(sbn);
|
|
13756
|
-
}
|
|
13757
|
-
}
|
|
13758
|
-
return lost;
|
|
13820
|
+
const head = fromSbn === releasedSbn ? toSbn : fromSbn;
|
|
13821
|
+
return fec.isoSpanResolution?.(packetId, catalogTrackName, releasedSbn, head) ?? null;
|
|
13759
13822
|
},
|
|
13760
13823
|
// The retention window itself: a frame is never still held when a block
|
|
13761
13824
|
// it waits on retires. Only consulted while resolvedLoss holds a gap,
|
|
13762
13825
|
// which needs blockResolution, so the check above guarantees the method.
|
|
13763
|
-
|
|
13826
|
+
// A packetId the manager no longer has configured (released) has no
|
|
13827
|
+
// block left to wait on: release its frames now (BLO-45953).
|
|
13828
|
+
holdLimitMs: (packetId) => {
|
|
13829
|
+
const fec = this.#fecManager;
|
|
13830
|
+
if (!fec?.terminalRetentionMsForPacket) return Infinity;
|
|
13831
|
+
return fec.terminalRetentionMsForPacket(packetId) ?? 0;
|
|
13832
|
+
},
|
|
13764
13833
|
decide: (before, after, lostBlocks, discontinuity) => {
|
|
13765
13834
|
const decision = this.#decideGap(before, after, lostBlocks, discontinuity);
|
|
13766
13835
|
this.#onFrameGap?.(decision);
|
|
@@ -13822,9 +13891,10 @@ var MmtpRouter = class {
|
|
|
13822
13891
|
}
|
|
13823
13892
|
/**
|
|
13824
13893
|
* The most media units a gap between two released frames can hold: the
|
|
13825
|
-
* FEC blocks
|
|
13826
|
-
* block touches (ceil(interleaveDepthMs / unit duration), §5.1).
|
|
13827
|
-
* either
|
|
13894
|
+
* FEC blocks between `before`'s anchor and `after`'s, times D, the media
|
|
13895
|
+
* units one block touches (ceil(interleaveDepthMs / unit duration), §5.1).
|
|
13896
|
+
* Null when either anchor, the unit duration or the catalog interleave is
|
|
13897
|
+
* unknown.
|
|
13828
13898
|
*/
|
|
13829
13899
|
#maxMissingUnits(before, after) {
|
|
13830
13900
|
const from = before.fecSbn;
|
|
@@ -13837,7 +13907,7 @@ var MmtpRouter = class {
|
|
|
13837
13907
|
const denominator = BigInt(timing.durationTicks) * 1000n;
|
|
13838
13908
|
const unitsPerBlock = (numerator + denominator - 1n) / denominator;
|
|
13839
13909
|
const blocks = BigInt(
|
|
13840
|
-
(
|
|
13910
|
+
(serial32Precedes(from, to) ? serial32Distance(from, to) : serial32Distance(to, from)) + 1
|
|
13841
13911
|
);
|
|
13842
13912
|
const bound = unitsPerBlock * blocks;
|
|
13843
13913
|
return bound > BigInt(Number.MAX_SAFE_INTEGER) ? null : Number(bound);
|
|
@@ -13887,8 +13957,9 @@ var MmtpRouter = class {
|
|
|
13887
13957
|
const cb = this.#trackInitCallbacks.get(packetId);
|
|
13888
13958
|
if (cb) {
|
|
13889
13959
|
cb(data);
|
|
13890
|
-
} else
|
|
13891
|
-
this.#
|
|
13960
|
+
} else {
|
|
13961
|
+
this.#unmappedInitDeliveries++;
|
|
13962
|
+
this.#onInitSegment?.(data);
|
|
13892
13963
|
}
|
|
13893
13964
|
console.log(
|
|
13894
13965
|
`[MmtpRouter] Init delivered for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${data.byteLength}B)`
|
|
@@ -13930,9 +14001,9 @@ var MmtpRouter = class {
|
|
|
13930
14001
|
if (!info.isRepair && this.#manifestVerifier && !manifestAdmitted) {
|
|
13931
14002
|
let identity = manifestIdentity;
|
|
13932
14003
|
if (!identity && info.fecPayloadId && !fecUnreadable && (this.#resolveManifestIdentityForRoute || this.#resolveManifestIdentity)) {
|
|
13933
|
-
const coordinates = this.#deriveSbnEsi(info.fecPayloadId.ssId, info.packetId);
|
|
13934
14004
|
const catalogTrackName = catalogTrackNameForPacket(ingress, info.packetId);
|
|
13935
|
-
|
|
14005
|
+
const coordinates = this.#locateFecSource(info.packetId, catalogTrackName, info.fecPayloadId.ssId);
|
|
14006
|
+
identity = coordinates === null ? void 0 : (this.#resolveManifestIdentityForRoute ? this.#resolveManifestIdentityForRoute({
|
|
13936
14007
|
packetId: info.packetId,
|
|
13937
14008
|
sbn: coordinates.sbn,
|
|
13938
14009
|
esi: coordinates.esi,
|
|
@@ -13984,79 +14055,7 @@ var MmtpRouter = class {
|
|
|
13984
14055
|
this.#noteUnreadableFecPayloadId(ownerPacketId, "repair");
|
|
13985
14056
|
return;
|
|
13986
14057
|
}
|
|
13987
|
-
|
|
13988
|
-
this.#handleIsoRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair, source);
|
|
13989
|
-
return;
|
|
13990
|
-
}
|
|
13991
|
-
const repairSbn = this.#deriveRepairSbn(repair.ssStart, repair.ssbLength, ownerPacketId);
|
|
13992
|
-
if (repairSbn === null) return;
|
|
13993
|
-
const repairEsi = this.#kForPacket(ownerPacketId) + repair.rsId;
|
|
13994
|
-
if (!this.#feedFecTransportSymbol(
|
|
13995
|
-
ownerPacketId,
|
|
13996
|
-
repairSbn,
|
|
13997
|
-
repairEsi,
|
|
13998
|
-
repair.symbolData,
|
|
13999
|
-
source,
|
|
14000
|
-
catalogTrackNameForPacket(ingress, ownerPacketId)
|
|
14001
|
-
)) return;
|
|
14002
|
-
const repairCatalogTrackName = catalogTrackNameForPacket(ingress, ownerPacketId);
|
|
14003
|
-
if (repairCatalogTrackName === void 0) {
|
|
14004
|
-
this.#fecManager.noteBlockActivity?.(repairSbn, ownerPacketId);
|
|
14005
|
-
} else {
|
|
14006
|
-
this.#fecManager.noteBlockActivity?.(
|
|
14007
|
-
repairSbn,
|
|
14008
|
-
ownerPacketId,
|
|
14009
|
-
repairCatalogTrackName
|
|
14010
|
-
);
|
|
14011
|
-
}
|
|
14012
|
-
const recovered = repairCatalogTrackName === void 0 ? this.#fecManager.feedRepairSymbol(
|
|
14013
|
-
ownerPacketId,
|
|
14014
|
-
repair.ssStart,
|
|
14015
|
-
repair.ssbLength,
|
|
14016
|
-
repair.rsId,
|
|
14017
|
-
repair.symbolData
|
|
14018
|
-
) : this.#fecManager.feedRepairSymbol(
|
|
14019
|
-
ownerPacketId,
|
|
14020
|
-
repair.ssStart,
|
|
14021
|
-
repair.ssbLength,
|
|
14022
|
-
repair.rsId,
|
|
14023
|
-
repair.symbolData,
|
|
14024
|
-
repairCatalogTrackName
|
|
14025
|
-
);
|
|
14026
|
-
if (recovered) {
|
|
14027
|
-
const alreadyCompleted = repairCatalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId) : this.#fecManager.isBlockCompleted(
|
|
14028
|
-
repairSbn,
|
|
14029
|
-
ownerPacketId,
|
|
14030
|
-
repairCatalogTrackName
|
|
14031
|
-
);
|
|
14032
|
-
if (repairCatalogTrackName === void 0) {
|
|
14033
|
-
this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId);
|
|
14034
|
-
} else {
|
|
14035
|
-
this.#fecManager.cancelBlockTimeout(
|
|
14036
|
-
repairSbn,
|
|
14037
|
-
ownerPacketId,
|
|
14038
|
-
repairCatalogTrackName
|
|
14039
|
-
);
|
|
14040
|
-
}
|
|
14041
|
-
if (!alreadyCompleted) {
|
|
14042
|
-
this.#fecManager.countRecovery(ownerPacketId, repairSbn, repairCatalogTrackName);
|
|
14043
|
-
if (repairCatalogTrackName === void 0) {
|
|
14044
|
-
this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId);
|
|
14045
|
-
} else {
|
|
14046
|
-
this.#fecManager.markBlockCompleted(
|
|
14047
|
-
repairSbn,
|
|
14048
|
-
ownerPacketId,
|
|
14049
|
-
repairCatalogTrackName
|
|
14050
|
-
);
|
|
14051
|
-
}
|
|
14052
|
-
this.#routeRecoveredBlock(
|
|
14053
|
-
repairSbn,
|
|
14054
|
-
recovered,
|
|
14055
|
-
ownerPacketId,
|
|
14056
|
-
catalogTrackNameForPacket(ingress, ownerPacketId)
|
|
14057
|
-
);
|
|
14058
|
-
}
|
|
14059
|
-
}
|
|
14058
|
+
this.#handleIsoRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair, source);
|
|
14060
14059
|
}
|
|
14061
14060
|
} else {
|
|
14062
14061
|
if (this.#classifyPacketId(info.packetId) === null) return;
|
|
@@ -14080,8 +14079,8 @@ var MmtpRouter = class {
|
|
|
14080
14079
|
}
|
|
14081
14080
|
const duplicates = this.#stream(info.packetId, catalogTrackName).duplicates;
|
|
14082
14081
|
const fecPayloadId = fecUnreadable ? null : info.fecPayloadId;
|
|
14083
|
-
const
|
|
14084
|
-
const coords =
|
|
14082
|
+
const fecSource = this.#fecManager !== null && fecPayloadId != null;
|
|
14083
|
+
const coords = fecSource && fecPayloadId ? this.#locateFecSource(info.packetId, catalogTrackName, fecPayloadId.ssId) ?? void 0 : void 0;
|
|
14085
14084
|
duplicates.advance(coords?.sbn);
|
|
14086
14085
|
if (source === "multicast" && !overlapCopy && !releasedFromQuarantine && catalogTrackName !== void 0 && duplicates.predatesHorizon(info.sequenceNumber, fecPayloadId?.ssId)) {
|
|
14087
14086
|
this.#publisherRestarts++;
|
|
@@ -14096,9 +14095,8 @@ var MmtpRouter = class {
|
|
|
14096
14095
|
}
|
|
14097
14096
|
if (this.#fecManager && fecPayloadId && coords) {
|
|
14098
14097
|
const fecId = fecPayloadId;
|
|
14099
|
-
const symbolSize = this.#symbolSizeForPacket(info.packetId);
|
|
14100
14098
|
const rawSymbol = packet.subarray(0, packet.length - 4);
|
|
14101
|
-
const oversized =
|
|
14099
|
+
const oversized = !(this.#fecManager.isoSourceFits?.(info.packetId, rawSymbol.length) ?? false);
|
|
14102
14100
|
let overlapBlockCompleted = false;
|
|
14103
14101
|
if (overlapCopy) {
|
|
14104
14102
|
overlapBlockCompleted = catalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(coords.sbn, info.packetId) : this.#fecManager.isBlockCompleted(
|
|
@@ -14133,7 +14131,7 @@ var MmtpRouter = class {
|
|
|
14133
14131
|
}
|
|
14134
14132
|
parseSourceSymbol = (overlapCopy ? !overlapBlockCompleted : sourceSymbolAccepted) && !duplicates.parsedSymbols.has(fecId.ssId);
|
|
14135
14133
|
fecInfo = { sbn: coords.sbn, esi: coords.esi, oversized };
|
|
14136
|
-
} else if (
|
|
14134
|
+
} else if (fecSource && this.#fecManager && fecPayloadId) {
|
|
14137
14135
|
const ssId = fecPayloadId.ssId;
|
|
14138
14136
|
if (overlapCopy) {
|
|
14139
14137
|
parseSourceSymbol = !duplicates.parsedSymbols.has(ssId);
|
|
@@ -14167,12 +14165,13 @@ var MmtpRouter = class {
|
|
|
14167
14165
|
this.#feedViaParser(packet, info, fecInfo, {
|
|
14168
14166
|
source,
|
|
14169
14167
|
catalogTrackName,
|
|
14170
|
-
|
|
14168
|
+
// An FEC source frame is anchored by its own SS_ID (C.5.2).
|
|
14169
|
+
...fecSource && fecPayloadId ? { fecSbn: fecPayloadId.ssId } : {}
|
|
14171
14170
|
}, parsed);
|
|
14172
14171
|
}
|
|
14173
14172
|
if (this.#classifyPacketId(info.packetId) === null) return;
|
|
14174
|
-
if (this.#fecManager && fecPayloadId && sourceSymbolAccepted && sourceSymbolTrackable) {
|
|
14175
|
-
const { sbn, esi } =
|
|
14173
|
+
if (this.#fecManager && fecPayloadId && coords && sourceSymbolAccepted && sourceSymbolTrackable) {
|
|
14174
|
+
const { sbn, esi } = coords;
|
|
14176
14175
|
if (this.#classifyPacketId(info.packetId) !== null && this.#fecManager.isTrackConfigured?.(info.packetId) !== false) {
|
|
14177
14176
|
this.#fecManager.trackBlock(
|
|
14178
14177
|
sbn,
|
|
@@ -14193,13 +14192,11 @@ var MmtpRouter = class {
|
|
|
14193
14192
|
const recovered = catalogTrackName === void 0 ? this.#fecManager.feedSourceSymbol(
|
|
14194
14193
|
info.packetId,
|
|
14195
14194
|
sourceFec.ssId,
|
|
14196
|
-
sourceFec.symbolData
|
|
14197
|
-
void 0
|
|
14195
|
+
sourceFec.symbolData
|
|
14198
14196
|
) : this.#fecManager.feedSourceSymbol(
|
|
14199
14197
|
info.packetId,
|
|
14200
14198
|
sourceFec.ssId,
|
|
14201
14199
|
sourceFec.symbolData,
|
|
14202
|
-
void 0,
|
|
14203
14200
|
catalogTrackName
|
|
14204
14201
|
);
|
|
14205
14202
|
if (recovered) {
|
|
@@ -14606,7 +14603,6 @@ var MmtpRouter = class {
|
|
|
14606
14603
|
// ── Private: FEC recovery routing ───────────────────────────────
|
|
14607
14604
|
/** Route WASM-recovered block through appropriate path based on container kind. */
|
|
14608
14605
|
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName) {
|
|
14609
|
-
const k = this.#kForPacket(ownerPacketId);
|
|
14610
14606
|
const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
|
|
14611
14607
|
if (ownerPacketId === void 0) {
|
|
14612
14608
|
throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
|
|
@@ -14619,7 +14615,6 @@ var MmtpRouter = class {
|
|
|
14619
14615
|
}
|
|
14620
14616
|
const symbolSize = packetSymbolSize;
|
|
14621
14617
|
const result = processFecBlock(recovered, {
|
|
14622
|
-
...blockProfile === "k-padded-v1" ? { k } : {},
|
|
14623
14618
|
symbolSize,
|
|
14624
14619
|
ownerPacketId,
|
|
14625
14620
|
blockProfile
|
|
@@ -14699,8 +14694,7 @@ var MmtpRouter = class {
|
|
|
14699
14694
|
const admission = verifier.admitObject(
|
|
14700
14695
|
{
|
|
14701
14696
|
...identity,
|
|
14702
|
-
packetId: ownerPacketId
|
|
14703
|
-
...ownerPacketId !== void 0 && this.#isIsoPacket(ownerPacketId) ? {} : { ssId: blockId * this.#kForPacket(ownerPacketId) + symbol.index }
|
|
14697
|
+
packetId: ownerPacketId
|
|
14704
14698
|
},
|
|
14705
14699
|
symbol.rawSymbol,
|
|
14706
14700
|
() => {
|
|
@@ -14803,7 +14797,7 @@ var MmtpRouter = class {
|
|
|
14803
14797
|
rapFlag: sym.fragment.rapFlag,
|
|
14804
14798
|
recovered: true,
|
|
14805
14799
|
deliveryProvenance,
|
|
14806
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
14800
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
14807
14801
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
14808
14802
|
});
|
|
14809
14803
|
continue;
|
|
@@ -14915,7 +14909,7 @@ var MmtpRouter = class {
|
|
|
14915
14909
|
rapFlag: assembled.rapFlag,
|
|
14916
14910
|
recovered: true,
|
|
14917
14911
|
...assembled.deliveryProvenance === void 0 ? {} : { deliveryProvenance: assembled.deliveryProvenance },
|
|
14918
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
14912
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
14919
14913
|
...recoveredCatalogTrackName === void 0 ? {} : { catalogTrackName: recoveredCatalogTrackName }
|
|
14920
14914
|
});
|
|
14921
14915
|
continue;
|
|
@@ -15052,7 +15046,7 @@ var MmtpRouter = class {
|
|
|
15052
15046
|
rapFlag: sym.fragment.rapFlag,
|
|
15053
15047
|
recovered: true,
|
|
15054
15048
|
deliveryProvenance,
|
|
15055
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
15049
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
15056
15050
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
15057
15051
|
});
|
|
15058
15052
|
} else {
|
|
@@ -15131,7 +15125,7 @@ var MmtpRouter = class {
|
|
|
15131
15125
|
rapFlag: assembled.rapFlag,
|
|
15132
15126
|
recovered: true,
|
|
15133
15127
|
deliveryProvenance,
|
|
15134
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
15128
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
15135
15129
|
...assembled.catalogTrackName === void 0 ? {} : { catalogTrackName: assembled.catalogTrackName }
|
|
15136
15130
|
});
|
|
15137
15131
|
}
|
|
@@ -15166,7 +15160,7 @@ var MmtpRouter = class {
|
|
|
15166
15160
|
...sym.fragment.sampleNumber === void 0 ? {} : { sampleNumber: sym.fragment.sampleNumber },
|
|
15167
15161
|
recovered: true,
|
|
15168
15162
|
deliveryProvenance,
|
|
15169
|
-
fecSbn: this.#fecAnchorForRecovered(
|
|
15163
|
+
fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
|
|
15170
15164
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
15171
15165
|
});
|
|
15172
15166
|
continue;
|
|
@@ -15217,22 +15211,17 @@ var MoqFecAlgorithm;
|
|
|
15217
15211
|
MoqFecAlgorithm2[MoqFecAlgorithm2["RaptorQ"] = 2] = "RaptorQ";
|
|
15218
15212
|
})(MoqFecAlgorithm || (MoqFecAlgorithm = {}));
|
|
15219
15213
|
|
|
15220
|
-
// node_modules/@blockcast/mmt-
|
|
15221
|
-
var
|
|
15222
|
-
var
|
|
15223
|
-
var
|
|
15224
|
-
|
|
15225
|
-
// node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/dist/mmtp.js
|
|
15226
|
-
var MPU_HEADER_SIZE4 = 8;
|
|
15227
|
-
var MPU_LENGTH_FIELD_SIZE4 = 2;
|
|
15228
|
-
var MPU_HEADER_REMAINDER_SIZE4 = MPU_HEADER_SIZE4 - MPU_LENGTH_FIELD_SIZE4;
|
|
15214
|
+
// node_modules/@blockcast/mmt-base/dist/mmtp.js
|
|
15215
|
+
var MPU_HEADER_SIZE2 = 8;
|
|
15216
|
+
var MPU_LENGTH_FIELD_SIZE2 = 2;
|
|
15217
|
+
var MPU_HEADER_REMAINDER_SIZE2 = MPU_HEADER_SIZE2 - MPU_LENGTH_FIELD_SIZE2;
|
|
15229
15218
|
var MMTP_SIGNALING_PACKET_ID2 = 0;
|
|
15230
15219
|
var MMTP_PACKET_ID_MAX2 = 65535;
|
|
15231
15220
|
|
|
15232
|
-
// node_modules/@blockcast/mmt-
|
|
15221
|
+
// node_modules/@blockcast/mmt-base/dist/net-constants.js
|
|
15233
15222
|
var DEFAULT_AMT_RELAY_PORT2 = 2268;
|
|
15234
15223
|
|
|
15235
|
-
// node_modules/@blockcast/mmt-
|
|
15224
|
+
// node_modules/@blockcast/mmt-base/dist/ntp-short.js
|
|
15236
15225
|
var NTP_SHORT_BITS2 = 32;
|
|
15237
15226
|
var NTP_SHORT_MODULUS2 = 2 ** NTP_SHORT_BITS2;
|
|
15238
15227
|
var NTP_SHORT_HALF_RANGE2 = 2 ** (NTP_SHORT_BITS2 - 1);
|
|
@@ -15253,9 +15242,8 @@ function compareNtpShort(a, b) {
|
|
|
15253
15242
|
return "unordered";
|
|
15254
15243
|
}
|
|
15255
15244
|
|
|
15256
|
-
// node_modules/@blockcast/mmt-
|
|
15245
|
+
// node_modules/@blockcast/mmt-base/dist/fec-block-profile.js
|
|
15257
15246
|
var FEC_BLOCK_PROFILES2 = Object.freeze([
|
|
15258
|
-
"k-padded-v1",
|
|
15259
15247
|
"iso-ssbg1-v1"
|
|
15260
15248
|
]);
|
|
15261
15249
|
|
|
@@ -15647,10 +15635,12 @@ var MulticastOrchestrator = class {
|
|
|
15647
15635
|
}
|
|
15648
15636
|
onGoodput(sample) {
|
|
15649
15637
|
const bitrate = sample.bitrate;
|
|
15650
|
-
if (!this.#current
|
|
15638
|
+
if (!this.#current)
|
|
15651
15639
|
return void 0;
|
|
15652
15640
|
if (this.#state === "joining")
|
|
15653
15641
|
return this.poll();
|
|
15642
|
+
if (this.options.adaptive === false)
|
|
15643
|
+
return void 0;
|
|
15654
15644
|
if (bitrate === void 0 || !Number.isFinite(bitrate))
|
|
15655
15645
|
return void 0;
|
|
15656
15646
|
this.#estimate = bitrate;
|
|
@@ -15717,6 +15707,27 @@ var MulticastOrchestrator = class {
|
|
|
15717
15707
|
this.#favored = void 0;
|
|
15718
15708
|
return { kind: "join-target", current: this.#current, target: desired };
|
|
15719
15709
|
}
|
|
15710
|
+
/**
|
|
15711
|
+
* Join `target` because the viewer selected it, not because goodput or
|
|
15712
|
+
* headroom ranked it. It runs whether or not the orchestrator adapts, and
|
|
15713
|
+
* takes the path an adaptive join takes: commit on the target's RAP at or
|
|
15714
|
+
* beyond the high-water mark, abandon at the join deadline. While it is
|
|
15715
|
+
* pending nothing re-ranks the ladder; goodput only advances the deadline.
|
|
15716
|
+
*
|
|
15717
|
+
* Returns undefined, changing nothing, while another join is pending (cancel
|
|
15718
|
+
* it first), when `target` is the current tier, or when it carries no media.
|
|
15719
|
+
*/
|
|
15720
|
+
select(target) {
|
|
15721
|
+
if (!this.#current || this.#state === "joining" || !carriesMedia(target))
|
|
15722
|
+
return void 0;
|
|
15723
|
+
if (endpointKey(target) === endpointKey(this.#current))
|
|
15724
|
+
return void 0;
|
|
15725
|
+
this.#state = "joining";
|
|
15726
|
+
this.#target = target;
|
|
15727
|
+
this.#joinStartedAt = this.#now();
|
|
15728
|
+
this.#favored = void 0;
|
|
15729
|
+
return { kind: "join-target", current: this.#current, target };
|
|
15730
|
+
}
|
|
15720
15731
|
/**
|
|
15721
15732
|
* Feed a packet from the active tier (to advance the high-water mark) or
|
|
15722
15733
|
* from the in-flight target (to find a committable RAP).
|
|
@@ -16756,7 +16767,7 @@ var MulticastTransport = class _MulticastTransport {
|
|
|
16756
16767
|
// inside the window, instead of a clear+set pair on every packet.
|
|
16757
16768
|
#watchdog = null;
|
|
16758
16769
|
#lastPacketAtMs = 0;
|
|
16759
|
-
#failbackWindowMs
|
|
16770
|
+
#failbackWindowMs;
|
|
16760
16771
|
#onTakeover = null;
|
|
16761
16772
|
#onFailback = null;
|
|
16762
16773
|
// Data-plane-live edge: the first packet this join accepted. Everything
|
|
@@ -17061,14 +17072,20 @@ var MulticastTransport = class _MulticastTransport {
|
|
|
17061
17072
|
return this.#dataPlaneLive;
|
|
17062
17073
|
}
|
|
17063
17074
|
setFailbackWindowMs(windowMs) {
|
|
17064
|
-
if (!Number.isFinite(windowMs) || windowMs <= 0) {
|
|
17075
|
+
if (windowMs !== null && (!Number.isFinite(windowMs) || windowMs <= 0)) {
|
|
17065
17076
|
throw new Error(
|
|
17066
17077
|
`[MulticastTransport] failbackWindowMs=${windowMs}. Derive a positive failback window from the stream catalog.`
|
|
17067
17078
|
);
|
|
17068
17079
|
}
|
|
17069
17080
|
this.#failbackWindowMs = windowMs;
|
|
17070
|
-
if (
|
|
17071
|
-
clearTimeout(this.#watchdog);
|
|
17081
|
+
if (windowMs === null) {
|
|
17082
|
+
if (this.#watchdog !== null) clearTimeout(this.#watchdog);
|
|
17083
|
+
this.#watchdog = null;
|
|
17084
|
+
return;
|
|
17085
|
+
}
|
|
17086
|
+
if (this.#watchdog !== null) clearTimeout(this.#watchdog);
|
|
17087
|
+
this.#watchdog = null;
|
|
17088
|
+
if (this.#dataPlaneLive && this.#onFailback) {
|
|
17072
17089
|
const silentMs = performance.now() - this.#lastPacketAtMs;
|
|
17073
17090
|
this.#armWatchdog(Math.max(0, Math.ceil(windowMs - silentMs)));
|
|
17074
17091
|
}
|
|
@@ -17394,7 +17411,8 @@ var MulticastTransport = class _MulticastTransport {
|
|
|
17394
17411
|
}
|
|
17395
17412
|
if (this.#watchdog !== null) return;
|
|
17396
17413
|
if (!this.#onFailback) return;
|
|
17397
|
-
if (this.#failbackWindowMs === null)
|
|
17414
|
+
if (this.#failbackWindowMs === null) return;
|
|
17415
|
+
if (this.#failbackWindowMs === void 0) {
|
|
17398
17416
|
throw new Error(
|
|
17399
17417
|
"[MulticastTransport] Failback watchdog requires a catalog-derived window"
|
|
17400
17418
|
);
|
|
@@ -17404,7 +17422,7 @@ var MulticastTransport = class _MulticastTransport {
|
|
|
17404
17422
|
#armWatchdog(delayMs) {
|
|
17405
17423
|
this.#watchdog = setTimeout(() => {
|
|
17406
17424
|
this.#watchdog = null;
|
|
17407
|
-
if (this.#disposed || !this.#onFailback || this.#failbackWindowMs
|
|
17425
|
+
if (this.#disposed || !this.#onFailback || this.#failbackWindowMs == null) return;
|
|
17408
17426
|
const silentMs = performance.now() - this.#lastPacketAtMs;
|
|
17409
17427
|
if (silentMs < this.#failbackWindowMs) {
|
|
17410
17428
|
this.#armWatchdog(Math.ceil(this.#failbackWindowMs - silentMs));
|
|
@@ -17438,6 +17456,35 @@ var BASE64URL_DECODE_TABLE = (() => {
|
|
|
17438
17456
|
return table;
|
|
17439
17457
|
})();
|
|
17440
17458
|
|
|
17459
|
+
// src/repair-layers.ts
|
|
17460
|
+
var RepairContractError = class extends Error {
|
|
17461
|
+
constructor(code, path, message) {
|
|
17462
|
+
super(`[repair-layers:${code}] ${path}: ${message}`);
|
|
17463
|
+
this.code = code;
|
|
17464
|
+
this.path = path;
|
|
17465
|
+
this.name = "RepairContractError";
|
|
17466
|
+
}
|
|
17467
|
+
};
|
|
17468
|
+
var throwRepairContractError = (code, path, message) => {
|
|
17469
|
+
throw new RepairContractError(code, path, message);
|
|
17470
|
+
};
|
|
17471
|
+
function readFecBlockProfile(value, path, declaresFecInstance, fail) {
|
|
17472
|
+
if (value === void 0 && !declaresFecInstance) return void 0;
|
|
17473
|
+
if (isFecBlockProfile(value)) return value;
|
|
17474
|
+
if (typeof value === "string" && value.length > 0) {
|
|
17475
|
+
return fail(
|
|
17476
|
+
"track_fec_block_profile_unimplemented",
|
|
17477
|
+
path,
|
|
17478
|
+
`${JSON.stringify(value)} is not an implemented FEC block profile; only "iso-ssbg1-v1" is implemented`
|
|
17479
|
+
);
|
|
17480
|
+
}
|
|
17481
|
+
try {
|
|
17482
|
+
return parseFecBlockProfile(value, "blockProfile");
|
|
17483
|
+
} catch (error) {
|
|
17484
|
+
return fail("track_fec_block_profile", path, error.message);
|
|
17485
|
+
}
|
|
17486
|
+
}
|
|
17487
|
+
|
|
17441
17488
|
// src/carp-catalog.ts
|
|
17442
17489
|
var VALID_ROLE_SUFFIXES = /* @__PURE__ */ new Set(["base", "delta", "repair"]);
|
|
17443
17490
|
function parseTrackRole(trackName) {
|
|
@@ -17477,12 +17524,49 @@ function hasSwitchingSetRole(names) {
|
|
|
17477
17524
|
return suffix === "base" || suffix === "delta";
|
|
17478
17525
|
});
|
|
17479
17526
|
}
|
|
17480
|
-
function
|
|
17527
|
+
function isFecCatalogGeometry(obj) {
|
|
17481
17528
|
if (typeof obj !== "object" || obj === null) return false;
|
|
17482
17529
|
const o = obj;
|
|
17483
17530
|
const algorithm = o["algorithm"];
|
|
17484
17531
|
const symbolSize = o["symbolSize"];
|
|
17485
|
-
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
|
|
17532
|
+
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;
|
|
17533
|
+
}
|
|
17534
|
+
function isFecCatalogConfig(obj) {
|
|
17535
|
+
if (!isFecCatalogGeometry(obj)) return false;
|
|
17536
|
+
const o = obj;
|
|
17537
|
+
return isFecBlockProfile(o["blockProfile"]) || o["algorithm"] === "none" && o["blockProfile"] === void 0;
|
|
17538
|
+
}
|
|
17539
|
+
|
|
17540
|
+
// src/catalog-fec-params.ts
|
|
17541
|
+
import { catalogFecInterleaveMs } from "@blockcast/mmt-fec";
|
|
17542
|
+
var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
|
|
17543
|
+
function recordOf2(value) {
|
|
17544
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
17545
|
+
}
|
|
17546
|
+
function blockProfileUnavailable(value, path, declaresFecInstance) {
|
|
17547
|
+
try {
|
|
17548
|
+
readFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);
|
|
17549
|
+
return void 0;
|
|
17550
|
+
} catch (error) {
|
|
17551
|
+
if (!(error instanceof RepairContractError) || error.code !== "track_fec_block_profile" && error.code !== "track_fec_block_profile_unimplemented") {
|
|
17552
|
+
throw error;
|
|
17553
|
+
}
|
|
17554
|
+
return Object.freeze({ code: error.code, path: error.path, message: error.message });
|
|
17555
|
+
}
|
|
17556
|
+
}
|
|
17557
|
+
function isoSpanUnavailable(fec, path, declaresFecInstance) {
|
|
17558
|
+
if (!declaresFecInstance || isoBlockSpanUsable(fec)) return void 0;
|
|
17559
|
+
return Object.freeze({
|
|
17560
|
+
code: "track_fec",
|
|
17561
|
+
path,
|
|
17562
|
+
message: `[repair-layers:track_fec] ${path}: must be a non-negative safe integer in milliseconds`
|
|
17563
|
+
});
|
|
17564
|
+
}
|
|
17565
|
+
function catalogFecUnavailable(fec, fecPath) {
|
|
17566
|
+
const block = recordOf2(fec);
|
|
17567
|
+
if (block === void 0) return void 0;
|
|
17568
|
+
const declaresFecInstance = block["algorithm"] !== "none";
|
|
17569
|
+
return blockProfileUnavailable(block["blockProfile"], `${fecPath}.blockProfile`, declaresFecInstance) ?? isoSpanUnavailable(block, `${fecPath}.interleaveDepthMs`, declaresFecInstance);
|
|
17486
17570
|
}
|
|
17487
17571
|
|
|
17488
17572
|
// src/multicast-manager.ts
|
|
@@ -17516,6 +17600,13 @@ function newBlockPolicyStats(packetId, catalogTrackName, epoch) {
|
|
|
17516
17600
|
deltaRepairsPreempted: 0,
|
|
17517
17601
|
deltaRepairsDeferred: 0,
|
|
17518
17602
|
repairsSkipped: 0,
|
|
17603
|
+
repairsSkippedRttUnmeasured: 0,
|
|
17604
|
+
repairsSkippedRttExceedsBudget: 0,
|
|
17605
|
+
repairsSkippedPresentationDelayUnavailable: 0,
|
|
17606
|
+
lastRepairSkip: null,
|
|
17607
|
+
keyframeRepairFetchesSent: 0,
|
|
17608
|
+
keyframeBudgetHolds: 0,
|
|
17609
|
+
keyframeBudgetHoldMs: 0,
|
|
17519
17610
|
deltaBlocksFailed: 0,
|
|
17520
17611
|
audioBlocksRecoveredInband: 0,
|
|
17521
17612
|
audioBlocksRecoveredQuic: 0,
|
|
@@ -17728,7 +17819,10 @@ function projectFecDiagnosticBlock(block) {
|
|
|
17728
17819
|
lastSymbolTs: block.lastSymbolTs,
|
|
17729
17820
|
...block.completedTs === void 0 ? {} : { completedTs: block.completedTs },
|
|
17730
17821
|
deadlineTs: block.deadlineTs,
|
|
17731
|
-
|
|
17822
|
+
// ponytail: the per-packet authenticator is gone, so this is always 0. It
|
|
17823
|
+
// stays only because pim-multicast-gateway's normalizeSubscriptionDiagnostics
|
|
17824
|
+
// still requires a number here; drop it once that validator stops asking.
|
|
17825
|
+
altaVerified: 0,
|
|
17732
17826
|
relayBlockSigValid: block.relayBlockSigValid
|
|
17733
17827
|
};
|
|
17734
17828
|
}
|
|
@@ -17748,7 +17842,6 @@ function projectFecDiagnosticConfig(config) {
|
|
|
17748
17842
|
interleaveMs: config.interleaveMs,
|
|
17749
17843
|
fecMode: config.fecMode,
|
|
17750
17844
|
...config.repairTrack === void 0 ? {} : { repairTrack: config.repairTrack },
|
|
17751
|
-
altaEnabled: config.altaEnabled,
|
|
17752
17845
|
relayBlockSigEnabled: config.relayBlockSigEnabled
|
|
17753
17846
|
};
|
|
17754
17847
|
}
|
|
@@ -17795,9 +17888,8 @@ function repairTrackNamesBySource(tracks) {
|
|
|
17795
17888
|
bySource.set(source, names);
|
|
17796
17889
|
};
|
|
17797
17890
|
for (const track of tracks) {
|
|
17798
|
-
|
|
17799
|
-
|
|
17800
|
-
}
|
|
17891
|
+
const repairTrack = typeof track.fec === "object" && track.fec !== null ? track.fec.repairTrack : void 0;
|
|
17892
|
+
if (typeof repairTrack === "string") add(track.name, repairTrack);
|
|
17801
17893
|
if (track.packaging === "fec-repair" && Array.isArray(track.depends) && track.depends.length === 1) {
|
|
17802
17894
|
add(track.depends[0], track.name);
|
|
17803
17895
|
}
|
|
@@ -17807,6 +17899,7 @@ function repairTrackNamesBySource(tracks) {
|
|
|
17807
17899
|
var MulticastManager = class {
|
|
17808
17900
|
#fec;
|
|
17809
17901
|
#router;
|
|
17902
|
+
#hostMfuTimeoutMs;
|
|
17810
17903
|
#transport;
|
|
17811
17904
|
#reportedFecDiagnosticsFailure = false;
|
|
17812
17905
|
// ── Stream-global FEC timing reduction ──
|
|
@@ -17905,6 +17998,7 @@ var MulticastManager = class {
|
|
|
17905
17998
|
this.#emitRepairLifecycle(decision);
|
|
17906
17999
|
}
|
|
17907
18000
|
});
|
|
18001
|
+
this.#hostMfuTimeoutMs = this.#router.reassembler.timeoutMs;
|
|
17908
18002
|
this.#transport = new MulticastTransport((packet, ingress) => {
|
|
17909
18003
|
this.#router.feedPacket(packet, "multicast", void 0, ingress);
|
|
17910
18004
|
});
|
|
@@ -18121,7 +18215,6 @@ var MulticastManager = class {
|
|
|
18121
18215
|
...this.#streamFecBaseline ? [this.#streamFecBaseline] : [],
|
|
18122
18216
|
...this.#trackFecTiming.values()
|
|
18123
18217
|
];
|
|
18124
|
-
if (contributions.length === 0) return;
|
|
18125
18218
|
let deliveryWindowMs;
|
|
18126
18219
|
let minimumFailbackWindowMs;
|
|
18127
18220
|
let mfuTimeoutMs;
|
|
@@ -18153,15 +18246,9 @@ var MulticastManager = class {
|
|
|
18153
18246
|
}
|
|
18154
18247
|
}
|
|
18155
18248
|
const failbackWindowMs = deliveryWindowMs === void 0 ? minimumFailbackWindowMs : Math.max(deliveryWindowMs, minimumFailbackWindowMs ?? 0);
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
if (mfuTimeoutMs !== void 0) {
|
|
18160
|
-
this.#router.setMfuTimeoutMs(mfuTimeoutMs);
|
|
18161
|
-
}
|
|
18162
|
-
if (reorderWindowFrames !== void 0) {
|
|
18163
|
-
this.#router.setReorderWindow(reorderWindowFrames);
|
|
18164
|
-
}
|
|
18249
|
+
this.#transport.setFailbackWindowMs(failbackWindowMs ?? null);
|
|
18250
|
+
this.#router.setMfuTimeoutMs(mfuTimeoutMs ?? this.#hostMfuTimeoutMs);
|
|
18251
|
+
this.#router.setReorderWindow(reorderWindowFrames ?? 0);
|
|
18165
18252
|
}
|
|
18166
18253
|
/**
|
|
18167
18254
|
* Configure one track's FEC geometry, block mapper, and block-inactivity
|
|
@@ -18188,9 +18275,9 @@ var MulticastManager = class {
|
|
|
18188
18275
|
`[MulticastManager] configureTrackFec(${packetId}) catalogTrackName must be non-empty when given`
|
|
18189
18276
|
);
|
|
18190
18277
|
}
|
|
18191
|
-
if (config.fecK === void 0
|
|
18278
|
+
if (config.fecK === void 0) {
|
|
18192
18279
|
throw new Error(
|
|
18193
|
-
`[MulticastManager] configureTrackFec(${packetId}) requires fecK
|
|
18280
|
+
`[MulticastManager] configureTrackFec(${packetId}) requires fecK. Derive the per-track FEC geometry from the stream catalog.`
|
|
18194
18281
|
);
|
|
18195
18282
|
}
|
|
18196
18283
|
const blockProfile = parseFecBlockProfile(
|
|
@@ -18198,7 +18285,10 @@ var MulticastManager = class {
|
|
|
18198
18285
|
`[MulticastManager] configureTrackFec(${packetId}) fecBlockProfile`
|
|
18199
18286
|
);
|
|
18200
18287
|
assertPositiveGeometry("fecK", config.fecK);
|
|
18201
|
-
|
|
18288
|
+
const interleaveDepthMs = config.fecInterleaveDepthMs ?? 0;
|
|
18289
|
+
if (!Number.isSafeInteger(interleaveDepthMs) || interleaveDepthMs < 0) {
|
|
18290
|
+
throw new Error("[MulticastManager] fecInterleaveDepthMs must be a non-negative safe integer");
|
|
18291
|
+
}
|
|
18202
18292
|
assertPositiveGeometry("fecSymbolSize", config.fecSymbolSize);
|
|
18203
18293
|
assertPositiveGeometry("fps", config.fps);
|
|
18204
18294
|
assertPositiveGeometry("bandwidth", config.bandwidth);
|
|
@@ -18214,8 +18304,8 @@ var MulticastManager = class {
|
|
|
18214
18304
|
"fecReorderToleranceMs",
|
|
18215
18305
|
config.fecReorderToleranceMs
|
|
18216
18306
|
);
|
|
18217
|
-
const deliveryWindowMs =
|
|
18218
|
-
interleaveDepthMs
|
|
18307
|
+
const deliveryWindowMs = interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
|
|
18308
|
+
interleaveDepthMs,
|
|
18219
18309
|
sourceSymbols: config.fecK,
|
|
18220
18310
|
repairSymbols: config.fecRepairSymbols,
|
|
18221
18311
|
symbolSize: config.fecSymbolSize,
|
|
@@ -18224,10 +18314,10 @@ var MulticastManager = class {
|
|
|
18224
18314
|
jitterBufferMs: config.fecReorderToleranceMs
|
|
18225
18315
|
}) : void 0;
|
|
18226
18316
|
const trackKind = config.trackKind ?? this.#router.trackKindFor(packetId);
|
|
18227
|
-
const geometry = trackKind === void 0 ? void 0 : deriveBlockClassifierGeometry({
|
|
18317
|
+
const geometry = trackKind === void 0 || interleaveDepthMs === 0 ? void 0 : deriveBlockClassifierGeometry({
|
|
18228
18318
|
trackKind,
|
|
18229
18319
|
framerate: config.fps,
|
|
18230
|
-
interleaveDepthMs
|
|
18320
|
+
interleaveDepthMs,
|
|
18231
18321
|
timescale: config.timescale,
|
|
18232
18322
|
keyframeIntervalTicks: config.keyframeIntervalTicks,
|
|
18233
18323
|
keyframeIntervalMs: config.keyframeIntervalMs
|
|
@@ -18236,7 +18326,7 @@ var MulticastManager = class {
|
|
|
18236
18326
|
blockProfile,
|
|
18237
18327
|
k: config.fecK,
|
|
18238
18328
|
symbolSize: config.fecSymbolSize,
|
|
18239
|
-
interleaveDepthMs
|
|
18329
|
+
interleaveDepthMs,
|
|
18240
18330
|
fps: config.fps,
|
|
18241
18331
|
timeoutMs: deliveryWindowMs,
|
|
18242
18332
|
...trackKind === void 0 ? {} : { trackKind },
|
|
@@ -18247,7 +18337,7 @@ var MulticastManager = class {
|
|
|
18247
18337
|
this.#configureTrackMapper(packetId, fecConfig);
|
|
18248
18338
|
this.#trackFecTiming.set(packetId, {
|
|
18249
18339
|
deliveryWindowMs,
|
|
18250
|
-
interleaveDepthMs
|
|
18340
|
+
interleaveDepthMs,
|
|
18251
18341
|
fps: config.fps,
|
|
18252
18342
|
k: config.fecK,
|
|
18253
18343
|
repairSymbols: config.fecRepairSymbols,
|
|
@@ -18256,6 +18346,10 @@ var MulticastManager = class {
|
|
|
18256
18346
|
this.#applyStreamGlobalFecTiming();
|
|
18257
18347
|
}
|
|
18258
18348
|
configureFec(config) {
|
|
18349
|
+
const interleaveDepthMs = config.fecInterleaveDepthMs ?? 0;
|
|
18350
|
+
if (!Number.isSafeInteger(interleaveDepthMs) || interleaveDepthMs < 0) {
|
|
18351
|
+
throw new Error("[MulticastManager] fecInterleaveDepthMs must be a non-negative safe integer");
|
|
18352
|
+
}
|
|
18259
18353
|
const minimumFailbackWindowMs = config.minimumFailbackWindowMs;
|
|
18260
18354
|
if (minimumFailbackWindowMs !== void 0 && (!Number.isFinite(minimumFailbackWindowMs) || minimumFailbackWindowMs <= 0)) {
|
|
18261
18355
|
throw new Error(
|
|
@@ -18267,7 +18361,7 @@ var MulticastManager = class {
|
|
|
18267
18361
|
config.fecReorderToleranceMs
|
|
18268
18362
|
);
|
|
18269
18363
|
const deliveryWindowMs = config.fecK && config.fecK > 0 && config.fecInterleaveDepthMs && config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
|
|
18270
|
-
interleaveDepthMs
|
|
18364
|
+
interleaveDepthMs,
|
|
18271
18365
|
sourceSymbols: config.fecK,
|
|
18272
18366
|
repairSymbols: config.fecRepairSymbols,
|
|
18273
18367
|
symbolSize: config.fecSymbolSize,
|
|
@@ -18278,13 +18372,13 @@ var MulticastManager = class {
|
|
|
18278
18372
|
this.#fec.configure({
|
|
18279
18373
|
k: config.fecK,
|
|
18280
18374
|
symbolSize: config.fecSymbolSize,
|
|
18281
|
-
interleaveDepthMs
|
|
18375
|
+
interleaveDepthMs,
|
|
18282
18376
|
fps: config.fps,
|
|
18283
18377
|
timeoutMs: deliveryWindowMs
|
|
18284
18378
|
});
|
|
18285
18379
|
this.#streamFecBaseline = {
|
|
18286
18380
|
deliveryWindowMs,
|
|
18287
|
-
interleaveDepthMs
|
|
18381
|
+
interleaveDepthMs,
|
|
18288
18382
|
fps: config.fps,
|
|
18289
18383
|
k: config.fecK,
|
|
18290
18384
|
repairSymbols: config.fecRepairSymbols,
|
|
@@ -18333,6 +18427,7 @@ var MulticastManager = class {
|
|
|
18333
18427
|
}
|
|
18334
18428
|
setMfuTimeoutMs(ms) {
|
|
18335
18429
|
this.#router.setMfuTimeoutMs(ms);
|
|
18430
|
+
this.#hostMfuTimeoutMs = this.#router.reassembler.timeoutMs;
|
|
18336
18431
|
}
|
|
18337
18432
|
setTrackCodec(packetId, codec) {
|
|
18338
18433
|
this.#router.setTrackCodec(packetId, codec);
|
|
@@ -18355,6 +18450,7 @@ var MulticastManager = class {
|
|
|
18355
18450
|
onTrackPacket(packetId, cb) {
|
|
18356
18451
|
this.#router.onTrackPacket(packetId, cb);
|
|
18357
18452
|
}
|
|
18453
|
+
/** @deprecated Use onTrackInit(packetId, cb); see MmtpRouter.onInitSegment. */
|
|
18358
18454
|
onInitSegment(cb) {
|
|
18359
18455
|
this.#router.onInitSegment(cb);
|
|
18360
18456
|
}
|
|
@@ -18427,7 +18523,7 @@ var MulticastManager = class {
|
|
|
18427
18523
|
)
|
|
18428
18524
|
)
|
|
18429
18525
|
};
|
|
18430
|
-
const amtRelay = config.amtRelay
|
|
18526
|
+
const amtRelay = config.amtRelay;
|
|
18431
18527
|
const transportConfig = {
|
|
18432
18528
|
sourceAddress: config.sourceAddress,
|
|
18433
18529
|
groupAddress: config.groupAddress,
|
|
@@ -18440,9 +18536,8 @@ var MulticastManager = class {
|
|
|
18440
18536
|
// endpoint-config.ts) deliberately has no implicit relay port — it throws
|
|
18441
18537
|
// and `MulticastTransport.join` resolves false — so the default is
|
|
18442
18538
|
// resolved HERE and arrives there explicit, exactly as the two catalog
|
|
18443
|
-
// readers do it. This is the last of the three readers:
|
|
18444
|
-
//
|
|
18445
|
-
// port field), and `resolveNetworkSource` emits the same port-less shape.
|
|
18539
|
+
// readers do it. This is the last of the three readers: `resolveNetworkSource`
|
|
18540
|
+
// emits a port-less relay shape.
|
|
18446
18541
|
// Keyed on the resolved relay, so a DRIAD entry — which pins no relay —
|
|
18447
18542
|
// never has a port manufactured for it. Blank counts as no relay, the
|
|
18448
18543
|
// same test `catalogAmtNetworkSource` applies (it trims, then skips) and
|
|
@@ -18666,7 +18761,10 @@ var MulticastManager = class {
|
|
|
18666
18761
|
console.warn("[MulticastManager] rendition switch onCancel failed", error);
|
|
18667
18762
|
}
|
|
18668
18763
|
}
|
|
18669
|
-
/**
|
|
18764
|
+
/**
|
|
18765
|
+
* Release what the manager and FEC hold for a stream whose packet ID the
|
|
18766
|
+
* router retired, or whose FEC a catalog reconfiguration no longer configures.
|
|
18767
|
+
*/
|
|
18670
18768
|
#releaseRetiredStream(stream) {
|
|
18671
18769
|
for (const observer of this.#trackResetObservers) {
|
|
18672
18770
|
try {
|
|
@@ -18685,7 +18783,10 @@ var MulticastManager = class {
|
|
|
18685
18783
|
for (const [key, stats] of [...this.#blockPolicyStats]) {
|
|
18686
18784
|
if (stats.packetId === stream.packetId) this.#blockPolicyStats.delete(key);
|
|
18687
18785
|
}
|
|
18688
|
-
this.#trackFecTiming.delete(stream.packetId)
|
|
18786
|
+
if (this.#trackFecTiming.delete(stream.packetId)) {
|
|
18787
|
+
this.#applyStreamGlobalFecTiming();
|
|
18788
|
+
}
|
|
18789
|
+
this.#multiTrackFecTracks.delete(stream.packetId);
|
|
18689
18790
|
this.#fec.releasePacket(stream.packetId);
|
|
18690
18791
|
}
|
|
18691
18792
|
/** Feed a raw packet from an external source (AMT tunnel, ASM subscription).
|
|
@@ -18730,12 +18831,53 @@ var MulticastManager = class {
|
|
|
18730
18831
|
* D-17: Builds altGroup → track-name index for getSwitchingSet().
|
|
18731
18832
|
* D-26: Registers packetId → TrackRole on MmtpRouter for block-complete fanout.
|
|
18732
18833
|
*
|
|
18834
|
+
* FEC availability is per track (draft-ramadan-moq-fec §5.1). A media track
|
|
18835
|
+
* whose `fec` names no implemented `blockProfile` (only `iso-ssbg1-v1` is
|
|
18836
|
+
* implemented; the retired `k-padded-v1` is not), or an `iso-ssbg1-v1` span
|
|
18837
|
+
* it cannot use, is consumed without FEC: it gets no decoder, and none of
|
|
18838
|
+
* its repair tracks (layer 0 by `fec.repairTrack`, further layers and the
|
|
18839
|
+
* overlay by `depends`) is given an owner, so the router drops their packets
|
|
18840
|
+
* before any decoder. Those legs keep the `repair` role and their owner's
|
|
18841
|
+
* switching set. Neither the call nor any other track's FEC fails on that
|
|
18842
|
+
* account; `trackFecUnavailable(name)` reports the reason. FEC a previous
|
|
18843
|
+
* call configured on a packetId this call does not is released
|
|
18844
|
+
* (FECManager.releasePacket); frames its release gate held are delivered in
|
|
18845
|
+
* order, each gap stalled. FEC wired by hand (setFecDecoder) is left alone.
|
|
18846
|
+
* Geometry a decoder could not configure (K, P, T, `repairTrack`,
|
|
18847
|
+
* algorithm) still throws.
|
|
18848
|
+
*
|
|
18733
18849
|
* @param catalog - Multi-track catalog (tracks array with name, altGroup, fec, packetId).
|
|
18734
18850
|
* @param wasmDecoderSource - One WASM FEC decoder for a single FEC stream, or
|
|
18735
18851
|
* a factory returning a distinct decoder per stream.
|
|
18736
18852
|
*/
|
|
18737
18853
|
configureMultiTrack(catalog, wasmDecoderSource) {
|
|
18738
18854
|
const repairNamesBySource = repairTrackNamesBySource(catalog.tracks);
|
|
18855
|
+
const declaredRepairNames = /* @__PURE__ */ new Set();
|
|
18856
|
+
for (const names of repairNamesBySource.values()) {
|
|
18857
|
+
for (const name of names) declaredRepairNames.add(name);
|
|
18858
|
+
}
|
|
18859
|
+
const fecUnavailableByTrack = /* @__PURE__ */ new Map();
|
|
18860
|
+
for (let i = 0; i < catalog.tracks.length; i++) {
|
|
18861
|
+
const track = catalog.tracks[i];
|
|
18862
|
+
if (track.fec === void 0 || track.packaging === "fec-repair" || declaredRepairNames.has(track.name)) {
|
|
18863
|
+
continue;
|
|
18864
|
+
}
|
|
18865
|
+
const fecUnavailable = catalogFecUnavailable(track.fec, `$.tracks[${i}].fec`);
|
|
18866
|
+
if (fecUnavailable === void 0) continue;
|
|
18867
|
+
fecUnavailableByTrack.set(track.name, fecUnavailable);
|
|
18868
|
+
const layerZero = track.fec.repairTrack;
|
|
18869
|
+
if (typeof layerZero === "string" && layerZero.length > 0) {
|
|
18870
|
+
const names = repairNamesBySource.get(track.name) ?? /* @__PURE__ */ new Set();
|
|
18871
|
+
names.add(layerZero);
|
|
18872
|
+
repairNamesBySource.set(track.name, names);
|
|
18873
|
+
}
|
|
18874
|
+
}
|
|
18875
|
+
const unownedRepairNames = /* @__PURE__ */ new Set();
|
|
18876
|
+
for (const sourceName of fecUnavailableByTrack.keys()) {
|
|
18877
|
+
for (const repairName of repairNamesBySource.get(sourceName) ?? []) {
|
|
18878
|
+
unownedRepairNames.add(repairName);
|
|
18879
|
+
}
|
|
18880
|
+
}
|
|
18739
18881
|
const repairOwnerGroup = /* @__PURE__ */ new Map();
|
|
18740
18882
|
const repairOwnerAltGroup = /* @__PURE__ */ new Map();
|
|
18741
18883
|
for (const track of catalog.tracks) {
|
|
@@ -18788,11 +18930,13 @@ var MulticastManager = class {
|
|
|
18788
18930
|
for (let i = 0; i < catalog.tracks.length; i++) {
|
|
18789
18931
|
const track = catalog.tracks[i];
|
|
18790
18932
|
if (resolvedRoles[i] === "repair" || track.fec === void 0) continue;
|
|
18791
|
-
|
|
18933
|
+
const unavailable = fecUnavailableByTrack.has(track.name);
|
|
18934
|
+
if (!(unavailable ? isFecCatalogGeometry(track.fec) : isFecCatalogConfig(track.fec))) {
|
|
18792
18935
|
throw new Error(
|
|
18793
18936
|
`[MulticastManager] invalid fec config for track '${track.name}'`
|
|
18794
18937
|
);
|
|
18795
18938
|
}
|
|
18939
|
+
if (unavailable) continue;
|
|
18796
18940
|
if (track.packetId !== void 0) fecTrackIndexes.push(i);
|
|
18797
18941
|
}
|
|
18798
18942
|
const fecDecoders = /* @__PURE__ */ new Map();
|
|
@@ -18821,6 +18965,20 @@ var MulticastManager = class {
|
|
|
18821
18965
|
this.#switchingSets.clear();
|
|
18822
18966
|
this.#repairPacketIds.clear();
|
|
18823
18967
|
this.#router.clearRepairPacketIdOwners();
|
|
18968
|
+
const fecPacketIds = new Set([...fecDecoders.keys()].map((i) => catalog.tracks[i].packetId));
|
|
18969
|
+
for (const [packetId, catalogTrackName] of this.#multiTrackFecTracks) {
|
|
18970
|
+
if (fecPacketIds.has(packetId)) continue;
|
|
18971
|
+
if (!this.#fec.isTrackConfigured(packetId)) {
|
|
18972
|
+
this.#multiTrackFecTracks.delete(packetId);
|
|
18973
|
+
continue;
|
|
18974
|
+
}
|
|
18975
|
+
this.#router.releaseFecPacket(packetId);
|
|
18976
|
+
this.#releaseRetiredStream({ packetId, catalogTrackName });
|
|
18977
|
+
}
|
|
18978
|
+
this.#fecUnavailableByTrack.clear();
|
|
18979
|
+
for (const [name, reason] of fecUnavailableByTrack) {
|
|
18980
|
+
this.#fecUnavailableByTrack.set(name, reason);
|
|
18981
|
+
}
|
|
18824
18982
|
const catalogTracksByName = new Map(
|
|
18825
18983
|
catalog.tracks.map((track) => [track.name, track])
|
|
18826
18984
|
);
|
|
@@ -18842,10 +19000,11 @@ var MulticastManager = class {
|
|
|
18842
19000
|
}
|
|
18843
19001
|
continue;
|
|
18844
19002
|
}
|
|
18845
|
-
if (track.fec !== void 0) {
|
|
19003
|
+
if (track.fec !== void 0 && !fecUnavailableByTrack.has(track.name)) {
|
|
18846
19004
|
const fecCfg = track.fec;
|
|
18847
19005
|
if (track.packetId !== void 0) {
|
|
18848
19006
|
for (const repairName of repairNamesBySource.get(track.name) ?? []) {
|
|
19007
|
+
if (unownedRepairNames.has(repairName)) continue;
|
|
18849
19008
|
const repairTrack = catalogTracksByName.get(repairName);
|
|
18850
19009
|
if (repairTrack?.packetId !== void 0) {
|
|
18851
19010
|
this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);
|
|
@@ -18866,17 +19025,18 @@ var MulticastManager = class {
|
|
|
18866
19025
|
blockProfile,
|
|
18867
19026
|
k: fecCfg.sourceSymbols,
|
|
18868
19027
|
symbolSize: fecCfg.symbolSize,
|
|
18869
|
-
interleaveDepthMs: fecCfg.interleaveDepthMs,
|
|
19028
|
+
interleaveDepthMs: fecCfg.interleaveDepthMs ?? 0,
|
|
18870
19029
|
fps: track.framerate,
|
|
18871
19030
|
trackKind: "video"
|
|
18872
19031
|
},
|
|
18873
19032
|
track.name
|
|
18874
19033
|
);
|
|
19034
|
+
this.#multiTrackFecTracks.set(track.packetId, track.name);
|
|
18875
19035
|
this.#configureTrackMapper(track.packetId, {
|
|
18876
19036
|
blockProfile,
|
|
18877
19037
|
k: fecCfg.sourceSymbols,
|
|
18878
19038
|
symbolSize: fecCfg.symbolSize,
|
|
18879
|
-
interleaveDepthMs: fecCfg.interleaveDepthMs,
|
|
19039
|
+
interleaveDepthMs: fecCfg.interleaveDepthMs ?? 0,
|
|
18880
19040
|
fps: track.framerate
|
|
18881
19041
|
});
|
|
18882
19042
|
this.#router.setPacketIdType(track.packetId, "video");
|
|
@@ -18884,6 +19044,24 @@ var MulticastManager = class {
|
|
|
18884
19044
|
}
|
|
18885
19045
|
}
|
|
18886
19046
|
}
|
|
19047
|
+
/**
|
|
19048
|
+
* Why `configureMultiTrack` consumes the named catalog track without FEC
|
|
19049
|
+
* although its `fec` block declares one (draft-ramadan-moq-fec §5.1): no
|
|
19050
|
+
* implemented `blockProfile` (`track_fec_block_profile_unimplemented`, e.g.
|
|
19051
|
+
* the retired `k-padded-v1`; `track_fec_block_profile` when absent), or an
|
|
19052
|
+
* invalid `iso-ssbg1-v1` span (`track_fec`). Absent/zero spans keep in-band
|
|
19053
|
+
* decoding enabled. The same reason the hang
|
|
19054
|
+
* resolver reports (`catalogFecUnavailable`). Undefined for every other
|
|
19055
|
+
* track, and for every track before the first call; each call replaces the
|
|
19056
|
+
* previous call's answers.
|
|
19057
|
+
*/
|
|
19058
|
+
trackFecUnavailable(trackName) {
|
|
19059
|
+
return this.#fecUnavailableByTrack.get(trackName);
|
|
19060
|
+
}
|
|
19061
|
+
/** Per-track FEC availability from the last `configureMultiTrack` call. */
|
|
19062
|
+
#fecUnavailableByTrack = /* @__PURE__ */ new Map();
|
|
19063
|
+
/** packetId → catalog track name of the FEC the last `configureMultiTrack` installed. */
|
|
19064
|
+
#multiTrackFecTracks = /* @__PURE__ */ new Map();
|
|
18887
19065
|
/**
|
|
18888
19066
|
* Return the track names belonging to the given altGroup switching set.
|
|
18889
19067
|
* D-17: altGroup groups base+delta+repair into one switching set.
|
|
@@ -18931,7 +19109,17 @@ var MulticastManager = class {
|
|
|
18931
19109
|
}
|
|
18932
19110
|
/** Record a repair request the scheduler sent no FETCH for. */
|
|
18933
19111
|
noteRepairSkipped(reason, measurement, sbn, packetId, catalogTrackName) {
|
|
18934
|
-
this.#statsFor(packetId, catalogTrackName)
|
|
19112
|
+
const stats = this.#statsFor(packetId, catalogTrackName);
|
|
19113
|
+
stats.repairsSkipped++;
|
|
19114
|
+
if (reason === "rtt-unmeasured") stats.repairsSkippedRttUnmeasured++;
|
|
19115
|
+
else if (reason === "rtt-exceeds-budget") stats.repairsSkippedRttExceedsBudget++;
|
|
19116
|
+
else stats.repairsSkippedPresentationDelayUnavailable++;
|
|
19117
|
+
stats.lastRepairSkip = {
|
|
19118
|
+
reason,
|
|
19119
|
+
rttMs: measurement.rttMs,
|
|
19120
|
+
budgetMs: measurement.budgetMs,
|
|
19121
|
+
priority: measurement.priority ?? null
|
|
19122
|
+
};
|
|
18935
19123
|
this.#emitRepairLifecycle({
|
|
18936
19124
|
version: 2,
|
|
18937
19125
|
type: "repair-skipped",
|
|
@@ -18941,6 +19129,24 @@ var MulticastManager = class {
|
|
|
18941
19129
|
budgetMs: measurement.budgetMs
|
|
18942
19130
|
});
|
|
18943
19131
|
}
|
|
19132
|
+
/**
|
|
19133
|
+
* Arrival time of the earliest frame the in-order release gate holds for
|
|
19134
|
+
* the stream: the gate releases it as a discontinuity once
|
|
19135
|
+
* `terminalRetentionMsForPacket` has passed since then.
|
|
19136
|
+
*/
|
|
19137
|
+
earliestGatedFrameAtMs(packetId, catalogTrackName) {
|
|
19138
|
+
return this.#router.fecReleaseEarliestHeldAtMs(packetId, catalogTrackName);
|
|
19139
|
+
}
|
|
19140
|
+
/** Record a keyframe late-repair budget the scheduler granted a block. */
|
|
19141
|
+
noteKeyframeBudgetGranted(extensionMs, _sbn, packetId, catalogTrackName) {
|
|
19142
|
+
const stats = this.#statsFor(packetId, catalogTrackName);
|
|
19143
|
+
stats.keyframeBudgetHolds++;
|
|
19144
|
+
stats.keyframeBudgetHoldMs += extensionMs;
|
|
19145
|
+
}
|
|
19146
|
+
/** Record a repair FETCH the scheduler admitted and sent. */
|
|
19147
|
+
noteRepairFetchSent(priority, _sbn, packetId, catalogTrackName) {
|
|
19148
|
+
if (priority === 0) this.#statsFor(packetId, catalogTrackName).keyframeRepairFetchesSent++;
|
|
19149
|
+
}
|
|
18944
19150
|
/**
|
|
18945
19151
|
* Record a repair-scheduler decision about a delta block: preempted for
|
|
18946
19152
|
* keyframe/audio work, or never dispatched before its block resolved.
|
|
@@ -18968,7 +19174,10 @@ var MulticastManager = class {
|
|
|
18968
19174
|
const out = {};
|
|
18969
19175
|
for (const [key, stats] of this.#blockPolicyStats) {
|
|
18970
19176
|
if (stats.epoch !== this.#fec.epochFor(stats.packetId)) continue;
|
|
18971
|
-
out[key] = {
|
|
19177
|
+
out[key] = {
|
|
19178
|
+
...stats,
|
|
19179
|
+
lastRepairSkip: stats.lastRepairSkip === null ? null : { ...stats.lastRepairSkip }
|
|
19180
|
+
};
|
|
18972
19181
|
}
|
|
18973
19182
|
return out;
|
|
18974
19183
|
}
|