@blockcast/mmt-container 0.5.0 → 1.0.0-main.3cb7359ea316

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.
Files changed (136) hide show
  1. package/README.md +21 -16
  2. package/dist/browser-runtime.js +1024 -1078
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.d.ts +33 -87
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +12 -434
  7. package/dist/carp-catalog.js.map +4 -4
  8. package/dist/catalog-fec-params.d.ts +48 -161
  9. package/dist/catalog-fec-params.d.ts.map +1 -1
  10. package/dist/catalog-fec-params.js +567 -592
  11. package/dist/catalog-fec-params.js.map +4 -4
  12. package/dist/catalog-network-source.js +6 -2
  13. package/dist/catalog-network-source.js.map +3 -3
  14. package/dist/delivery-catalog.js +12 -4
  15. package/dist/delivery-catalog.js.map +4 -4
  16. package/dist/delta-fallback-config.d.ts +21 -26
  17. package/dist/delta-fallback-config.d.ts.map +1 -1
  18. package/dist/delta-fallback-config.js +582 -24
  19. package/dist/delta-fallback-config.js.map +4 -4
  20. package/dist/fec-manager.js +6 -2
  21. package/dist/fec-manager.js.map +3 -3
  22. package/dist/fec-state.d.ts +5 -43
  23. package/dist/fec-state.d.ts.map +1 -1
  24. package/dist/fec-state.js +529 -34
  25. package/dist/fec-state.js.map +4 -4
  26. package/dist/fec-worker-setup.js +6 -2
  27. package/dist/fec-worker-setup.js.map +3 -3
  28. package/dist/held-layer-admission.js +6 -2
  29. package/dist/held-layer-admission.js.map +3 -3
  30. package/dist/index.d.ts +3 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +5 -39
  33. package/dist/index.js.map +2 -2
  34. package/dist/iso-block-locator.js +6 -2
  35. package/dist/iso-block-locator.js.map +3 -3
  36. package/dist/mfu-reassembler.js +6 -2
  37. package/dist/mfu-reassembler.js.map +3 -3
  38. package/dist/mmtp-container-parser.js +6 -2
  39. package/dist/mmtp-container-parser.js.map +3 -3
  40. package/dist/mmtp-container-parser.no-modules.js +551 -452
  41. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  42. package/dist/mmtp-decode-pipeline.js +6 -2
  43. package/dist/mmtp-decode-pipeline.js.map +3 -3
  44. package/dist/mmtp-fec-session.d.ts +7 -6
  45. package/dist/mmtp-fec-session.d.ts.map +1 -1
  46. package/dist/mmtp-fec-session.js +13 -9
  47. package/dist/mmtp-fec-session.js.map +3 -3
  48. package/dist/mmtp-router.d.ts +7 -16
  49. package/dist/mmtp-router.d.ts.map +1 -1
  50. package/dist/mmtp-router.js +19 -29
  51. package/dist/mmtp-router.js.map +3 -3
  52. package/dist/multicast-auth-catalog.js +7 -3
  53. package/dist/multicast-auth-catalog.js.map +3 -3
  54. package/dist/multicast-input.js +7 -3
  55. package/dist/multicast-input.js.map +3 -3
  56. package/dist/multicast-manager.d.ts +29 -32
  57. package/dist/multicast-manager.d.ts.map +1 -1
  58. package/dist/multicast-manager.js +682 -253
  59. package/dist/multicast-manager.js.map +4 -4
  60. package/dist/multicast-transport.d.ts +23 -2
  61. package/dist/multicast-transport.d.ts.map +1 -1
  62. package/dist/multicast-transport.js +57 -16
  63. package/dist/multicast-transport.js.map +4 -4
  64. package/dist/multicast-unwrap.js +6 -2
  65. package/dist/multicast-unwrap.js.map +3 -3
  66. package/dist/no-modules.js +551 -452
  67. package/dist/no-modules.js.map +4 -4
  68. package/dist/phase14-catalog-override.d.ts +10 -5
  69. package/dist/phase14-catalog-override.d.ts.map +1 -1
  70. package/dist/phase14-catalog-override.js +35 -11
  71. package/dist/phase14-catalog-override.js.map +2 -2
  72. package/dist/repair-fetch.d.ts +3 -1
  73. package/dist/repair-fetch.d.ts.map +1 -1
  74. package/dist/repair-fetch.js +16 -3
  75. package/dist/repair-fetch.js.map +3 -3
  76. package/dist/repair-layers.d.ts +0 -123
  77. package/dist/repair-layers.d.ts.map +1 -1
  78. package/dist/repair-layers.js +1 -349
  79. package/dist/repair-layers.js.map +3 -3
  80. package/dist/signaling.js +6 -2
  81. package/dist/signaling.js.map +3 -3
  82. package/dist/timestamp-codec.js +6 -2
  83. package/dist/timestamp-codec.js.map +3 -3
  84. package/dist/wasm-mfu-reassembler.js +6 -2
  85. package/dist/wasm-mfu-reassembler.js.map +3 -3
  86. package/dist/wasm-mmtp-container-parser.js +6 -2
  87. package/dist/wasm-mmtp-container-parser.js.map +3 -3
  88. package/package.json +9 -5
  89. package/schemas/catalog.schema.json +53 -58
  90. package/src/carp-catalog.ts +39 -229
  91. package/src/catalog-fec-params.ts +130 -493
  92. package/src/delta-fallback-config.ts +63 -50
  93. package/src/fec-state.ts +7 -62
  94. package/src/index.ts +10 -30
  95. package/src/mmtp-fec-session.ts +11 -10
  96. package/src/mmtp-router.ts +14 -29
  97. package/src/multicast-manager.ts +89 -121
  98. package/src/multicast-transport.ts +65 -12
  99. package/src/phase14-catalog-override.ts +67 -16
  100. package/src/repair-fetch.ts +27 -3
  101. package/src/repair-layers.ts +7 -520
  102. package/test-vectors/catalog/README.md +39 -67
  103. package/test-vectors/catalog/fec-01-runtime/README.md +15 -0
  104. package/test-vectors/catalog/{negative/fec-block-profile-retired.json → fec-01-runtime/fec-iso-ssbg1.json} +1 -1
  105. package/test-vectors/catalog/fec-01-runtime/fec-layered-repair.json +106 -0
  106. package/test-vectors/catalog/{negative/fec-block-profile-unknown.json → fec-01-runtime/fec-multicast.json} +2 -1
  107. package/test-vectors/catalog/fec-01-runtime/multicast-native-ssm-no-network-source.json +44 -0
  108. package/test-vectors/catalog/negative/fec-block-profile-missing.json +24 -11
  109. package/test-vectors/catalog/negative/fec-iso-symbol-size-below-floor.json +13 -8
  110. package/test-vectors/catalog/negative/fec-reed-solomon-iso-symbol-size-13.json +13 -8
  111. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +20 -10
  112. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +20 -10
  113. package/test-vectors/catalog/negative/fec-repair-overlay-source-symbols-zero.json +25 -17
  114. package/test-vectors/catalog/negative/fec-repair-symbols-zero.json +13 -8
  115. package/test-vectors/catalog/negative/fec-source-symbols-zero.json +13 -8
  116. package/test-vectors/catalog/negative/fec-symbol-size-zero.json +13 -8
  117. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +18 -9
  118. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +13 -8
  119. package/test-vectors/catalog/positive/fec-algorithm-unimplemented.json +14 -9
  120. package/test-vectors/catalog/positive/fec-iso-ssbg1.json +25 -12
  121. package/test-vectors/catalog/positive/fec-layered-repair.json +79 -35
  122. package/test-vectors/catalog/positive/fec-multicast.json +26 -13
  123. package/test-vectors/catalog/positive/fec-reed-solomon-layered.json +29 -18
  124. package/test-vectors/catalog/positive/multicast-draft01-4-1-example.json +28 -20
  125. package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +14 -10
  126. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +22 -12
  127. package/test-vectors/catalog/section-codes.json +13 -22
  128. package/test-vectors/catalog-timing.json +2 -2
  129. package/test-vectors/catalog-validation.json +30 -243
  130. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +0 -28
  131. package/test-vectors/catalog/negative/fec-reed-solomon-k-plus-p-over-255.json +0 -39
  132. package/test-vectors/catalog/negative/fec-reed-solomon-overlay-over-255.json +0 -40
  133. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +0 -32
  134. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +0 -34
  135. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +0 -24
  136. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +0 -33
