@blockcast/mmt-container 0.5.0-main.8fbc25eb9deb → 0.5.0-main.d53b0164c5de

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.
@@ -19459,6 +19459,9 @@ var NO_BLOCK_SPAN_HORIZON = Object.freeze({
19459
19459
  reason: "no-block-span"
19460
19460
  });
19461
19461
  var DIRECT_RECOVERED_VIDEO_QUEUE_LIMIT = 512;
19462
+ var ASSIST_BLOCK_LIMIT = 64;
19463
+ var ASSIST_FAILED_DECODES_MAX = 2;
19464
+ var assistKey = (ownerPacketId, catalogTrackName, blockStart) => `${ownerPacketId}/${catalogTrackName ?? ""}/${blockStart}`;
19462
19465
  var DIRECT_RECOVERED_AUDIO_QUEUE_LIMIT = 512;
19463
19466
  function sourceSymbolEvidence(packet, header, rapFlag, packetSequenceNumber) {
19464
19467
  const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
@@ -19483,6 +19486,11 @@ var MmtpRouter = class {
19483
19486
  #provenance = /* @__PURE__ */ new Map();
19484
19487
  #onFlowProtected = null;
19485
19488
  #unadmittedListedDrops = 0;
19489
+ #unlistedRepairSetAside = 0;
19490
+ #assistBlocks = /* @__PURE__ */ new Map();
19491
+ #scratchDecoderFactory = null;
19492
+ #unlistedAssistedRecoveries = 0;
19493
+ #unlistedAssistedRefusals = 0;
19486
19494
  /** The ingress a held datagram arrived with; the receiver returns the same bytes on release. */
19487
19495
  #heldIngress = /* @__PURE__ */ new WeakMap();
19488
19496
  #unicastRepair = false;
@@ -20198,14 +20206,13 @@ var MmtpRouter = class {
20198
20206
  updateProvenanceFlowIngress(ingress) {
20199
20207
  const entry = this.#provenance.get(ingress.flow);
20200
20208
  if (!entry) return;
20209
+ const added = entry.protected ? ingress.tracks.filter(({ name, packetId }) => name && !entry.ingress.tracks.some((t) => t.name === name && t.packetId === packetId)) : [];
20210
+ const governed = added.map(({ packetId }) => [...this.#repairOwnersByPacketId.get(packetId) ?? [packetId]].some((owner) => this.#governs(owner)));
20201
20211
  this.#provenance.set(ingress.flow, { ...entry, ingress });
20202
- if (entry.protected) {
20203
- for (const { name, packetId } of ingress.tracks) {
20204
- if (name && !entry.ingress.tracks.some((t) => t.name === name && t.packetId === packetId)) {
20205
- this.#fecManager?.resetTrack?.(packetId, name);
20206
- }
20207
- }
20208
- }
20212
+ added.forEach(({ name, packetId }, i) => {
20213
+ if (governed[i]) for (const block of this.#assistBlocks.values()) block.pooled.clear();
20214
+ else this.#fecManager?.resetTrack?.(packetId, name);
20215
+ });
20209
20216
  }
20210
20217
  /**
20211
20218
  * Called once per protection of a flow: at the admission that makes this
@@ -20226,11 +20233,138 @@ var MmtpRouter = class {
20226
20233
  if (!entry) return;
20227
20234
  entry.receiver.release();
20228
20235
  entry.protected = false;
20236
+ this.#assistBlocks.clear();
20229
20237
  }
20230
20238
  /** MoQ packets of a track a protected flow lists, dropped on its unicast path: duplicate, overflow, or listed by more than one flow. */
20231
20239
  get unadmittedListedDropCount() {
20232
20240
  return this.#unadmittedListedDrops;
20233
20241
  }
20242
+ /** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
20243
+ get unlistedRepairSetAsideCount() {
20244
+ return this.#unlistedRepairSetAside;
20245
+ }
20246
+ /** Governed blocks recovered with the help of unlisted repair (7.2.7 step 2). */
20247
+ get unlistedAssistedRecoveryCount() {
20248
+ return this.#unlistedAssistedRecoveries;
20249
+ }
20250
+ /** Unlisted-assisted decodes refused because two already failed for the block (7.2.7 step 2). */
20251
+ get unlistedAssistedRefusalCount() {
20252
+ return this.#unlistedAssistedRefusals;
20253
+ }
20254
+ /**
20255
+ * Wire the decoder an unlisted-assisted decode runs in: a fresh, configured
20256
+ * decoder of the owner's track, used once and discarded, never the live one.
20257
+ * Without it unlisted repair on a governed instance is set aside and dropped.
20258
+ */
20259
+ setScratchFecDecoderFactory(factory) {
20260
+ this.#scratchDecoderFactory = factory;
20261
+ if (!factory) this.#assistBlocks.clear();
20262
+ }
20263
+ /** Whether a protected flow lists `ownerPacketId`: its FEC instance is governed (7.2.7). */
20264
+ #governs(ownerPacketId) {
20265
+ for (const entry of this.#provenance.values()) {
20266
+ if (entry.protected && ingressHasPacketId(entry.ingress, ownerPacketId)) return true;
20267
+ }
20268
+ return false;
20269
+ }
20270
+ /**
20271
+ * A MoQ repair symbol no protected flow lists, owned by a source track one
20272
+ * does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
20273
+ * symbols, so the symbol is set aside: pooled for an unlisted-assisted
20274
+ * decode of a block admitted repair already delimits, else dropped. It
20275
+ * never states a block, so the live locator and decoder are unchanged.
20276
+ * Un-listing needs no hook: an unprotected or released flow lists nothing.
20277
+ */
20278
+ #setAsideUnlistedRepair(ownerPacketId, catalogTrackName, repair) {
20279
+ if (!this.#governs(ownerPacketId)) return false;
20280
+ this.#unlistedRepairSetAside++;
20281
+ const key = assistKey(ownerPacketId, catalogTrackName, repair.ssStart);
20282
+ const block = this.#assistBlocks.get(key);
20283
+ if (block !== void 0 && block.ssbLength === repair.ssbLength && !block.repairs.has(repair.rsId) && block.pooled.size < block.ssbLength && repair.symbolData.byteLength === this.#symbolSizeForPacket(ownerPacketId) && this.#fecManager?.admitRepairBlockLength?.(ownerPacketId, repair.ssbLength, repair.rsbLength, repair.rsId) !== false) {
20284
+ block.pooled.set(repair.rsId, repair.symbolData);
20285
+ this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, repair.ssStart);
20286
+ }
20287
+ return true;
20288
+ }
20289
+ /**
20290
+ * An admitted repair placed a governed block (its SSB_length delimits it):
20291
+ * open the block's record, which the symbols its live decoder takes join.
20292
+ */
20293
+ #openAssistBlock(ownerPacketId, catalogTrackName, blockStart, ssbLength) {
20294
+ const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
20295
+ if (!this.#scratchDecoderFactory || this.#assistBlocks.has(key) || !this.#governs(ownerPacketId)) return;
20296
+ this.#assistBlocks.set(key, { ssbLength, sources: /* @__PURE__ */ new Map(), repairs: /* @__PURE__ */ new Map(), pooled: /* @__PURE__ */ new Map(), failed: 0 });
20297
+ for (const oldest of this.#assistBlocks.keys()) {
20298
+ if (this.#assistBlocks.size <= ASSIST_BLOCK_LIMIT) break;
20299
+ this.#assistBlocks.delete(oldest);
20300
+ }
20301
+ }
20302
+ /** A symbol fed to an open governed block's live decoder that did not recover it. */
20303
+ #noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, symbol, data) {
20304
+ const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
20305
+ const block = this.#assistBlocks.get(key);
20306
+ if (block === void 0) return;
20307
+ if ("rsId" in symbol) {
20308
+ block.repairs.set(symbol.rsId, data);
20309
+ block.pooled.delete(symbol.rsId);
20310
+ } else {
20311
+ block.sources.set(symbol.esi, data);
20312
+ }
20313
+ this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart);
20314
+ }
20315
+ /**
20316
+ * 7.2.7 step 2, phase two: a governed block its admitted symbols cannot
20317
+ * decode (the live decoder already had them all) is decoded in a scratch
20318
+ * decoder from its admitted symbols plus pooled unlisted repair, only as
20319
+ * many as close the shortfall. What it recovers is released only through
20320
+ * admission (#routeRecoveredBlock), so the live decoder is never touched and
20321
+ * a refused recovery leaves the block to fail at its FEC deadline. At most
20322
+ * two such decodes may fail per block; a third is refused.
20323
+ */
20324
+ #tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart) {
20325
+ const fec = this.#fecManager;
20326
+ const admitted = block.sources.size + block.repairs.size;
20327
+ if (!fec || block.pooled.size === 0) return;
20328
+ if (admitted >= block.ssbLength || admitted + block.pooled.size < block.ssbLength) return;
20329
+ if (fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName)) {
20330
+ this.#assistBlocks.delete(key);
20331
+ return;
20332
+ }
20333
+ if (block.failed >= ASSIST_FAILED_DECODES_MAX) {
20334
+ this.#unlistedAssistedRefusals++;
20335
+ return;
20336
+ }
20337
+ const decoder = this.#scratchDecoderFactory?.(ownerPacketId, catalogTrackName);
20338
+ if (!decoder) return;
20339
+ let recovered = null;
20340
+ const fed = [];
20341
+ try {
20342
+ for (const [esi, data] of block.sources) decoder.add_source(serial32Add(blockStart, esi), data, 0);
20343
+ for (const [rsId, data] of block.repairs) {
20344
+ recovered ??= decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
20345
+ }
20346
+ for (const [rsId, data] of block.pooled) {
20347
+ if (recovered || fed.length >= block.ssbLength - admitted) break;
20348
+ fed.push(rsId);
20349
+ recovered = decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
20350
+ }
20351
+ } catch {
20352
+ recovered = null;
20353
+ } finally {
20354
+ decoder.free?.();
20355
+ }
20356
+ if (recovered && this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
20357
+ fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
20358
+ fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20359
+ fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20360
+ })) {
20361
+ this.#unlistedAssistedRecoveries++;
20362
+ this.#assistBlocks.delete(key);
20363
+ return;
20364
+ }
20365
+ for (const rsId of fed) block.pooled.delete(rsId);
20366
+ block.failed++;
20367
+ }
20234
20368
  /**
20235
20369
  * The admission that makes this router protected under `flow` (7.2.7):
20236
20370
  * its FEC instances discard what they hold of the flow's listed tracks so
@@ -20406,8 +20540,9 @@ var MmtpRouter = class {
20406
20540
  );
20407
20541
  }
20408
20542
  if (ownerPacketId != null && !admitted) {
20409
- const admit = this.#admitUnicast(ownerPacketId, packet, () => this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName, true));
20543
+ const admit = this.#admitUnicast(repairPacketId, packet, () => this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName, true));
20410
20544
  if (admit === "held" || admit === "dropped") return;
20545
+ if (admit === "unlisted" && this.#fecManager && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackName, parsed)) return;
20411
20546
  }
20412
20547
  if (ownerPacketId != null && this.#fecManager) {
20413
20548
  this.#moqRepairPidCounts[repairPacketId] = (this.#moqRepairPidCounts[repairPacketId] || 0) + 1;
@@ -20527,6 +20662,7 @@ var MmtpRouter = class {
20527
20662
  const placement = fec.placeIsoRepair(ownerPacketId, catalogTrackName, repair.ssStart, repair.ssbLength);
20528
20663
  if (placement.status !== "placed") return;
20529
20664
  const blockStart = placement.block.start;
20665
+ this.#openAssistBlock(ownerPacketId, catalogTrackName, blockStart, repair.ssbLength);
20530
20666
  if (placement.span !== void 0) {
20531
20667
  this.#replayIsoSources(ownerPacketId, catalogTrackName, placement.span.block.start, placement.span.placed);
20532
20668
  }
@@ -20557,6 +20693,8 @@ var MmtpRouter = class {
20557
20693
  fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20558
20694
  fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20559
20695
  });
20696
+ } else if (!recovered) {
20697
+ this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { rsId: repair.rsId }, repair.symbolData);
20560
20698
  }
20561
20699
  }
20562
20700
  /**
@@ -20596,6 +20734,7 @@ var MmtpRouter = class {
20596
20734
  if (payload.oversized) continue;
20597
20735
  const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20598
20736
  const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, catalogTrackName);
20737
+ if (!recovered) this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { esi }, payload.symbolData);
20599
20738
  if (!recovered || alreadyCompleted) continue;
20600
20739
  this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
20601
20740
  fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
@@ -20666,6 +20805,8 @@ var MmtpRouter = class {
20666
20805
  }
20667
20806
  }
20668
20807
  this.#parser.resetTrack(packetId, catalogTrackName);
20808
+ const assistPrefix = assistKey(packetId, catalogTrackName, 0).slice(0, -1);
20809
+ for (const key of this.#assistBlocks.keys()) if (key.startsWith(assistPrefix)) this.#assistBlocks.delete(key);
20669
20810
  const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);
20670
20811
  this.#trackSynced.delete(streamKey);
20671
20812
  this.#droppedPreSyncVideo.delete(streamKey);
@@ -21326,6 +21467,7 @@ var MmtpRouter = class {
21326
21467
  const ownerPacketId = this.#resolveRepairOwnerPacketId(info.packetId, ingress);
21327
21468
  if (ownerPacketId != null && this.#fecManager) {
21328
21469
  if (hasAmbiguousCatalogTrackName(ingress, ownerPacketId)) return;
21470
+ if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
21329
21471
  this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
21330
21472
  const count = this.#repairPidCounts[info.packetId];
21331
21473
  if (count <= 2 || count % 500 === 0) {
@@ -21493,6 +21635,15 @@ var MmtpRouter = class {
21493
21635
  sourceFec.symbolData,
21494
21636
  catalogTrackName
21495
21637
  );
21638
+ if (!recovered) {
21639
+ this.#noteAssistAdmitted(
21640
+ info.packetId,
21641
+ catalogTrackName,
21642
+ sourceFec.sbn,
21643
+ { esi: serial32Distance(sourceFec.sbn, sourceFec.ssId) },
21644
+ sourceFec.symbolData
21645
+ );
21646
+ }
21496
21647
  if (recovered && !alreadyCompleted) {
21497
21648
  const fec = this.#fecManager;
21498
21649
  const sbn = sourceFec.sbn;
@@ -28120,6 +28271,14 @@ var MulticastManager = class {
28120
28271
  }
28121
28272
  }
28122
28273
  }
28274
+ const factory = typeof wasmDecoderSource === "function" ? wasmDecoderSource : null;
28275
+ const fecTracks = fecTrackIndexes.map((i) => catalog.tracks[i]);
28276
+ this.#router.setScratchFecDecoderFactory(factory && ((ownerPacketId, catalogTrackName) => {
28277
+ const track = fecTracks.find((t) => t.packetId === ownerPacketId && t.name === catalogTrackName);
28278
+ if (!track) return null;
28279
+ const decoder = factory(track);
28280
+ return [...fecDecoders.values()].includes(decoder) ? null : decoder;
28281
+ }));
28123
28282
  this.#switchingSets.clear();
28124
28283
  this.#repairPacketIds.clear();
28125
28284
  this.#router.clearRepairPacketIdOwners();