@blockcast/mmt-container 0.5.0 → 1.0.0-main.63922d1832ee

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 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.5.0`; see `RELEASES.md`.
273
+ > `1.0.0`; see `RELEASES.md`.
274
274
 
275
275
  #### Track-role convention (Phase 14)
276
276
 
@@ -19622,8 +19622,6 @@ var MmtpRouter = class {
19622
19622
  // Callbacks (legacy — used when per-track callbacks are not registered)
19623
19623
  #onVideoFrame = null;
19624
19624
  #onAudioFrame = null;
19625
- // Init segment (ftyp+moov) from MMTP MPU metadata — needed for MSE SourceBuffer
19626
- #onInitSegment = null;
19627
19625
  // Per-track init segment callbacks keyed by packetId
19628
19626
  #trackInitCallbacks = /* @__PURE__ */ new Map();
19629
19627
  // Track whether init has been delivered per physical stream — prevent duplicates
@@ -19886,11 +19884,10 @@ var MmtpRouter = class {
19886
19884
  };
19887
19885
  }
19888
19886
  /**
19889
- * Init segments delivered for a packetId with no `onTrackInit` handler
19890
- * since construction, whether the deprecated aggregate `onInitSegment`
19891
- * callback then received them or nothing did. A supported deployment reads
19892
- * 0 here; a non-zero value is the evidence that blocks removing the
19893
- * aggregate callback (MOQT-01, BLO-21181).
19887
+ * Init segments rejected since construction because their packetId had no
19888
+ * `onTrackInit` handler. A rejected init is delivered nowhere; there is no
19889
+ * aggregate fallback (removed in 1.0.0, MOQT-01 / BLO-21181). A supported
19890
+ * deployment reads 0 here.
19894
19891
  */
19895
19892
  get unmappedInitDeliveryCount() {
19896
19893
  return this.#unmappedInitDeliveries;
@@ -20067,20 +20064,10 @@ var MmtpRouter = class {
20067
20064
  onTrackPacket(packetId, cb) {
20068
20065
  this.#trackPacketCallbacks.set(packetId, cb);
20069
20066
  }
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
20067
  /** Register an init segment callback for a specific MMTP packetId.
20083
- * Called when FT=0 init arrives via direct source or FEC recovery. */
20068
+ * Called when FT=0 init arrives via direct source or FEC recovery. An init
20069
+ * for a packetId with no handler is rejected and counted by
20070
+ * `unmappedInitDeliveryCount`. */
20084
20071
  onTrackInit(packetId, cb) {
20085
20072
  this.#trackInitCallbacks.set(packetId, cb);
20086
20073
  }
@@ -21480,16 +21467,15 @@ var MmtpRouter = class {
21480
21467
  if (this.#trackInitDelivered.has(streamKey)) return;
21481
21468
  if (data.byteLength <= 8) return;
21482
21469
  this.#trackInitDelivered.add(streamKey);
21470
+ const track = `packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`}`;
21483
21471
  const cb = this.#trackInitCallbacks.get(packetId);
21484
- if (cb) {
21485
- cb(data);
21486
- } else {
21472
+ if (!cb) {
21487
21473
  this.#unmappedInitDeliveries++;
21488
- this.#onInitSegment?.(data);
21474
+ console.log(`[MmtpRouter] Init rejected for ${track}: no onTrackInit handler`);
21475
+ return;
21489
21476
  }
21490
- console.log(
21491
- `[MmtpRouter] Init delivered for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${data.byteLength}B)`
21492
- );
21477
+ cb(data);
21478
+ console.log(`[MmtpRouter] Init delivered for ${track} (${data.byteLength}B)`);
21493
21479
  }