@@ -3,70 +3,83 @@
3
3
  * {@link MediaRouter.configureDelta} (BLO-45475).
4
4
  *
5
5
  * Generic client policy selects WHICH group to admit (roles, groups, endpoint
6
- * availability); this module owns the MMTP/FEC half of that decision — the
7
- * shared repair pointer and the delta block geometry — so no player re-states
6
+ * availability); this module owns the MMTP/FEC half of that decision — each
7
+ * track's repair track and the delta block geometry — so no player re-states
8
8
  * the catalog FEC schema. It validates and never defaults: a group whose
9
9
  * required metadata is absent or contradictory throws, and the caller decides
10
10
  * that such a group invalidates the catalog rather than being skipped.
11
+ *
12
+ * FEC comes only from `resolveFecBindings` (BLO-50171), the validator's
13
+ * per-instance result: base and delta each bind their own instance and their
14
+ * own repair track (draft-ramadan-moq-fec-catalog-00 5 rule 2 forbids one
15
+ * Track Name in two `repairTrack` members, so a shared repair track is no FEC
16
+ * on either). The Phase-14 extension keys `deltaFailWindowBlocks` and
17
+ * `deltaProbeIntervalMs` are read from the bound instance objects; the
18
+ * validator ignores them as unrecognized instance keys (catalog-00 3.4).
11
19
  */
