@blockcast/mmt-container 0.5.0 → 1.0.0-main.215b8665a0eb
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 +1 -1
- package/dist/browser-runtime.js +367 -52
- package/dist/browser-runtime.js.map +4 -4
- package/dist/fec-catalog.d.ts +33 -0
- package/dist/fec-catalog.d.ts.map +1 -0
- package/dist/fec-catalog.js +296 -0
- package/dist/fec-catalog.js.map +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +2 -2
- package/dist/mmtp-router.d.ts +7 -16
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +13 -27
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.d.ts +5 -5
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +69 -48
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.d.ts +23 -2
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +45 -12
- package/dist/multicast-transport.js.map +2 -2
- package/package.json +9 -5
- package/src/fec-catalog.ts +428 -0
- package/src/index.ts +11 -0
- package/src/mmtp-router.ts +14 -29
- package/src/multicast-manager.ts +17 -11
- package/src/multicast-transport.ts +65 -12
package/README.md
CHANGED
|
@@ -270,7 +270,7 @@ per-commit `0.1.0-main.<sha>` prerelease from `stamp-prerelease.mjs`), and a
|
|
|
270
270
|
> Superseded by BLO-40665. Every package is now bumped in lockstep by a `v*`
|
|
271
271
|
> tag and the sibling ranges move with it, so the objection above no longer
|
|
272
272
|
> applies -- it was an argument against bumping *one* package. The set is at
|
|
273
|
-
> `0.
|
|
273
|
+
> `1.0.0`; see `RELEASES.md`.
|
|
274
274
|
|
|
275
275
|
#### Track-role convention (Phase 14)
|
|
276
276
|
|
package/dist/browser-runtime.js
CHANGED
|
@@ -7090,6 +7090,299 @@ function validateSwitchingSetTiming(tracks) {
|
|
|
7090
7090
|
return { ok: false, expectedTicks, mismatches };
|
|
7091
7091
|
}
|
|
7092
7092
|
|
|
7093
|
+
// src/fec-catalog.ts
|
|
7094
|
+
var DEFAULT_SCHEMES = ["reed-solomon", "raptorq"];
|
|
7095
|
+
var DEFAULT_PROFILES = ["moq-object-v1", "iso-ssbg1-v1"];
|
|
7096
|
+
var RS_MAX_BLOCK = 255;
|
|
7097
|
+
var RAPTORQ_MAX_K = 56403;
|
|
7098
|
+
var MAX_24 = 16777215;
|
|
7099
|
+
var ISO_MIN_T = 14;
|
|
7100
|
+
var GEOMETRY_KEYS = [
|
|
7101
|
+
"scheme",
|
|
7102
|
+
"blockProfile",
|
|
7103
|
+
"mmtpMode",
|
|
7104
|
+
"timescale",
|
|
7105
|
+
"symbolSize",
|
|
7106
|
+
"interleaveDepthMs",
|
|
7107
|
+
"reorderToleranceMs",
|
|
7108
|
+
"sourceSymbols",
|
|
7109
|
+
"fec"
|
|
7110
|
+
];
|
|
7111
|
+
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7112
|
+
var has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
7113
|
+
var isInt = (v) => typeof v === "number" && Number.isInteger(v);
|
|
7114
|
+
var intIn = (v, lo, hi = Number.MAX_SAFE_INTEGER) => isInt(v) && v >= lo && v <= hi;
|
|
7115
|
+
var profileAdmits = (profile, packaging) => profile !== "iso-ssbg1-v1" || packaging === "mmtp";
|
|
7116
|
+
function validateFecCatalog(catalog, receiver = {}) {
|
|
7117
|
+
const schemes = receiver.schemes ?? DEFAULT_SCHEMES;
|
|
7118
|
+
const profiles = receiver.blockProfiles ?? DEFAULT_PROFILES;
|
|
7119
|
+
const overlays = receiver.overlays ?? true;
|
|
7120
|
+
const all = [];
|
|
7121
|
+
const report = (code, path, inst) => {
|
|
7122
|
+
const issue = { code, path };
|
|
7123
|
+
all.push(issue);
|
|
7124
|
+
inst?.issues.push(issue);
|
|
7125
|
+
};
|
|
7126
|
+
const tracks = isObj(catalog) && Array.isArray(catalog.tracks) ? catalog.tracks : [];
|
|
7127
|
+
if (!isObj(catalog) || !Array.isArray(catalog.tracks)) report("fec_member", "/tracks");
|
|
7128
|
+
let rejectAll = false;
|
|
7129
|
+
tracks.forEach((t, ti) => {
|
|
7130
|
+
if (!isObj(t)) return;
|
|
7131
|
+
if (has(t, "enhancementRepair")) {
|
|
7132
|
+
rejectAll = true;
|
|
7133
|
+
report("fec_enhancement_repair", `/tracks/${ti}/enhancementRepair`);
|
|
7134
|
+
}
|
|
7135
|
+
if (Array.isArray(t.fec))
|
|
7136
|
+
t.fec.forEach((o, ii) => {
|
|
7137
|
+
if (isObj(o) && has(o, "enhancementRepair")) {
|
|
7138
|
+
rejectAll = true;
|
|
7139
|
+
report("fec_enhancement_repair", `/tracks/${ti}/fec/${ii}/enhancementRepair`);
|
|
7140
|
+
}
|
|
7141
|
+
});
|
|
7142
|
+
});
|
|
7143
|
+
const key = (ns, name) => JSON.stringify([ns ?? null, name]);
|
|
7144
|
+
const byName = /* @__PURE__ */ new Map();
|
|
7145
|
+
tracks.forEach((t, ti) => {
|
|
7146
|
+
if (isObj(t) && typeof t.name === "string") {
|
|
7147
|
+
const k = key(t.namespace, t.name);
|
|
7148
|
+
if (!byName.has(k)) byName.set(k, ti);
|
|
7149
|
+
}
|
|
7150
|
+
});
|
|
7151
|
+
const trackAt = (ti) => tracks[ti];
|
|
7152
|
+
const insts = [];
|
|
7153
|
+
tracks.forEach((t, ti) => {
|
|
7154
|
+
if (!isObj(t) || !has(t, "fec") || t.packaging === "fec-repair" || t.packaging === "fec-source") return;
|
|
7155
|
+
if (!Array.isArray(t.fec)) {
|
|
7156
|
+
report("fec_member", `/tracks/${ti}/fec`);
|
|
7157
|
+
return;
|
|
7158
|
+
}
|
|
7159
|
+
t.fec.forEach((o, ii) => {
|
|
7160
|
+
const path = `/tracks/${ti}/fec/${ii}`;
|
|
7161
|
+
const inst = {
|
|
7162
|
+
path,
|
|
7163
|
+
ti,
|
|
7164
|
+
track: t,
|
|
7165
|
+
obj: isObj(o) ? o : null,
|
|
7166
|
+
id: null,
|
|
7167
|
+
unavailable: false,
|
|
7168
|
+
overlay: false,
|
|
7169
|
+
issues: [],
|
|
7170
|
+
layer0: null,
|
|
7171
|
+
symbolNamed: null,
|
|
7172
|
+
attached: []
|
|
7173
|
+
};
|
|
7174
|
+
insts.push(inst);
|
|
7175
|
+
if (!inst.obj) return report("fec_member", path, inst);
|
|
7176
|
+
const o2 = inst.obj;
|
|
7177
|
+
if (!has(o2, "instanceId")) inst.id = 0;
|
|
7178
|
+
else if (intIn(o2.instanceId, 0, 255)) inst.id = o2.instanceId;
|
|
7179
|
+
else report("fec_member", `${path}/instanceId`, inst);
|
|
7180
|
+
inst.overlay = has(o2, "scope");
|
|
7181
|
+
});
|
|
7182
|
+
});
|
|
7183
|
+
const declaresFec = insts.length > 0;
|
|
7184
|
+
for (const inst of insts) {
|
|
7185
|
+
const o = inst.obj;
|
|
7186
|
+
if (!o) continue;
|
|
7187
|
+
const avail = (field, impl) => {
|
|
7188
|
+
const p = `${inst.path}/${field}`;
|
|
7189
|
+
const absent = field === "scheme" ? "fec_scheme_absent" : "fec_block_profile_absent";
|
|
7190
|
+
const unimpl = field === "scheme" ? "fec_scheme_unimplemented" : "fec_block_profile_unimplemented";
|
|
7191
|
+
if (!has(o, field)) return report(absent, p, inst), false;
|
|
7192
|
+
if (typeof o[field] !== "string") return report("fec_member", p, inst), true;
|
|
7193
|
+
if (!impl.includes(o[field])) return report(unimpl, p, inst), false;
|
|
7194
|
+
return true;
|
|
7195
|
+
};
|
|
7196
|
+
if (!avail("scheme", schemes) || !avail("blockProfile", profiles)) inst.unavailable = true;
|
|
7197
|
+
else if (typeof o.blockProfile === "string" && !profileAdmits(o.blockProfile, inst.track.packaging)) {
|
|
7198
|
+
report("fec_block_profile_packaging", `${inst.path}/blockProfile`, inst);
|
|
7199
|
+
inst.unavailable = true;
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
const live = (i) => !i.unavailable;
|
|
7203
|
+
for (const inst of insts.filter(live)) {
|
|
7204
|
+
const o = inst.obj;
|
|
7205
|
+
if (!o) continue;
|
|
7206
|
+
const p = inst.path;
|
|
7207
|
+
for (const m of ["sourceSymbols", "repairSymbols", "symbolSize"])
|
|
7208
|
+
if (!intIn(o[m], 1)) report("fec_member", `${p}/${m}`, inst);
|
|
7209
|
+
if (typeof o.repairTrack !== "string") report("fec_member", `${p}/repairTrack`, inst);
|
|
7210
|
+
if (has(o, "symbolTrack") && typeof o.symbolTrack !== "string") report("fec_member", `${p}/symbolTrack`, inst);
|
|
7211
|
+
if (has(o, "interleaveDepthMs")) {
|
|
7212
|
+
const d = o.interleaveDepthMs;
|
|
7213
|
+
if (!intIn(d, 0) || d === 0 && o.blockProfile !== "iso-ssbg1-v1")
|
|
7214
|
+
report("fec_member", `${p}/interleaveDepthMs`, inst);
|
|
7215
|
+
}
|
|
7216
|
+
if (has(o, "reorderToleranceMs")) {
|
|
7217
|
+
const r = o.reorderToleranceMs;
|
|
7218
|
+
if (typeof r !== "number" || !Number.isFinite(r) || r < 0) report("fec_member", `${p}/reorderToleranceMs`, inst);
|
|
7219
|
+
}
|
|
7220
|
+
if (has(o, "label") && typeof o.label !== "string") report("fec_member", `${p}/label`, inst);
|
|
7221
|
+
if (inst.overlay && o.scope !== "keyframe") report("fec_member", `${p}/scope`, inst);
|
|
7222
|
+
}
|
|
7223
|
+
const idGroups = /* @__PURE__ */ new Map();
|
|
7224
|
+
for (const inst of insts)
|
|
7225
|
+
if (inst.id !== null) {
|
|
7226
|
+
const k = `${inst.ti}:${inst.id}`;
|
|
7227
|
+
idGroups.set(k, [...idGroups.get(k) ?? [], inst]);
|
|
7228
|
+
}
|
|
7229
|
+
for (const group of idGroups.values())
|
|
7230
|
+
if (group.length > 1)
|
|
7231
|
+
for (const inst of group.filter(live))
|
|
7232
|
+
report("fec_instance_id", inst.obj && has(inst.obj, "instanceId") ? `${inst.path}/instanceId` : inst.path, inst);
|
|
7233
|
+
const nameUses = /* @__PURE__ */ new Map();
|
|
7234
|
+
for (const inst of insts)
|
|
7235
|
+
for (const field of ["repairTrack", "symbolTrack"])
|
|
7236
|
+
if (inst.obj && typeof inst.obj[field] === "string") {
|
|
7237
|
+
const k = key(inst.track.namespace, inst.obj[field]);
|
|
7238
|
+
nameUses.set(k, [...nameUses.get(k) ?? [], { inst, field }]);
|
|
7239
|
+
}
|
|
7240
|
+
const namedBy = /* @__PURE__ */ new Map();
|
|
7241
|
+
for (const [k, uses] of nameUses) {
|
|
7242
|
+
const tj = byName.get(k);
|
|
7243
|
+
for (const { inst, field } of uses) {
|
|
7244
|
+
const path = `${inst.path}/${field}`;
|
|
7245
|
+
if (tj === void 0 || trackAt(tj).packaging !== (field === "repairTrack" ? "fec-repair" : "fec-source")) {
|
|
7246
|
+
if (live(inst)) report("fec_naming", path, inst);
|
|
7247
|
+
} else if (uses.length > 1) {
|
|
7248
|
+
if (live(inst)) report("fec_naming", path, inst);
|
|
7249
|
+
if (!namedBy.has(tj)) namedBy.set(tj, inst);
|
|
7250
|
+
} else {
|
|
7251
|
+
namedBy.set(tj, inst);
|
|
7252
|
+
if (field === "repairTrack") inst.layer0 = tj;
|
|
7253
|
+
else inst.symbolNamed = tj;
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
const owner = /* @__PURE__ */ new Map();
|
|
7258
|
+
if (declaresFec)
|
|
7259
|
+
tracks.forEach((t, tj) => {
|
|
7260
|
+
if (!isObj(t) || t.packaging !== "fec-repair" && t.packaging !== "fec-source") return;
|
|
7261
|
+
const tp = `/tracks/${tj}`;
|
|
7262
|
+
const named = namedBy.get(tj);
|
|
7263
|
+
if (named) {
|
|
7264
|
+
owner.set(tj, named);
|
|
7265
|
+
if (!live(named)) return;
|
|
7266
|
+
if (has(t, "source") && t.source !== named.track.name) report("fec_resolution", `${tp}/source`, named);
|
|
7267
|
+
if (has(t, "instanceId") && t.instanceId !== named.id) report("fec_resolution", `${tp}/instanceId`, named);
|
|
7268
|
+
return;
|
|
7269
|
+
}
|
|
7270
|
+
if (!has(t, "source")) return report("fec_orphan", tp);
|
|
7271
|
+
const id = has(t, "instanceId") ? t.instanceId : 0;
|
|
7272
|
+
const src = typeof t.source === "string" ? byName.get(key(t.namespace, t.source)) : void 0;
|
|
7273
|
+
const target = src === void 0 ? void 0 : insts.find((i) => i.ti === src && i.id === id);
|
|
7274
|
+
if (!target) return report("fec_naming", has(t, "instanceId") && src !== void 0 ? `${tp}/instanceId` : `${tp}/source`);
|
|
7275
|
+
owner.set(tj, target);
|
|
7276
|
+
target.attached.push(tj);
|
|
7277
|
+
});
|
|
7278
|
+
for (const [tj, inst] of owner) {
|
|
7279
|
+
if (!live(inst)) continue;
|
|
7280
|
+
const t = trackAt(tj);
|
|
7281
|
+
const tp = `/tracks/${tj}`;
|
|
7282
|
+
const symbol = t.packaging === "fec-source";
|
|
7283
|
+
if (has(t, "source") && typeof t.source !== "string") report("fec_member", `${tp}/source`, inst);
|
|
7284
|
+
if (has(t, "instanceId") && !intIn(t.instanceId, 0, 255)) report("fec_member", `${tp}/instanceId`, inst);
|
|
7285
|
+
for (const m of ["repairLayer", "repairSymbols"]) {
|
|
7286
|
+
if (!has(t, m)) continue;
|
|
7287
|
+
if (symbol || !intIn(t[m], m === "repairLayer" ? 0 : 1)) report("fec_member", `${tp}/${m}`, inst);
|
|
7288
|
+
}
|
|
7289
|
+
if (has(t, "priority") && !intIn(t.priority, symbol ? 0 : 192, 255)) report("fec_member", `${tp}/priority`, inst);
|
|
7290
|
+
for (const k of Object.keys(t))
|
|
7291
|
+
if (GEOMETRY_KEYS.includes(k) || k.startsWith("groupDuration")) report("fec_track_geometry", `${tp}/${k}`, inst);
|
|
7292
|
+
}
|
|
7293
|
+
for (const inst of insts.filter(live)) {
|
|
7294
|
+
const o = inst.obj;
|
|
7295
|
+
if (!o) continue;
|
|
7296
|
+
const repairs = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-repair");
|
|
7297
|
+
const symbols = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-source");
|
|
7298
|
+
if (symbols.length + (inst.symbolNamed !== null ? 1 : 0) > 1)
|
|
7299
|
+
for (const tj of symbols) report("fec_symbol_tracks", `/tracks/${tj}`, inst);
|
|
7300
|
+
const l0 = inst.layer0 === null ? null : trackAt(inst.layer0);
|
|
7301
|
+
const l0p = `/tracks/${inst.layer0}`;
|
|
7302
|
+
if (inst.overlay) {
|
|
7303
|
+
if (l0 && has(l0, "repairLayer")) report("fec_overlay", `${l0p}/repairLayer`, inst);
|
|
7304
|
+
for (const tj of repairs)
|
|
7305
|
+
report("fec_overlay", has(trackAt(tj), "repairLayer") ? `/tracks/${tj}/repairLayer` : `/tracks/${tj}`, inst);
|
|
7306
|
+
} else {
|
|
7307
|
+
if (l0 && has(l0, "repairLayer") && l0.repairLayer !== 0) report("fec_layers", `${l0p}/repairLayer`, inst);
|
|
7308
|
+
if (l0 && has(l0, "repairSymbols") && intIn(o.repairSymbols, 1) && l0.repairSymbols !== o.repairSymbols)
|
|
7309
|
+
report("fec_layer0", `${l0p}/repairSymbols`, inst);
|
|
7310
|
+
const layers = [];
|
|
7311
|
+
for (const tj of repairs) {
|
|
7312
|
+
const t = trackAt(tj);
|
|
7313
|
+
if (!has(t, "repairLayer")) report("fec_member", `/tracks/${tj}/repairLayer`, inst);
|
|
7314
|
+
else if (t.repairLayer === 0) report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
|
|
7315
|
+
else if (intIn(t.repairLayer, 1)) layers.push({ tj, layer: t.repairLayer });
|
|
7316
|
+
if (!has(t, "repairSymbols")) report("fec_member", `/tracks/${tj}/repairSymbols`, inst);
|
|
7317
|
+
}
|
|
7318
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7319
|
+
for (const { layer } of layers) counts.set(layer, (counts.get(layer) ?? 0) + 1);
|
|
7320
|
+
let missing = 1;
|
|
7321
|
+
while (counts.has(missing)) missing++;
|
|
7322
|
+
for (const { tj, layer } of layers)
|
|
7323
|
+
if (counts.get(layer) > 1 || layer > missing) report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
|
|
7324
|
+
const chain = [
|
|
7325
|
+
{ layer: 0, tj: inst.layer0, pri: l0 && intIn(l0.priority, 192, 255) ? l0.priority : l0 && has(l0, "priority") ? null : 240 },
|
|
7326
|
+
...layers.map(({ tj, layer }) => {
|
|
7327
|
+
const pr = trackAt(tj).priority;
|
|
7328
|
+
return { layer, tj, pri: intIn(pr, 192, 255) ? pr : null };
|
|
7329
|
+
})
|
|
7330
|
+
].sort((a, b) => a.layer - b.layer);
|
|
7331
|
+
let prev = null;
|
|
7332
|
+
for (const c of chain) {
|
|
7333
|
+
if (c.pri === null) continue;
|
|
7334
|
+
if (prev !== null && c.pri < prev) report("fec_priority", `/tracks/${c.tj}/priority`, inst);
|
|
7335
|
+
prev = Math.max(prev ?? c.pri, c.pri);
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
const K = o.sourceSymbols;
|
|
7339
|
+
const T = o.symbolSize;
|
|
7340
|
+
const P0 = o.repairSymbols;
|
|
7341
|
+
if (intIn(K, 1) && intIn(P0, 1)) {
|
|
7342
|
+
let P = P0;
|
|
7343
|
+
if (!inst.overlay)
|
|
7344
|
+
for (const tj of repairs) {
|
|
7345
|
+
const t = trackAt(tj);
|
|
7346
|
+
if (intIn(t.repairLayer, 1) && intIn(t.repairSymbols, 1)) P += t.repairSymbols;
|
|
7347
|
+
}
|
|
7348
|
+
if (P > MAX_24 || K + P - 1 > MAX_24) report("fec_bounds", inst.path, inst);
|
|
7349
|
+
else if (o.scheme === "reed-solomon" && K + P > RS_MAX_BLOCK) report("fec_bounds", inst.path, inst);
|
|
7350
|
+
}
|
|
7351
|
+
if (o.scheme === "raptorq" && intIn(K, 1) && K > RAPTORQ_MAX_K) report("fec_bounds", `${inst.path}/sourceSymbols`, inst);
|
|
7352
|
+
if (intIn(T, 1)) {
|
|
7353
|
+
if (o.scheme === "raptorq" && T % 8 !== 0) report("fec_bounds", `${inst.path}/symbolSize`, inst);
|
|
7354
|
+
else if (o.blockProfile === "iso-ssbg1-v1" && T < ISO_MIN_T) report("fec_bounds", `${inst.path}/symbolSize`, inst);
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7357
|
+
const overlayTracks = new Set(insts.filter((i) => live(i) && i.overlay).map((i) => i.ti));
|
|
7358
|
+
for (const ti of overlayTracks) {
|
|
7359
|
+
const ofTrack = insts.filter((i) => i.ti === ti && live(i));
|
|
7360
|
+
const ovs = ofTrack.filter((i) => i.overlay);
|
|
7361
|
+
if (!overlays) {
|
|
7362
|
+
for (const ov of ovs) report("fec_overlay_unsupported", `${ov.path}/scope`, ov);
|
|
7363
|
+
for (const i of ofTrack) i.issues.length === 0 && i.issues.push({ code: "fec_overlay_unsupported", path: `${ovs[0].path}/scope` });
|
|
7364
|
+
continue;
|
|
7365
|
+
}
|
|
7366
|
+
if (ovs.length > 1) for (const ov of ovs) report("fec_overlay", `${ov.path}/scope`, ov);
|
|
7367
|
+
for (const ov of ovs)
|
|
7368
|
+
if (ov.obj?.blockProfile !== "iso-ssbg1-v1") report("fec_overlay", `${ov.path}/blockProfile`, ov);
|
|
7369
|
+
const cause = ovs.flatMap((ov) => ov.issues).find((x) => x.code === "fec_overlay");
|
|
7370
|
+
if (cause) {
|
|
7371
|
+
for (const ov of ovs) if (ov.issues.length === 0) ov.issues.push(cause);
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
return {
|
|
7375
|
+
instances: insts.map((i) => ({
|
|
7376
|
+
path: i.path,
|
|
7377
|
+
track: typeof i.track.name === "string" ? i.track.name : null,
|
|
7378
|
+
instanceId: i.id,
|
|
7379
|
+
outcome: rejectAll || !i.unavailable && i.issues.length > 0 ? "violation" : i.unavailable ? "unavailable" : "ok",
|
|
7380
|
+
issues: i.issues
|
|
7381
|
+
})),
|
|
7382
|
+
issues: all
|
|
7383
|
+
};
|
|
7384
|
+
}
|
|
7385
|
+
|
|
7093
7386
|
// node_modules/@blockcast/mmt-transport/dist/multicast-tier.js
|
|
7094
7387
|
function endpointKey(endpoint) {
|
|
7095
7388
|
return `${endpoint.sourceAddress ?? ""}|${endpoint.groupAddress}|${endpoint.port}`;
|
|
@@ -19622,8 +19915,6 @@ var MmtpRouter = class {
|
|
|
19622
19915
|
// Callbacks (legacy — used when per-track callbacks are not registered)
|
|
19623
19916
|
#onVideoFrame = null;
|
|
19624
19917
|
#onAudioFrame = null;
|
|
19625
|
-
// Init segment (ftyp+moov) from MMTP MPU metadata — needed for MSE SourceBuffer
|
|
19626
|
-
#onInitSegment = null;
|
|
19627
19918
|
// Per-track init segment callbacks keyed by packetId
|
|
19628
19919
|
#trackInitCallbacks = /* @__PURE__ */ new Map();
|
|
19629
19920
|
// Track whether init has been delivered per physical stream — prevent duplicates
|
|
@@ -19886,11 +20177,10 @@ var MmtpRouter = class {
|
|
|
19886
20177
|
};
|
|
19887
20178
|
}
|
|
19888
20179
|
/**
|
|
19889
|
-
* Init segments
|
|
19890
|
-
*
|
|
19891
|
-
*
|
|
19892
|
-
* 0 here
|
|
19893
|
-
* aggregate callback (MOQT-01, BLO-21181).
|
|
20180
|
+
* Init segments rejected since construction because their packetId had no
|
|
20181
|
+
* `onTrackInit` handler. A rejected init is delivered nowhere; there is no
|
|
20182
|
+
* aggregate fallback (removed in 1.0.0, MOQT-01 / BLO-21181). A supported
|
|
20183
|
+
* deployment reads 0 here.
|
|
19894
20184
|
*/
|
|
19895
20185
|
get unmappedInitDeliveryCount() {
|
|
19896
20186
|
return this.#unmappedInitDeliveries;
|
|
@@ -20067,20 +20357,10 @@ var MmtpRouter = class {
|
|
|
20067
20357
|
onTrackPacket(packetId, cb) {
|
|
20068
20358
|
this.#trackPacketCallbacks.set(packetId, cb);
|
|
20069
20359
|
}
|
|
20070
|
-
/**
|
|
20071
|
-
* Register an init segment callback (ftyp+moov from MMTP MPU metadata).
|
|
20072
|
-
* Receives an init only for a packetId with no `onTrackInit` handler.
|
|
20073
|
-
*
|
|
20074
|
-
* @deprecated Use onTrackInit(packetId, cb). Still delivered as a fallback;
|
|
20075
|
-
* removed in the 1.0.0 major, no earlier than one minor release after this
|
|
20076
|
-
* deprecation reaches the `latest` dist-tag (BLO-21181). Count fallback
|
|
20077
|
-
* deliveries with `unmappedInitDeliveryCount`.
|
|
20078
|
-
*/
|
|
20079
|
-
onInitSegment(cb) {
|
|
20080
|
-
this.#onInitSegment = cb;
|
|
20081
|
-
}
|
|
20082
20360
|
/** Register an init segment callback for a specific MMTP packetId.
|
|
20083
|
-
* Called when FT=0 init arrives via direct source or FEC recovery.
|
|
20361
|
+
* Called when FT=0 init arrives via direct source or FEC recovery. An init
|
|
20362
|
+
* for a packetId with no handler is rejected and counted by
|
|
20363
|
+
* `unmappedInitDeliveryCount`. */
|
|
20084
20364
|
onTrackInit(packetId, cb) {
|
|
20085
20365
|
this.#trackInitCallbacks.set(packetId, cb);
|
|
20086
20366
|
}
|
|
@@ -21480,16 +21760,15 @@ var MmtpRouter = class {
|
|
|
21480
21760
|
if (this.#trackInitDelivered.has(streamKey)) return;
|
|
21481
21761
|
if (data.byteLength <= 8) return;
|
|
21482
21762
|
this.#trackInitDelivered.add(streamKey);
|
|
21763
|
+
const track = `packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`}`;
|
|
21483
21764
|
const cb = this.#trackInitCallbacks.get(packetId);
|
|
21484
|
-
if (cb) {
|
|
21485
|
-
cb(data);
|
|
21486
|
-
} else {
|
|
21765
|
+
if (!cb) {
|
|
21487
21766
|
this.#unmappedInitDeliveries++;
|
|
21488
|
-
|
|
21767
|
+
console.log(`[MmtpRouter] Init rejected for ${track}: no onTrackInit handler`);
|
|
21768
|
+
return;
|
|
21489
21769
|
}
|
|
21490
|
-
|
|
21491
|
-
|
|
21492
|
-
);
|
|
21770
|
+
cb(data);
|
|
21771
|
+
console.log(`[MmtpRouter] Init delivered for ${track} (${data.byteLength}B)`);
|
|
21493
21772
|
}
|
|
21494
21773
|
/**
|
|
21495
21774
|
* Core MMTP packet processing — source packets → play + FEC, repair → FEC only.
|
|
@@ -22989,6 +23268,10 @@ function getMulticastGlobals() {
|
|
|
22989
23268
|
function hasMulticastGateway() {
|
|
22990
23269
|
return typeof getMulticastGlobals().multicast?.subscribe === "function";
|
|
22991
23270
|
}
|
|
23271
|
+
function senderOf(event) {
|
|
23272
|
+
const sourceAddress = event.sourceAddress;
|
|
23273
|
+
return typeof sourceAddress === "string" ? sourceAddress : void 0;
|
|
23274
|
+
}
|
|
22992
23275
|
function normalizeBytesEventDetail(detail) {
|
|
22993
23276
|
const value = detail && typeof detail === "object" && "data" in detail ? detail.data : detail;
|
|
22994
23277
|
if (value instanceof Uint8Array) return value;
|
|
@@ -23035,7 +23318,8 @@ var MulticastTransport = class {
|
|
|
23035
23318
|
bytesReceived: 0,
|
|
23036
23319
|
packetsDroppedUnregisteredPid: 0,
|
|
23037
23320
|
packetsDroppedUnparseable: 0,
|
|
23038
|
-
packetsDroppedSignaling: 0
|
|
23321
|
+
packetsDroppedSignaling: 0,
|
|
23322
|
+
packetsDroppedForeignSender: 0
|
|
23039
23323
|
};
|
|
23040
23324
|
// join() calls by how they resolved; see joinStats.
|
|
23041
23325
|
#joinsSucceeded = 0;
|
|
@@ -23060,6 +23344,11 @@ var MulticastTransport = class {
|
|
|
23060
23344
|
// per packet once a count outgrows the small-integer range.
|
|
23061
23345
|
#acceptedBytesByPacketId = /* @__PURE__ */ new Map();
|
|
23062
23346
|
#packetIds = null;
|
|
23347
|
+
// The sender S this join's accepted packets came from, once known; see
|
|
23348
|
+
// packetsDroppedForeignSender. Cleared by every join and by a silence
|
|
23349
|
+
// failback, so a replacement publisher is not locked out.
|
|
23350
|
+
#sender = void 0;
|
|
23351
|
+
#lockedTo;
|
|
23063
23352
|
// What the live join delivers with each accepted packet. A field, not a
|
|
23064
23353
|
// listener closure, so an in-place admission change (setContext) applies
|
|
23065
23354
|
// from the next packet on without rejoining.
|
|
@@ -23081,9 +23370,15 @@ var MulticastTransport = class {
|
|
|
23081
23370
|
#joinGeneration = 0;
|
|
23082
23371
|
// The provider to subscribe through; window.multicast when unset.
|
|
23083
23372
|
#gateway;
|
|
23373
|
+
/**
|
|
23374
|
+
* `lockedTo` reads the sender another transport is locked to (a repair
|
|
23375
|
+
* group follows its media join, whose publisher sends both). While it reads
|
|
23376
|
+
* a sender, this join admits only that one; until then its own lock applies.
|
|
23377
|
+
*/
|
|
23084
23378
|
constructor(onPacket, options = {}) {
|
|
23085
23379
|
this.#onPacket = onPacket;
|
|
23086
23380
|
this.#gateway = options.gateway;
|
|
23381
|
+
this.#lockedTo = options.lockedTo;
|
|
23087
23382
|
}
|
|
23088
23383
|
static isAvailable() {
|
|
23089
23384
|
return hasMulticastGateway();
|
|
@@ -23092,6 +23387,7 @@ var MulticastTransport = class {
|
|
|
23092
23387
|
const generation = ++this.#joinGeneration;
|
|
23093
23388
|
this.#releaseSubscription();
|
|
23094
23389
|
this.#dataPlaneLive = false;
|
|
23390
|
+
this.#sender = void 0;
|
|
23095
23391
|
if (this.#disposed) return this.#declined(generation, "disposed");
|
|
23096
23392
|
const gateway = this.#gateway ?? getMulticastGlobals().multicast;
|
|
23097
23393
|
if (typeof gateway?.subscribe !== "function") {
|
|
@@ -23133,7 +23429,8 @@ var MulticastTransport = class {
|
|
|
23133
23429
|
}
|
|
23134
23430
|
this.#subscription = subscription;
|
|
23135
23431
|
const live = () => !this.#disposed && generation === this.#joinGeneration;
|
|
23136
|
-
const
|
|
23432
|
+
const anySource = !config.sourceAddress && !config.senderAddress && !config.atsc3;
|
|
23433
|
+
const packetListener = !anySource && subscriptionDeliversPacketBatches(subscription) ? {
|
|
23137
23434
|
type: "packets",
|
|
23138
23435
|
listener: (event) => {
|
|
23139
23436
|
if (!live()) return;
|
|
@@ -23143,7 +23440,7 @@ var MulticastTransport = class {
|
|
|
23143
23440
|
type: "bytes",
|
|
23144
23441
|
listener: (event) => {
|
|
23145
23442
|
if (!live()) return;
|
|
23146
|
-
if (this.#deliver(event.detail)) this.#resetWatchdog();
|
|
23443
|
+
if (this.#deliver(event.detail, senderOf(event))) this.#resetWatchdog();
|
|
23147
23444
|
}
|
|
23148
23445
|
};
|
|
23149
23446
|
this.#packetListener = packetListener;
|
|
@@ -23234,13 +23531,13 @@ var MulticastTransport = class {
|
|
|
23234
23531
|
* One packet from the subscription: filter, count, and hand it to the
|
|
23235
23532
|
* callback. Returns whether it was delivered; the caller notes liveness.
|
|
23236
23533
|
*/
|
|
23237
|
-
#deliver(detail) {
|
|
23534
|
+
#deliver(detail, sourceAddress) {
|
|
23238
23535
|
const packet = normalizeBytesEventDetail(detail);
|
|
23239
23536
|
if (!packet) {
|
|
23240
23537
|
this.#stats.packetsDroppedUnparseable++;
|
|
23241
23538
|
return false;
|
|
23242
23539
|
}
|
|
23243
|
-
if (!this.#acceptPacket(packet)) return false;
|
|
23540
|
+
if (!this.#acceptPacket(packet, sourceAddress)) return false;
|
|
23244
23541
|
this.#onPacket(packet, this.#context);
|
|
23245
23542
|
return true;
|
|
23246
23543
|
}
|
|
@@ -23281,11 +23578,15 @@ var MulticastTransport = class {
|
|
|
23281
23578
|
throw failures[0];
|
|
23282
23579
|
}
|
|
23283
23580
|
}
|
|
23284
|
-
/**
|
|
23285
|
-
|
|
23581
|
+
/**
|
|
23582
|
+
* Feed a raw packet from an external source (e.g., AMT tunnel or ASM
|
|
23583
|
+
* subscription). `sourceAddress` is the datagram's sender when the source
|
|
23584
|
+
* knows it; see packetsDroppedForeignSender.
|
|
23585
|
+
*/
|
|
23586
|
+
feedPacket(data, context, sourceAddress) {
|
|
23286
23587
|
if (this.#disposed) return;
|
|
23287
23588
|
const packet = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
23288
|
-
if (!this.#acceptPacket(packet)) return;
|
|
23589
|
+
if (!this.#acceptPacket(packet, sourceAddress)) return;
|
|
23289
23590
|
this.#onPacket(packet, context);
|
|
23290
23591
|
this.#resetWatchdog();
|
|
23291
23592
|
}
|
|
@@ -23435,6 +23736,10 @@ var MulticastTransport = class {
|
|
|
23435
23736
|
get withdrawnPacketIdDrops() {
|
|
23436
23737
|
return this.#withdrawnPacketIdDrops;
|
|
23437
23738
|
}
|
|
23739
|
+
/** The sender this join is locked to; undefined until one is known. */
|
|
23740
|
+
get sender() {
|
|
23741
|
+
return this.#sender;
|
|
23742
|
+
}
|
|
23438
23743
|
/** Whether a subscription is live: joined, and not yet released or replaced. */
|
|
23439
23744
|
get joined() {
|
|
23440
23745
|
return this.#subscription !== null;
|
|
@@ -23597,11 +23902,16 @@ var MulticastTransport = class {
|
|
|
23597
23902
|
this.#withdrawnPacketIds.clear();
|
|
23598
23903
|
}
|
|
23599
23904
|
/** The ingress filter; an accepted packet is counted here, once. */
|
|
23600
|
-
#acceptPacket(packet) {
|
|
23905
|
+
#acceptPacket(packet, sourceAddress) {
|
|
23906
|
+
const owner = this.#lockedTo?.() ?? this.#sender;
|
|
23907
|
+
if (sourceAddress !== void 0 && owner !== void 0 && sourceAddress !== owner) {
|
|
23908
|
+
this.#stats.packetsDroppedForeignSender++;
|
|
23909
|
+
return false;
|
|
23910
|
+
}
|
|
23601
23911
|
const packetId = readMMTPPacketId(packet);
|
|
23602
23912
|
if (packetId === null) {
|
|
23603
23913
|
if (!this.#packetIds) {
|
|
23604
|
-
this.#countAccepted(packet, null);
|
|
23914
|
+
this.#countAccepted(packet, null, sourceAddress);
|
|
23605
23915
|
return true;
|
|
23606
23916
|
}
|
|
23607
23917
|
this.#stats.packetsDroppedUnparseable++;
|
|
@@ -23612,14 +23922,15 @@ var MulticastTransport = class {
|
|
|
23612
23922
|
return false;
|
|
23613
23923
|
}
|
|
23614
23924
|
if (!this.#packetIds || this.#packetIds.has(packetId)) {
|
|
23615
|
-
this.#countAccepted(packet, packetId);
|
|
23925
|
+
this.#countAccepted(packet, packetId, sourceAddress);
|
|
23616
23926
|
return true;
|
|
23617
23927
|
}
|
|
23618
23928
|
this.#recordUnregisteredPidDrop(packetId);
|
|
23619
23929
|
return false;
|
|
23620
23930
|
}
|
|
23621
23931
|
/** Receive counters for one accepted packet; `packetId` is null when it has none. */
|
|
23622
|
-
#countAccepted(packet, packetId) {
|
|
23932
|
+
#countAccepted(packet, packetId, sourceAddress) {
|
|
23933
|
+
this.#sender ??= sourceAddress;
|
|
23623
23934
|
this.#stats.packetsReceived++;
|
|
23624
23935
|
this.#stats.bytesReceived += packet.byteLength;
|
|
23625
23936
|
if (packetId === null) return;
|
|
@@ -23690,6 +24001,7 @@ var MulticastTransport = class {
|
|
|
23690
24001
|
console.warn(
|
|
23691
24002
|
`[MulticastTransport] No packets for ${this.#failbackWindowMs}ms; failing back to MoQ`
|
|
23692
24003
|
);
|
|
24004
|
+
this.#sender = void 0;
|
|
23693
24005
|
this.#onFailback({ kind: "silence", windowMs: this.#failbackWindowMs });
|
|
23694
24006
|
}, delayMs);
|
|
23695
24007
|
}
|
|
@@ -26440,7 +26752,7 @@ function catalogAmtNetworkSource(networkSource) {
|
|
|
26440
26752
|
}
|
|
26441
26753
|
return void 0;
|
|
26442
26754
|
}
|
|
26443
|
-
function
|
|
26755
|
+
function intIn2(value, min, max) {
|
|
26444
26756
|
return Number.isInteger(value) && value >= min && value <= max;
|
|
26445
26757
|
}
|
|
26446
26758
|
function catalogAtsc3NetworkSource(networkSource) {
|
|
@@ -26448,12 +26760,12 @@ function catalogAtsc3NetworkSource(networkSource) {
|
|
|
26448
26760
|
for (const raw of entries) {
|
|
26449
26761
|
if (!raw || typeof raw !== "object") continue;
|
|
26450
26762
|
const ns = raw;
|
|
26451
|
-
if (ns.type !== "atsc3" || !
|
|
26763
|
+
if (ns.type !== "atsc3" || !intIn2(ns.frequency, 1, Number.MAX_SAFE_INTEGER)) continue;
|
|
26452
26764
|
const slsUri = typeof ns.slsUri === "string" ? ns.slsUri.trim() : "";
|
|
26453
26765
|
return {
|
|
26454
26766
|
frequency: ns.frequency,
|
|
26455
|
-
...
|
|
26456
|
-
...
|
|
26767
|
+
...intIn2(ns.plpId, 0, 63) ? { plpId: ns.plpId } : {},
|
|
26768
|
+
...intIn2(ns.serviceId, 0, 65535) ? { serviceId: ns.serviceId } : {},
|
|
26457
26769
|
...slsUri ? { slsUri } : {}
|
|
26458
26770
|
};
|
|
26459
26771
|
}
|
|
@@ -27704,10 +28016,6 @@ var MulticastManager = class {
|
|
|
27704
28016
|
onTrackPacket(packetId, cb) {
|
|
27705
28017
|
this.#router.onTrackPacket(packetId, cb);
|
|
27706
28018
|
}
|
|
27707
|
-
/** @deprecated Use onTrackInit(packetId, cb); see MmtpRouter.onInitSegment. */
|
|
27708
|
-
onInitSegment(cb) {
|
|
27709
|
-
this.#router.onInitSegment(cb);
|
|
27710
|
-
}
|
|
27711
28019
|
onTrackInit(packetId, cb) {
|
|
27712
28020
|
this.#router.onTrackInit(packetId, cb);
|
|
27713
28021
|
}
|
|
@@ -27931,15 +28239,21 @@ var MulticastManager = class {
|
|
|
27931
28239
|
tracks: Object.freeze(endpoint.tracks.map(({ name, packetId }) => Object.freeze({ name, packetId }))),
|
|
27932
28240
|
...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
|
|
27933
28241
|
};
|
|
27934
|
-
|
|
27935
|
-
|
|
28242
|
+
let sender;
|
|
28243
|
+
const onPacket = (packet, sourceAddress) => {
|
|
28244
|
+
if (released) return;
|
|
28245
|
+
if (sourceAddress !== void 0 && sourceAddress !== (this.#transport.sender ?? (sender ??= sourceAddress))) return;
|
|
28246
|
+
this.#router.feedPacket(packet, "multicast", ingress);
|
|
27936
28247
|
};
|
|
27937
28248
|
const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
|
|
27938
28249
|
if (typeof provider === "function") {
|
|
27939
28250
|
Promise.resolve().then(() => provider(endpoint, onPacket)).then((release2) => released ? safely(release2) : releases.push(release2), fail);
|
|
27940
28251
|
continue;
|
|
27941
28252
|
}
|
|
27942
|
-
const transport = new MulticastTransport((packet) => onPacket(packet), {
|
|
28253
|
+
const transport = new MulticastTransport((packet) => onPacket(packet), {
|
|
28254
|
+
gateway: provider,
|
|
28255
|
+
lockedTo: () => this.#transport.sender
|
|
28256
|
+
});
|
|
27943
28257
|
releases.push(() => transport.dispose());
|
|
27944
28258
|
void transport.join({
|
|
27945
28259
|
sourceAddress: endpoint.source,
|
|
@@ -28218,8 +28532,8 @@ var MulticastManager = class {
|
|
|
28218
28532
|
* transport attribution in FEC path counters.
|
|
28219
28533
|
* The `router` accessor above is already public.
|
|
28220
28534
|
*/
|
|
28221
|
-
feedPacket(data, ingress) {
|
|
28222
|
-
this.#transport.feedPacket(data, ingress);
|
|
28535
|
+
feedPacket(data, ingress, sourceAddress) {
|
|
28536
|
+
this.#transport.feedPacket(data, ingress, sourceAddress);
|
|
28223
28537
|
}
|
|
28224
28538
|
/**
|
|
28225
28539
|
* Declare how packets given to feedPacket() reach this manager. A consumer
|
|
@@ -31716,6 +32030,7 @@ export {
|
|
|
31716
32030
|
splitH264SampleNalUnits,
|
|
31717
32031
|
unwrapMulticastPacket,
|
|
31718
32032
|
validateCmsfMmtpCatalog,
|
|
32033
|
+
validateFecCatalog,
|
|
31719
32034
|
validateMulticastContainerContract,
|
|
31720
32035
|
validateRepairTrackFields,
|
|
31721
32036
|
validateSwitchingSetTiming,
|