@blockcast/mmt-container 2.1.0 → 2.2.0-main.7a01a10f6629
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/dist/browser-runtime.js +325 -42
- package/dist/browser-runtime.js.map +4 -4
- package/dist/cmcd-producer.d.ts +5 -0
- package/dist/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js.map +2 -2
- package/dist/mmtp-router.d.ts +9 -1
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +1456 -14
- package/dist/mmtp-router.js.map +4 -4
- package/dist/multicast-manager.d.ts +13 -2
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +15234 -14951
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +11 -3
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +255 -19
- package/dist/multicast-transport.js.map +4 -4
- package/dist/signaling.js.map +2 -2
- package/package.json +5 -5
- package/src/cmcd-producer.ts +5 -0
- package/src/mmtp-router.ts +81 -16
- package/src/multicast-manager.ts +23 -5
- package/src/multicast-transport.ts +44 -20
package/dist/browser-runtime.js
CHANGED
|
@@ -7457,11 +7457,33 @@ var AdaptiveBitrateController = class {
|
|
|
7457
7457
|
};
|
|
7458
7458
|
|
|
7459
7459
|
// node_modules/@blockcast/mmt-base/dist/mmtp.js
|
|
7460
|
+
var MMTP_HEADER_SIZE2 = 12;
|
|
7460
7461
|
var MPU_HEADER_SIZE2 = 8;
|
|
7461
7462
|
var MPU_LENGTH_FIELD_SIZE2 = 2;
|
|
7462
7463
|
var MPU_HEADER_REMAINDER_SIZE2 = MPU_HEADER_SIZE2 - MPU_LENGTH_FIELD_SIZE2;
|
|
7463
7464
|
var MMTP_SIGNALING_PACKET_ID2 = 0;
|
|
7464
7465
|
var MMTP_PACKET_ID_MAX2 = 65535;
|
|
7466
|
+
function readMMTPPacketId2(buffer) {
|
|
7467
|
+
if (buffer.length < MMTP_HEADER_SIZE2)
|
|
7468
|
+
return null;
|
|
7469
|
+
const flags = buffer[0];
|
|
7470
|
+
if ((flags >> 6 & 3) !== 0)
|
|
7471
|
+
return null;
|
|
7472
|
+
let offset = MMTP_HEADER_SIZE2;
|
|
7473
|
+
if ((flags & 32) !== 0) {
|
|
7474
|
+
if (offset + 4 > buffer.length)
|
|
7475
|
+
return null;
|
|
7476
|
+
offset += 4;
|
|
7477
|
+
}
|
|
7478
|
+
if ((flags & 2) !== 0) {
|
|
7479
|
+
if (offset + 4 > buffer.length)
|
|
7480
|
+
return null;
|
|
7481
|
+
const length = buffer[offset + 2] << 8 | buffer[offset + 3];
|
|
7482
|
+
if (offset + 4 + length > buffer.length)
|
|
7483
|
+
return null;
|
|
7484
|
+
}
|
|
7485
|
+
return buffer[2] << 8 | buffer[3];
|
|
7486
|
+
}
|
|
7465
7487
|
|
|
7466
7488
|
// node_modules/@blockcast/mmt-base/dist/net-constants.js
|
|
7467
7489
|
var DEFAULT_AMT_RELAY_PORT2 = 2268;
|
|
@@ -8447,6 +8469,205 @@ var TransportManager = class {
|
|
|
8447
8469
|
};
|
|
8448
8470
|
var transportManager = new TransportManager();
|
|
8449
8471
|
|
|
8472
|
+
// node_modules/@blockcast/mmt-transport/dist/multicast-receiver-gateway.js
|
|
8473
|
+
var REGISTRY = /* @__PURE__ */ Symbol.for("@blockcast/mmt-transport/native-provider-registry/v1");
|
|
8474
|
+
var USER_AGENT_VERIFIED = /* @__PURE__ */ Symbol.for("@blockcast/mmt-transport/user-agent-verified/v1");
|
|
8475
|
+
var INTEGRITY_SCHEME = "manifest-ed25519-2";
|
|
8476
|
+
var MulticastError = class extends Error {
|
|
8477
|
+
code;
|
|
8478
|
+
constructor(code, message, cause) {
|
|
8479
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
8480
|
+
this.name = "MulticastError";
|
|
8481
|
+
this.code = code;
|
|
8482
|
+
}
|
|
8483
|
+
};
|
|
8484
|
+
function datagramsOf(chunk) {
|
|
8485
|
+
const list = Array.isArray(chunk) ? chunk : chunk?.datagrams ?? [];
|
|
8486
|
+
const out = [];
|
|
8487
|
+
for (const d of list) {
|
|
8488
|
+
const data = d instanceof Uint8Array || d instanceof ArrayBuffer ? d : d?.data;
|
|
8489
|
+
if (data instanceof Uint8Array)
|
|
8490
|
+
out.push(data);
|
|
8491
|
+
else if (data instanceof ArrayBuffer)
|
|
8492
|
+
out.push(new Uint8Array(data));
|
|
8493
|
+
}
|
|
8494
|
+
return out;
|
|
8495
|
+
}
|
|
8496
|
+
function reasonOf(value) {
|
|
8497
|
+
return typeof value === "object" && value !== null && "reason" in value ? value.reason : value;
|
|
8498
|
+
}
|
|
8499
|
+
function registry() {
|
|
8500
|
+
const g = globalThis;
|
|
8501
|
+
if (g[REGISTRY] === void 0) {
|
|
8502
|
+
const providers = /* @__PURE__ */ new WeakSet();
|
|
8503
|
+
const byScope = /* @__PURE__ */ new WeakMap();
|
|
8504
|
+
Object.defineProperty(g, REGISTRY, {
|
|
8505
|
+
value: Object.freeze({
|
|
8506
|
+
isProvider: (x) => typeof x === "object" && x !== null && providers.has(x),
|
|
8507
|
+
providerFor: (scope) => {
|
|
8508
|
+
let provider = byScope.get(scope);
|
|
8509
|
+
if (provider === void 0) {
|
|
8510
|
+
provider = createProvider(scope);
|
|
8511
|
+
providers.add(provider);
|
|
8512
|
+
byScope.set(scope, provider);
|
|
8513
|
+
}
|
|
8514
|
+
return provider;
|
|
8515
|
+
}
|
|
8516
|
+
}),
|
|
8517
|
+
writable: false,
|
|
8518
|
+
configurable: false,
|
|
8519
|
+
enumerable: false
|
|
8520
|
+
});
|
|
8521
|
+
}
|
|
8522
|
+
return g[REGISTRY];
|
|
8523
|
+
}
|
|
8524
|
+
function resolveMulticastGateway(scope = globalThis) {
|
|
8525
|
+
const s = scope;
|
|
8526
|
+
if (typeof s.MulticastReceiver === "function")
|
|
8527
|
+
return registry().providerFor(scope);
|
|
8528
|
+
if (typeof s.multicast?.subscribe === "function")
|
|
8529
|
+
return s.multicast;
|
|
8530
|
+
return void 0;
|
|
8531
|
+
}
|
|
8532
|
+
function isUserAgentVerifiedGateway(gateway) {
|
|
8533
|
+
return registry().isProvider(gateway);
|
|
8534
|
+
}
|
|
8535
|
+
function userAgentVerifiedIngress(ingress, gateway) {
|
|
8536
|
+
if (isUserAgentVerifiedGateway(gateway))
|
|
8537
|
+
return { ...ingress, admittedBy: USER_AGENT_VERIFIED };
|
|
8538
|
+
if (!("admittedBy" in ingress))
|
|
8539
|
+
return ingress;
|
|
8540
|
+
const { admittedBy: _, ...rest } = ingress;
|
|
8541
|
+
return rest;
|
|
8542
|
+
}
|
|
8543
|
+
function unsupported(message, cause) {
|
|
8544
|
+
return new MulticastError("capability-unsupported", message, cause);
|
|
8545
|
+
}
|
|
8546
|
+
function createProvider(scope) {
|
|
8547
|
+
const Receiver = scope.MulticastReceiver;
|
|
8548
|
+
let gate = null;
|
|
8549
|
+
return {
|
|
8550
|
+
capabilities: () => Promise.resolve({ consumeModes: ["bytes"] }),
|
|
8551
|
+
subscribe: async (config) => {
|
|
8552
|
+
const c = config;
|
|
8553
|
+
if (c.consume !== "bytes")
|
|
8554
|
+
throw unsupported(`consume mode ${String(c.consume)} is not supported`);
|
|
8555
|
+
const origin = c.origin ?? "ip-multicast";
|
|
8556
|
+
if (origin !== "ip-multicast" && !(origin === "amt" && c.discovery === "driad")) {
|
|
8557
|
+
throw unsupported(`origin ${String(origin)} is not supported by MulticastReceiver`);
|
|
8558
|
+
}
|
|
8559
|
+
if (c.relay !== void 0)
|
|
8560
|
+
throw unsupported("MulticastReceiver picks its own AMT relay");
|
|
8561
|
+
if (!c.source)
|
|
8562
|
+
throw unsupported("MulticastReceiver needs an SSM source");
|
|
8563
|
+
if (c.senderAddress !== void 0 && c.senderAddress !== c.source) {
|
|
8564
|
+
throw unsupported("MulticastReceiver filters by source only");
|
|
8565
|
+
}
|
|
8566
|
+
const options = { sourceAddress: c.source, groupAddress: c.group, localPort: c.port };
|
|
8567
|
+
if (gate !== null)
|
|
8568
|
+
await gate;
|
|
8569
|
+
let rx;
|
|
8570
|
+
try {
|
|
8571
|
+
rx = new Receiver(options);
|
|
8572
|
+
} catch (error) {
|
|
8573
|
+
throw unsupported("MulticastReceiver refused the request", error);
|
|
8574
|
+
}
|
|
8575
|
+
const opened = rx.opened;
|
|
8576
|
+
if (gate === null)
|
|
8577
|
+
gate = opened.then(() => void 0, () => void 0);
|
|
8578
|
+
return openSubscription(rx, opened, options, c.packetIds);
|
|
8579
|
+
}
|
|
8580
|
+
};
|
|
8581
|
+
}
|
|
8582
|
+
async function openSubscription(rx, openedPromise, options, packetIds) {
|
|
8583
|
+
let opened;
|
|
8584
|
+
try {
|
|
8585
|
+
opened = await openedPromise;
|
|
8586
|
+
} catch (error) {
|
|
8587
|
+
throw new MulticastError("subscription-failed", "MulticastReceiver did not open", error);
|
|
8588
|
+
}
|
|
8589
|
+
if (rx.integrityScheme !== INTEGRITY_SCHEME) {
|
|
8590
|
+
await rx.close();
|
|
8591
|
+
throw unsupported(`integrity scheme ${String(rx.integrityScheme)} is not ${INTEGRITY_SCHEME}`);
|
|
8592
|
+
}
|
|
8593
|
+
const events = new EventTarget();
|
|
8594
|
+
let state = "connected";
|
|
8595
|
+
let filter = packetIds?.length ? new Set(packetIds) : null;
|
|
8596
|
+
let closing = false;
|
|
8597
|
+
const setState = (next) => {
|
|
8598
|
+
if (state === next)
|
|
8599
|
+
return;
|
|
8600
|
+
state = next;
|
|
8601
|
+
events.dispatchEvent(new CustomEvent("state", { detail: next }));
|
|
8602
|
+
};
|
|
8603
|
+
const sourceAddress = options.sourceAddress;
|
|
8604
|
+
const subscription = {
|
|
8605
|
+
id: `${options.sourceAddress}|${options.groupAddress}|${options.localPort}`,
|
|
8606
|
+
get state() {
|
|
8607
|
+
return state;
|
|
8608
|
+
},
|
|
8609
|
+
delivery: "datagram",
|
|
8610
|
+
eventTypes: Object.freeze(["bytes", "packets", "state", "error"]),
|
|
8611
|
+
setPacketIds(ids) {
|
|
8612
|
+
filter = ids.length ? new Set(ids) : null;
|
|
8613
|
+
},
|
|
8614
|
+
addEventListener: events.addEventListener.bind(events),
|
|
8615
|
+
removeEventListener: events.removeEventListener.bind(events),
|
|
8616
|
+
async unsubscribe() {
|
|
8617
|
+
closing = true;
|
|
8618
|
+
await rx.close();
|
|
8619
|
+
setState("closed");
|
|
8620
|
+
}
|
|
8621
|
+
};
|
|
8622
|
+
void (async () => {
|
|
8623
|
+
const reader = opened.readable.getReader();
|
|
8624
|
+
try {
|
|
8625
|
+
for (; ; ) {
|
|
8626
|
+
const { value, done } = await reader.read();
|
|
8627
|
+
if (done)
|
|
8628
|
+
break;
|
|
8629
|
+
const packets = [];
|
|
8630
|
+
for (const data of datagramsOf(value)) {
|
|
8631
|
+
if (filter !== null) {
|
|
8632
|
+
const id = readMMTPPacketId2(data);
|
|
8633
|
+
if (id === null || !filter.has(id))
|
|
8634
|
+
continue;
|
|
8635
|
+
}
|
|
8636
|
+
const copy = data.slice().buffer;
|
|
8637
|
+
packets.push(copy);
|
|
8638
|
+
events.dispatchEvent(Object.assign(new CustomEvent("bytes", { detail: copy }), { sourceAddress }));
|
|
8639
|
+
}
|
|
8640
|
+
if (packets.length > 0)
|
|
8641
|
+
events.dispatchEvent(new CustomEvent("packets", { detail: packets }));
|
|
8642
|
+
}
|
|
8643
|
+
} catch {
|
|
8644
|
+
}
|
|
8645
|
+
})();
|
|
8646
|
+
void rx.closed.then((value) => {
|
|
8647
|
+
const reason = reasonOf(value);
|
|
8648
|
+
if (!closing) {
|
|
8649
|
+
if (reason === "consent-withdrawn") {
|
|
8650
|
+
events.dispatchEvent(new CustomEvent("error", {
|
|
8651
|
+
detail: new MulticastError("origin-not-trusted", "multicast consent withdrawn")
|
|
8652
|
+
}));
|
|
8653
|
+
} else if (reason === "oversubscribed" || reason === "network") {
|
|
8654
|
+
events.dispatchEvent(new CustomEvent("error", {
|
|
8655
|
+
detail: new MulticastError("subscription-failed", `MulticastReceiver closed: ${reason}`)
|
|
8656
|
+
}));
|
|
8657
|
+
}
|
|
8658
|
+
}
|
|
8659
|
+
setState("closed");
|
|
8660
|
+
}, (error) => {
|
|
8661
|
+
if (!closing) {
|
|
8662
|
+
events.dispatchEvent(new CustomEvent("error", {
|
|
8663
|
+
detail: new MulticastError("subscription-failed", "MulticastReceiver failed", error)
|
|
8664
|
+
}));
|
|
8665
|
+
}
|
|
8666
|
+
setState("closed");
|
|
8667
|
+
});
|
|
8668
|
+
return subscription;
|
|
8669
|
+
}
|
|
8670
|
+
|
|
8450
8671
|
// node_modules/@blockcast/mmt-transport/dist/moqt-priority.js
|
|
8451
8672
|
var MAX_MOQT_PRIORITY = 255;
|
|
8452
8673
|
function moqtPriority(value, field) {
|
|
@@ -8733,9 +8954,9 @@ function validateTemplatePair(mediaUriTemplate, mediaObjectTemplate, path) {
|
|
|
8733
8954
|
}
|
|
8734
8955
|
function validateMmtpDashTemplate(template, path) {
|
|
8735
8956
|
const variables = [...template.matchAll(TEMPLATE_VARIABLE)].map((match) => match[1]);
|
|
8736
|
-
const
|
|
8737
|
-
if (
|
|
8738
|
-
throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${
|
|
8957
|
+
const unsupported2 = variables.find((variable) => variable !== "Number" && variable !== "RepresentationID");
|
|
8958
|
+
if (unsupported2 !== void 0) {
|
|
8959
|
+
throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${unsupported2}$`);
|
|
8739
8960
|
}
|
|
8740
8961
|
if (variables.filter((variable) => variable === "Number").length !== 1) {
|
|
8741
8962
|
throw new Error(`${path} must contain exactly one $Number$ for MMTP-backed DASH`);
|
|
@@ -17098,17 +17319,17 @@ var FECManager = class _FECManager {
|
|
|
17098
17319
|
|
|
17099
17320
|
// src/mfu-reassembly-policy.ts
|
|
17100
17321
|
function validateReliableMoqOptionShape(value) {
|
|
17101
|
-
const
|
|
17322
|
+
const unsupported2 = Object.keys(value).filter(
|
|
17102
17323
|
(key) => key !== "transport" && key !== "startsAtRandomAccessGroup"
|
|
17103
17324
|
);
|
|
17104
|
-
if (
|
|
17105
|
-
if (
|
|
17325
|
+
if (unsupported2.length > 0) {
|
|
17326
|
+
if (unsupported2.includes("maxRacingGroups")) {
|
|
17106
17327
|
throw new Error(
|
|
17107
17328
|
"[MFU reassembly] maxRacingGroups was removed; reliable MoQ completion is transport-owned"
|
|
17108
17329
|
);
|
|
17109
17330
|
}
|
|
17110
17331
|
throw new Error(
|
|
17111
|
-
`[MFU reassembly] unsupported reliable MoQ option(s): ${
|
|
17332
|
+
`[MFU reassembly] unsupported reliable MoQ option(s): ${unsupported2.join(", ")}`
|
|
17112
17333
|
);
|
|
17113
17334
|
}
|
|
17114
17335
|
const startsAtRandomAccessGroup = value["startsAtRandomAccessGroup"];
|
|
@@ -17152,12 +17373,12 @@ function assertValidMfuReassemblyPolicy(policy) {
|
|
|
17152
17373
|
return;
|
|
17153
17374
|
}
|
|
17154
17375
|
if (value["transport"] === "datagram") {
|
|
17155
|
-
const
|
|
17376
|
+
const unsupported2 = Object.keys(value).filter(
|
|
17156
17377
|
(key) => key !== "transport" && key !== "deadlineMs"
|
|
17157
17378
|
);
|
|
17158
|
-
if (
|
|
17379
|
+
if (unsupported2.length > 0) {
|
|
17159
17380
|
throw new Error(
|
|
17160
|
-
`[MFU reassembly] unsupported datagram option(s): ${
|
|
17381
|
+
`[MFU reassembly] unsupported datagram option(s): ${unsupported2.join(", ")}`
|
|
17161
17382
|
);
|
|
17162
17383
|
}
|
|
17163
17384
|
assertValidDatagramDeadline(value["deadlineMs"]);
|
|
@@ -23579,10 +23800,11 @@ var MmtpRouter = class {
|
|
|
23579
23800
|
if (!entry) return;
|
|
23580
23801
|
entry.receiver.release();
|
|
23581
23802
|
entry.protected = false;
|
|
23803
|
+
entry.userAgent = false;
|
|
23582
23804
|
this.#assistBlocks.clear();
|
|
23583
23805
|
this.#assistFailures.clear();
|
|
23584
23806
|
}
|
|
23585
|
-
/**
|
|
23807
|
+
/** Packets of a track a protected flow lists, dropped unread: a MoQ copy (duplicate, overflow, or listed by more than one flow) or a multicast source datagram that names no flow, of a track a user-agent-admitted flow lists. */
|
|
23586
23808
|
get unadmittedListedDropCount() {
|
|
23587
23809
|
return this.#unadmittedListedDrops;
|
|
23588
23810
|
}
|
|
@@ -23623,6 +23845,13 @@ var MmtpRouter = class {
|
|
|
23623
23845
|
}
|
|
23624
23846
|
return false;
|
|
23625
23847
|
}
|
|
23848
|
+
/** Whether a protected flow the user agent admitted lists `packetId`. */
|
|
23849
|
+
#userAgentGoverns(packetId) {
|
|
23850
|
+
for (const entry of this.#provenance.values()) {
|
|
23851
|
+
if (entry.protected && entry.userAgent && ingressHasPacketId(entry.ingress, packetId)) return true;
|
|
23852
|
+
}
|
|
23853
|
+
return false;
|
|
23854
|
+
}
|
|
23626
23855
|
/**
|
|
23627
23856
|
* A MoQ repair symbol no protected flow lists, owned by a source track one
|
|
23628
23857
|
* does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
|
|
@@ -23713,7 +23942,7 @@ var MmtpRouter = class {
|
|
|
23713
23942
|
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
23714
23943
|
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
23715
23944
|
fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
23716
|
-
})) {
|
|
23945
|
+
}, true)) {
|
|
23717
23946
|
this.#unlistedAssistedRecoveries++;
|
|
23718
23947
|
this.#assistBlocks.delete(key);
|
|
23719
23948
|
return;
|
|
@@ -24869,6 +25098,12 @@ var MmtpRouter = class {
|
|
|
24869
25098
|
debugRouter(`[MmtpRouter] Dropping multicast packet pid=${info.packetId}: flow ${ingress.flow} has no receiver`);
|
|
24870
25099
|
return;
|
|
24871
25100
|
}
|
|
25101
|
+
if (ingress.admittedBy === USER_AGENT_VERIFIED) {
|
|
25102
|
+
this.#noteFlowAdmitted(ingress.flow);
|
|
25103
|
+
protectedFlow.userAgent = true;
|
|
25104
|
+
this.#processMMTP(packet, source, true, ingress, overlapCopy);
|
|
25105
|
+
return;
|
|
25106
|
+
}
|
|
24872
25107
|
const { verdict } = protectedFlow.receiver.onDatagram(packet, "multicast");
|
|
24873
25108
|
if (verdict === "admitted") {
|
|
24874
25109
|
this.#noteFlowAdmitted(ingress.flow);
|
|
@@ -24886,6 +25121,11 @@ var MmtpRouter = class {
|
|
|
24886
25121
|
return;
|
|
24887
25122
|
}
|
|
24888
25123
|
}
|
|
25124
|
+
const flowless = source === "multicast" && !manifestAdmitted && ingress?.flow === void 0;
|
|
25125
|
+
if (flowless && !info.isRepair && this.#userAgentGoverns(info.packetId)) {
|
|
25126
|
+
this.#unadmittedListedDrops++;
|
|
25127
|
+
return;
|
|
25128
|
+
}
|
|
24889
25129
|
if (info.isRepair) {
|
|
24890
25130
|
const overlay = this.#overlayRepairTarget(info.packetId, catalogTrackNameForPacket(ingress, info.packetId));
|
|
24891
25131
|
if (overlay !== void 0) {
|
|
@@ -24903,7 +25143,7 @@ var MmtpRouter = class {
|
|
|
24903
25143
|
);
|
|
24904
25144
|
if (!repair) return;
|
|
24905
25145
|
if (ownerPacketId != null && this.#fecManager) {
|
|
24906
|
-
if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
25146
|
+
if ((flowless && this.#userAgentGoverns(ownerPacketId) || source === "moq" && !manifestAdmitted) && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
24907
25147
|
this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
|
|
24908
25148
|
const count = this.#repairPidCounts[info.packetId];
|
|
24909
25149
|
if (count <= 2 || count % 500 === 0) {
|
|
@@ -25482,7 +25722,7 @@ var MmtpRouter = class {
|
|
|
25482
25722
|
}
|
|
25483
25723
|
// ── Private: FEC recovery routing ───────────────────────────────
|
|
25484
25724
|
/** Route WASM-recovered block through appropriate path based on container kind. */
|
|
25485
|
-
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete) {
|
|
25725
|
+
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
25486
25726
|
const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
|
|
25487
25727
|
if (ownerPacketId === void 0) {
|
|
25488
25728
|
throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
|
|
@@ -25499,7 +25739,7 @@ var MmtpRouter = class {
|
|
|
25499
25739
|
ownerPacketId,
|
|
25500
25740
|
blockProfile
|
|
25501
25741
|
});
|
|
25502
|
-
return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete);
|
|
25742
|
+
return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted);
|
|
25503
25743
|
}
|
|
25504
25744
|
/**
|
|
25505
25745
|
* A recovered source packet of a protected flow is admitted on its own
|
|
@@ -25511,9 +25751,24 @@ var MmtpRouter = class {
|
|
|
25511
25751
|
* recovered packets is refused is unrecovered (7.2.7): its caller leaves it
|
|
25512
25752
|
* open, so it reaches its FEC deadline and fails by class. Always true
|
|
25513
25753
|
* outside a protected flow.
|
|
25514
|
-
|
|
25515
|
-
|
|
25516
|
-
|
|
25754
|
+
*
|
|
25755
|
+
* On a flow the user agent admitted, the live decoder's recoveries are kept
|
|
25756
|
+
* without a record: while the flow is protected that decoder takes only
|
|
25757
|
+
* symbols the user agent or a record verified (a flow-less multicast
|
|
25758
|
+
* datagram of a listed track is dropped or set aside in #processMMTP), so
|
|
25759
|
+
* what it recovers is as verified as they are. An `assisted` (scratch-decoder) recovery mixes in
|
|
25760
|
+
* unlisted MoQ repair and still needs a record.
|
|
25761
|
+
* ponytail: per-flow flag; per-block provenance only if a protected flow
|
|
25762
|
+
* ever mixes verified and unverified live inputs.
|
|
25763
|
+
* ponytail: a worker frame computed before the protection reset can still
|
|
25764
|
+
* arrive after it (frames carry no epoch); its inputs are MoQ copies read
|
|
25765
|
+
* and played before protection, so it admits nothing new. Drop pre-reset
|
|
25766
|
+
* frames by epoch only if pre-protection input ever matters.
|
|
25767
|
+
*/
|
|
25768
|
+
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
25769
|
+
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
25770
|
+
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
25771
|
+
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
25517
25772
|
let recovered = true;
|
|
25518
25773
|
if (receiver) {
|
|
25519
25774
|
const parsed = this.#stream(ownerPacketId, catalogTrackName).duplicates.parsedSymbols;
|
|
@@ -25525,6 +25780,8 @@ var MmtpRouter = class {
|
|
|
25525
25780
|
let admitted = false;
|
|
25526
25781
|
if (receiver === "ambiguous") {
|
|
25527
25782
|
this.#ambiguousRecoveredDrops++;
|
|
25783
|
+
} else if (userAgent) {
|
|
25784
|
+
admitted = true;
|
|
25528
25785
|
} else {
|
|
25529
25786
|
const wire = new Uint8Array(sym.rawSymbol.byteLength + 4);
|
|
25530
25787
|
wire.set(sym.rawSymbol);
|
|
@@ -25552,12 +25809,12 @@ var MmtpRouter = class {
|
|
|
25552
25809
|
catalogTrackName === void 0 || !byName ? byName?.values() : byName.has(catalogTrackName) ? [byName.get(catalogTrackName)] : []
|
|
25553
25810
|
);
|
|
25554
25811
|
if (flows.size > 1) return "ambiguous";
|
|
25555
|
-
for (const bound of flows) return this.#provenance.get(bound)
|
|
25812
|
+
for (const bound of flows) return this.#provenance.get(bound) ?? "ambiguous";
|
|
25556
25813
|
let match;
|
|
25557
|
-
for (const
|
|
25558
|
-
if (ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
|
|
25814
|
+
for (const entry of this.#provenance.values()) {
|
|
25815
|
+
if (entry.ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
|
|
25559
25816
|
if (match) return "ambiguous";
|
|
25560
|
-
match =
|
|
25817
|
+
match = entry;
|
|
25561
25818
|
}
|
|
25562
25819
|
}
|
|
25563
25820
|
return match;
|
|
@@ -26339,11 +26596,8 @@ function providerErrorOf(error) {
|
|
|
26339
26596
|
return Object.freeze(typeof code === "string" ? { name, message, code } : { name, message });
|
|
26340
26597
|
}
|
|
26341
26598
|
var MAX_TRACKED_DROPPED_PIDS = 16;
|
|
26342
|
-
function getMulticastGlobals() {
|
|
26343
|
-
return globalThis;
|
|
26344
|
-
}
|
|
26345
26599
|
function hasMulticastGateway() {
|
|
26346
|
-
return typeof
|
|
26600
|
+
return typeof resolveMulticastGateway()?.subscribe === "function";
|
|
26347
26601
|
}
|
|
26348
26602
|
function senderOf(event) {
|
|
26349
26603
|
const sourceAddress = event.sourceAddress;
|
|
@@ -26435,6 +26689,9 @@ var MulticastTransport = class {
|
|
|
26435
26689
|
// listener closure, so an in-place admission change (setContext) applies
|
|
26436
26690
|
// from the next packet on without rejoining.
|
|
26437
26691
|
#context = void 0;
|
|
26692
|
+
#deliveredContext = void 0;
|
|
26693
|
+
/** The live join's gateway, when it is the user agent's MulticastReceiver provider. */
|
|
26694
|
+
#userAgentGateway = void 0;
|
|
26438
26695
|
// The live join's provider-side filter. An in-place widening must reach it,
|
|
26439
26696
|
// or the provider never delivers the new ID; null without a live join.
|
|
26440
26697
|
#providerFilter = null;
|
|
@@ -26471,7 +26728,7 @@ var MulticastTransport = class {
|
|
|
26471
26728
|
this.#dataPlaneLive = false;
|
|
26472
26729
|
this.#sender = void 0;
|
|
26473
26730
|
if (this.#disposed) return this.#declined(generation, "disposed");
|
|
26474
|
-
const gateway = this.#gateway ??
|
|
26731
|
+
const gateway = this.#gateway ?? resolveMulticastGateway();
|
|
26475
26732
|
if (typeof gateway?.subscribe !== "function") {
|
|
26476
26733
|
console.warn("[MulticastTransport] Multicast API not available");
|
|
26477
26734
|
return this.#declined(generation, "unavailable");
|
|
@@ -26487,15 +26744,16 @@ var MulticastTransport = class {
|
|
|
26487
26744
|
console.log(
|
|
26488
26745
|
`[MulticastTransport] window.multicast.subscribe(${config.sourceAddress ?? config.senderAddress ?? "*"}, ${config.groupAddress}, ${config.port})`
|
|
26489
26746
|
);
|
|
26747
|
+
const userAgent = isUserAgentVerifiedGateway(gateway);
|
|
26490
26748
|
const subscribeConfig = toWindowMulticastSubscribeConfig({
|
|
26491
26749
|
source: config.sourceAddress,
|
|
26492
26750
|
senderAddress: config.senderAddress,
|
|
26493
26751
|
group: config.groupAddress,
|
|
26494
26752
|
port: config.port,
|
|
26495
|
-
mode: config.mode,
|
|
26496
|
-
amtRelay: config.amtRelay,
|
|
26497
|
-
amtRelayPort: config.amtRelayPort,
|
|
26498
|
-
amtDiscovery: config.amtDiscovery,
|
|
26753
|
+
mode: userAgent ? "native" : config.mode,
|
|
26754
|
+
amtRelay: userAgent ? void 0 : config.amtRelay,
|
|
26755
|
+
amtRelayPort: userAgent ? void 0 : config.amtRelayPort,
|
|
26756
|
+
amtDiscovery: userAgent ? void 0 : config.amtDiscovery,
|
|
26499
26757
|
packetIds: config.packetIds,
|
|
26500
26758
|
tracks: config.tracks,
|
|
26501
26759
|
atsc3: config.atsc3
|
|
@@ -26541,7 +26799,8 @@ var MulticastTransport = class {
|
|
|
26541
26799
|
acknowledgedUpdate: 0,
|
|
26542
26800
|
inFlight: 0
|
|
26543
26801
|
};
|
|
26544
|
-
this.#
|
|
26802
|
+
this.#userAgentGateway = userAgent ? gateway : void 0;
|
|
26803
|
+
this.#setContext(context);
|
|
26545
26804
|
if (packetIds) {
|
|
26546
26805
|
for (const packetId of packetIds) {
|
|
26547
26806
|
if (!this.#acceptedBytesByPacketId.has(packetId)) {
|
|
@@ -26549,7 +26808,7 @@ var MulticastTransport = class {
|
|
|
26549
26808
|
}
|
|
26550
26809
|
}
|
|
26551
26810
|
}
|
|
26552
|
-
this.#resolvedTransport = subscribeConfig.origin === "amt" ? "amt" : "native";
|
|
26811
|
+
this.#resolvedTransport = userAgent ? "user-agent" : subscribeConfig.origin === "amt" ? "amt" : "native";
|
|
26553
26812
|
const delivery = subscription.delivery ?? "datagram";
|
|
26554
26813
|
if (delivery !== "datagram" && delivery !== "ordered-relay") {
|
|
26555
26814
|
throw new Error(
|
|
@@ -26630,7 +26889,7 @@ var MulticastTransport = class {
|
|
|
26630
26889
|
}
|
|
26631
26890
|
if (!this.#acceptPacket(packet, sourceAddress)) return false;
|
|
26632
26891
|
const liveness = !this.#isRecordCandidate(packet);
|
|
26633
|
-
this.#onPacket(packet, this.#
|
|
26892
|
+
this.#onPacket(packet, this.#deliveredContext);
|
|
26634
26893
|
return liveness;
|
|
26635
26894
|
}
|
|
26636
26895
|
#isRecordCandidate(packet) {
|
|
@@ -26841,8 +27100,10 @@ var MulticastTransport = class {
|
|
|
26841
27100
|
return this.#subscription !== null;
|
|
26842
27101
|
}
|
|
26843
27102
|
/**
|
|
26844
|
-
*
|
|
26845
|
-
*
|
|
27103
|
+
* The live join's context as the caller gave it (the join's `context`, or
|
|
27104
|
+
* the last setContext), or undefined without a live join. On a native
|
|
27105
|
+
* provider each accepted packet is delivered with a marked copy of it
|
|
27106
|
+
* (`userAgentVerifiedIngress`); this getter never carries the mark.
|
|
26846
27107
|
*/
|
|
26847
27108
|
get context() {
|
|
26848
27109
|
return this.#context;
|
|
@@ -26866,7 +27127,16 @@ var MulticastTransport = class {
|
|
|
26866
27127
|
"[MulticastTransport] setContext needs a live join; join() carries the first context"
|
|
26867
27128
|
);
|
|
26868
27129
|
}
|
|
27130
|
+
this.#setContext(context);
|
|
27131
|
+
}
|
|
27132
|
+
/**
|
|
27133
|
+
* The join's context, and what the subscription delivers with each packet:
|
|
27134
|
+
* marked admitted by the user agent when the gateway is its native
|
|
27135
|
+
* MulticastReceiver provider. feedPacket never marks.
|
|
27136
|
+
*/
|
|
27137
|
+
#setContext(context) {
|
|
26869
27138
|
this.#context = context;
|
|
27139
|
+
this.#deliveredContext = this.#userAgentGateway !== void 0 && typeof context === "object" && context !== null ? userAgentVerifiedIngress(context, this.#userAgentGateway) : context;
|
|
26870
27140
|
}
|
|
26871
27141
|
/**
|
|
26872
27142
|
* Replace the live join's packet-ID allow-list in place, without rejoining.
|
|
@@ -26998,6 +27268,8 @@ var MulticastTransport = class {
|
|
|
26998
27268
|
this.#matchedSource = void 0;
|
|
26999
27269
|
this.#providerFilter = null;
|
|
27000
27270
|
this.#context = void 0;
|
|
27271
|
+
this.#deliveredContext = void 0;
|
|
27272
|
+
this.#userAgentGateway = void 0;
|
|
27001
27273
|
this.#withdrawnPacketIds.clear();
|
|
27002
27274
|
}
|
|
27003
27275
|
/** The ingress filter; an accepted packet is counted here, once. */
|
|
@@ -28350,8 +28622,17 @@ var MulticastManager = class {
|
|
|
28350
28622
|
* repair roles and owners are registered here; in-band owners stay the
|
|
28351
28623
|
* caller's. Joining is best effort: a group that fails to join is logged
|
|
28352
28624
|
* and leaves the media join as it was.
|
|
28353
|
-
|
|
28354
|
-
|
|
28625
|
+
*
|
|
28626
|
+
* `options.gateway` names the gateway a subscribe callback reads through.
|
|
28627
|
+
* When it is the browser's own MulticastReceiver provider, the callback's
|
|
28628
|
+
* packets are fed as admitted by the user agent
|
|
28629
|
+
* (`userAgentVerifiedIngress`); nothing the callback passes can mark them.
|
|
28630
|
+
* The mark rests on this statement, not on the data path: every packet the
|
|
28631
|
+
* callback delivers must come from a subscription opened on
|
|
28632
|
+
* `options.gateway`. A callback that also merges another source (a MoQ or
|
|
28633
|
+
* WebSocket repair feed) must not name a native provider here.
|
|
28634
|
+
*/
|
|
28635
|
+
joinRepairEndpoints(endpoints, provider, options) {
|
|
28355
28636
|
const releases = [];
|
|
28356
28637
|
let released = false;
|
|
28357
28638
|
const safely = (each) => {
|
|
@@ -28373,17 +28654,19 @@ var MulticastManager = class {
|
|
|
28373
28654
|
...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
|
|
28374
28655
|
};
|
|
28375
28656
|
let sender;
|
|
28376
|
-
const
|
|
28657
|
+
const feed = (packet, sourceAddress, ingressToFeed) => {
|
|
28377
28658
|
if (released) return;
|
|
28378
28659
|
if (sourceAddress !== void 0 && !sameDatagramSource(sourceAddress, this.#transport.sender ?? (sender ??= sourceAddress))) return;
|
|
28379
|
-
this.#router.feedPacket(packet, "multicast",
|
|
28660
|
+
this.#router.feedPacket(packet, "multicast", ingressToFeed);
|
|
28380
28661
|
};
|
|
28381
28662
|
const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
|
|
28382
28663
|
if (typeof provider === "function") {
|
|
28664
|
+
const marked = userAgentVerifiedIngress(ingress, options?.gateway);
|
|
28665
|
+
const onPacket = (packet, sourceAddress) => feed(packet, sourceAddress, marked);
|
|
28383
28666
|
Promise.resolve().then(() => provider(endpoint, onPacket)).then((release2) => released ? safely(release2) : releases.push(release2), fail);
|
|
28384
28667
|
continue;
|
|
28385
28668
|
}
|
|
28386
|
-
const transport = new MulticastTransport((packet) =>
|
|
28669
|
+
const transport = new MulticastTransport((packet, delivered) => feed(packet, void 0, delivered ?? ingress), {
|
|
28387
28670
|
gateway: provider,
|
|
28388
28671
|
lockedTo: () => this.#transport.sender
|
|
28389
28672
|
});
|
|
@@ -28399,7 +28682,7 @@ var MulticastManager = class {
|
|
|
28399
28682
|
tracks: endpoint.tracks.map(({ name, packetId }) => ({ name, packetId })),
|
|
28400
28683
|
// A protected repair group's records ride in band on its port (7.2.3).
|
|
28401
28684
|
...endpoint.flow === void 0 ? {} : { protected: true }
|
|
28402
|
-
}).then((joined) => {
|
|
28685
|
+
}, ingress).then((joined) => {
|
|
28403
28686
|
if (!joined && !released) fail(transport.lastJoinOutcome);
|
|
28404
28687
|
});
|
|
28405
28688
|
}
|