@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
@@ -425,31 +425,536 @@ function isoBlocksBehindLowerBound(a, b, kMax) {
425
425
  return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0;
426
426
  }
427
427
 
428
- // ../mmt-base/dist/catalog-fec-source.js
429
- var ALL_ROLES = Object.freeze(["video", "audio"]);
430
- function recordOf(value) {
431
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
432
- }
433
- function isoBlockSpanUsable(fec) {
434
- const block = recordOf(fec);
435
- if (block === void 0 || block["blockProfile"] !== "iso-ssbg1-v1")
436
- return true;
437
- const ms = block["interleaveDepthMs"];
438
- return ms === void 0 || typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0;
428
+ // ../mmt-base/dist/fec-catalog.js
429
+ var DEFAULT_SCHEMES = ["reed-solomon", "raptorq"];
430
+ var DEFAULT_PROFILES = ["moq-object-v1", "iso-ssbg1-v1"];
431
+ var RS_MAX_BLOCK = 255;
432
+ var RAPTORQ_MAX_K = 56403;
433
+ var MAX_24 = 16777215;
434
+ var ISO_MIN_T = 14;
435
+ var GEOMETRY_KEYS = [
436
+ "scheme",
437
+ "blockProfile",
438
+ "mmtpMode",
439
+ "timescale",
440
+ "symbolSize",
441
+ "interleaveDepthMs",
442
+ "reorderToleranceMs",
443
+ "sourceSymbols",
444
+ "fec"
445
+ ];
446
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
447
+ var has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
448
+ var isInt = (v) => typeof v === "number" && Number.isInteger(v);
449
+ var intIn = (v, lo, hi = Number.MAX_SAFE_INTEGER) => isInt(v) && v >= lo && v <= hi;
450
+ var profileAdmits = (profile, packaging) => profile !== "iso-ssbg1-v1" || packaging === "mmtp";
451
+ function validateFecCatalog(catalog, receiver = {}) {
452
+ const schemes = receiver.schemes ?? DEFAULT_SCHEMES;
453
+ const profiles = receiver.blockProfiles ?? DEFAULT_PROFILES;
454
+ const overlays = receiver.overlays ?? true;
455
+ const all = [];
456
+ const report = (code, path, inst) => {
457
+ const issue = { code, path };
458
+ all.push(issue);
459
+ inst?.issues.push(issue);
460
+ };
461
+ const tracks = isObj(catalog) && Array.isArray(catalog.tracks) ? catalog.tracks : [];
462
+ if (!isObj(catalog) || !Array.isArray(catalog.tracks))
463
+ report("fec_member", "/tracks");
464
+ let rejectAll = false;
465
+ tracks.forEach((t, ti) => {
466
+ if (!isObj(t))
467
+ return;
468
+ if (has(t, "enhancementRepair")) {
469
+ rejectAll = true;
470
+ report("fec_enhancement_repair", `/tracks/${ti}/enhancementRepair`);
471
+ }
472
+ if (Array.isArray(t.fec))
473
+ t.fec.forEach((o, ii) => {
474
+ if (isObj(o) && has(o, "enhancementRepair")) {
475
+ rejectAll = true;
476
+ report("fec_enhancement_repair", `/tracks/${ti}/fec/${ii}/enhancementRepair`);
477
+ }
478
+ });
479
+ });
480
+ const key = (ns, name) => JSON.stringify([ns ?? null, name]);
481
+ const byName = /* @__PURE__ */ new Map();
482
+ tracks.forEach((t, ti) => {
483
+ if (isObj(t) && typeof t.name === "string") {
484
+ const k = key(t.namespace, t.name);
485
+ if (!byName.has(k))
486
+ byName.set(k, ti);
487
+ }
488
+ });
489
+ const trackAt = (ti) => tracks[ti];
490
+ const insts = [];
491
+ tracks.forEach((t, ti) => {
492
+ if (!isObj(t) || !has(t, "fec") || t.packaging === "fec-repair" || t.packaging === "fec-source")
493
+ return;
494
+ if (!Array.isArray(t.fec)) {
495
+ report("fec_member", `/tracks/${ti}/fec`);
496
+ return;
497
+ }
498
+ t.fec.forEach((o, ii) => {
499
+ const path = `/tracks/${ti}/fec/${ii}`;
500
+ const inst = {
501
+ path,
502
+ ti,
503
+ track: t,
504
+ obj: isObj(o) ? o : null,
505
+ id: null,
506
+ unavailable: false,
507
+ overlay: false,
508
+ issues: [],
509
+ layer0: null,
510
+ symbolNamed: null,
511
+ attached: []
512
+ };
513
+ insts.push(inst);
514
+ if (!inst.obj)
515
+ return report("fec_member", path, inst);
516
+ const o2 = inst.obj;
517
+ if (!has(o2, "instanceId"))
518
+ inst.id = 0;
519
+ else if (intIn(o2.instanceId, 0, 255))
520
+ inst.id = o2.instanceId;
521
+ else
522
+ report("fec_member", `${path}/instanceId`, inst);
523
+ inst.overlay = has(o2, "scope");
524
+ });
525
+ });
526
+ const declaresFec = insts.length > 0;
527
+ for (const inst of insts) {
528
+ const o = inst.obj;
529
+ if (!o)
530
+ continue;
531
+ const avail = (field, impl) => {
532
+ const p = `${inst.path}/${field}`;
533
+ const absent = field === "scheme" ? "fec_scheme_absent" : "fec_block_profile_absent";
534
+ const unimpl = field === "scheme" ? "fec_scheme_unimplemented" : "fec_block_profile_unimplemented";
535
+ if (!has(o, field))
536
+ return report(absent, p, inst), false;
537
+ if (typeof o[field] !== "string")
538
+ return report("fec_member", p, inst), true;
539
+ if (!impl.includes(o[field]))
540
+ return report(unimpl, p, inst), false;
541
+ return true;
542
+ };
543
+ if (!avail("scheme", schemes) || !avail("blockProfile", profiles))
544
+ inst.unavailable = true;
545
+ else if (typeof o.blockProfile === "string" && !profileAdmits(o.blockProfile, inst.track.packaging)) {
546
+ report("fec_block_profile_packaging", `${inst.path}/blockProfile`, inst);
547
+ inst.unavailable = true;
548
+ }
549
+ }
550
+ const live = (i) => !i.unavailable;
551
+ for (const inst of insts.filter(live)) {
552
+ const o = inst.obj;
553
+ if (!o)
554
+ continue;
555
+ const p = inst.path;
556
+ for (const m of ["sourceSymbols", "repairSymbols", "symbolSize"])
557
+ if (!intIn(o[m], 1))
558
+ report("fec_member", `${p}/${m}`, inst);
559
+ if (typeof o.repairTrack !== "string")
560
+ report("fec_member", `${p}/repairTrack`, inst);
561
+ if (has(o, "symbolTrack") && typeof o.symbolTrack !== "string")
562
+ report("fec_member", `${p}/symbolTrack`, inst);
563
+ if (has(o, "interleaveDepthMs")) {
564
+ const d = o.interleaveDepthMs;
565
+ if (!intIn(d, 0) || d === 0 && o.blockProfile !== "iso-ssbg1-v1")
566
+ report("fec_member", `${p}/interleaveDepthMs`, inst);
567
+ }
568
+ if (has(o, "reorderToleranceMs")) {
569
+ const r = o.reorderToleranceMs;
570
+ if (typeof r !== "number" || !Number.isFinite(r) || r < 0)
571
+ report("fec_member", `${p}/reorderToleranceMs`, inst);
572
+ }
573
+ if (has(o, "label") && typeof o.label !== "string")
574
+ report("fec_member", `${p}/label`, inst);
575
+ if (inst.overlay && o.scope !== "keyframe")
576
+ report("fec_member", `${p}/scope`, inst);
577
+ }
578
+ const idGroups = /* @__PURE__ */ new Map();
579
+ for (const inst of insts)
580
+ if (inst.id !== null) {
581
+ const k = `${inst.ti}:${inst.id}`;
582
+ idGroups.set(k, [...idGroups.get(k) ?? [], inst]);
583
+ }
584
+ for (const group of idGroups.values())
585
+ if (group.length > 1)
586
+ for (const inst of group.filter(live))
587
+ report("fec_instance_id", inst.obj && has(inst.obj, "instanceId") ? `${inst.path}/instanceId` : inst.path, inst);
588
+ const nameUses = /* @__PURE__ */ new Map();
589
+ for (const inst of insts)
590
+ for (const field of ["repairTrack", "symbolTrack"])
591
+ if (inst.obj && typeof inst.obj[field] === "string") {
592
+ const k = key(inst.track.namespace, inst.obj[field]);
593
+ nameUses.set(k, [...nameUses.get(k) ?? [], { inst, field }]);
594
+ }
595
+ const namedBy = /* @__PURE__ */ new Map();
596
+ for (const [k, uses] of nameUses) {
597
+ const tj = byName.get(k);
598
+ for (const { inst, field } of uses) {
599
+ const path = `${inst.path}/${field}`;
600
+ if (tj === void 0 || trackAt(tj).packaging !== (field === "repairTrack" ? "fec-repair" : "fec-source")) {
601
+ if (live(inst))
602
+ report("fec_naming", path, inst);
603
+ } else if (uses.length > 1) {
604
+ if (live(inst))
605
+ report("fec_naming", path, inst);
606
+ if (!namedBy.has(tj))
607
+ namedBy.set(tj, inst);
608
+ } else {
609
+ namedBy.set(tj, inst);
610
+ if (field === "repairTrack")
611
+ inst.layer0 = tj;
612
+ else
613
+ inst.symbolNamed = tj;
614
+ }
615
+ }
616
+ }
617
+ const owner = /* @__PURE__ */ new Map();
618
+ if (declaresFec)
619
+ tracks.forEach((t, tj) => {
620
+ if (!isObj(t) || t.packaging !== "fec-repair" && t.packaging !== "fec-source")
621
+ return;
622
+ const tp = `/tracks/${tj}`;
623
+ const named = namedBy.get(tj);
624
+ if (named) {
625
+ owner.set(tj, named);
626
+ if (!live(named))
627
+ return;
628
+ if (has(t, "source") && t.source !== named.track.name)
629
+ report("fec_resolution", `${tp}/source`, named);
630
+ if (has(t, "instanceId") && t.instanceId !== named.id)
631
+ report("fec_resolution", `${tp}/instanceId`, named);
632
+ return;
633
+ }
634
+ if (!has(t, "source"))
635
+ return report("fec_orphan", tp);
636
+ const id = has(t, "instanceId") ? t.instanceId : 0;
637
+ const src = typeof t.source === "string" ? byName.get(key(t.namespace, t.source)) : void 0;
638
+ const target = src === void 0 ? void 0 : insts.find((i) => i.ti === src && i.id === id);
639
+ if (!target)
640
+ return report("fec_naming", has(t, "instanceId") && src !== void 0 ? `${tp}/instanceId` : `${tp}/source`);
641
+ owner.set(tj, target);
642
+ target.attached.push(tj);
643
+ });
644
+ for (const [tj, inst] of owner) {
645
+ if (!live(inst))
646
+ continue;
647
+ const t = trackAt(tj);
648
+ const tp = `/tracks/${tj}`;
649
+ const symbol = t.packaging === "fec-source";
650
+ if (has(t, "source") && typeof t.source !== "string")
651
+ report("fec_member", `${tp}/source`, inst);
652
+ if (has(t, "instanceId") && !intIn(t.instanceId, 0, 255))
653
+ report("fec_member", `${tp}/instanceId`, inst);
654
+ for (const m of ["repairLayer", "repairSymbols"]) {
655
+ if (!has(t, m))
656
+ continue;
657
+ if (symbol || !intIn(t[m], m === "repairLayer" ? 0 : 1))
658
+ report("fec_member", `${tp}/${m}`, inst);
659
+ }
660
+ if (has(t, "priority") && !intIn(t.priority, symbol ? 0 : 192, 255))
661
+ report("fec_member", `${tp}/priority`, inst);
662
+ for (const k of Object.keys(t))
663
+ if (GEOMETRY_KEYS.includes(k) || k.startsWith("groupDuration"))
664
+ report("fec_track_geometry", `${tp}/${k.replace(/~/g, "~0").replace(/\//g, "~1")}`, inst);
665
+ }
666
+ for (const inst of insts.filter(live)) {
667
+ const o = inst.obj;
668
+ if (!o)
669
+ continue;
670
+ const repairs = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-repair");
671
+ const symbols = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-source");
672
+ if (symbols.length + (inst.symbolNamed !== null ? 1 : 0) > 1)
673
+ for (const tj of symbols)
674
+ report("fec_symbol_tracks", `/tracks/${tj}`, inst);
675
+ const l0 = inst.layer0 === null ? null : trackAt(inst.layer0);
676
+ const l0p = `/tracks/${inst.layer0}`;
677
+ if (inst.overlay) {
678
+ if (l0 && has(l0, "repairLayer"))
679
+ report("fec_overlay", `${l0p}/repairLayer`, inst);
680
+ for (const tj of repairs)
681
+ report("fec_overlay", has(trackAt(tj), "repairLayer") ? `/tracks/${tj}/repairLayer` : `/tracks/${tj}`, inst);
682
+ } else {
683
+ if (l0 && has(l0, "repairLayer") && l0.repairLayer !== 0)
684
+ report("fec_layers", `${l0p}/repairLayer`, inst);
685
+ if (l0 && has(l0, "repairSymbols") && intIn(o.repairSymbols, 1) && l0.repairSymbols !== o.repairSymbols)
686
+ report("fec_layer0", `${l0p}/repairSymbols`, inst);
687
+ const layers = [];
688
+ for (const tj of repairs) {
689
+ const t = trackAt(tj);
690
+ if (!has(t, "repairLayer"))
691
+ report("fec_member", `/tracks/${tj}/repairLayer`, inst);
692
+ else if (t.repairLayer === 0)
693
+ report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
694
+ else if (intIn(t.repairLayer, 1))
695
+ layers.push({ tj, layer: t.repairLayer });
696
+ if (!has(t, "repairSymbols"))
697
+ report("fec_member", `/tracks/${tj}/repairSymbols`, inst);
698
+ }
699
+ const counts = /* @__PURE__ */ new Map();
700
+ for (const { layer } of layers)
701
+ counts.set(layer, (counts.get(layer) ?? 0) + 1);
702
+ let missing = 1;
703
+ while (counts.has(missing))
704
+ missing++;
705
+ for (const { tj, layer } of layers)
706
+ if (counts.get(layer) > 1 || layer > missing)
707
+ report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
708
+ const chain = [
709
+ { layer: 0, tj: inst.layer0, pri: l0 && intIn(l0.priority, 192, 255) ? l0.priority : l0 && has(l0, "priority") ? null : 240 },
710
+ ...layers.map(({ tj, layer }) => {
711
+ const pr = trackAt(tj).priority;
712
+ return { layer, tj, pri: intIn(pr, 192, 255) ? pr : null };
713
+ })
714
+ ].sort((a, b) => a.layer - b.layer);
715
+ let prev = null;
716
+ for (const c of chain) {
717
+ if (c.pri === null)
718
+ continue;
719
+ if (prev !== null && c.pri < prev)
720
+ report("fec_priority", `/tracks/${c.tj}/priority`, inst);
721
+ prev = Math.max(prev ?? c.pri, c.pri);
722
+ }
723
+ }
724
+ const K = o.sourceSymbols;
725
+ const T = o.symbolSize;
726
+ const P0 = o.repairSymbols;
727
+ if (intIn(K, 1) && intIn(P0, 1)) {
728
+ let P = P0;
729
+ if (!inst.overlay)
730
+ for (const tj of repairs) {
731
+ const t = trackAt(tj);
732
+ if (intIn(t.repairLayer, 1) && intIn(t.repairSymbols, 1))
733
+ P += t.repairSymbols;
734
+ }
735
+ if (P > MAX_24 || K + P - 1 > MAX_24)
736
+ report("fec_bounds", inst.path, inst);
737
+ else if (o.scheme === "reed-solomon" && K + P > RS_MAX_BLOCK)
738
+ report("fec_bounds", inst.path, inst);
739
+ }
740
+ if (o.scheme === "raptorq" && intIn(K, 1) && K > RAPTORQ_MAX_K)
741
+ report("fec_bounds", `${inst.path}/sourceSymbols`, inst);
742
+ if (intIn(T, 1)) {
743
+ if (o.scheme === "raptorq" && T % 8 !== 0)
744
+ report("fec_bounds", `${inst.path}/symbolSize`, inst);
745
+ else if (o.blockProfile === "iso-ssbg1-v1" && T < ISO_MIN_T)
746
+ report("fec_bounds", `${inst.path}/symbolSize`, inst);
747
+ }
748
+ }
749
+ const overlayTracks = new Set(insts.filter((i) => live(i) && i.overlay).map((i) => i.ti));
750
+ for (const ti of overlayTracks) {
751
+ const ofTrack = insts.filter((i) => i.ti === ti && live(i));
752
+ const ovs = ofTrack.filter((i) => i.overlay);
753
+ if (!overlays) {
754
+ for (const ov of ovs)
755
+ report("fec_overlay_unsupported", `${ov.path}/scope`, ov);
756
+ for (const i of ofTrack)
757
+ i.issues.length === 0 && i.issues.push({ code: "fec_overlay_unsupported", path: `${ovs[0].path}/scope` });
758
+ continue;
759
+ }
760
+ if (ovs.length > 1)
761
+ for (const ov of ovs)
762
+ report("fec_overlay", `${ov.path}/scope`, ov);
763
+ for (const ov of ovs)
764
+ if (ov.obj?.blockProfile !== "iso-ssbg1-v1")
765
+ report("fec_overlay", `${ov.path}/blockProfile`, ov);
766
+ const cause = ovs.flatMap((ov) => ov.issues).find((x) => x.code === "fec_overlay");
767
+ if (cause) {
768
+ for (const ov of ovs)
769
+ if (ov.issues.length === 0)
770
+ ov.issues.push(cause);
771
+ }
772
+ }
773
+ const rule4 = all.find((x) => x.code === "fec_enhancement_repair");
774
+ if (rejectAll && rule4) {
775
+ for (const i of insts)
776
+ if (i.issues.length === 0)
777
+ i.issues.push(rule4);
778
+ }
779
+ return {
780
+ instances: insts.map((i) => ({
781
+ path: i.path,
782
+ track: typeof i.track.name === "string" ? i.track.name : null,
783
+ instanceId: i.id,
784
+ outcome: rejectAll || !i.unavailable && i.issues.length > 0 ? "violation" : i.unavailable ? "unavailable" : "ok",
785
+ issues: i.issues,
786
+ trackIndex: i.ti,
787
+ repairTrack: i.layer0,
788
+ boundRepairTracks: i.attached.filter((tj) => trackAt(tj).packaging === "fec-repair"),
789
+ symbolTrack: i.symbolNamed ?? i.attached.find((tj) => trackAt(tj).packaging === "fec-source") ?? null
790
+ })),
791
+ issues: all
792
+ };
439
793
  }
440
- function catalogFecAvailable(fec) {
441
- const block = recordOf(fec);
442
- if (block === void 0)
443
- return true;
444
- if (fecAlgorithmUnimplemented(block["algorithm"]))
445
- return false;
446
- const declaresFecInstance = block["algorithm"] !== "none";
447
- const profile = block["blockProfile"];
448
- const profileOk = isFecBlockProfile(profile) || profile === void 0 && !declaresFecInstance;
449
- return profileOk && (!declaresFecInstance || isoBlockSpanUsable(block));
794
+
795
+ // ../mmt-base/dist/fec-bindings.js
796
+ var RUNTIME_FEC_RECEIVER = Object.freeze({
797
+ schemes: Object.freeze(["raptorq", "reed-solomon"]),
798
+ blockProfiles: Object.freeze(["iso-ssbg1-v1"]),
799
+ overlays: true
800
+ });
801
+ var DEFAULT_FEC_REPAIR_PRIORITY = 240;
802
+ var recordOf = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
803
+ var escape = (s) => s.replace(/~/g, "~0").replace(/\//g, "~1");
804
+ function fecCatalogTracks(catalog) {
805
+ const root = recordOf(catalog) ?? {};
806
+ const tracks = [];
807
+ const pointers = [];
808
+ const hangNames = /* @__PURE__ */ new Set();
809
+ for (const role of ["video", "audio"]) {
810
+ for (const [name, r] of Object.entries(recordOf(recordOf(root[role])?.["renditions"]) ?? {})) {
811
+ const entry = recordOf(r);
812
+ if (!entry)
813
+ continue;
814
+ hangNames.add(name);
815
+ const kind = recordOf(entry["container"])?.["kind"];
816
+ const view2 = { name, packaging: kind === void 0 || kind === "mmtp-fec" ? "mmtp" : kind };
817
+ if (entry["fec"] !== void 0)
818
+ view2["fec"] = entry["fec"];
819
+ tracks.push(view2);
820
+ pointers.push(`/${role}/renditions/${escape(name)}`);
821
+ }
822
+ }
823
+ const hangRenditions = tracks.length;
824
+ for (const [name, r] of Object.entries(recordOf(recordOf(root["repair"])?.["renditions"]) ?? {})) {
825
+ hangNames.add(name);
826
+ tracks.push({ packaging: "fec-repair", ...recordOf(r), name });
827
+ pointers.push(`/repair/renditions/${escape(name)}`);
828
+ }
829
+ const raw = Array.isArray(root["tracks"]) ? root["tracks"] : [];
830
+ const named = new Set(hangNames);
831
+ raw.forEach((t, i) => {
832
+ const name = recordOf(t)?.["name"];
833
+ if (typeof name === "string")
834
+ named.add(name);
835
+ if (typeof name === "string" && hangNames.has(name))
836
+ return;
837
+ tracks.push(t);
838
+ pointers.push(`/tracks/${i}`);
839
+ });
840
+ tracks.slice(0, hangRenditions).forEach((t, ti) => {
841
+ const view2 = t;
842
+ if (!Array.isArray(view2["fec"]))
843
+ return;
844
+ view2["fec"].forEach((o, ii) => {
845
+ const repairTrack = recordOf(o)?.["repairTrack"];
846
+ if (typeof repairTrack !== "string" || named.has(repairTrack))
847
+ return;
848
+ named.add(repairTrack);
849
+ const instanceId = recordOf(o)?.["instanceId"];
850
+ tracks.push({ name: repairTrack, packaging: "fec-repair", source: view2["name"], ...instanceId === void 0 ? {} : { instanceId } });
851
+ pointers.push(`${pointers[ti]}/fec/${ii}/repairTrack`);
852
+ });
853
+ });
854
+ return { tracks, pointers };
855
+ }
856
+ function resolveFecBindings(catalog, receiver = RUNTIME_FEC_RECEIVER) {
857
+ const { tracks, pointers } = fecCatalogTracks(catalog);
858
+ const report = validateFecCatalog({ tracks }, receiver);
859
+ const at = (path) => {
860
+ const m = /^\/tracks\/(\d+)(.*)$/.exec(path);
861
+ return m ? pointers[Number(m[1])] + m[2] : path;
862
+ };
863
+ const trackAt = (i) => recordOf(tracks[i]) ?? {};
864
+ const nameAt = (i) => i === null ? void 0 : trackAt(i)["name"];
865
+ const why = (i) => Object.freeze({
866
+ code: i.issues[0]?.code ?? "fec_member",
867
+ path: at(i.issues[0]?.path ?? i.path),
868
+ outcome: i.outcome === "unavailable" ? "unavailable" : "violation"
869
+ });
870
+ const out = /* @__PURE__ */ new Map();
871
+ tracks.forEach((t, ti) => {
872
+ const track = recordOf(t);
873
+ const name = track?.["name"];
874
+ if (!track || typeof name !== "string" || out.has(name))
875
+ return;
876
+ if (track["packaging"] === "fec-repair" || track["packaging"] === "fec-source")
877
+ return;
878
+ const fec = track["fec"];
879
+ if (fec === void 0)
880
+ return;
881
+ if (!Array.isArray(fec)) {
882
+ const reason = { code: "fec_member", path: at(`/tracks/${ti}/fec`), outcome: "violation" };
883
+ out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason: Object.freeze(reason) }));
884
+ return;
885
+ }
886
+ if (fec.length === 0)
887
+ return;
888
+ const insts = report.instances.filter((i) => i.trackIndex === ti);
889
+ const isOverlay = (i) => Object.hasOwn(recordOf(fec[Number(i.path.split("/").pop())]) ?? {}, "scope");
890
+ const bases = insts.filter((i) => !isOverlay(i));
891
+ const okBases = bases.filter((i) => i.outcome === "ok");
892
+ if (okBases.length !== 1) {
893
+ const reason = (
894
+ // No base instance at all (overlay-only `fec`) is a count of 0, not
895
+ // the overlay's own issue.
896
+ okBases.length > 1 || bases.length === 0 ? Object.freeze({ code: "fec_base_instances", path: at(`/tracks/${ti}/fec`), outcome: "violation" }) : why(bases[0] ?? insts[0])
897
+ );
898
+ out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason }));
899
+ return;
900
+ }
901
+ const b = okBases[0];
902
+ const base = geometry(recordOf(fec[Number(b.path.split("/").pop())]), b.instanceId, nameAt(b.symbolTrack));
903
+ const priorityOf = (i) => {
904
+ const p = i === null ? void 0 : trackAt(i)["priority"];
905
+ return typeof p === "number" ? p : DEFAULT_FEC_REPAIR_PRIORITY;
906
+ };
907
+ const layers = [
908
+ { layer: 0, repairTrackName: base.repairTrack, repairSymbols: base.repairSymbols, rsIdOffset: 0, priority: priorityOf(b.repairTrack) }
909
+ ];
910
+ const more = b.boundRepairTracks.map((tj) => ({ tj, layer: trackAt(tj)["repairLayer"] })).sort((x, y) => x.layer - y.layer);
911
+ let offset = base.repairSymbols;
912
+ for (const { tj, layer } of more) {
913
+ const repairSymbols = trackAt(tj)["repairSymbols"];
914
+ layers.push({ layer, repairTrackName: nameAt(tj), repairSymbols, rsIdOffset: offset, priority: priorityOf(tj) });
915
+ offset += repairSymbols;
916
+ }
917
+ let overlay;
918
+ let overlayUnavailable;
919
+ const ov = insts.find(isOverlay);
920
+ if (ov?.outcome === "ok") {
921
+ const g = geometry(recordOf(fec[Number(ov.path.split("/").pop())]), ov.instanceId, nameAt(ov.symbolTrack));
922
+ overlay = Object.freeze({
923
+ repairTrackName: g.repairTrack,
924
+ scope: "keyframe",
925
+ sourceSymbols: g.sourceSymbols,
926
+ repairSymbols: g.repairSymbols,
927
+ priority: priorityOf(ov.repairTrack),
928
+ geometry: g
929
+ });
930
+ } else if (ov)
931
+ overlayUnavailable = why(ov);
932
+ out.set(name, Object.freeze({
933
+ status: "ok",
934
+ track: name,
935
+ pointer: pointers[ti],
936
+ base,
937
+ layers: Object.freeze(layers.map((l) => Object.freeze(l))),
938
+ totalBaseRepairSymbols: offset,
939
+ ...overlay ? { overlay } : {},
940
+ ...overlayUnavailable ? { overlayUnavailable } : {}
941
+ }));
942
+ });
943
+ return out;
450
944
  }