12
20
 
21
+ import { resolveFecBindings, type FecSourceBinding } from '@blockcast/mmt-base'
13
22
  import { assertDeltaFallbackConfig, type DeltaFallbackConfig } from './media-router.js'
14
23
 
15
- /** The catalog fields this mapping reads from one base or delta track. */
16
- export interface DeltaFallbackTrack {
17
- name: string
18
- framerate?: number
19
- fec?: {
20
- repairTrack?: string
21
- sourceSymbols?: number
22
- repairSymbols?: number
23
- interleaveDepthMs?: number
24
- deltaFailWindowBlocks?: number
25
- deltaProbeIntervalMs?: number
26
- }
27
- }
28
-
29
24
  export interface ResolvedDeltaFallback {
30
- /** The repair track both base and delta name; required at the endpoint. */
31
- repairTrackName: string
25
+ /** The base track's layer-0 repair track. */
26
+ baseRepairTrackName: string
27
+ /** The delta track's layer-0 repair track. */
28
+ deltaRepairTrackName: string
32
29
  config: DeltaFallbackConfig
33
30
  }
34
31
 
32
+ type Obj = Record<string, unknown>
33
+ const recordOf = (v: unknown): Obj | undefined =>
34
+ typeof v === 'object' && v !== null && !Array.isArray(v) ? (v as Obj) : undefined
35
+
36
+ /** RFC 6901 lookup; the binding pointers are the validator's own. */
37
+ function at(root: unknown, pointer: string): Obj | undefined {
38
+ let cur: unknown = root
39
+ for (const raw of pointer.split('/').slice(1)) {
40
+ const key = raw.replace(/~1/g, '/').replace(/~0/g, '~')
41
+ cur = Array.isArray(cur) ? cur[Number(key)] : recordOf(cur)?.[key]
42
+ }
43
+ return recordOf(cur)
44
+ }
45
+
35
46
  /**
36
- * Resolve one switching group. Throws when either track omits
37
- * `fec.repairTrack`, when the two pointers differ, when the base framerate is
38
- * not positive, or when the delta omits `fec.sourceSymbols`/`repairSymbols`.
39
- * Fallback-window, probe and interleave fields are read from the base first
40
- * (where the catalog places them), then the delta. The assembled config is then
41
- * held to {@link assertDeltaFallbackConfig}, the same check configureDelta
42
- * runs, so a group this resolves is a group configureDelta accepts — including
43
- * the requirement that a probe interval or an interleave depth be present.
47
+ * Resolve one switching group of `catalog` (either dialect). Throws when
48
+ * either track has no `ok` FEC binding (the message carries the validator's
49
+ * code and path), when the base framerate is not positive, or when the
50
+ * assembled config fails {@link assertDeltaFallbackConfig}, the same check
51
+ * configureDelta runs. Fallback-window and probe keys are read from the base
52
+ * instance first, then the delta's; the interleave depth likewise.
44
53
  */