21494
21480
  /**
21495
21481
  * Core MMTP packet processing — source packets → play + FEC, repair → FEC only.
@@ -22989,6 +22975,10 @@ function getMulticastGlobals() {
22989
22975
  function hasMulticastGateway() {
22990
22976
  return typeof getMulticastGlobals().multicast?.subscribe === "function";
22991
22977
  }
22978
+ function senderOf(event) {
22979
+ const sourceAddress = event.sourceAddress;
22980
+ return typeof sourceAddress === "string" ? sourceAddress : void 0;
22981
+ }
22992
22982
  function normalizeBytesEventDetail(detail) {
22993
22983
  const value = detail && typeof detail === "object" && "data" in detail ? detail.data : detail;
22994
22984
  if (value instanceof Uint8Array) return value;
@@ -23035,7 +23025,8 @@ var MulticastTransport = class {
23035
23025
  bytesReceived: 0,
23036
23026
  packetsDroppedUnregisteredPid: 0,
23037
23027
  packetsDroppedUnparseable: 0,
23038
- packetsDroppedSignaling: 0
23028
+ packetsDroppedSignaling: 0,
23029
+ packetsDroppedForeignSender: 0
23039
23030
  };
23040
23031
  // join() calls by how they resolved; see joinStats.
23041
23032
  #joinsSucceeded = 0;
@@ -23060,6 +23051,11 @@ var MulticastTransport = class {
23060
23051
  // per packet once a count outgrows the small-integer range.
23061
23052
  #acceptedBytesByPacketId = /* @__PURE__ */ new Map();
23062
23053
  #packetIds = null;
23054
+ // The sender S this join's accepted packets came from, once known; see
23055
+ // packetsDroppedForeignSender. Cleared by every join and by a silence
23056
+ // failback, so a replacement publisher is not locked out.
23057
+ #sender = void 0;
23058
+ #lockedTo;
23063
23059
  // What the live join delivers with each accepted packet. A field, not a
23064
23060
  // listener closure, so an in-place admission change (setContext) applies
23065
23061
  // from the next packet on without rejoining.