451
- function fecAlgorithmUnimplemented(algorithm) {
452
- return typeof algorithm === "string" && algorithm !== "none" && algorithm !== "raptorq" && algorithm !== "reed-solomon";
945
+ function geometry(o, instanceId, symbolTrack) {
946
+ return Object.freeze({
947
+ instanceId,
948
+ scheme: o["scheme"],
949
+ blockProfile: o["blockProfile"],
950
+ sourceSymbols: o["sourceSymbols"],
951
+ repairSymbols: o["repairSymbols"],
952
+ symbolSize: o["symbolSize"],
953
+ ...typeof o["interleaveDepthMs"] === "number" ? { interleaveDepthMs: o["interleaveDepthMs"] } : {},
954
+ ...typeof o["reorderToleranceMs"] === "number" ? { reorderToleranceMs: o["reorderToleranceMs"] } : {},
955
+ repairTrack: o["repairTrack"],
956
+ ...symbolTrack !== void 0 ? { symbolTrack } : {}
957
+ });
453
958
  }
454
959
 
455
960
  // src/track-session.ts
@@ -969,10 +1474,10 @@ function deriveBlockClassifierGeometry(input) {
969
1474
  keyframeInterval
970
1475
  };
971
1476
  }
972
- function keyframeIntervalMediaTicks(geometry, mediaTimescale) {
973
- if (geometry.trackKind !== "video" || geometry.mode !== "classifying") return null;
1477
+ function keyframeIntervalMediaTicks(geometry2, mediaTimescale) {
1478
+ if (geometry2.trackKind !== "video" || geometry2.mode !== "classifying") return null;
974
1479
  if (!Number.isSafeInteger(mediaTimescale) || mediaTimescale <= 0) return null;
975
- const { ticks, timescale } = geometry.keyframeInterval;
1480
+ const { ticks, timescale } = geometry2.keyframeInterval;
976
1481
  if (!Number.isSafeInteger(ticks) || !Number.isSafeInteger(timescale)) return null;
977
1482
  const scaled = BigInt(ticks) * BigInt(mediaTimescale);
978
1483
  const divisor = BigInt(timescale);
@@ -1007,11 +1512,11 @@ var BlockClassifier = class {
1007
1512
  /** MPU → movie_fragment_sequence_number observed in it. */
1008
1513
  #mpuFragments = /* @__PURE__ */ new Map();
1009
1514
  #lastRap = null;
1010
- constructor(geometry) {
1011
- this.#geometry = geometry;
1515
+ constructor(geometry2) {
1516
+ this.#geometry = geometry2;
1012
1517
  this.#mode = "classifying";
1013
1518
  this.#reason = null;
1014
- this.reset(geometry);
1519
+ this.reset(geometry2);
1015
1520
  }
1016
1521
  get geometry() {
1017
1522
  return this.#geometry;
@@ -1031,16 +1536,16 @@ var BlockClassifier = class {
1031
1536
  return this.#mpuBoundsRaps;
1032
1537
  }
1033
1538
  /** Begin a fresh epoch (catalog change or track reset). */
1034
- reset(geometry = this.#geometry) {
1035
- this.#geometry = geometry;
1539
+ reset(geometry2 = this.#geometry) {
1540
+ this.#geometry = geometry2;
1036
1541
  this.#blocks.clear();
1037
1542
  this.#clearContiguityLedger();
1038
1543
  this.#mpuFragments.clear();
1039
1544
  this.#lastRap = null;
1040
1545
  this.#mpuBoundsRaps = true;
1041
- if (geometry.trackKind === "video" && geometry.mode === "all-protected") {
1546
+ if (geometry2.trackKind === "video" && geometry2.mode === "all-protected") {
1042
1547
  this.#mode = "all-protected";
1043
- this.#reason = geometry.reason;
1548
+ this.#reason = geometry2.reason;
1044
1549
  } else {
1045
1550
  this.#mode = "classifying";
1046
1551
  this.#reason = null;
@@ -1156,15 +1661,15 @@ var BlockClassifier = class {
1156
1661
  }
1157
1662
  #checkInvariants(sbn, block, evidence) {
1158
1663
  if (this.#mode === "all-protected" || this.#geometry.trackKind !== "video") return null;
1159
- const geometry = this.#geometry;
1160
- if (geometry.mode !== "classifying") return null;
1664
+ const geometry2 = this.#geometry;
1665
+ if (geometry2.mode !== "classifying") return null;
1161
1666
  if (evidence.rap && evidence.idx !== void 0 && evidence.idx !== 0) {
1162
1667
  return "rap-not-mpu-start";
1163
1668
  }
1164
- if (evidence.idx !== void 0 && evidence.idx >= geometry.gopFrames) {
1669
+ if (evidence.idx !== void 0 && evidence.idx >= geometry2.gopFrames) {
1165
1670
  return "mpu-longer-than-gop";
1166
1671
  }
1167
- if (block.mpus.size === 1 && block.idxMin !== void 0 && block.idxMax !== void 0 && block.idxMax - block.idxMin >= geometry.interleaveUnits) {
1672
+ if (block.mpus.size === 1 && block.idxMin !== void 0 && block.idxMax !== void 0 && block.idxMax - block.idxMin >= geometry2.interleaveUnits) {
1168
1673
  return "block-wider-than-interleave";
1169
1674
  }
1170
1675
  if (evidence.mpu !== void 0 && evidence.movieFragment !== void 0) {
@@ -1280,11 +1785,11 @@ var BlockClassifier = class {
1280
1785
  #classify(block) {
1281
1786
  if (block.rapSeen || block.mpus.size >= 2) return "keyframe";
1282
1787
  if (this.#mode === "all-protected") return "unknown";
1283
- const geometry = this.#geometry;
1284
- if (geometry.trackKind !== "video" || geometry.mode !== "classifying") return "unknown";
1788
+ const geometry2 = this.#geometry;
1789
+ if (geometry2.trackKind !== "video" || geometry2.mode !== "classifying") return "unknown";
1285
1790
  if (block.mpus.size === 1 && block.idxMin !== void 0 && block.idxMax !== void 0) {
1286
- const span = geometry.interleaveUnits - 1;
1287
- if (block.idxMax - span >= 1 && block.idxMin + span <= geometry.gopFrames - 1) {
1791
+ const span = geometry2.interleaveUnits - 1;
1792
+ if (block.idxMax - span >= 1 && block.idxMin + span <= geometry2.gopFrames - 1) {
1288
1793
  return "delta";
1289
1794
  }
1290
1795
  }
@@ -2682,15 +3187,15 @@ var FECManager = class _FECManager {
2682
3187
  */
2683
3188
  #refreshDeliveryGapWindow() {
2684
3189
  let windowMs;
2685
- const consider = (geometry, packetId) => {
3190
+ const consider = (geometry2, packetId) => {
2686
3191
  if (!this.#hasCatalogTimingForPacket(packetId)) return;
2687
- if (geometry?.trackKind !== "video" || geometry.mode !== "classifying") return;
2688
- const { ticks, timescale } = geometry.keyframeInterval;
3192
+ if (geometry2?.trackKind !== "video" || geometry2.mode !== "classifying") return;
3193
+ const { ticks, timescale } = geometry2.keyframeInterval;
2689
3194
  const ms = ticks / timescale * 1e3;
2690
3195
  if (Number.isFinite(ms) && ms > 0 && (windowMs === void 0 || ms > windowMs)) windowMs = ms;
2691
3196
  };
2692
- for (const [streamKey, geometry] of this.#streamGeometries) {
2693
- consider(geometry, this.#parseStreamKey(streamKey).packetId);
3197
+ for (const [streamKey, geometry2] of this.#streamGeometries) {
3198
+ consider(geometry2, this.#parseStreamKey(streamKey).packetId);
2694
3199
  }
2695
3200
  for (const [packetId, config] of this.#trackConfigs) consider(config.geometry, packetId);
2696
3201
  this.#deliveryGaps.setWindowMs(windowMs);
@@ -2938,8 +3443,8 @@ var FECManager = class _FECManager {
2938
3443
  for (const [key, classifier] of [...this.#classifiers]) {
2939
3444
  if (!this.#belongsToPacket(key, packetId)) continue;
2940
3445
  const stream = this.#parseStreamKey(key);
2941
- const geometry = this.#geometryFor(stream.packetId, stream.catalogTrackName);
2942
- if (hasCatalogTiming && JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
3446
+ const geometry2 = this.#geometryFor(stream.packetId, stream.catalogTrackName);
3447
+ if (hasCatalogTiming && JSON.stringify(geometry2) === JSON.stringify(classifier.geometry)) continue;
2943
3448
  this.#classifiers.delete(key);
2944
3449
  if (!hasCatalogTiming) continue;
2945
3450
  this.#applyClassifierChanges(stream.packetId, stream.catalogTrackName, classifier.retire());
@@ -12675,12 +13180,12 @@ function assertPositiveMs(name, value) {
12675
13180
  );
12676
13181
  }
12677
13182
  }
12678
- function duplicateRetentionMs(geometry) {
12679
- assertPositiveMs("blockSpanMs", geometry.blockSpanMs);
12680
- assertPositiveMs("blockLifetimeMs", geometry.blockLifetimeMs);
12681
- const untilTerminalMs = geometry.blockLifetimeMs;
12682
- const terminalRecordMs = geometry.blockLifetimeMs;
12683
- return geometry.blockSpanMs + untilTerminalMs + terminalRecordMs;
13183
+ function duplicateRetentionMs(geometry2) {
13184
+ assertPositiveMs("blockSpanMs", geometry2.blockSpanMs);
13185
+ assertPositiveMs("blockLifetimeMs", geometry2.blockLifetimeMs);
13186
+ const untilTerminalMs = geometry2.blockLifetimeMs;
13187
+ const terminalRecordMs = geometry2.blockLifetimeMs;
13188
+ return geometry2.blockSpanMs + untilTerminalMs + terminalRecordMs;
12684
13189
  }
12685
13190
  var GenerationalNumberSet = class {
12686
13191
  #members = /* @__PURE__ */ new Set();
@@ -13086,8 +13591,6 @@ var MmtpRouter = class {
13086
13591
  // Callbacks (legacy — used when per-track callbacks are not registered)
13087
13592
  #onVideoFrame = null;
13088
13593
  #onAudioFrame = null;
13089
- // Init segment (ftyp+moov) from MMTP MPU metadata — needed for MSE SourceBuffer
13090
- #onInitSegment = null;
13091
13594
  // Per-track init segment callbacks keyed by packetId
13092
13595
  #trackInitCallbacks = /* @__PURE__ */ new Map();
13093
13596
  // Track whether init has been delivered per physical stream — prevent duplicates
@@ -13350,11 +13853,10 @@ var MmtpRouter = class {
13350
13853
  };
13351
13854
  }
13352
13855
  /**
13353
- * Init segments delivered for a packetId with no `onTrackInit` handler
13354
- * since construction, whether the deprecated aggregate `onInitSegment`
13355
- * callback then received them or nothing did. A supported deployment reads
13356
- * 0 here; a non-zero value is the evidence that blocks removing the
13357
- * aggregate callback (MOQT-01, BLO-21181).
13856
+ * Init segments rejected since construction because their packetId had no
13857
+ * `onTrackInit` handler. A rejected init is delivered nowhere; there is no
13858
+ * aggregate fallback (removed in 1.0.0, MOQT-01 / BLO-21181). A supported
13859
+ * deployment reads 0 here.
13358
13860
  */
13359
13861
  get unmappedInitDeliveryCount() {
13360
13862
  return this.#unmappedInitDeliveries;
@@ -13531,20 +14033,10 @@ var MmtpRouter = class {
13531
14033
  onTrackPacket(packetId, cb) {
13532
14034
  this.#trackPacketCallbacks.set(packetId, cb);
13533
14035
  }
13534
- /**
13535
- * Register an init segment callback (ftyp+moov from MMTP MPU metadata).
13536
- * Receives an init only for a packetId with no `onTrackInit` handler.
13537
- *
13538
- * @deprecated Use onTrackInit(packetId, cb). Still delivered as a fallback;
13539
- * removed in the 1.0.0 major, no earlier than one minor release after this
13540
- * deprecation reaches the `latest` dist-tag (BLO-21181). Count fallback
13541
- * deliveries with `unmappedInitDeliveryCount`.
13542
- */
13543
- onInitSegment(cb) {
13544
- this.#onInitSegment = cb;
13545
- }
13546
14036
  /** Register an init segment callback for a specific MMTP packetId.
13547
- * Called when FT=0 init arrives via direct source or FEC recovery. */
14037
+ * Called when FT=0 init arrives via direct source or FEC recovery. An init
14038
+ * for a packetId with no handler is rejected and counted by
14039
+ * `unmappedInitDeliveryCount`. */
13548
14040
  onTrackInit(packetId, cb) {
13549
14041
  this.#trackInitCallbacks.set(packetId, cb);
13550
14042
  }
@@ -14944,16 +15436,15 @@ var MmtpRouter = class {
14944
15436
  if (this.#trackInitDelivered.has(streamKey)) return;
14945
15437
  if (data.byteLength <= 8) return;
14946
15438
  this.#trackInitDelivered.add(streamKey);
15439
+ const track = `packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`}`;
14947
15440
  const cb = this.#trackInitCallbacks.get(packetId);
14948
- if (cb) {
14949
- cb(data);
14950
- } else {
15441
+ if (!cb) {
14951
15442
  this.#unmappedInitDeliveries++;
14952
- this.#onInitSegment?.(data);
15443
+ console.log(`[MmtpRouter] Init rejected for ${track}: no onTrackInit handler`);
15444
+ return;
14953
15445
  }
14954
- console.log(
14955
- `[MmtpRouter] Init delivered for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${data.byteLength}B)`
14956
- );
15446
+ cb(data);
15447
+ console.log(`[MmtpRouter] Init delivered for ${track} (${data.byteLength}B)`);
14957
15448
  }
14958
15449
  /**
14959
15450
  * Core MMTP packet processing — source packets → play + FEC, repair → FEC only.
@@ -16416,8 +16907,12 @@ var FEC_BLOCK_PROFILES2 = Object.freeze([
16416
16907
  "iso-ssbg1-v1"
16417
16908
  ]);
16418
16909
 
16419
- // node_modules/@blockcast/mmt-base/dist/catalog-fec-source.js
16420
- var ALL_ROLES2 = Object.freeze(["video", "audio"]);
16910
+ // node_modules/@blockcast/mmt-base/dist/fec-bindings.js
16911
+ var RUNTIME_FEC_RECEIVER2 = Object.freeze({
16912
+ schemes: Object.freeze(["raptorq", "reed-solomon"]),
16913
+ blockProfiles: Object.freeze(["iso-ssbg1-v1"]),
16914
+ overlays: true
16915
+ });
16421
16916
 
16422
16917
  // node_modules/@blockcast/mmt-transport/dist/multicast-orchestrator.js
16423
16918
  var DEFAULT_DWELL_MS = 3e3;
@@ -17594,6 +18089,10 @@ function getMulticastGlobals() {
17594
18089
  function hasMulticastGateway() {
17595
18090
  return typeof getMulticastGlobals().multicast?.subscribe === "function";
17596
18091
  }
18092
+ function senderOf(event) {
18093
+ const sourceAddress = event.sourceAddress;
18094
+ return typeof sourceAddress === "string" ? sourceAddress : void 0;
18095
+ }
17597
18096
  function normalizeBytesEventDetail(detail) {
17598
18097
  const value = detail && typeof detail === "object" && "data" in detail ? detail.data : detail;
17599
18098
  if (value instanceof Uint8Array) return value;
@@ -17640,7 +18139,8 @@ var MulticastTransport = class {
17640
18139
  bytesReceived: 0,
17641
18140
  packetsDroppedUnregisteredPid: 0,
17642
18141
  packetsDroppedUnparseable: 0,
17643
- packetsDroppedSignaling: 0
18142
+ packetsDroppedSignaling: 0,
18143
+ packetsDroppedForeignSender: 0
17644
18144
  };
17645
18145
  // join() calls by how they resolved; see joinStats.
17646
18146
  #joinsSucceeded = 0;
@@ -17665,6 +18165,11 @@ var MulticastTransport = class {
17665
18165
  // per packet once a count outgrows the small-integer range.
17666
18166
  #acceptedBytesByPacketId = /* @__PURE__ */ new Map();
17667
18167
  #packetIds = null;
18168
+ // The sender S this join's accepted packets came from, once known; see
18169
+ // packetsDroppedForeignSender. Cleared by every join and by a silence
18170
+ // failback, so a replacement publisher is not locked out.
18171
+ #sender = void 0;
18172
+ #lockedTo;
17668
18173
  // What the live join delivers with each accepted packet. A field, not a
17669
18174
  // listener closure, so an in-place admission change (setContext) applies
17670
18175
  // from the next packet on without rejoining.
@@ -17686,9 +18191,15 @@ var MulticastTransport = class {
17686
18191
  #joinGeneration = 0;
17687
18192
  // The provider to subscribe through; window.multicast when unset.
17688
18193
  #gateway;
18194
+ /**
18195
+ * `lockedTo` reads the sender another transport is locked to (a repair
18196
+ * group follows its media join, whose publisher sends both). While it reads
18197
+ * a sender, this join admits only that one; until then its own lock applies.
18198
+ */
17689
18199
  constructor(onPacket, options = {}) {
17690
18200
  this.#onPacket = onPacket;
17691
18201
  this.#gateway = options.gateway;
18202
+ this.#lockedTo = options.lockedTo;
17692
18203
  }
17693
18204
  static isAvailable() {
17694
18205
  return hasMulticastGateway();
@@ -17697,6 +18208,7 @@ var MulticastTransport = class {
17697
18208
  const generation = ++this.#joinGeneration;
17698
18209
  this.#releaseSubscription();
17699
18210
  this.#dataPlaneLive = false;
18211
+ this.#sender = void 0;
17700
18212
  if (this.#disposed) return this.#declined(generation, "disposed");
17701
18213
  const gateway = this.#gateway ?? getMulticastGlobals().multicast;
17702
18214
  if (typeof gateway?.subscribe !== "function") {
@@ -17738,7 +18250,8 @@ var MulticastTransport = class {
17738
18250
  }
17739
18251
  this.#subscription = subscription;
17740
18252
  const live = () => !this.#disposed && generation === this.#joinGeneration;
17741
- const packetListener = subscriptionDeliversPacketBatches(subscription) ? {
18253
+ const anySource = !config.sourceAddress && !config.senderAddress && !config.atsc3;
18254
+ const packetListener = !anySource && subscriptionDeliversPacketBatches(subscription) ? {
17742
18255
  type: "packets",
17743
18256
  listener: (event) => {
17744
18257
  if (!live()) return;
@@ -17748,7 +18261,7 @@ var MulticastTransport = class {
17748
18261
  type: "bytes",
17749
18262
  listener: (event) => {
17750
18263
  if (!live()) return;
17751
- if (this.#deliver(event.detail)) this.#resetWatchdog();
18264
+ if (this.#deliver(event.detail, senderOf(event))) this.#resetWatchdog();
17752
18265
  }
17753
18266
  };
17754
18267
  this.#packetListener = packetListener;
@@ -17839,13 +18352,13 @@ var MulticastTransport = class {
17839
18352
  * One packet from the subscription: filter, count, and hand it to the
17840
18353
  * callback. Returns whether it was delivered; the caller notes liveness.
17841
18354
  */
17842
- #deliver(detail) {
18355
+ #deliver(detail, sourceAddress) {
17843
18356
  const packet = normalizeBytesEventDetail(detail);
17844
18357
  if (!packet) {
17845
18358
  this.#stats.packetsDroppedUnparseable++;
17846
18359
  return false;
17847
18360
  }
17848
- if (!this.#acceptPacket(packet)) return false;
18361
+ if (!this.#acceptPacket(packet, sourceAddress)) return false;
17849
18362
  this.#onPacket(packet, this.#context);
17850
18363
  return true;
17851
18364
  }
@@ -17886,11 +18399,15 @@ var MulticastTransport = class {
17886
18399
  throw failures[0];
17887
18400
  }
17888
18401
  }
17889
- /** Feed a raw packet from an external source (e.g., AMT tunnel or ASM subscription). */
17890
- feedPacket(data, context) {
18402
+ /**
18403
+ * Feed a raw packet from an external source (e.g., AMT tunnel or ASM
18404
+ * subscription). `sourceAddress` is the datagram's sender when the source
18405
+ * knows it; see packetsDroppedForeignSender.
18406
+ */
18407
+ feedPacket(data, context, sourceAddress) {
17891
18408
  if (this.#disposed) return;
17892
18409
  const packet = data instanceof Uint8Array ? data : new Uint8Array(data);
17893
- if (!this.#acceptPacket(packet)) return;
18410
+ if (!this.#acceptPacket(packet, sourceAddress)) return;
17894
18411
  this.#onPacket(packet, context);
17895
18412
  this.#resetWatchdog();
17896
18413
  }
@@ -18040,6 +18557,10 @@ var MulticastTransport = class {
18040
18557
  get withdrawnPacketIdDrops() {
18041
18558
  return this.#withdrawnPacketIdDrops;
18042
18559
  }
18560
+ /** The sender this join is locked to; undefined until one is known. */
18561
+ get sender() {
18562
+ return this.#sender;
18563
+ }
18043
18564
  /** Whether a subscription is live: joined, and not yet released or replaced. */
18044
18565
  get joined() {
18045
18566
  return this.#subscription !== null;
@@ -18202,11 +18723,16 @@ var MulticastTransport = class {
18202
18723
  this.#withdrawnPacketIds.clear();
18203
18724
  }
18204
18725
  /** The ingress filter; an accepted packet is counted here, once. */
18205
- #acceptPacket(packet) {
18726
+ #acceptPacket(packet, sourceAddress) {
18727
+ const owner = this.#lockedTo?.() ?? this.#sender;
18728
+ if (sourceAddress !== void 0 && owner !== void 0 && sourceAddress !== owner) {
18729
+ this.#stats.packetsDroppedForeignSender++;
18730
+ return false;
18731
+ }
18206
18732
  const packetId = readMMTPPacketId(packet);
18207
18733
  if (packetId === null) {
18208
18734
  if (!this.#packetIds) {
18209
- this.#countAccepted(packet, null);
18735
+ this.#countAccepted(packet, null, sourceAddress);
18210
18736
  return true;
18211
18737
  }
18212
18738
  this.#stats.packetsDroppedUnparseable++;
@@ -18217,14 +18743,15 @@ var MulticastTransport = class {
18217
18743
  return false;
18218
18744
  }
18219
18745
  if (!this.#packetIds || this.#packetIds.has(packetId)) {
18220
- this.#countAccepted(packet, packetId);
18746
+ this.#countAccepted(packet, packetId, sourceAddress);
18221
18747
  return true;
18222
18748
  }
18223
18749
  this.#recordUnregisteredPidDrop(packetId);
18224
18750
  return false;
18225
18751
  }
18226
18752
  /** Receive counters for one accepted packet; `packetId` is null when it has none. */
18227
- #countAccepted(packet, packetId) {
18753
+ #countAccepted(packet, packetId, sourceAddress) {
18754
+ this.#sender ??= sourceAddress;
18228
18755
  this.#stats.packetsReceived++;
18229
18756
  this.#stats.bytesReceived += packet.byteLength;
18230
18757
  if (packetId === null) return;
@@ -18295,6 +18822,7 @@ var MulticastTransport = class {
18295
18822
  console.warn(
18296
18823
  `[MulticastTransport] No packets for ${this.#failbackWindowMs}ms; failing back to MoQ`
18297
18824
  );
18825
+ this.#sender = void 0;
18298
18826
  this.#onFailback({ kind: "silence", windowMs: this.#failbackWindowMs });
18299
18827
  }, delayMs);
18300
18828
  }
@@ -21030,35 +21558,6 @@ var BcProvenanceV2Receiver = class {
21030
21558
  // src/multicast-manager.ts
21031
21559
  import { deriveFecInterleaveDepth as deriveFecInterleaveDepth3, FecBlockMapper as FecBlockMapper2 } from "@blockcast/mmt-fec";
21032
21560
 
21033
- // src/repair-layers.ts
21034
- var RepairContractError = class extends Error {
21035
- constructor(code, path, message) {
21036
- super(`[repair-layers:${code}] ${path}: ${message}`);
21037
- this.code = code;
21038
- this.path = path;
21039
- this.name = "RepairContractError";
21040
- }
21041
- };
21042
- var throwRepairContractError = (code, path, message) => {
21043
- throw new RepairContractError(code, path, message);
21044
- };
21045
- function readFecBlockProfile(value, path, declaresFecInstance, fail) {
21046
- if (value === void 0 && !declaresFecInstance) return void 0;
21047
- if (isFecBlockProfile(value)) return value;
21048
- if (typeof value === "string" && value.length > 0) {
21049
- return fail(
21050
- "track_fec_block_profile_unimplemented",
21051
- path,
21052
- `${JSON.stringify(value)} is not an implemented FEC block profile; only "iso-ssbg1-v1" is implemented`
21053
- );
21054
- }
21055
- try {
21056
- return parseFecBlockProfile(value, "blockProfile");
21057
- } catch (error) {
21058
- return fail("track_fec_block_profile", path, error.message);
21059
- }
21060
- }
21061
-
21062
21561
  // src/carp-catalog.ts
21063
21562
  var VALID_ROLE_SUFFIXES = /* @__PURE__ */ new Set(["base", "delta", "repair"]);
21064
21563
  function parseTrackRole(trackName) {
@@ -21098,61 +21597,6 @@ function hasSwitchingSetRole(names) {
21098
21597
  return suffix === "base" || suffix === "delta";
21099
21598
  });
21100
21599
  }
21101
- function isFecCatalogGeometry(obj) {
21102
- if (typeof obj !== "object" || obj === null) return false;
21103
- const o = obj;
21104
- const algorithm = o["algorithm"];
21105
- const symbolSize = o["symbolSize"];
21106
- return typeof algorithm === "string" && algorithm.length > 0 && 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;
21107
- }
21108
- function isFecCatalogConfig(obj) {
21109
- if (!isFecCatalogGeometry(obj)) return false;
21110
- const o = obj;
21111
- return isFecBlockProfile(o["blockProfile"]) || o["algorithm"] === "none" && o["blockProfile"] === void 0;
21112
- }
21113
-
21114
- // src/catalog-fec-params.ts
21115
- import { catalogFecInterleaveMs } from "@blockcast/mmt-fec";
21116
- var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
21117
- function recordOf2(value) {
21118
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
21119
- }
21120
- function blockProfileUnavailable(value, path, declaresFecInstance) {
21121
- try {
21122
- readFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);
21123
- return void 0;
21124
- } catch (error) {
21125
- if (!(error instanceof RepairContractError) || error.code !== "track_fec_block_profile" && error.code !== "track_fec_block_profile_unimplemented") {
21126
- throw error;
21127
- }
21128
- return Object.freeze({ code: error.code, path: error.path, message: error.message });
21129
- }
21130
- }
21131
- function isoSpanUnavailable(fec, path, declaresFecInstance) {
21132
- if (!declaresFecInstance || isoBlockSpanUsable(fec)) return void 0;
21133
- return Object.freeze({
21134
- code: "track_fec",
21135
- path,
21136
- message: `[repair-layers:track_fec] ${path}: must be a non-negative safe integer in milliseconds`
21137
- });
21138
- }
21139
- function catalogFecUnavailable(fec, fecPath) {
21140
- if (catalogFecAvailable(fec)) return void 0;
21141
- const block = recordOf2(fec);
21142
- if (fecAlgorithmUnimplemented(block["algorithm"])) {
21143
- return Object.freeze({
21144
- code: "track_fec_algorithm_unimplemented",
21145
- path: `${fecPath}.algorithm`,
21146
- message: `algorithm ${JSON.stringify(block["algorithm"])} is not implemented: the track plays without FEC`
21147
- });
21148
- }
21149
- const declaresFecInstance = block["algorithm"] !== "none";
21150
- const reason = blockProfileUnavailable(block["blockProfile"], `${fecPath}.blockProfile`, declaresFecInstance) ?? isoSpanUnavailable(block, `${fecPath}.interleaveDepthMs`, declaresFecInstance);
21151
- if (reason === void 0) {
21152
- throw new Error(`catalogFecUnavailable: ${fecPath} is unavailable but no reason applies (rule drift)`);
21153
- }
21154
- return reason;
21155
- }
21156
21600
 
21157
21601
  // src/multicast-manager.ts
21158
21602
  function freezeIngress(tracks, flow) {
@@ -21510,20 +21954,25 @@ function altGroupKey(track) {
21510
21954
  function repairTrackNamesBySource(tracks) {
21511
21955
  const bySource = /* @__PURE__ */ new Map();
21512
21956
  const add2 = (source, repair) => {
21513
- if (!source || !repair) return;
21957
+ if (typeof source !== "string" || typeof repair !== "string" || !source || !repair) return;
21514
21958
  const names = bySource.get(source) ?? /* @__PURE__ */ new Set();
21515
21959
  names.add(repair);
21516
21960
  bySource.set(source, names);
21517
21961
  };
21518
21962
  for (const track of tracks) {
21519
- const repairTrack = typeof track.fec === "object" && track.fec !== null ? track.fec.repairTrack : void 0;
21520
- if (typeof repairTrack === "string") add2(track.name, repairTrack);
21521
- if (track.packaging === "fec-repair" && Array.isArray(track.depends) && track.depends.length === 1) {
21522
- add2(track.depends[0], track.name);
21963
+ if (Array.isArray(track.fec)) {
21964
+ for (const instance of track.fec) add2(track.name, instance?.repairTrack);
21523
21965
  }
21966
+ if (track.packaging === "fec-repair") add2(track.source, track.name);
21524
21967
  }
21525
21968
  return bySource;
21526
21969
  }
21970
+ function boundRepairNames(binding) {
21971
+ if (binding?.status !== "ok") return [];
21972
+ const names = binding.layers.map((layer) => layer.repairTrackName);
21973
+ if (binding.overlay) names.push(binding.overlay.repairTrackName);
21974
+ return names;
21975
+ }
21527
21976
  var MulticastManager = class {
21528
21977
  #fec;
21529
21978
  #router;
@@ -21949,7 +22398,7 @@ var MulticastManager = class {
21949
22398
  jitterBufferMs: config.fecReorderToleranceMs
21950
22399
  }) : void 0;
21951
22400
  const trackKind = config.trackKind ?? this.#router.trackKindFor(packetId);
21952
- const geometry = trackKind === void 0 || interleaveDepthMs === 0 ? void 0 : deriveBlockClassifierGeometry({
22401
+ const geometry2 = trackKind === void 0 || interleaveDepthMs === 0 ? void 0 : deriveBlockClassifierGeometry({
21953
22402
  trackKind,
21954
22403
  framerate: config.fps,
21955
22404
  interleaveDepthMs,
@@ -21966,7 +22415,7 @@ var MulticastManager = class {
21966
22415
  timeoutMs: deliveryWindowMs,
21967
22416
  ...trackKind === void 0 ? {} : { trackKind },
21968
22417
  ...config.codec === void 0 ? {} : { codec: config.codec },
21969
- ...geometry === void 0 ? {} : { geometry }
22418
+ ...geometry2 === void 0 ? {} : { geometry: geometry2 }
21970
22419
  };
21971
22420
  this.#fec.configureTrack(packetId, fecConfig, catalogTrackName);
21972
22421
  this.#configureTrackMapper(packetId, fecConfig);
@@ -22086,10 +22535,6 @@ var MulticastManager = class {
22086
22535
  onTrackPacket(packetId, cb) {
22087
22536
  this.#router.onTrackPacket(packetId, cb);
22088
22537
  }
22089
- /** @deprecated Use onTrackInit(packetId, cb); see MmtpRouter.onInitSegment. */
22090
- onInitSegment(cb) {
22091
- this.#router.onInitSegment(cb);
22092
- }
22093
22538
  onTrackInit(packetId, cb) {
22094
22539
  this.#router.onTrackInit(packetId, cb);
22095
22540
  }
@@ -22313,15 +22758,21 @@ var MulticastManager = class {
22313
22758
  tracks: Object.freeze(endpoint.tracks.map(({ name, packetId }) => Object.freeze({ name, packetId }))),
22314
22759
  ...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
22315
22760
  };
22316
- const onPacket = (packet) => {
22317
- if (!released) this.#router.feedPacket(packet, "multicast", ingress);
22761
+ let sender;
22762
+ const onPacket = (packet, sourceAddress) => {
22763
+ if (released) return;
22764
+ if (sourceAddress !== void 0 && sourceAddress !== (this.#transport.sender ?? (sender ??= sourceAddress))) return;
22765
+ this.#router.feedPacket(packet, "multicast", ingress);
22318
22766
  };
22319
22767
  const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
22320
22768
  if (typeof provider === "function") {
22321
22769
  Promise.resolve().then(() => provider(endpoint, onPacket)).then((release2) => released ? safely(release2) : releases.push(release2), fail);
22322
22770
  continue;
22323
22771
  }
22324
- const transport = new MulticastTransport((packet) => onPacket(packet), { gateway: provider });
22772
+ const transport = new MulticastTransport((packet) => onPacket(packet), {
22773
+ gateway: provider,
22774
+ lockedTo: () => this.#transport.sender
22775
+ });
22325
22776
  releases.push(() => transport.dispose());
22326
22777
  void transport.join({
22327
22778
  sourceAddress: endpoint.source,
@@ -22600,8 +23051,8 @@ var MulticastManager = class {
22600
23051
  * transport attribution in FEC path counters.
22601
23052
  * The `router` accessor above is already public.
22602
23053
  */
22603
- feedPacket(data, ingress) {
22604
- this.#transport.feedPacket(data, ingress);
23054
+ feedPacket(data, ingress, sourceAddress) {
23055
+ this.#transport.feedPacket(data, ingress, sourceAddress);
22605
23056
  }
22606
23057
  /**
22607
23058
  * Declare how packets given to feedPacket() reach this manager. A consumer
@@ -22634,20 +23085,18 @@ var MulticastManager = class {
22634
23085
  * D-17: Builds altGroup → track-name index for getSwitchingSet() (repair legs excluded).
22635
23086
  * D-26: Registers packetId → TrackRole on MmtpRouter for block-complete fanout.
22636
23087
  *
22637
- * FEC availability is per track (draft-ramadan-moq-fec §5.1). A media track
22638
- * whose `fec` names no implemented `blockProfile` (only `iso-ssbg1-v1` is
22639
- * implemented; the retired `k-padded-v1` is not), or an `iso-ssbg1-v1` span
22640
- * it cannot use, is consumed without FEC: it gets no decoder, and none of
22641
- * its repair tracks (layer 0 by `fec.repairTrack`, further layers and the
22642
- * overlay by `depends`) is given an owner, so the router drops their packets
22643
- * before any decoder. Those legs keep the `repair` role and their owner's
22644
- * switching set. Neither the call nor any other track's FEC fails on that
22645
- * account; `trackFecUnavailable(name)` reports the reason. FEC a previous
22646
- * call configured on a packetId this call does not is released
22647
- * (FECManager.releasePacket); frames its release gate held are delivered in
22648
- * order, each gap stalled. FEC wired by hand (setFecDecoder) is left alone.
22649
- * Geometry a decoder could not configure (K, P, T, `repairTrack`,
22650
- * algorithm) still throws.
23088
+ * FEC is per track and per instance (`resolveFecBindings`,
23089
+ * draft-ramadan-moq-fec-catalog-00 array, fec-core-00 10.3). A media track
23090
+ * whose base instance is not `ok` (an invalid instance, a profile other than
23091
+ * `iso-ssbg1-v1`, a draft-01 object `fec`) is consumed without FEC: it gets
23092
+ * no decoder, and none of its repair tracks is given an owner, so the router
23093
+ * drops their packets before any decoder. Those legs keep the `repair` role
23094
+ * and their owner's switching set. Neither the call nor any other track's
23095
+ * FEC fails on that account; `trackFecUnavailable(name)` reports the reason.
23096
+ * FEC a previous call configured on a packetId this call does not is
23097
+ * released (FECManager.releasePacket); frames its release gate held are
23098
+ * delivered in order, each gap stalled. FEC wired by hand (setFecDecoder) is
23099
+ * left alone.
22651
23100
  *
22652
23101
  * @param catalog - Multi-track catalog (tracks array with name, altGroup, fec, packetId).
22653
23102
  * @param wasmDecoderSource - One WASM FEC decoder for a single FEC stream, or
@@ -22655,32 +23104,21 @@ var MulticastManager = class {
22655
23104
  */
22656
23105
  configureMultiTrack(catalog, wasmDecoderSource) {
22657
23106
  const repairNamesBySource = repairTrackNamesBySource(catalog.tracks);
22658
- const declaredRepairNames = /* @__PURE__ */ new Set();
22659
- for (const names of repairNamesBySource.values()) {
22660
- for (const name of names) declaredRepairNames.add(name);
22661
- }
23107
+ const bindings = resolveFecBindings({
23108
+ tracks: catalog.tracks.map(
23109
+ (track) => track.packaging === void 0 ? { ...track, packaging: "mmtp" } : track
23110
+ )
23111
+ });
22662
23112
  const fecUnavailableByTrack = /* @__PURE__ */ new Map();
22663
- for (let i = 0; i < catalog.tracks.length; i++) {
22664
- const track = catalog.tracks[i];
22665
- if (track.fec === void 0 || track.packaging === "fec-repair" || declaredRepairNames.has(track.name)) {
22666
- continue;
22667
- }
22668
- const fecUnavailable = catalogFecUnavailable(track.fec, `$.tracks[${i}].fec`);
22669
- if (fecUnavailable === void 0) continue;
22670
- fecUnavailableByTrack.set(track.name, fecUnavailable);
22671
- const layerZero = track.fec.repairTrack;
22672
- if (typeof layerZero === "string" && layerZero.length > 0) {
22673
- const names = repairNamesBySource.get(track.name) ?? /* @__PURE__ */ new Set();
22674
- names.add(layerZero);
22675
- repairNamesBySource.set(track.name, names);
22676
- }
23113
+ for (const binding of bindings.values()) {
23114
+ if (binding.status === "unavailable") fecUnavailableByTrack.set(binding.track, binding.reason);
22677
23115
  }
22678
- const unownedRepairNames = /* @__PURE__ */ new Set();
22679
- for (const sourceName of fecUnavailableByTrack.keys()) {
22680
- for (const repairName of repairNamesBySource.get(sourceName) ?? []) {
22681
- unownedRepairNames.add(repairName);
22682
- }
23116
+ const ownedRepairNames = /* @__PURE__ */ new Set();
23117
+ const claimed = /* @__PURE__ */ new Map();
23118
+ for (const binding of bindings.values()) {
23119
+ for (const name of boundRepairNames(binding)) claimed.set(name, (claimed.get(name) ?? 0) + 1);
22683
23120
  }
23121
+ for (const [name, count] of claimed) if (count === 1) ownedRepairNames.add(name);
22684
23122
  const repairOwnerGroup = /* @__PURE__ */ new Map();
22685
23123
  for (const track of catalog.tracks) {
22686
23124
  for (const repairName of repairNamesBySource.get(track.name) ?? []) {
@@ -22726,15 +23164,8 @@ var MulticastManager = class {
22726
23164
  const fecTrackIndexes = [];
22727
23165
  for (let i = 0; i < catalog.tracks.length; i++) {
22728
23166
  const track = catalog.tracks[i];
22729
- if (resolvedRoles[i] === "repair" || track.fec === void 0) continue;
22730
- const unavailable = fecUnavailableByTrack.has(track.name);
22731
- if (!(unavailable ? isFecCatalogGeometry(track.fec) : isFecCatalogConfig(track.fec))) {
22732
- throw new Error(
22733
- `[MulticastManager] invalid fec config for track '${track.name}'`
22734
- );
22735
- }
22736
- if (unavailable) continue;
22737
- if (track.packetId !== void 0) fecTrackIndexes.push(i);
23167
+ if (resolvedRoles[i] === "repair" || track.packetId === void 0) continue;
23168
+ if (bindings.get(track.name)?.status === "ok") fecTrackIndexes.push(i);
22738
23169
  }
22739
23170
  const fecDecoders = /* @__PURE__ */ new Map();
22740
23171
  if (wasmDecoderSource !== void 0 && fecTrackIndexes.length > 0) {
@@ -22805,11 +23236,12 @@ var MulticastManager = class {
22805
23236
  }
22806
23237
  continue;
22807
23238
  }
22808
- if (track.fec !== void 0 && !fecUnavailableByTrack.has(track.name)) {
22809
- const fecCfg = track.fec;
23239
+ const binding = bindings.get(track.name);
23240
+ if (binding?.status === "ok") {
23241
+ const fecCfg = binding.base;
22810
23242
  if (track.packetId !== void 0) {
22811
- for (const repairName of repairNamesBySource.get(track.name) ?? []) {
22812
- if (unownedRepairNames.has(repairName)) continue;
23243
+ for (const repairName of boundRepairNames(binding)) {
23244
+ if (!ownedRepairNames.has(repairName)) continue;
22813
23245
  const repairTrack = catalogTracksByName.get(repairName);
22814
23246
  if (repairTrack?.packetId !== void 0) {
22815
23247
  this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);
@@ -22851,14 +23283,11 @@ var MulticastManager = class {
22851
23283
  }
22852
23284
  /**
22853
23285
  * Why `configureMultiTrack` consumes the named catalog track without FEC
22854
- * although its `fec` block declares one (draft-ramadan-moq-fec §5.1): no
22855
- * implemented `blockProfile` (`track_fec_block_profile_unimplemented`, e.g.
22856
- * the retired `k-padded-v1`; `track_fec_block_profile` when absent), or an
22857
- * invalid `iso-ssbg1-v1` span (`track_fec`). Absent/zero spans keep in-band
22858
- * decoding enabled. The same reason the hang
22859
- * resolver reports (`catalogFecUnavailable`). Undefined for every other
22860
- * track, and for every track before the first call; each call replaces the
22861
- * previous call's answers.
23286
+ * although it declares some: its base instance is not `ok`, with the
23287
+ * validator's code and path (`TrackFecUnavailable`), the same reason the
23288
+ * hang resolver reports (`catalogTrackFecUnavailable`). Undefined for every
23289
+ * other track, and for every track before the first call; each call
23290
+ * replaces the previous call's answers.
22862
23291
  */
22863
23292
  trackFecUnavailable(trackName) {
22864
23293
  return this.#fecUnavailableByTrack.get(trackName);