@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
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* switching set without asking the publisher to change its catalog.
|
|
15
15
|
* - Tracks present only in the override are appended as-is.
|
|
16
16
|
* - Tracks present only in the MoQ catalog are preserved unchanged.
|
|
17
|
+
* - `fec` is the draft-ramadan-moq-fec-catalog-00 instance array; a repair
|
|
18
|
+
* track is `packaging: "fec-repair"` with `source` + `instanceId`, and each
|
|
19
|
+
* of base and delta names its own (one Track Name in two `repairTrack`
|
|
20
|
+
* members is no FEC on either, catalog-00 5 rule 2).
|
|
17
21
|
*
|
|
18
22
|
* This module is deliberately player-agnostic. It exposes:
|
|
19
23
|
*
|
|
@@ -29,7 +33,7 @@
|
|
|
29
33
|
* Production builds never set the global, so all three helpers are no-ops at
|
|
30
34
|
* runtime unless the test harness opts in.
|
|
31
35
|
*/
|
|
32
|
-
import type {
|
|
36
|
+
import type { FecCatalogConfig } from "./carp-catalog.js";
|
|
33
37
|
/**
|
|
34
38
|
* A single override track entry — mirrors the fixture at
|
|
35
39
|
* `packages/moq-server/tests/fixtures/multi-track-catalog.json`.
|
|
@@ -42,7 +46,10 @@ export interface Phase14OverrideTrack {
|
|
|
42
46
|
name: string;
|
|
43
47
|
/** CMSF packaging kind. Optional per track — when absent, the merge
|
|
44
48
|
* derives it from the override's top-level `streamingFormat`. */
|
|
45
|
-
packaging?: "mmtp" | "cmaf" | "loc" | "timeline";
|
|
49
|
+
packaging?: "mmtp" | "cmaf" | "loc" | "timeline" | "fec-repair";
|
|
50
|
+
/** A `fec-repair` track's source track and instance (catalog-00 3.2). */
|
|
51
|
+
source?: string;
|
|
52
|
+
instanceId?: number;
|
|
46
53
|
/** Required when packaging is `mmtp`; the current publisher emits MFU mode. */
|
|
47
54
|
mmtpMode?: "mfu";
|
|
48
55
|
/** Required when packaging is `mmtp`; copied into synthesized CMSF tracks. */
|
|
@@ -57,9 +64,7 @@ export interface Phase14OverrideTrack {
|
|
|
57
64
|
role?: "video" | "audio" | "timeline";
|
|
58
65
|
altGroup?: number;
|
|
59
66
|
framerate?: number;
|
|
60
|
-
fec?:
|
|
61
|
-
algorithm: FecAlgorithm;
|
|
62
|
-
};
|
|
67
|
+
fec?: FecCatalogConfig;
|
|
63
68
|
}
|
|
64
69
|
/**
|
|
65
70
|
* The shape the 7point harness writes to `globalThis.__PHASE14_CATALOG_OVERRIDE`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase14-catalog-override.d.ts","sourceRoot":"","sources":["../src/phase14-catalog-override.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"phase14-catalog-override.d.ts","sourceRoot":"","sources":["../src/phase14-catalog-override.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAM1D;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb;sEACkE;IAClE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,GAAG,YAAY,CAAC;IAChE,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;+CAG2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,gBAAgB,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,oBAAoB,EAAE,CAAC;CAC/B;AAMD;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,sBAAsB,GAAG,IAAI,CAkCzE;AAMD;;;;;;;;;;;;;;;GAeG;AAEH,wBAAgB,+BAA+B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5E,OAAO,EAAE,CAAC,EACV,QAAQ,EAAE,sBAAsB,GAAG,IAAI,GACrC,CAAC,CAiEH;AAkBD;;;;GAIG;AACH,KAAK,aAAa,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrB,CAAC;AAMF;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CAC1C,MAAM,EAAE,aAAa,EAAE,EACvB,QAAQ,EAAE,sBAAsB,GAAG,IAAI,GACrC,aAAa,EAAE,CAiHjB"}
|
|
@@ -32,6 +32,7 @@ function getPhase14CatalogOverride() {
|
|
|
32
32
|
function mergeOverrideIntoHangRenditions(catalog, override) {
|
|
33
33
|
if (!override) return catalog;
|
|
34
34
|
for (const track of override.tracks) {
|
|
35
|
+
if (track.packaging === "fec-repair") continue;
|
|
35
36
|
const role = track.role === "audio" || track.name.startsWith("audio") ? "audio" : "video";
|
|
36
37
|
const bagKey = role === "audio" ? "audio" : "video";
|
|
37
38
|
const bag = catalog[bagKey] ?? { renditions: {}, priority: 1 };
|
|
@@ -50,8 +51,24 @@ function mergeOverrideIntoHangRenditions(catalog, override) {
|
|
|
50
51
|
bag.renditions[track.name] = merged;
|
|
51
52
|
catalog[bagKey] = bag;
|
|
52
53
|
}
|
|
54
|
+
for (const track of override.tracks) {
|
|
55
|
+
if (track.packaging !== "fec-repair") continue;
|
|
56
|
+
const source = catalog["video"]?.renditions?.[track.source] ?? catalog["audio"]?.renditions?.[track.source];
|
|
57
|
+
if (!namesRepairTrack(source?.fec, track.name, track.instanceId)) continue;
|
|
58
|
+
const repair = catalog["repair"] ?? {};
|
|
59
|
+
repair.renditions ??= {};
|
|
60
|
+
const { name, packaging: _p, mmtpMode: _m, timescale: _t, groupDurationMs: _g, altGroup: _a, ...entry } = track;
|
|
61
|
+
repair.renditions[name] ??= entry;
|
|
62
|
+
catalog["repair"] = repair;
|
|
63
|
+
}
|
|
53
64
|
return catalog;
|
|
54
65
|
}
|
|
66
|
+
function namesRepairTrack(fec, name, instanceId) {
|
|
67
|
+
const id = instanceId ?? 0;
|
|
68
|
+
return Array.isArray(fec) && fec.some(
|
|
69
|
+
(i) => typeof i === "object" && i !== null && (i.instanceId ?? 0) === id && i.repairTrack === name
|
|
70
|
+
);
|
|
71
|
+
}
|
|
55
72
|
function isPositiveInteger(value) {
|
|
56
73
|
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
57
74
|
}
|
|
@@ -68,7 +85,11 @@ function mergeOverrideIntoCmsfTracks(tracks, override) {
|
|
|
68
85
|
const existingAudioTrack = tracks.find(
|
|
69
86
|
(t) => t.role === "audio" && typeof t.codec === "string" && t.codec.length > 0
|
|
70
87
|
);
|
|
71
|
-
|
|
88
|
+
const ordered = [
|
|
89
|
+
...override.tracks.filter((t) => t.packaging !== "fec-repair"),
|
|
90
|
+
...override.tracks.filter((t) => t.packaging === "fec-repair")
|
|
91
|
+
];
|
|
92
|
+
for (const ot of ordered) {
|
|
72
93
|
const existing = byName.get(ot.name);
|
|
73
94
|
if (existing !== void 0) {
|
|
74
95
|
if (existing.fec === void 0 && ot.fec !== void 0) existing.fec = ot.fec;
|
|
@@ -106,10 +127,14 @@ function mergeOverrideIntoCmsfTracks(tracks, override) {
|
|
|
106
127
|
`[phase14-catalog-override] track "${ot.name}" has no packaging \u2014 supply per-track or set top-level streamingFormat`
|
|
107
128
|
);
|
|
108
129
|
}
|
|
109
|
-
const
|
|
130
|
+
const isRepair = packaging === "fec-repair";
|
|
131
|
+
if (isRepair && !namesRepairTrack(byName.get(ot.source)?.fec, ot.name, ot.instanceId)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const role = ot.role ?? (isRepair ? void 0 : "video");
|
|
110
135
|
const inheritedCodec = role === "audio" ? existingAudioTrack?.codec : existingVideoTrack?.codec;
|
|
111
136
|
const codec = ot.codec ?? inheritedCodec;
|
|
112
|
-
if (packaging !== "timeline" && (typeof codec !== "string" || codec.length === 0)) {
|
|
137
|
+
if (packaging !== "timeline" && !isRepair && (typeof codec !== "string" || codec.length === 0)) {
|
|
113
138
|
throw new Error(
|
|
114
139
|
`[phase14-catalog-override] track "${ot.name}" has no codec (packaging='${packaging}') \u2014 supply per-track or ensure the publisher catalog has a ${role ?? "video"} track to inherit from`
|
|
115
140
|
);
|
|
@@ -130,14 +155,13 @@ function mergeOverrideIntoCmsfTracks(tracks, override) {
|
|
|
130
155
|
`[phase14-catalog-override] track "${ot.name}" has packaging='mmtp' but no valid groupDurationMs`
|
|
131
156
|
);
|
|
132
157
|
}
|
|
133
|
-
const entry = {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
if (ot.
|
|
140
|
-
if (ot.groupDurationMs !== void 0) entry.groupDurationMs = ot.groupDurationMs;
|
|
158
|
+
const entry = { name: ot.name, packaging };
|
|
159
|
+
if (codec !== void 0 && !isRepair) entry.codec = codec;
|
|
160
|
+
if (ot.source !== void 0) entry["source"] = ot.source;
|
|
161
|
+
if (ot.instanceId !== void 0) entry["instanceId"] = ot.instanceId;
|
|
162
|
+
if (mmtpMode !== void 0 && !isRepair) entry.mmtpMode = mmtpMode;
|
|
163
|
+
if (ot.timescale !== void 0 && !isRepair) entry.timescale = ot.timescale;
|
|
164
|
+
if (ot.groupDurationMs !== void 0 && !isRepair) entry.groupDurationMs = ot.groupDurationMs;
|
|
141
165
|
if (role !== void 0) entry.role = role;
|
|
142
166
|
if (ot.framerate !== void 0) entry.framerate = ot.framerate;
|
|
143
167
|
if (ot.fec !== void 0) entry.fec = ot.fec;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/phase14-catalog-override.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Phase 14 catalog-override hook (test harness only).\n *\n * When `globalThis.__PHASE14_CATALOG_OVERRIDE` is set (injected by the\n * 7point test via `page.addInitScript` \u2014 see\n * packages/moq-server/tests/7point-validation.test.mjs `applyPhase14CatalogOverride`)\n * the override's `tracks[]` are merged into whatever catalog the MoQ-delivered\n * JSON produced:\n *\n * - Dedupe by `track.name`.\n * - When both sides carry a track with the same name, the override's\n * `fec` config wins \u2014 this is how the 7point harness steers multi-track\n * tests onto the Phase 14 `video/base` + `video/delta` + `video/repair`\n * switching set without asking the publisher to change its catalog.\n * - Tracks present only in the override are appended as-is.\n * - Tracks present only in the MoQ catalog are preserved unchanged.\n *\n * This module is deliberately player-agnostic. It exposes:\n *\n * getPhase14CatalogOverride() \u2014 read + shape-check the global.\n * mergeOverrideIntoHangRenditions() \u2014 merge into a `@moq/hang`\n * Catalog.Root `video.renditions` /\n * `audio.renditions` dict. Used by\n * hang-mmt-fec/js/watch/src/broadcast.ts.\n * mergeOverrideIntoCmsfTracks() \u2014 merge into moqtail's CMSF-flat\n * `tracks[]` array. Used by\n * moqtail-private CMSFCatalog.from().\n *\n * Production builds never set the global, so all three helpers are no-ops at\n * runtime unless the test harness opts in.\n */\n\nimport type { FecAlgorithm, FecCatalogConfig } from \"./carp-catalog.js\";\n\n// ---------------------------------------------------------------------------\n// Override fixture shape (IETF draft-ietf-moq-catalogformat-01)\n// ---------------------------------------------------------------------------\n\n/**\n * A single override track entry \u2014 mirrors the fixture at\n * `packages/moq-server/tests/fixtures/multi-track-catalog.json`.\n *\n * Kept intentionally permissive: the canonical fixture carries `name`,\n * `altGroup`, `framerate`, and `fec`, but future 7point scenarios may extend\n * with additional hints without wanting this module to gate-keep them.\n */\nexport interface Phase14OverrideTrack {\n\tname: string;\n\t/** CMSF packaging kind. Optional per track \u2014 when absent, the merge\n\t * derives it from the override's top-level `streamingFormat`. */\n\tpackaging?: \"mmtp\" | \"cmaf\" | \"loc\" | \"timeline\";\n\t/** Required when packaging is `mmtp`; the current publisher emits MFU mode. */\n\tmmtpMode?: \"mfu\";\n\t/** Required when packaging is `mmtp`; copied into synthesized CMSF tracks. */\n\ttimescale?: number;\n\t/** Required when packaging is `mmtp`; copied into synthesized CMSF tracks. */\n\tgroupDurationMs?: number;\n\t/** Codec string. Optional per track \u2014 when absent, the merge inherits\n\t * from the existing video/audio rendition in the MoQ catalog (the\n\t * publisher's declaration). Throws only when neither the override nor\n\t * any existing rendition supplies one. */\n\tcodec?: string;\n\trole?: \"video\" | \"audio\" | \"timeline\";\n\taltGroup?: number;\n\tframerate?: number;\n\tfec?: Partial<FecCatalogConfig> & { algorithm: FecAlgorithm };\n}\n\n/**\n * The shape the 7point harness writes to `globalThis.__PHASE14_CATALOG_OVERRIDE`.\n * Extra top-level fields (`version`, `streamingFormat`, ...) are ignored \u2014\n * the merge only consumes `tracks[]`.\n */\nexport interface Phase14CatalogOverride {\n\tversion?: string;\n\tstreamingFormat?: string;\n\ttracks: Phase14OverrideTrack[];\n}\n\n// ---------------------------------------------------------------------------\n// Global reader\n// ---------------------------------------------------------------------------\n\n/**\n * Read `globalThis.__PHASE14_CATALOG_OVERRIDE` and return it only when it has\n * a well-formed `tracks[]` array. Malformed values log + return `null` rather\n * than throw so a broken fixture never kills the MoQ path for a real stream.\n */\nexport function getPhase14CatalogOverride(): Phase14CatalogOverride | null {\n\t// `globalThis` is defined in workers, service workers, and the main window;\n\t// it's the portable way to reach the test-injected hook from any context.\n\tconst g = globalThis as Record<string, unknown>;\n\tconst raw = g[\"__PHASE14_CATALOG_OVERRIDE\"];\n\tif (raw === undefined || raw === null) return null;\n\tif (typeof raw !== \"object\") {\n\t\tconsole.warn(\"[phase14-catalog-override] hook is not an object; ignoring\");\n\t\treturn null;\n\t}\n\tconst obj = raw as Record<string, unknown>;\n\tconst tracks = obj[\"tracks\"];\n\tif (!Array.isArray(tracks)) {\n\t\tconsole.warn(\"[phase14-catalog-override] hook.tracks is not an array; ignoring\");\n\t\treturn null;\n\t}\n\t// Shallow filter: keep only entries that at least have a string name.\n\t// Each consumer (hang or CMSF merge) validates shape further.\n\tconst filtered: Phase14OverrideTrack[] = [];\n\tfor (const t of tracks) {\n\t\tif (typeof t === \"object\" && t !== null) {\n\t\t\tconst entry = t as Record<string, unknown>;\n\t\t\tif (typeof entry[\"name\"] === \"string\" && (entry[\"name\"] as string).length > 0) {\n\t\t\t\tfiltered.push(entry as unknown as Phase14OverrideTrack);\n\t\t\t}\n\t\t}\n\t}\n\tif (filtered.length === 0) return null;\n\treturn {\n\t\tversion: typeof obj[\"version\"] === \"string\" ? (obj[\"version\"] as string) : undefined,\n\t\tstreamingFormat:\n\t\t\ttypeof obj[\"streamingFormat\"] === \"string\" ? (obj[\"streamingFormat\"] as string) : undefined,\n\t\ttracks: filtered,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Hang renditions merge (moq-lib / hang-mmt-fec)\n// ---------------------------------------------------------------------------\n\n/**\n * Merge override tracks into a `@moq/hang` Catalog.Root-like object. The\n * caller passes the already-parsed Catalog.Root (mutates in place and also\n * returns it for convenience). No-op when `override` is null.\n *\n * Dedupe rule: override wins for `fec` config and `framerate`. Everything\n * else (codec, container kind) is preserved from the MoQ-delivered rendition\n * when it exists, or synthesised from the override when it does not.\n *\n * Typed `any` here rather than narrow-matching `Catalog.Root` because the\n * upstream `@moq/hang` schema is branded (u53) and the container `kind` union\n * varies by version (`loc` kind appears in hang-mmt-fec's fork). Matching\n * that surface exactly would over-couple this module to a single upstream\n * revision; the runtime shape we rely on (`.video.renditions` dict mutation)\n * is stable.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function mergeOverrideIntoHangRenditions<T extends Record<string, any>>(\n\tcatalog: T,\n\toverride: Phase14CatalogOverride | null,\n): T {\n\tif (!override) return catalog;\n\n\tfor (const track of override.tracks) {\n\t\t// Default role inference: names without an explicit role field are\n\t\t// treated as video unless the name suffix says audio.\n\t\tconst role: \"video\" | \"audio\" =\n\t\t\ttrack.role === \"audio\" || track.name.startsWith(\"audio\") ? \"audio\" : \"video\";\n\n\t\tconst bagKey = role === \"audio\" ? \"audio\" : \"video\";\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst bag: { renditions: Record<string, any>; priority?: number } =\n\t\t\tcatalog[bagKey] ?? { renditions: {}, priority: 1 };\n\t\tconst existing = bag.renditions[track.name];\n\n\t\t// Merge: override fills gaps, does NOT overwrite existing fec/framerate.\n\t\t// The publisher's real catalog is ground truth for wire parameters:\n\t\t// if the existing rendition already has a matching fec config, that's\n\t\t// what the publisher's packets actually carry. Overwriting it with a\n\t\t// fixture value (e.g. Phase 14 target K=768 when publisher emits K=4)\n\t\t// causes the FEC manager to wait for symbols that never arrive.\n\t\t// Only when the rendition is brand-new (no existing fec) do we seed\n\t\t// from the fixture.\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst merged: Record<string, any> = {\n\t\t\t...(existing ?? {}),\n\t\t\tcontainer: existing?.container ?? { kind: \"mmtp-fec\" },\n\t\t\tcodec: existing?.codec ?? \"\",\n\t\t};\n\t\tif (existing?.framerate === undefined && track.framerate !== undefined) {\n\t\t\tmerged[\"framerate\"] = track.framerate;\n\t\t}\n\t\tif (existing?.fec === undefined && track.fec !== undefined) {\n\t\t\tmerged[\"fec\"] = track.fec;\n\t\t}\n\n\t\tbag.renditions[track.name] = merged;\n\t\t// Reassign so a freshly-created bag is actually reachable on the\n\t\t// catalog when the MoQ side was missing video/audio entirely.\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t(catalog as any)[bagKey] = bag;\n\t}\n\treturn catalog;\n}\n\n// ---------------------------------------------------------------------------\n// CMSF tracks merge (moqtail-private)\n// ---------------------------------------------------------------------------\n\n/**\n * Shape-minimal description of a CMSF track input (pre-validation JSON, not\n * the parsed `CMSFTrack`). Keeps this module free of a hard dependency on\n * moqtail-ts.\n */\ntype CmsfTrackJson = {\n\tname: string;\n\tpackaging?: string;\n\tmmtpMode?: string;\n\ttimescale?: number;\n\tgroupDurationMs?: number;\n\trole?: string;\n\tcodec?: string;\n\tframerate?: number;\n\tfec?: unknown;\n\t[k: string]: unknown;\n};\n\nfunction isPositiveInteger(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value > 0;\n}\n\n/**\n * Merge override tracks into a raw CMSF `tracks[]` JSON array (as produced\n * by `JSON.parse(catalog.json)` inside `CMSFCatalog.from`).\n *\n * Dedupe by `name`. For name collisions, publisher-emitted `fec` + `framerate`\n * are preserved (the publisher is ground truth for wire parameters). For new\n * track names, missing `packaging` is derived from the override's top-level\n * `streamingFormat`, and missing `codec` is inherited from any existing video\n * track in the catalog. Throws on bad init when neither source supplies them.\n */\nexport function mergeOverrideIntoCmsfTracks(\n\ttracks: CmsfTrackJson[],\n\toverride: Phase14CatalogOverride | null,\n): CmsfTrackJson[] {\n\tif (!override) return tracks;\n\n\tconst byName = new Map<string, CmsfTrackJson>();\n\tfor (const t of tracks) {\n\t\tif (typeof t?.name === \"string\") byName.set(t.name, t);\n\t}\n\n\t// Catalog-derived fallbacks (NOT magic numbers) \u2014 inherited from existing\n\t// publisher tracks. Used only when the override track doesn't carry them.\n\tconst derivedPackaging = override.streamingFormat;\n\tconst existingVideoTrack = tracks.find(\n\t\t(t) => t.role === \"video\" && typeof t.codec === \"string\" && (t.codec as string).length > 0,\n\t);\n\tconst existingAudioTrack = tracks.find(\n\t\t(t) => t.role === \"audio\" && typeof t.codec === \"string\" && (t.codec as string).length > 0,\n\t);\n\n\tfor (const ot of override.tracks) {\n\t\tconst existing = byName.get(ot.name);\n\t\tif (existing !== undefined) {\n\t\t\t// Preserve publisher-emitted fec + framerate \u2014 those describe what\n\t\t\t// is actually on the wire. Overwriting with fixture values causes\n\t\t\t// the player's FEC manager to wait for symbols that never arrive\n\t\t\t// when the fixture's K/P don't match the publisher's.\n\t\t\tif (existing.fec === undefined && ot.fec !== undefined) existing.fec = ot.fec;\n\t\t\tif (existing.framerate === undefined && ot.framerate !== undefined) {\n\t\t\t\texisting.framerate = ot.framerate;\n\t\t\t}\n\t\t\tif (ot.role !== undefined && existing.role === undefined) existing.role = ot.role;\n\t\t\tif (existing.packaging === \"mmtp\") {\n\t\t\t\tconst mode = existing.mmtpMode ?? ot.mmtpMode;\n\t\t\t\tif (mode !== \"mfu\") {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid mmtpMode`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\texisting.mmtpMode = mode;\n\t\t\t\tif (existing.timescale === undefined && ot.timescale !== undefined) existing.timescale = ot.timescale;\n\t\t\t\tif (existing.groupDurationMs === undefined && ot.groupDurationMs !== undefined) {\n\t\t\t\t\texisting.groupDurationMs = ot.groupDurationMs;\n\t\t\t\t}\n\t\t\t\tif (!isPositiveInteger(existing.timescale)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid timescale`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!isPositiveInteger(existing.groupDurationMs)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid groupDurationMs`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconst packaging = ot.packaging ?? derivedPackaging;\n\t\t\tif (typeof packaging !== \"string\" || packaging.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has no packaging \u2014 supply per-track or set top-level streamingFormat`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Role inference: repair tracks (ending in /repair) stay role-less\n\t\t\t// per CMSF convention; others default to video.\n\t\t\tconst role = ot.role ?? (ot.name.endsWith(\"/repair\") ? undefined : \"video\");\n\t\t\tconst inheritedCodec =\n\t\t\t\trole === \"audio\" ? existingAudioTrack?.codec : existingVideoTrack?.codec;\n\t\t\tconst codec = ot.codec ?? inheritedCodec;\n\t\t\tif (packaging !== \"timeline\" && (typeof codec !== \"string\" || codec.length === 0)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has no codec (packaging='${packaging}') \u2014 supply per-track or ensure the publisher catalog has a ${role ?? \"video\"} track to inherit from`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst mmtpMode = ot.mmtpMode;\n\t\t\tif (packaging === \"mmtp\" && mmtpMode !== \"mfu\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid mmtpMode`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (packaging === \"mmtp\" && !isPositiveInteger(ot.timescale)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid timescale`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (packaging === \"mmtp\" && !isPositiveInteger(ot.groupDurationMs)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid groupDurationMs`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst entry: CmsfTrackJson = {\n\t\t\t\tname: ot.name,\n\t\t\t\tpackaging,\n\t\t\t\tcodec: codec as string,\n\t\t\t};\n\t\t\tif (mmtpMode !== undefined) entry.mmtpMode = mmtpMode;\n\t\t\tif (ot.timescale !== undefined) entry.timescale = ot.timescale;\n\t\t\tif (ot.groupDurationMs !== undefined) entry.groupDurationMs = ot.groupDurationMs;\n\t\t\tif (role !== undefined) entry.role = role;\n\t\t\tif (ot.framerate !== undefined) entry.framerate = ot.framerate;\n\t\t\tif (ot.fec !== undefined) entry.fec = ot.fec;\n\t\t\ttracks.push(entry);\n\t\t\tbyName.set(ot.name, entry);\n\t\t}\n\t}\n\treturn tracks;\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["/**\n * Phase 14 catalog-override hook (test harness only).\n *\n * When `globalThis.__PHASE14_CATALOG_OVERRIDE` is set (injected by the\n * 7point test via `page.addInitScript` \u2014 see\n * packages/moq-server/tests/7point-validation.test.mjs `applyPhase14CatalogOverride`)\n * the override's `tracks[]` are merged into whatever catalog the MoQ-delivered\n * JSON produced:\n *\n * - Dedupe by `track.name`.\n * - When both sides carry a track with the same name, the override's\n * `fec` config wins \u2014 this is how the 7point harness steers multi-track\n * tests onto the Phase 14 `video/base` + `video/delta` + `video/repair`\n * switching set without asking the publisher to change its catalog.\n * - Tracks present only in the override are appended as-is.\n * - Tracks present only in the MoQ catalog are preserved unchanged.\n * - `fec` is the draft-ramadan-moq-fec-catalog-00 instance array; a repair\n * track is `packaging: \"fec-repair\"` with `source` + `instanceId`, and each\n * of base and delta names its own (one Track Name in two `repairTrack`\n * members is no FEC on either, catalog-00 5 rule 2).\n *\n * This module is deliberately player-agnostic. It exposes:\n *\n * getPhase14CatalogOverride() \u2014 read + shape-check the global.\n * mergeOverrideIntoHangRenditions() \u2014 merge into a `@moq/hang`\n * Catalog.Root `video.renditions` /\n * `audio.renditions` dict. Used by\n * hang-mmt-fec/js/watch/src/broadcast.ts.\n * mergeOverrideIntoCmsfTracks() \u2014 merge into moqtail's CMSF-flat\n * `tracks[]` array. Used by\n * moqtail-private CMSFCatalog.from().\n *\n * Production builds never set the global, so all three helpers are no-ops at\n * runtime unless the test harness opts in.\n */\n\nimport type { FecCatalogConfig } from \"./carp-catalog.js\";\n\n// ---------------------------------------------------------------------------\n// Override fixture shape (IETF draft-ietf-moq-catalogformat-01)\n// ---------------------------------------------------------------------------\n\n/**\n * A single override track entry \u2014 mirrors the fixture at\n * `packages/moq-server/tests/fixtures/multi-track-catalog.json`.\n *\n * Kept intentionally permissive: the canonical fixture carries `name`,\n * `altGroup`, `framerate`, and `fec`, but future 7point scenarios may extend\n * with additional hints without wanting this module to gate-keep them.\n */\nexport interface Phase14OverrideTrack {\n\tname: string;\n\t/** CMSF packaging kind. Optional per track \u2014 when absent, the merge\n\t * derives it from the override's top-level `streamingFormat`. */\n\tpackaging?: \"mmtp\" | \"cmaf\" | \"loc\" | \"timeline\" | \"fec-repair\";\n\t/** A `fec-repair` track's source track and instance (catalog-00 3.2). */\n\tsource?: string;\n\tinstanceId?: number;\n\t/** Required when packaging is `mmtp`; the current publisher emits MFU mode. */\n\tmmtpMode?: \"mfu\";\n\t/** Required when packaging is `mmtp`; copied into synthesized CMSF tracks. */\n\ttimescale?: number;\n\t/** Required when packaging is `mmtp`; copied into synthesized CMSF tracks. */\n\tgroupDurationMs?: number;\n\t/** Codec string. Optional per track \u2014 when absent, the merge inherits\n\t * from the existing video/audio rendition in the MoQ catalog (the\n\t * publisher's declaration). Throws only when neither the override nor\n\t * any existing rendition supplies one. */\n\tcodec?: string;\n\trole?: \"video\" | \"audio\" | \"timeline\";\n\taltGroup?: number;\n\tframerate?: number;\n\tfec?: FecCatalogConfig;\n}\n\n/**\n * The shape the 7point harness writes to `globalThis.__PHASE14_CATALOG_OVERRIDE`.\n * Extra top-level fields (`version`, `streamingFormat`, ...) are ignored \u2014\n * the merge only consumes `tracks[]`.\n */\nexport interface Phase14CatalogOverride {\n\tversion?: string;\n\tstreamingFormat?: string;\n\ttracks: Phase14OverrideTrack[];\n}\n\n// ---------------------------------------------------------------------------\n// Global reader\n// ---------------------------------------------------------------------------\n\n/**\n * Read `globalThis.__PHASE14_CATALOG_OVERRIDE` and return it only when it has\n * a well-formed `tracks[]` array. Malformed values log + return `null` rather\n * than throw so a broken fixture never kills the MoQ path for a real stream.\n */\nexport function getPhase14CatalogOverride(): Phase14CatalogOverride | null {\n\t// `globalThis` is defined in workers, service workers, and the main window;\n\t// it's the portable way to reach the test-injected hook from any context.\n\tconst g = globalThis as Record<string, unknown>;\n\tconst raw = g[\"__PHASE14_CATALOG_OVERRIDE\"];\n\tif (raw === undefined || raw === null) return null;\n\tif (typeof raw !== \"object\") {\n\t\tconsole.warn(\"[phase14-catalog-override] hook is not an object; ignoring\");\n\t\treturn null;\n\t}\n\tconst obj = raw as Record<string, unknown>;\n\tconst tracks = obj[\"tracks\"];\n\tif (!Array.isArray(tracks)) {\n\t\tconsole.warn(\"[phase14-catalog-override] hook.tracks is not an array; ignoring\");\n\t\treturn null;\n\t}\n\t// Shallow filter: keep only entries that at least have a string name.\n\t// Each consumer (hang or CMSF merge) validates shape further.\n\tconst filtered: Phase14OverrideTrack[] = [];\n\tfor (const t of tracks) {\n\t\tif (typeof t === \"object\" && t !== null) {\n\t\t\tconst entry = t as Record<string, unknown>;\n\t\t\tif (typeof entry[\"name\"] === \"string\" && (entry[\"name\"] as string).length > 0) {\n\t\t\t\tfiltered.push(entry as unknown as Phase14OverrideTrack);\n\t\t\t}\n\t\t}\n\t}\n\tif (filtered.length === 0) return null;\n\treturn {\n\t\tversion: typeof obj[\"version\"] === \"string\" ? (obj[\"version\"] as string) : undefined,\n\t\tstreamingFormat:\n\t\t\ttypeof obj[\"streamingFormat\"] === \"string\" ? (obj[\"streamingFormat\"] as string) : undefined,\n\t\ttracks: filtered,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Hang renditions merge (moq-lib / hang-mmt-fec)\n// ---------------------------------------------------------------------------\n\n/**\n * Merge override tracks into a `@moq/hang` Catalog.Root-like object. The\n * caller passes the already-parsed Catalog.Root (mutates in place and also\n * returns it for convenience). No-op when `override` is null.\n *\n * Dedupe rule: override wins for `fec` config and `framerate`. Everything\n * else (codec, container kind) is preserved from the MoQ-delivered rendition\n * when it exists, or synthesised from the override when it does not.\n *\n * Typed `any` here rather than narrow-matching `Catalog.Root` because the\n * upstream `@moq/hang` schema is branded (u53) and the container `kind` union\n * varies by version (`loc` kind appears in hang-mmt-fec's fork). Matching\n * that surface exactly would over-couple this module to a single upstream\n * revision; the runtime shape we rely on (`.video.renditions` dict mutation)\n * is stable.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function mergeOverrideIntoHangRenditions<T extends Record<string, any>>(\n\tcatalog: T,\n\toverride: Phase14CatalogOverride | null,\n): T {\n\tif (!override) return catalog;\n\n\tfor (const track of override.tracks) {\n\t\t// Repair tracks are placed after the media merge, below.\n\t\tif (track.packaging === \"fec-repair\") continue;\n\t\t// Default role inference: names without an explicit role field are\n\t\t// treated as video unless the name suffix says audio.\n\t\tconst role: \"video\" | \"audio\" =\n\t\t\ttrack.role === \"audio\" || track.name.startsWith(\"audio\") ? \"audio\" : \"video\";\n\n\t\tconst bagKey = role === \"audio\" ? \"audio\" : \"video\";\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst bag: { renditions: Record<string, any>; priority?: number } =\n\t\t\tcatalog[bagKey] ?? { renditions: {}, priority: 1 };\n\t\tconst existing = bag.renditions[track.name];\n\n\t\t// Merge: override fills gaps, does NOT overwrite existing fec/framerate.\n\t\t// The publisher's real catalog is ground truth for wire parameters:\n\t\t// if the existing rendition already has a matching fec config, that's\n\t\t// what the publisher's packets actually carry. Overwriting it with a\n\t\t// fixture value (e.g. Phase 14 target K=768 when publisher emits K=4)\n\t\t// causes the FEC manager to wait for symbols that never arrive.\n\t\t// Only when the rendition is brand-new (no existing fec) do we seed\n\t\t// from the fixture.\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst merged: Record<string, any> = {\n\t\t\t...(existing ?? {}),\n\t\t\tcontainer: existing?.container ?? { kind: \"mmtp-fec\" },\n\t\t\tcodec: existing?.codec ?? \"\",\n\t\t};\n\t\tif (existing?.framerate === undefined && track.framerate !== undefined) {\n\t\t\tmerged[\"framerate\"] = track.framerate;\n\t\t}\n\t\tif (existing?.fec === undefined && track.fec !== undefined) {\n\t\t\tmerged[\"fec\"] = track.fec;\n\t\t}\n\n\t\tbag.renditions[track.name] = merged;\n\t\t// Reassign so a freshly-created bag is actually reachable on the\n\t\t// catalog when the MoQ side was missing video/audio entirely.\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t(catalog as any)[bagKey] = bag;\n\t}\n\n\t// A repair track is a hang `repair.renditions` entry, bound by `source` +\n\t// `instanceId`, never a media rendition. It is added only when the\n\t// source's effective `fec` names it: a publisher `fec` that won the merge\n\t// may bind a different repair track, and attaching this one would cost\n\t// that instance its FEC (rule 3).\n\tfor (const track of override.tracks) {\n\t\tif (track.packaging !== \"fec-repair\") continue;\n\t\tconst source =\n\t\t\tcatalog[\"video\"]?.renditions?.[track.source as string] ??\n\t\t\tcatalog[\"audio\"]?.renditions?.[track.source as string];\n\t\tif (!namesRepairTrack(source?.fec, track.name, track.instanceId)) continue;\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst repair: { renditions?: Record<string, any> } = catalog[\"repair\"] ?? {};\n\t\trepair.renditions ??= {};\n\t\tconst { name, packaging: _p, mmtpMode: _m, timescale: _t, groupDurationMs: _g, altGroup: _a, ...entry } = track;\n\t\trepair.renditions[name] ??= entry;\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t(catalog as any)[\"repair\"] = repair;\n\t}\n\treturn catalog;\n}\n\n/** Whether `fec` holds the instance `instanceId` (default 0) whose `repairTrack` is `name`. */\nfunction namesRepairTrack(fec: unknown, name: string, instanceId: unknown): boolean {\n\tconst id = instanceId ?? 0;\n\treturn (\n\t\tArray.isArray(fec) &&\n\t\tfec.some(\n\t\t\t(i) =>\n\t\t\t\ttypeof i === \"object\" && i !== null && (i.instanceId ?? 0) === id && i.repairTrack === name,\n\t\t)\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// CMSF tracks merge (moqtail-private)\n// ---------------------------------------------------------------------------\n\n/**\n * Shape-minimal description of a CMSF track input (pre-validation JSON, not\n * the parsed `CMSFTrack`). Keeps this module free of a hard dependency on\n * moqtail-ts.\n */\ntype CmsfTrackJson = {\n\tname: string;\n\tpackaging?: string;\n\tmmtpMode?: string;\n\ttimescale?: number;\n\tgroupDurationMs?: number;\n\trole?: string;\n\tcodec?: string;\n\tframerate?: number;\n\tfec?: unknown;\n\t[k: string]: unknown;\n};\n\nfunction isPositiveInteger(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value > 0;\n}\n\n/**\n * Merge override tracks into a raw CMSF `tracks[]` JSON array (as produced\n * by `JSON.parse(catalog.json)` inside `CMSFCatalog.from`).\n *\n * Dedupe by `name`. For name collisions, publisher-emitted `fec` + `framerate`\n * are preserved (the publisher is ground truth for wire parameters). For new\n * track names, missing `packaging` is derived from the override's top-level\n * `streamingFormat`, and missing `codec` is inherited from any existing video\n * track in the catalog. Throws on bad init when neither source supplies them.\n */\nexport function mergeOverrideIntoCmsfTracks(\n\ttracks: CmsfTrackJson[],\n\toverride: Phase14CatalogOverride | null,\n): CmsfTrackJson[] {\n\tif (!override) return tracks;\n\n\tconst byName = new Map<string, CmsfTrackJson>();\n\tfor (const t of tracks) {\n\t\tif (typeof t?.name === \"string\") byName.set(t.name, t);\n\t}\n\n\t// Catalog-derived fallbacks (NOT magic numbers) \u2014 inherited from existing\n\t// publisher tracks. Used only when the override track doesn't carry them.\n\tconst derivedPackaging = override.streamingFormat;\n\tconst existingVideoTrack = tracks.find(\n\t\t(t) => t.role === \"video\" && typeof t.codec === \"string\" && (t.codec as string).length > 0,\n\t);\n\tconst existingAudioTrack = tracks.find(\n\t\t(t) => t.role === \"audio\" && typeof t.codec === \"string\" && (t.codec as string).length > 0,\n\t);\n\n\t// Media first, so a repair track can check its source's effective `fec`.\n\tconst ordered = [\n\t\t...override.tracks.filter((t) => t.packaging !== \"fec-repair\"),\n\t\t...override.tracks.filter((t) => t.packaging === \"fec-repair\"),\n\t];\n\tfor (const ot of ordered) {\n\t\tconst existing = byName.get(ot.name);\n\t\tif (existing !== undefined) {\n\t\t\t// Preserve publisher-emitted fec + framerate \u2014 those describe what\n\t\t\t// is actually on the wire. Overwriting with fixture values causes\n\t\t\t// the player's FEC manager to wait for symbols that never arrive\n\t\t\t// when the fixture's K/P don't match the publisher's.\n\t\t\tif (existing.fec === undefined && ot.fec !== undefined) existing.fec = ot.fec;\n\t\t\tif (existing.framerate === undefined && ot.framerate !== undefined) {\n\t\t\t\texisting.framerate = ot.framerate;\n\t\t\t}\n\t\t\tif (ot.role !== undefined && existing.role === undefined) existing.role = ot.role;\n\t\t\tif (existing.packaging === \"mmtp\") {\n\t\t\t\tconst mode = existing.mmtpMode ?? ot.mmtpMode;\n\t\t\t\tif (mode !== \"mfu\") {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid mmtpMode`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\texisting.mmtpMode = mode;\n\t\t\t\tif (existing.timescale === undefined && ot.timescale !== undefined) existing.timescale = ot.timescale;\n\t\t\t\tif (existing.groupDurationMs === undefined && ot.groupDurationMs !== undefined) {\n\t\t\t\t\texisting.groupDurationMs = ot.groupDurationMs;\n\t\t\t\t}\n\t\t\t\tif (!isPositiveInteger(existing.timescale)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid timescale`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!isPositiveInteger(existing.groupDurationMs)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid groupDurationMs`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconst packaging = ot.packaging ?? derivedPackaging;\n\t\t\tif (typeof packaging !== \"string\" || packaging.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has no packaging \u2014 supply per-track or set top-level streamingFormat`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Role inference: repair tracks stay role-less per CMSF convention;\n\t\t\t// others default to video. Decided by packaging, never by name.\n\t\t\tconst isRepair = packaging === \"fec-repair\";\n\t\t\t// Same rule as the hang path: only a repair track its source names.\n\t\t\tif (isRepair && !namesRepairTrack(byName.get(ot.source as string)?.fec, ot.name, ot.instanceId)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst role = ot.role ?? (isRepair ? undefined : \"video\");\n\t\t\tconst inheritedCodec =\n\t\t\t\trole === \"audio\" ? existingAudioTrack?.codec : existingVideoTrack?.codec;\n\t\t\tconst codec = ot.codec ?? inheritedCodec;\n\t\t\tif (packaging !== \"timeline\" && !isRepair && (typeof codec !== \"string\" || codec.length === 0)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has no codec (packaging='${packaging}') \u2014 supply per-track or ensure the publisher catalog has a ${role ?? \"video\"} track to inherit from`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst mmtpMode = ot.mmtpMode;\n\t\t\tif (packaging === \"mmtp\" && mmtpMode !== \"mfu\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid mmtpMode`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (packaging === \"mmtp\" && !isPositiveInteger(ot.timescale)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid timescale`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (packaging === \"mmtp\" && !isPositiveInteger(ot.groupDurationMs)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[phase14-catalog-override] track \"${ot.name}\" has packaging='mmtp' but no valid groupDurationMs`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst entry: CmsfTrackJson = { name: ot.name, packaging };\n\t\t\tif (codec !== undefined && !isRepair) entry.codec = codec as string;\n\t\t\tif (ot.source !== undefined) entry[\"source\"] = ot.source;\n\t\t\tif (ot.instanceId !== undefined) entry[\"instanceId\"] = ot.instanceId;\n\t\t\t// Block geometry on a repair track costs its instance FEC (rule 1).\n\t\t\tif (mmtpMode !== undefined && !isRepair) entry.mmtpMode = mmtpMode;\n\t\t\tif (ot.timescale !== undefined && !isRepair) entry.timescale = ot.timescale;\n\t\t\tif (ot.groupDurationMs !== undefined && !isRepair) entry.groupDurationMs = ot.groupDurationMs;\n\t\t\tif (role !== undefined) entry.role = role;\n\t\t\tif (ot.framerate !== undefined) entry.framerate = ot.framerate;\n\t\t\tif (ot.fec !== undefined) entry.fec = ot.fec;\n\t\t\ttracks.push(entry);\n\t\t\tbyName.set(ot.name, entry);\n\t\t}\n\t}\n\treturn tracks;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/repair-fetch.d.ts
CHANGED
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
* below a RAP the receiver already holds complete is not requested at
|
|
30
30
|
* all (`moot-rap`), and a P0 FETCH still running when such a RAP
|
|
31
31
|
* arrives is cancelled (`repair-stopped`, `moot`): the receiver resumes
|
|
32
|
-
* from that RAP (draft-ramadan-moq-fec §11.4.2).
|
|
32
|
+
* from that RAP (draft-ramadan-moq-fec §11.4.2). Only a repair that
|
|
33
|
+
* could not land by the block's playout deadline (it relies on the
|
|
34
|
+
* keyframe budget's extension) is moot: one that can stalls nothing.
|
|
33
35
|
* - P1 (audio) dispatches, uncapped, while no P0 is queued or in flight;
|
|
34
36
|
* P2 (delta) only while no P0/P1 is, earliest absolute fail deadline
|
|
35
37
|
* first (§11.4.5 item 3).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repair-fetch.d.ts","sourceRoot":"","sources":["../src/repair-fetch.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"repair-fetch.d.ts","sourceRoot":"","sources":["../src/repair-fetch.ts"],"names":[],"mappings":"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"}
|
package/dist/repair-fetch.js
CHANGED
|
@@ -24,8 +24,12 @@ function parseFecBlockProfile(value, where) {
|
|
|
24
24
|
throw new Error(`${where} must be "iso-ssbg1-v1" (got ${JSON.stringify(value)})`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// ../mmt-base/dist/
|
|
28
|
-
var
|
|
27
|
+
// ../mmt-base/dist/fec-bindings.js
|
|
28
|
+
var RUNTIME_FEC_RECEIVER = Object.freeze({
|
|
29
|
+
schemes: Object.freeze(["raptorq", "reed-solomon"]),
|
|
30
|
+
blockProfiles: Object.freeze(["iso-ssbg1-v1"]),
|
|
31
|
+
overlays: true
|
|
32
|
+
});
|
|
29
33
|
|
|
30
34
|
// src/repair-fetch.ts
|
|
31
35
|
import { extendSsId, repairFetchRange } from "@blockcast/mmt-fec";
|
|
@@ -123,6 +127,9 @@ function wireRepairFetch(mc, opts) {
|
|
|
123
127
|
};
|
|
124
128
|
const moot = (entry) => {
|
|
125
129
|
if (entry.priority !== 0) return false;
|
|
130
|
+
const rttMs = readRtt();
|
|
131
|
+
const sentAt = entry.running?.sentAt ?? performance.now();
|
|
132
|
+
if (rttMs !== null && sentAt + rttMs < entry.playoutDeadlineAt) return false;
|
|
126
133
|
const last = lastSsId(entry);
|
|
127
134
|
if (last === void 0) return false;
|
|
128
135
|
try {
|
|
@@ -217,7 +224,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
217
224
|
bound: entry.bound,
|
|
218
225
|
...entry.range ? { range: true } : {}
|
|
219
226
|
});
|
|
220
|
-
const run = { controller: new AbortController(), stopped: null };
|
|
227
|
+
const run = { controller: new AbortController(), sentAt: now, stopped: null };
|
|
221
228
|
entry.running = run;
|
|
222
229
|
if (!entry.range) {
|
|
223
230
|
mc.fecManager.holdBlockForRepair(
|
|
@@ -376,6 +383,10 @@ function wireRepairFetch(mc, opts) {
|
|
|
376
383
|
);
|
|
377
384
|
if (promotedDeadline !== null && promotedDeadline.deadlineAt > entry2.deadlineAt) {
|
|
378
385
|
entry2.deadlineAt = promotedDeadline.deadlineAt;
|
|
386
|
+
entry2.playoutDeadlineAt = Math.max(
|
|
387
|
+
entry2.playoutDeadlineAt,
|
|
388
|
+
promotedDeadline.deadlineAt - promotedDeadline.extensionMs
|
|
389
|
+
);
|
|
379
390
|
entry2.anchorAt = promotedDeadline.anchorAt;
|
|
380
391
|
entry2.delayMs = promotedDeadline.delayMs;
|
|
381
392
|
entry2.bound = promotedDeadline.bound;
|
|
@@ -507,6 +518,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
507
518
|
priority,
|
|
508
519
|
subscriberPriority: wire.subscriberPriority,
|
|
509
520
|
deadlineAt: deadline.deadlineAt,
|
|
521
|
+
playoutDeadlineAt: deadline.deadlineAt - deadline.extensionMs,
|
|
510
522
|
anchorAt: deadline.anchorAt,
|
|
511
523
|
delayMs: deadline.delayMs,
|
|
512
524
|
bound: deadline.bound,
|
|
@@ -576,6 +588,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
576
588
|
priority,
|
|
577
589
|
subscriberPriority: wire.subscriberPriority,
|
|
578
590
|
deadlineAt: deadline.deadlineAt,
|
|
591
|
+
playoutDeadlineAt: deadline.deadlineAt - deadline.extensionMs,
|
|
579
592
|
anchorAt: deadline.anchorAt,
|
|
580
593
|
delayMs: deadline.delayMs,
|
|
581
594
|
bound: deadline.bound,
|