@@ -23081,9 +23077,15 @@ var MulticastTransport = class {
23081
23077
  #joinGeneration = 0;
23082
23078
  // The provider to subscribe through; window.multicast when unset.
23083
23079
  #gateway;
23080
+ /**
23081
+ * `lockedTo` reads the sender another transport is locked to (a repair
23082
+ * group follows its media join, whose publisher sends both). While it reads
23083
+ * a sender, this join admits only that one; until then its own lock applies.
23084
+ */
23084
23085
  constructor(onPacket, options = {}) {
23085
23086
  this.#onPacket = onPacket;
23086
23087
  this.#gateway = options.gateway;
23088
+ this.#lockedTo = options.lockedTo;
23087
23089
  }
23088
23090
  static isAvailable() {
23089
23091
  return hasMulticastGateway();
@@ -23092,6 +23094,7 @@ var MulticastTransport = class {
23092
23094
  const generation = ++this.#joinGeneration;
23093
23095
  this.#releaseSubscription();
23094
23096
  this.#dataPlaneLive = false;
23097
+ this.#sender = void 0;
23095
23098
  if (this.#disposed) return this.#declined(generation, "disposed");
23096
23099
  const gateway = this.#gateway ?? getMulticastGlobals().multicast;
23097
23100
  if (typeof gateway?.subscribe !== "function") {
@@ -23133,7 +23136,8 @@ var MulticastTransport = class {
23133
23136
  }
23134
23137
  this.#subscription = subscription;
23135
23138
  const live = () => !this.#disposed && generation === this.#joinGeneration;
23136
- const packetListener = subscriptionDeliversPacketBatches(subscription) ? {
23139
+ const anySource = !config.sourceAddress && !config.senderAddress && !config.atsc3;
23140
+ const packetListener = !anySource && subscriptionDeliversPacketBatches(subscription) ? {
23137
23141
  type: "packets",
23138
23142
  listener: (event) => {
23139
23143
  if (!live()) return;
@@ -23143,7 +23147,7 @@ var MulticastTransport = class {
23143
23147
  type: "bytes",
23144
23148
  listener: (event) => {
23145
23149
  if (!live()) return;
23146
- if (this.#deliver(event.detail)) this.#resetWatchdog();
23150
+ if (this.#deliver(event.detail, senderOf(event))) this.#resetWatchdog();
23147
23151
  }
23148
23152
  };
23149
23153
  this.#packetListener = packetListener;
@@ -23234,13 +23238,13 @@ var MulticastTransport = class {
23234
23238
  * One packet from the subscription: filter, count, and hand it to the
23235
23239
  * callback. Returns whether it was delivered; the caller notes liveness.
23236
23240
  */
23237
- #deliver(detail) {
23241
+ #deliver(detail, sourceAddress) {
23238
23242
  const packet = normalizeBytesEventDetail(detail);
23239
23243
  if (!packet) {
23240
23244
  this.#stats.packetsDroppedUnparseable++;
23241
23245
  return false;
23242
23246
  }
23243
- if (!this.#acceptPacket(packet)) return false;
23247
+ if (!this.#acceptPacket(packet, sourceAddress)) return false;
23244
23248
  this.#onPacket(packet, this.#context);
23245
23249
  return true;
23246
23250
  }
@@ -23281,11 +23285,15 @@ var MulticastTransport = class {
23281
23285
  throw failures[0];
23282
23286
  }
23283
23287
  }
23284
- /** Feed a raw packet from an external source (e.g., AMT tunnel or ASM subscription). */
23285
- feedPacket(data, context) {
23288
+ /**
23289
+ * Feed a raw packet from an external source (e.g., AMT tunnel or ASM
23290
+ * subscription). `sourceAddress` is the datagram's sender when the source
23291
+ * knows it; see packetsDroppedForeignSender.
23292
+ */
23293
+ feedPacket(data, context, sourceAddress) {
23286
23294
  if (this.#disposed) return;
23287
23295
  const packet = data instanceof Uint8Array ? data : new Uint8Array(data);
23288
- if (!this.#acceptPacket(packet)) return;
23296
+ if (!this.#acceptPacket(packet, sourceAddress)) return;
23289
23297
  this.#onPacket(packet, context);
23290
23298
  this.#resetWatchdog();
23291
23299
  }
@@ -23435,6 +23443,10 @@ var MulticastTransport = class {
23435
23443
  get withdrawnPacketIdDrops() {
23436
23444
  return this.#withdrawnPacketIdDrops;
23437
23445
  }
23446
+ /** The sender this join is locked to; undefined until one is known. */
23447
+ get sender() {
23448
+ return this.#sender;
23449
+ }
23438
23450
  /** Whether a subscription is live: joined, and not yet released or replaced. */
23439
23451
  get joined() {
23440
23452
  return this.#subscription !== null;
@@ -23597,11 +23609,16 @@ var MulticastTransport = class {
23597
23609
  this.#withdrawnPacketIds.clear();
23598
23610
  }
23599
23611
  /** The ingress filter; an accepted packet is counted here, once. */
23600
- #acceptPacket(packet) {
23612
+ #acceptPacket(packet, sourceAddress) {
23613
+ const owner = this.#lockedTo?.() ?? this.#sender;
23614
+ if (sourceAddress !== void 0 && owner !== void 0 && sourceAddress !== owner) {
23615
+ this.#stats.packetsDroppedForeignSender++;
23616
+ return false;
23617
+ }
23601
23618
  const packetId = readMMTPPacketId(packet);
23602
23619
  if (packetId === null) {
23603
23620
  if (!this.#packetIds) {
23604
- this.#countAccepted(packet, null);
23621
+ this.#countAccepted(packet, null, sourceAddress);
23605
23622
  return true;
23606
23623
  }
23607
23624
  this.#stats.packetsDroppedUnparseable++;
@@ -23612,14 +23629,15 @@ var MulticastTransport = class {
23612
23629
  return false;
23613
23630
  }
23614
23631
  if (!this.#packetIds || this.#packetIds.has(packetId)) {
23615
- this.#countAccepted(packet, packetId);
23632
+ this.#countAccepted(packet, packetId, sourceAddress);
23616
23633
  return true;
23617
23634
  }
23618
23635
  this.#recordUnregisteredPidDrop(packetId);
23619
23636
  return false;
23620
23637
  }
23621
23638
  /** Receive counters for one accepted packet; `packetId` is null when it has none. */
23622
- #countAccepted(packet, packetId) {
23639
+ #countAccepted(packet, packetId, sourceAddress) {
23640
+ this.#sender ??= sourceAddress;
23623
23641
  this.#stats.packetsReceived++;
23624
23642
  this.#stats.bytesReceived += packet.byteLength;
23625
23643
  if (packetId === null) return;
@@ -23690,6 +23708,7 @@ var MulticastTransport = class {
23690
23708
  console.warn(
23691
23709
  `[MulticastTransport] No packets for ${this.#failbackWindowMs}ms; failing back to MoQ`
23692
23710
  );
23711
+ this.#sender = void 0;
23693
23712
  this.#onFailback({ kind: "silence", windowMs: this.#failbackWindowMs });
23694
23713
  }, delayMs);
23695
23714
  }
@@ -27704,10 +27723,6 @@ var MulticastManager = class {
27704
27723
  onTrackPacket(packetId, cb) {
27705
27724
  this.#router.onTrackPacket(packetId, cb);
27706
27725
  }
27707
- /** @deprecated Use onTrackInit(packetId, cb); see MmtpRouter.onInitSegment. */
27708
- onInitSegment(cb) {
27709
- this.#router.onInitSegment(cb);
27710
- }
27711
27726
  onTrackInit(packetId, cb) {
27712
27727
  this.#router.onTrackInit(packetId, cb);
27713
27728
  }
@@ -27931,15 +27946,21 @@ var MulticastManager = class {
27931
27946
  tracks: Object.freeze(endpoint.tracks.map(({ name, packetId }) => Object.freeze({ name, packetId }))),
27932
27947
  ...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
27933
27948
  };
27934
- const onPacket = (packet) => {
27935
- if (!released) this.#router.feedPacket(packet, "multicast", ingress);
27949
+ let sender;
27950
+ const onPacket = (packet, sourceAddress) => {
27951
+ if (released) return;
27952
+ if (sourceAddress !== void 0 && sourceAddress !== (this.#transport.sender ?? (sender ??= sourceAddress))) return;
27953
+ this.#router.feedPacket(packet, "multicast", ingress);
27936
27954
  };
27937
27955
  const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
27938
27956
  if (typeof provider === "function") {
27939
27957
  Promise.resolve().then(() => provider(endpoint, onPacket)).then((release2) => released ? safely(release2) : releases.push(release2), fail);
27940
27958
  continue;
27941
27959
  }
27942
- const transport = new MulticastTransport((packet) => onPacket(packet), { gateway: provider });
27960
+ const transport = new MulticastTransport((packet) => onPacket(packet), {
27961
+ gateway: provider,
27962
+ lockedTo: () => this.#transport.sender
27963
+ });
27943
27964
  releases.push(() => transport.dispose());
27944
27965
  void transport.join({
27945
27966
  sourceAddress: endpoint.source,
@@ -28218,8 +28239,8 @@ var MulticastManager = class {
28218
28239
  * transport attribution in FEC path counters.
28219
28240
  * The `router` accessor above is already public.
28220
28241
  */
28221
- feedPacket(data, ingress) {
28222
- this.#transport.feedPacket(data, ingress);
28242
+ feedPacket(data, ingress, sourceAddress) {
28243
+ this.#transport.feedPacket(data, ingress, sourceAddress);
28223
28244
  }
28224
28245
  /**
28225
28246
  * Declare how packets given to feedPacket() reach this manager. A consumer