@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.
- package/README.md +21 -16
- package/dist/browser-runtime.js +1024 -1078
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.d.ts +33 -87
- package/dist/carp-catalog.d.ts.map +1 -1
- package/dist/carp-catalog.js +12 -434
- package/dist/carp-catalog.js.map +4 -4
- package/dist/catalog-fec-params.d.ts +48 -161
- package/dist/catalog-fec-params.d.ts.map +1 -1
- package/dist/catalog-fec-params.js +567 -592
- package/dist/catalog-fec-params.js.map +4 -4
- package/dist/catalog-network-source.js +6 -2
- package/dist/catalog-network-source.js.map +3 -3
- package/dist/delivery-catalog.js +12 -4
- package/dist/delivery-catalog.js.map +4 -4
- package/dist/delta-fallback-config.d.ts +21 -26
- package/dist/delta-fallback-config.d.ts.map +1 -1
- package/dist/delta-fallback-config.js +582 -24
- package/dist/delta-fallback-config.js.map +4 -4
- package/dist/fec-manager.js +6 -2
- package/dist/fec-manager.js.map +3 -3
- package/dist/fec-state.d.ts +5 -43
- package/dist/fec-state.d.ts.map +1 -1
- package/dist/fec-state.js +529 -34
- package/dist/fec-state.js.map +4 -4
- package/dist/fec-worker-setup.js +6 -2
- package/dist/fec-worker-setup.js.map +3 -3
- package/dist/held-layer-admission.js +6 -2
- package/dist/held-layer-admission.js.map +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -39
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.js +6 -2
- package/dist/iso-block-locator.js.map +3 -3
- package/dist/mfu-reassembler.js +6 -2
- package/dist/mfu-reassembler.js.map +3 -3
- package/dist/mmtp-container-parser.js +6 -2
- package/dist/mmtp-container-parser.js.map +3 -3
- package/dist/mmtp-container-parser.no-modules.js +551 -452
- package/dist/mmtp-container-parser.no-modules.js.map +4 -4
- package/dist/mmtp-decode-pipeline.js +6 -2
- package/dist/mmtp-decode-pipeline.js.map +3 -3
- package/dist/mmtp-fec-session.d.ts +7 -6
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +13 -9
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +7 -16
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +19 -29
- package/dist/mmtp-router.js.map +3 -3
- package/dist/multicast-auth-catalog.js +7 -3
- package/dist/multicast-auth-catalog.js.map +3 -3
- package/dist/multicast-input.js +7 -3
- package/dist/multicast-input.js.map +3 -3
- package/dist/multicast-manager.d.ts +29 -32
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +682 -253
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +23 -2
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +57 -16
- package/dist/multicast-transport.js.map +4 -4
- package/dist/multicast-unwrap.js +6 -2
- package/dist/multicast-unwrap.js.map +3 -3
- package/dist/no-modules.js +551 -452
- package/dist/no-modules.js.map +4 -4
- package/dist/phase14-catalog-override.d.ts +10 -5
- package/dist/phase14-catalog-override.d.ts.map +1 -1
- package/dist/phase14-catalog-override.js +35 -11
- package/dist/phase14-catalog-override.js.map +2 -2
- package/dist/repair-fetch.d.ts +3 -1
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +16 -3
- package/dist/repair-fetch.js.map +3 -3
- package/dist/repair-layers.d.ts +0 -123
- package/dist/repair-layers.d.ts.map +1 -1
- package/dist/repair-layers.js +1 -349
- package/dist/repair-layers.js.map +3 -3
- package/dist/signaling.js +6 -2
- package/dist/signaling.js.map +3 -3
- package/dist/timestamp-codec.js +6 -2
- package/dist/timestamp-codec.js.map +3 -3
- package/dist/wasm-mfu-reassembler.js +6 -2
- package/dist/wasm-mfu-reassembler.js.map +3 -3
- package/dist/wasm-mmtp-container-parser.js +6 -2
- package/dist/wasm-mmtp-container-parser.js.map +3 -3
- package/package.json +9 -5
- package/schemas/catalog.schema.json +53 -58
- package/src/carp-catalog.ts +39 -229
- package/src/catalog-fec-params.ts +130 -493
- package/src/delta-fallback-config.ts +63 -50
- package/src/fec-state.ts +7 -62
- package/src/index.ts +10 -30
- package/src/mmtp-fec-session.ts +11 -10
- package/src/mmtp-router.ts +14 -29
- package/src/multicast-manager.ts +89 -121
- package/src/multicast-transport.ts +65 -12
- package/src/phase14-catalog-override.ts +67 -16
- package/src/repair-fetch.ts +27 -3
- package/src/repair-layers.ts +7 -520
- package/test-vectors/catalog/README.md +39 -67
- package/test-vectors/catalog/fec-01-runtime/README.md +15 -0
- package/test-vectors/catalog/{negative/fec-block-profile-retired.json → fec-01-runtime/fec-iso-ssbg1.json} +1 -1
- package/test-vectors/catalog/fec-01-runtime/fec-layered-repair.json +106 -0
- package/test-vectors/catalog/{negative/fec-block-profile-unknown.json → fec-01-runtime/fec-multicast.json} +2 -1
- package/test-vectors/catalog/fec-01-runtime/multicast-native-ssm-no-network-source.json +44 -0
- package/test-vectors/catalog/negative/fec-block-profile-missing.json +24 -11
- package/test-vectors/catalog/negative/fec-iso-symbol-size-below-floor.json +13 -8
- package/test-vectors/catalog/negative/fec-reed-solomon-iso-symbol-size-13.json +13 -8
- package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +20 -10
- package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +20 -10
- package/test-vectors/catalog/negative/fec-repair-overlay-source-symbols-zero.json +25 -17
- package/test-vectors/catalog/negative/fec-repair-symbols-zero.json +13 -8
- package/test-vectors/catalog/negative/fec-source-symbols-zero.json +13 -8
- package/test-vectors/catalog/negative/fec-symbol-size-zero.json +13 -8
- package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +18 -9
- package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +13 -8
- package/test-vectors/catalog/positive/fec-algorithm-unimplemented.json +14 -9
- package/test-vectors/catalog/positive/fec-iso-ssbg1.json +25 -12
- package/test-vectors/catalog/positive/fec-layered-repair.json +79 -35
- package/test-vectors/catalog/positive/fec-multicast.json +26 -13
- package/test-vectors/catalog/positive/fec-reed-solomon-layered.json +29 -18
- package/test-vectors/catalog/positive/multicast-draft01-4-1-example.json +28 -20
- package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +14 -10
- package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +22 -12
- package/test-vectors/catalog/section-codes.json +13 -22
- package/test-vectors/catalog-timing.json +2 -2
- package/test-vectors/catalog-validation.json +30 -243
- package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +0 -28
- package/test-vectors/catalog/negative/fec-reed-solomon-k-plus-p-over-255.json +0 -39
- package/test-vectors/catalog/negative/fec-reed-solomon-overlay-over-255.json +0 -40
- package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +0 -32
- package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +0 -34
- package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +0 -24
- 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/
|
|
429
|
-
var
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
|
452
|
-
return
|
|
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(
|
|
973
|
-
if (
|
|
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 } =
|
|
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(
|
|
1011
|
-
this.#geometry =
|
|
1515
|
+
constructor(geometry2) {
|
|
1516
|
+
this.#geometry = geometry2;
|
|
1012
1517
|
this.#mode = "classifying";
|
|
1013
1518
|
this.#reason = null;
|
|
1014
|
-
this.reset(
|
|
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(
|
|
1035
|
-
this.#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 (
|
|
1546
|
+
if (geometry2.trackKind === "video" && geometry2.mode === "all-protected") {
|
|
1042
1547
|
this.#mode = "all-protected";
|
|
1043
|
-
this.#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
|
|
1160
|
-
if (
|
|
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 >=
|
|
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 >=
|
|
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
|
|
1284
|
-
if (
|
|
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 =
|
|
1287
|
-
if (block.idxMax - span >= 1 && block.idxMin + span <=
|
|
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 = (
|
|
3190
|
+
const consider = (geometry2, packetId) => {
|
|
2686
3191
|
if (!this.#hasCatalogTimingForPacket(packetId)) return;
|
|
2687
|
-
if (
|
|
2688
|
-
const { ticks, timescale } =
|
|
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,
|
|
2693
|
-
consider(
|
|
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
|
|
2942
|
-
if (hasCatalogTiming && JSON.stringify(
|
|
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(
|
|
12679
|
-
assertPositiveMs("blockSpanMs",
|
|
12680
|
-
assertPositiveMs("blockLifetimeMs",
|
|
12681
|
-
const untilTerminalMs =
|
|
12682
|
-
const terminalRecordMs =
|
|
12683
|
-
return
|
|
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
|
|
13354
|
-
*
|
|
13355
|
-
*
|
|
13356
|
-
* 0 here
|
|
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
|
-
|
|
15443
|
+
console.log(`[MmtpRouter] Init rejected for ${track}: no onTrackInit handler`);
|
|
15444
|
+
return;
|
|
14953
15445
|
}
|
|
14954
|
-
|
|
14955
|
-
|
|
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/
|
|
16420
|
-
var
|
|
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
|
|
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
|
-
/**
|
|
17890
|
-
|
|
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
|
-
|
|
21520
|
-
|
|
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
|
|
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
|
-
...
|
|
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
|
-
|
|
22317
|
-
|
|
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), {
|
|
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
|
|
22638
|
-
*
|
|
22639
|
-
*
|
|
22640
|
-
*
|
|
22641
|
-
* its repair tracks
|
|
22642
|
-
*
|
|
22643
|
-
*
|
|
22644
|
-
*
|
|
22645
|
-
*
|
|
22646
|
-
*
|
|
22647
|
-
*
|
|
22648
|
-
*
|
|
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
|
|
22659
|
-
|
|
22660
|
-
|
|
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 (
|
|
22664
|
-
|
|
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
|
|
22679
|
-
|
|
22680
|
-
|
|
22681
|
-
|
|
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.
|
|
22730
|
-
|
|
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
|
-
|
|
22809
|
-
|
|
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
|
|
22812
|
-
if (
|
|
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
|
|
22855
|
-
*
|
|
22856
|
-
*
|
|
22857
|
-
*
|
|
22858
|
-
*
|
|
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);
|