@blockcast/mmt-container 2.1.0 → 2.2.0-main.73af0eedcbc8

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.
@@ -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 unsupported = variables.find((variable) => variable !== "Number" && variable !== "RepresentationID");
8737
- if (unsupported !== void 0) {
8738
- throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${unsupported}$`);
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 unsupported = Object.keys(value).filter(
17322
+ const unsupported2 = Object.keys(value).filter(
17102
17323
  (key) => key !== "transport" && key !== "startsAtRandomAccessGroup"
17103
17324
  );
17104
- if (unsupported.length > 0) {
17105
- if (unsupported.includes("maxRacingGroups")) {
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): ${unsupported.join(", ")}`
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 unsupported = Object.keys(value).filter(
17376
+ const unsupported2 = Object.keys(value).filter(
17156
17377
  (key) => key !== "transport" && key !== "deadlineMs"
17157
17378
  );
17158
- if (unsupported.length > 0) {
17379
+ if (unsupported2.length > 0) {
17159
17380
  throw new Error(
17160
- `[MFU reassembly] unsupported datagram option(s): ${unsupported.join(", ")}`
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
- /** MoQ packets of a track a protected flow lists, dropped on its unicast path: duplicate, overflow, or listed by more than one flow. */
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
- #routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete) {
25516
- const receiver = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
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)?.receiver ?? "ambiguous";
25812
+ for (const bound of flows) return this.#provenance.get(bound) ?? "ambiguous";
25556
25813
  let match;
25557
- for (const { receiver, ingress } of this.#provenance.values()) {
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 = receiver;
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 getMulticastGlobals().multicast?.subscribe === "function";
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 ?? getMulticastGlobals().multicast;
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.#context = context;
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.#context);
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
- * What the live join delivers with every accepted packet (the join's
26845
- * `context`, or the last setContext), or undefined without a live join.
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
- joinRepairEndpoints(endpoints, provider) {
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 onPacket = (packet, sourceAddress) => {
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", ingress);
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) => onPacket(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
  });
@@ -28395,11 +28678,12 @@ var MulticastManager = class {
28395
28678
  mode: endpoint.mode,
28396
28679
  amtRelay: endpoint.amtRelay,
28397
28680
  amtRelayPort: endpoint.amtRelayPort ?? (endpoint.amtRelay?.trim() ? DEFAULT_AMT_RELAY_PORT : void 0),
28681
+ amtDiscovery: endpoint.amtDiscovery,
28398
28682
  packetIds: endpoint.packetIds,
28399
28683
  tracks: endpoint.tracks.map(({ name, packetId }) => ({ name, packetId })),
28400
28684
  // A protected repair group's records ride in band on its port (7.2.3).
28401
28685
  ...endpoint.flow === void 0 ? {} : { protected: true }
28402
- }).then((joined) => {
28686
+ }, ingress).then((joined) => {
28403
28687
  if (!joined && !released) fail(transport.lastJoinOutcome);
28404
28688
  });
28405
28689
  }