45
- export function resolveDeltaFallback(base: DeltaFallbackTrack, delta: DeltaFallbackTrack): ResolvedDeltaFallback {
46
- const repair = base.fec?.repairTrack
47
- if (!repair) throw new Error(`[delta-fallback] base track "${base.name}" missing fec.repairTrack`)
48
- const deltaFec = delta.fec
49
- if (!deltaFec?.repairTrack) throw new Error(`[delta-fallback] delta track "${delta.name}" missing fec.repairTrack`)
50
- if (deltaFec.repairTrack !== repair)
51
- throw new Error(
52
- `[delta-fallback] inconsistent repair tracks: ${base.name} references "${repair}" but ${delta.name} references "${deltaFec.repairTrack}"`
53
- )
54
- if (!(typeof base.framerate === 'number' && Number.isFinite(base.framerate) && base.framerate > 0))
55
- throw new Error(`[delta-fallback] base track "${base.name}" missing positive framerate`)
56
- if (!(isPositive(deltaFec.sourceSymbols) && isPositive(deltaFec.repairSymbols)))
57
- throw new Error(`[delta-fallback] delta track "${delta.name}" missing positive fec.sourceSymbols/repairSymbols`)
54
+ export function resolveDeltaFallback(catalog: unknown, baseName: string, deltaName: string): ResolvedDeltaFallback {
55
+ const bindings = resolveFecBindings(catalog)
56
+ const bound = (name: string, role: string) => {
57
+ const b = bindings.get(name)
58
+ if (!b) throw new Error(`[delta-fallback] ${role} track "${name}" declares no fec`)
59
+ if (b.status !== 'ok')
60
+ throw new Error(`[delta-fallback] ${role} track "${name}" has no FEC: ${b.reason.code} at ${b.reason.path}`)
61
+ const track = at(catalog, b.pointer) ?? {}
62
+ const instance = (Array.isArray(track['fec']) ? track['fec'] : [])
63
+ .map(recordOf)
64
+ .find((o) => o && (o['instanceId'] ?? 0) === b.base.instanceId) ?? {}
65
+ return { b, track, instance }
66
+ }
67
+ const base = bound(baseName, 'base')
68
+ const delta = bound(deltaName, 'delta')
69
+ const framerate = base.track['framerate']
70
+ if (!(typeof framerate === 'number' && Number.isFinite(framerate) && framerate > 0))
71
+ throw new Error(`[delta-fallback] base track "${baseName}" missing positive framerate`)
72
+ const key = (k: string) => (base.instance[k] ?? delta.instance[k]) as number | undefined
58
73
  const config: DeltaFallbackConfig = {
59
- framerate: base.framerate,
60
- sourceSymbols: deltaFec.sourceSymbols,
61
- repairSymbols: deltaFec.repairSymbols,
62
- deltaFailWindowBlocks: base.fec?.deltaFailWindowBlocks ?? deltaFec.deltaFailWindowBlocks,
63
- deltaProbeIntervalMs: base.fec?.deltaProbeIntervalMs ?? deltaFec.deltaProbeIntervalMs,
64
- interleaveDepthMs: base.fec?.interleaveDepthMs ?? deltaFec.interleaveDepthMs,
74
+ framerate,
75
+ sourceSymbols: delta.b.base.sourceSymbols,
76
+ repairSymbols: delta.b.base.repairSymbols,
77
+ deltaFailWindowBlocks: key('deltaFailWindowBlocks'),
78
+ deltaProbeIntervalMs: key('deltaProbeIntervalMs'),
79
+ interleaveDepthMs: base.b.base.interleaveDepthMs ?? delta.b.base.interleaveDepthMs,
65
80
  }
66
- assertDeltaFallbackConfig(config, `[delta-fallback] group ${base.name}/${delta.name}`)
67
- return { repairTrackName: repair, config }
81
+ assertDeltaFallbackConfig(config, `[delta-fallback] group ${baseName}/${deltaName}`)
82
+ return { baseRepairTrackName: repairOf(base.b), deltaRepairTrackName: repairOf(delta.b), config }
68
83
  }
69
84
 
70
- function isPositive(value: unknown): value is number {
71
- return typeof value === 'number' && Number.isFinite(value) && value > 0
72
- }
85
+ const repairOf = (b: Extract<FecSourceBinding, { status: 'ok' }>) => b.layers[0]!.repairTrackName
package/src/fec-state.ts CHANGED
@@ -13,15 +13,7 @@
13
13
  // timers or elapsed time. The rules live here so the three players cannot
14
14
  // disagree about precedence.
15
15
 
16
- import {
17
- catalogFecDeclaresUnconfigurableInstance,
18
- fecAlgorithmUnimplemented,
19
- isFecBlockProfile,
20
- isoBlockSpanUsable,
21
- } from '@blockcast/mmt-base'
22
-
23
- /** The optional elapsed-span rule lives in mmt-base beside the other catalog FEC rules. */
24
- export { isoBlockSpanUsable }
16
+ import { resolveFecBindings } from '@blockcast/mmt-base'
25
17
 
26
18
  /** Smallest positive catalog elapsed span; zero/absence means no elapsed deadline. */
27
19
  export const MIN_INTERLEAVE_DEPTH_MS = 1
@@ -100,61 +92,14 @@ function recordOf(value: unknown): Record<string, unknown> | undefined {
100
92
  : undefined
101
93
  }
102
94
 
103
- function isPositiveSafeInteger(value: unknown): boolean {
104
- return typeof value === 'number' && Number.isSafeInteger(value) && value > 0
105
- }
106
-
107
95
  /**
108
- * Whether one catalog `fec` block (a hang rendition's `fec` or an MSF/CMSF
109
- * track's `fec`) declares an FEC instance a receiver can configure: source
110
- * symbols per block (K) and a symbol size (T) are both positive integers, the
111
- * algorithm is neither `none` nor a token this receiver does not implement
112
- * (`fecAlgorithmUnimplemented`, draft-ramadan-moq-fec-01 4), `blockProfile` names the implemented profile
113
- * `iso-ssbg1-v1` (BLO-35597), and the block carries that profile's usable
114
- * span field (`isoBlockSpanUsable`); zero/absence still permits in-band FEC. Total: it never throws, and answers false for
115
- * any other value.
116
- *
117
- * `resolveCatalogTrackFec` reads this predicate, so the players' parameters and
118
- * this state never disagree. Of the blocks this rejects, it returns no FEC
119
- * geometry only for `algorithm: "none"`. One with any other algorithm is a
120
- * malformed catalog (`FecCatalogConfig` marks both fields REQUIRED; the MSF
121
- * schema and `validateCmsfMmtpCatalog` enforce them on `tracks[]`, but the
122
- * hang dialect's `renditions` schema is permissive, so this is where a hang
123
- * block fails closed), and `resolveCatalogTrackFec` throws a `RepairContractError` for
124
- * it (`track_fec` unless another repair-contract violation fires first),
125
- * whether or not the source has repair layers, rather than silently dropping
126
- * the track's repair subscription.
127
- *
128
- * The block profile is part of this predicate because the profile selects the
129
- * block math and is never defaulted: draft-ramadan-moq-fec §5.1 defines one,
130
- * `iso-ssbg1-v1`, and a receiver MUST NOT apply FEC under any other token. The
131
- * retired `k-padded-v1` is simply not a known profile. `resolveCatalogTrackFec`
132
- * and `MulticastManager.configureMultiTrack` resolve such a track to no FEC
133
- * with a typed `TrackFecUnavailable` reason (`catalogFecUnavailable`: code
134
- * `track_fec_block_profile_unimplemented` for an unimplemented token,
135
- * `track_fec_block_profile` for an absent one), scoped to that track, so their
136
- * parameters and this state still agree. The CMSF conformance validator
137
- * (`validateCmsfMmtpCatalog`) rejects the catalog with the same code. An
138
- * `iso-ssbg1-v1` instance with an invalid span is likewise unavailable for its
139
- * track alone. Zero or absence supplies no elapsed deadline and uses bounded
140
- * receiver-local state while decoding in-band FEC.
141
- *
142
- * The repair-contract resolvers (`resolveRepairLayers`) gate a `depends` target
143
- * on `algorithm` alone and never read a source's `symbolSize`. They check its
144
- * `sourceSymbols` for the RaptorQ K bound, and require a positive integer
145
- * `sourceSymbols` and `repairSymbols` only when the source has repair layers or
146
- * an overlay beyond layer 0.
96
+ * Whether the catalog gives the named source track FEC this receiver can run:
97
+ * its base instance is `ok` (`resolveFecBindings`, draft-ramadan-moq-fec-catalog-00).
98
+ * `resolveCatalogTrackFec` reads the same binding, so the players' parameters
99
+ * and this state never disagree. Total: never throws.
147
100
  */
148
- export function catalogDeclaresFec(fec: unknown): boolean {
149
- const block = recordOf(fec)
150
- if (block === undefined || block['algorithm'] === 'none' || fecAlgorithmUnimplemented(block['algorithm'])) return false
151
- return (
152
- isPositiveSafeInteger(block['sourceSymbols']) &&
153
- isPositiveSafeInteger(block['symbolSize']) &&
154
- isFecBlockProfile(block['blockProfile']) &&
155
- isoBlockSpanUsable(block) &&
156
- !catalogFecDeclaresUnconfigurableInstance(block)
157
- )
101
+ export function catalogDeclaresFec(catalog: unknown, track: string): boolean {
102
+ return resolveFecBindings(catalog).get(track)?.status === 'ok'
158
103
  }
159
104
 
160
105
  function requireBoolean(value: unknown, field: string): boolean {
package/src/index.ts CHANGED
@@ -119,10 +119,8 @@ export {
119
119
  // CARP Catalog Extensions (draft-law-ramadan-moq-carp-reliability §3-4)
120
120
  // + Track Timing (draft-ramadan-moq-mmt-01 §4.4.2)
121
121
  export {
122
- type FecAlgorithm,
123
122
  type FecCatalogConfig,
124
- isFecCatalogConfig,
125
- isFecCatalogGeometry,
123
+ type FecInstanceConfig,
126
124
  type MulticastProtocol,
127
125
  type MulticastTrack,
128
126
  type MulticastTrackObject,
@@ -429,7 +427,6 @@ export {
429
427
  } from './media-router.js'
430
428
  export {
431
429
  resolveDeltaFallback,
432
- type DeltaFallbackTrack,
433
430
  type ResolvedDeltaFallback,
434
431
  } from './delta-fallback-config.js'
435
432
 
@@ -543,13 +540,7 @@ export {
543
540
  type KeyframeCadenceCatalog,
544
541
  type TrackFecParams,
545
542
  type TrackFecUnavailable,
546
- catalogFecUnavailable,
547
- catalogFecAvailable,
548
- catalogFecDeclaresUnconfigurableInstance,
549
- fecAlgorithmUnimplemented,
550
- locateCatalogFecSource,
551
- type CatalogFecRole,
552
- type CatalogFecSource,
543
+ catalogTrackFecUnavailable,
553
544
  catalogGroupDurationMs,
554
545
  catalogKeyframeCadenceMs,
555
546
  catalogFecMediaUnitsPerSecond,
@@ -559,38 +550,27 @@ export {
559
550
  FEC_BLOCK_PROFILES,
560
551
  isFecBlockProfile,
561
552
  parseFecBlockProfile,
562
- readFecBlockProfile,
563
553
  type FecBlockProfile,
564
554
  resolveRepairLayers,
565
555
  catalogRepairTarget,
566
- assembleRepairLayers,
567
- assertRepairTrackReachable,
568
- repairReachability,
569
- type AssembleRepairLayersOptions,
570
- type RepairReachability,
571
- dependsTarget,
572
- describeRepairTrack,
573
- rejectRepairFieldsOnMediaTrack,
574
- rejectRepairTrackGeometry,
575
- REPAIR_TRACK_GEOMETRY_KEYS,
576
- REPAIR_TRACK_ONLY_KEYS,
577
- validateRepairTrackFields,
578
- RepairContractError,
579
556
  DEFAULT_REPAIR_PRIORITY,
580
557
  MAX_ENCODING_SYMBOL_ID,
581
558
  MAX_RAPTORQ_SOURCE_SYMBOLS,
582
559
  MAX_REPAIR_SYMBOLS,
583
560
  REPAIR_PRIORITY_MAX,
584
561
  REPAIR_PRIORITY_MIN,
585
- type RepairContractCode,
586
- type RepairContractFail,
587
562
  type RepairLayerCatalog,
588
563
  type RepairLayerParams,
589
564
  type RepairLayersResolution,
590
565
  type RepairOverlayParams,
591
- type RepairRenditionCatalog,
592
- type RepairSourceGeometry,
593
- type RepairTrackDescriptor,
566
+ // FEC catalog binding, draft-ramadan-moq-fec-catalog-00 array shape (BLO-50171).
567
+ validateFecCatalog,
568
+ type FecCatalogCode,
569
+ type FecCatalogInstance,
570
+ type FecCatalogIssue,
571
+ type FecCatalogOutcome,
572
+ type FecCatalogReceiver,
573
+ type FecCatalogReport,
594
574
  } from "./catalog-fec-params.js";
595
575
 
596
576
  // Typed FEC state (active with counters, or inactive with a reason) shared by
@@ -74,10 +74,11 @@ export type MmtpFecSessionDecoderConstructor = new (
74
74
  algorithm?: string,
75
75
  ) => MmtpFecSessionDecoder
76
76
 
77
- /** The catalog `fec` fields the session reads. */
77
+ /** The fields of one `ok` draft-ramadan-moq-fec-catalog-00 instance object
78
+ * the session reads (the source track's base instance). */
78
79
  export interface MmtpFecSessionCatalogFec {
79
- /** `raptorq` (the default when absent) or `reed-solomon`. */
80
- algorithm?: unknown
80
+ /** `raptorq` or `reed-solomon`; required. */
81
+ scheme?: unknown
81
82
  blockProfile?: unknown
82
83
  sourceSymbols?: unknown
83
84
  /** P_0: layer 0's repair symbols per block, the fec-01 6.3.9 total B_0. */
@@ -235,20 +236,20 @@ export class MmtpFecSession {
235
236
  }
236
237
 
237
238
  /**
238
- * @throws when the catalog block lacks or misstates a field the session
239
- * needs: the block profile, K, P_0 (`repairSymbols`), T, `interleaveDepthMs` or
240
- * `reorderToleranceMs`. None is defaulted.
239
+ * @throws when the instance lacks or misstates a field the session needs:
240
+ * the scheme, the block profile, K, P_0 (`repairSymbols`), T,
241
+ * `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
241
242
  */
242
243
  constructor(config: MmtpFecSessionConfig) {
243
244
  const fec = config?.fec
244
245
  if (!fec || typeof fec !== 'object') {
245
- throw new Error('MmtpFecSession: the catalog fec block is required')
246
+ throw new Error('MmtpFecSession: the catalog fec instance is required')
246
247
  }
247
248
  this.blockProfile = parseFecBlockProfile(fec.blockProfile, 'fec.blockProfile')
248
- if (fec.algorithm !== undefined && fec.algorithm !== 'raptorq' && fec.algorithm !== 'reed-solomon') {
249
- throw new Error(`MmtpFecSession: fec.algorithm ${JSON.stringify(fec.algorithm)} is not implemented`)
249
+ if (fec.scheme !== 'raptorq' && fec.scheme !== 'reed-solomon') {
250
+ throw new Error(`MmtpFecSession: fec.scheme ${JSON.stringify(fec.scheme)} is not implemented`)
250
251
  }
251
- this.algorithm = fec.algorithm === 'reed-solomon' ? 'reed-solomon' : 'raptorq'
252
+ this.algorithm = fec.scheme
252
253
  this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, 'fec.sourceSymbols')
253
254
  this.repairSymbols = requirePositiveInteger(fec.repairSymbols, 'fec.repairSymbols')
254
255
  this.symbolSize = requirePositiveInteger(fec.symbolSize, 'fec.symbolSize')
@@ -989,9 +989,6 @@ export class MmtpRouter {
989
989
  #onVideoFrame: FrameCallback | null = null;
990
990
  #onAudioFrame: FrameCallback | null = null;
991
991
 
992
- // Init segment (ftyp+moov) from MMTP MPU metadata — needed for MSE SourceBuffer
993
- #onInitSegment: ((data: Uint8Array) => void) | null = null;
994
-
995
992
  // Per-track init segment callbacks keyed by packetId
996
993
  #trackInitCallbacks = new Map<number, (data: Uint8Array) => void>();
997
994
  // Track whether init has been delivered per physical stream — prevent duplicates
@@ -1418,11 +1415,10 @@ export class MmtpRouter {
1418
1415
  }
1419
1416
 
1420
1417
  /**
1421
- * Init segments delivered for a packetId with no `onTrackInit` handler
1422
- * since construction, whether the deprecated aggregate `onInitSegment`
1423
- * callback then received them or nothing did. A supported deployment reads
1424
- * 0 here; a non-zero value is the evidence that blocks removing the
1425
- * aggregate callback (MOQT-01, BLO-21181).
1418
+ * Init segments rejected since construction because their packetId had no
1419
+ * `onTrackInit` handler. A rejected init is delivered nowhere; there is no
1420
+ * aggregate fallback (removed in 1.0.0, MOQT-01 / BLO-21181). A supported
1421
+ * deployment reads 0 here.
1426
1422
  */
1427
1423
  get unmappedInitDeliveryCount(): number {
1428
1424
  return this.#unmappedInitDeliveries;
@@ -1640,21 +1636,10 @@ export class MmtpRouter {
1640
1636
  this.#trackPacketCallbacks.set(packetId, cb);
1641
1637
  }
1642
1638
 
1643
- /**
1644
- * Register an init segment callback (ftyp+moov from MMTP MPU metadata).
1645
- * Receives an init only for a packetId with no `onTrackInit` handler.
1646
- *
1647
- * @deprecated Use onTrackInit(packetId, cb). Still delivered as a fallback;
1648
- * removed in the 1.0.0 major, no earlier than one minor release after this
1649
- * deprecation reaches the `latest` dist-tag (BLO-21181). Count fallback
1650
- * deliveries with `unmappedInitDeliveryCount`.
1651
- */
1652
- onInitSegment(cb: (data: Uint8Array) => void): void {
1653
- this.#onInitSegment = cb;
1654
- }
1655
-
1656
1639
  /** Register an init segment callback for a specific MMTP packetId.
1657
- * Called when FT=0 init arrives via direct source or FEC recovery. */
1640
+ * Called when FT=0 init arrives via direct source or FEC recovery. An init
1641
+ * for a packetId with no handler is rejected and counted by
1642
+ * `unmappedInitDeliveryCount`. */
1658
1643
  onTrackInit(packetId: number, cb: (data: Uint8Array) => void): void {
1659
1644
  this.#trackInitCallbacks.set(packetId, cb);
1660
1645
  }
@@ -3404,16 +3389,16 @@ export class MmtpRouter {
3404
3389
  if (this.#trackInitDelivered.has(streamKey)) return;
3405
3390
  if (data.byteLength <= 8) return;
3406
3391
  this.#trackInitDelivered.add(streamKey);
3392
+ const track = `packetId=${packetId}${catalogTrackName === undefined ? "" : ` track=${catalogTrackName}`}`;
3407
3393
  const cb = this.#trackInitCallbacks.get(packetId);
3408
- if (cb) {
3409
- cb(data);
3410
- } else {
3394
+ if (!cb) {
3395
+ // No aggregate fallback (MOQT-01): an init nobody mapped is rejected.
3411
3396
  this.#unmappedInitDeliveries++;
3412
- this.#onInitSegment?.(data);
3397
+ console.log(`[MmtpRouter] Init rejected for ${track}: no onTrackInit handler`);
3398
+ return;
3413
3399
  }
3414
- console.log(
3415
- `[MmtpRouter] Init delivered for packetId=${packetId}${catalogTrackName === undefined ? "" : ` track=${catalogTrackName}`} (${data.byteLength}B)`,
3416
- );
3400
+ cb(data);
3401
+ console.log(`[MmtpRouter] Init delivered for ${track} (${data.byteLength}B)`);
3417
3402
  }
3418
3403
 
3419
3404
  /**