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

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,10 @@ 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_FAILURE_LEDGER_LIMIT = 4 * ASSIST_BLOCK_LIMIT;
19464
+ var ASSIST_FAILED_DECODES_MAX = 2;
19465
+ var assistKey = (ownerPacketId, catalogTrackName, blockStart) => `${ownerPacketId}/${catalogTrackName ?? ""}/${blockStart}`;
19462
19466
  var DIRECT_RECOVERED_AUDIO_QUEUE_LIMIT = 512;
19463
19467
  function sourceSymbolEvidence(packet, header, rapFlag, packetSequenceNumber) {
19464
19468
  const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
@@ -19483,6 +19487,12 @@ var MmtpRouter = class {
19483
19487
  #provenance = /* @__PURE__ */ new Map();
19484
19488
  #onFlowProtected = null;
19485
19489
  #unadmittedListedDrops = 0;
19490
+ #unlistedRepairSetAside = 0;
19491
+ #assistBlocks = /* @__PURE__ */ new Map();
19492
+ #assistFailures = /* @__PURE__ */ new Map();
19493
+ #scratchDecoderFactory = null;
19494
+ #unlistedAssistedRecoveries = 0;
19495
+ #unlistedAssistedRefusals = 0;
19486
19496
  /** The ingress a held datagram arrived with; the receiver returns the same bytes on release. */
19487
19497
  #heldIngress = /* @__PURE__ */ new WeakMap();
19488
19498
  #unicastRepair = false;
@@ -20198,14 +20208,13 @@ var MmtpRouter = class {
20198
20208
  updateProvenanceFlowIngress(ingress) {
20199
20209
  const entry = this.#provenance.get(ingress.flow);
20200
20210
  if (!entry) return;
20211
+ const added = entry.protected ? ingress.tracks.filter(({ name, packetId }) => name && !entry.ingress.tracks.some((t) => t.name === name && t.packetId === packetId)) : [];
20212
+ const governed = added.map(({ packetId }) => [...this.#repairOwnersByPacketId.get(packetId) ?? [packetId]].some((owner) => this.#governs(owner)));
20201
20213
  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
- }
20214
+ added.forEach(({ name, packetId }, i) => {
20215
+ if (governed[i]) for (const block of this.#assistBlocks.values()) block.pooled.clear();
20216
+ else this.#fecManager?.resetTrack?.(packetId, name);
20217
+ });
20209
20218
  }
20210
20219
  /**
20211
20220
  * Called once per protection of a flow: at the admission that makes this
@@ -20226,11 +20235,154 @@ var MmtpRouter = class {
20226
20235
  if (!entry) return;
20227
20236
  entry.receiver.release();
20228
20237
  entry.protected = false;
20238
+ this.#assistBlocks.clear();
20239
+ this.#assistFailures.clear();
20229
20240
  }
20230
20241
  /** MoQ packets of a track a protected flow lists, dropped on its unicast path: duplicate, overflow, or listed by more than one flow. */
20231
20242
  get unadmittedListedDropCount() {
20232
20243
  return this.#unadmittedListedDrops;
20233
20244
  }
20245
+ /** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
20246
+ get unlistedRepairSetAsideCount() {
20247
+ return this.#unlistedRepairSetAside;
20248
+ }
20249
+ /** Governed blocks recovered with the help of unlisted repair (7.2.7 step 2). */
20250
+ get unlistedAssistedRecoveryCount() {
20251
+ return this.#unlistedAssistedRecoveries;
20252
+ }
20253
+ /** Unlisted-assisted decodes refused because two already failed for the block (7.2.7 step 2). */
20254
+ get unlistedAssistedRefusalCount() {
20255
+ return this.#unlistedAssistedRefusals;
20256
+ }
20257
+ /** Unlisted repair symbols pooled now, across every governed block (7.2.7 step 2). */
20258
+ get unlistedPoolSymbolCount() {
20259
+ let n = 0;
20260
+ for (const block of this.#assistBlocks.values()) n += block.pooled.size;
20261
+ return n;
20262
+ }
20263
+ /**
20264
+ * Wire the decoder an unlisted-assisted decode runs in: a fresh, configured
20265
+ * decoder of the owner's track, used once and discarded, never the live one.
20266
+ * Without it unlisted repair on a governed instance is set aside and dropped.
20267
+ */
20268
+ setScratchFecDecoderFactory(factory) {
20269
+ this.#scratchDecoderFactory = factory;
20270
+ if (!factory) {
20271
+ this.#assistBlocks.clear();
20272
+ this.#assistFailures.clear();
20273
+ }
20274
+ }
20275
+ /** Whether a protected flow lists `ownerPacketId`: its FEC instance is governed (7.2.7). */
20276
+ #governs(ownerPacketId) {
20277
+ for (const entry of this.#provenance.values()) {
20278
+ if (entry.protected && ingressHasPacketId(entry.ingress, ownerPacketId)) return true;
20279
+ }
20280
+ return false;
20281
+ }
20282
+ /**
20283
+ * A MoQ repair symbol no protected flow lists, owned by a source track one
20284
+ * does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
20285
+ * symbols, so the symbol is set aside: pooled for an unlisted-assisted
20286
+ * decode of a block admitted repair already delimits, else dropped. It
20287
+ * never states a block, so the live locator and decoder are unchanged.
20288
+ * Un-listing needs no hook: an unprotected or released flow lists nothing.
20289
+ */
20290
+ #setAsideUnlistedRepair(ownerPacketId, catalogTrackName, repair) {
20291
+ if (!this.#governs(ownerPacketId)) return false;
20292
+ this.#unlistedRepairSetAside++;
20293
+ const key = assistKey(ownerPacketId, catalogTrackName, repair.ssStart);
20294
+ const block = this.#assistBlocks.get(key);
20295
+ 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) {
20296
+ block.pooled.set(repair.rsId, repair.symbolData);
20297
+ this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, repair.ssStart);
20298
+ }
20299
+ return true;
20300
+ }
20301
+ /**
20302
+ * An admitted repair placed a governed block (its SSB_length delimits it):
20303
+ * open the block's record, which the symbols its live decoder takes join.
20304
+ */
20305
+ #openAssistBlock(ownerPacketId, catalogTrackName, blockStart, ssbLength) {
20306
+ const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
20307
+ if (!this.#scratchDecoderFactory || this.#assistBlocks.has(key) || !this.#governs(ownerPacketId)) return;
20308
+ this.#assistBlocks.set(key, { ssbLength, sources: /* @__PURE__ */ new Map(), repairs: /* @__PURE__ */ new Map(), pooled: /* @__PURE__ */ new Map() });
20309
+ for (const oldest of this.#assistBlocks.keys()) {
20310
+ if (this.#assistBlocks.size <= ASSIST_BLOCK_LIMIT) break;
20311
+ this.#assistBlocks.delete(oldest);
20312
+ }
20313
+ }
20314
+ /** A symbol fed to an open governed block's live decoder that did not recover it. */
20315
+ #noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, symbol, data) {
20316
+ const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
20317
+ const block = this.#assistBlocks.get(key);
20318
+ if (block === void 0) return;
20319
+ if ("rsId" in symbol) {
20320
+ block.repairs.set(symbol.rsId, data);
20321
+ block.pooled.delete(symbol.rsId);
20322
+ } else {
20323
+ block.sources.set(symbol.esi, data);
20324
+ }
20325
+ this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart);
20326
+ }
20327
+ /**
20328
+ * 7.2.7 step 2, phase two: a governed block its admitted symbols cannot
20329
+ * decode (the live decoder already had them all) is decoded in a scratch
20330
+ * decoder from its admitted symbols plus pooled unlisted repair, only as
20331
+ * many as close the shortfall. What it recovers is released only through
20332
+ * admission (#routeRecoveredBlock), so the live decoder is never touched and
20333
+ * a refused recovery leaves the block to fail at its FEC deadline. At most
20334
+ * two such decodes may fail per block; a third is refused.
20335
+ */
20336
+ #tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart) {
20337
+ const fec = this.#fecManager;
20338
+ const admitted = block.sources.size + block.repairs.size;
20339
+ if (!fec || block.pooled.size === 0) return;
20340
+ if (admitted >= block.ssbLength || admitted + block.pooled.size < block.ssbLength) return;
20341
+ if (fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName)) {
20342
+ this.#assistBlocks.delete(key);
20343
+ return;
20344
+ }
20345
+ if ((this.#assistFailures.get(key) ?? 0) >= ASSIST_FAILED_DECODES_MAX) {
20346
+ this.#unlistedAssistedRefusals++;
20347
+ return;
20348
+ }
20349
+ const decoder = this.#scratchDecoderFactory?.(ownerPacketId, catalogTrackName);
20350
+ if (!decoder) return;
20351
+ let recovered = null;
20352
+ const fed = [];
20353
+ try {
20354
+ for (const [esi, data] of block.sources) decoder.add_source(serial32Add(blockStart, esi), data, 0);
20355
+ for (const [rsId, data] of block.repairs) {
20356
+ recovered ??= decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
20357
+ }
20358
+ for (const [rsId, data] of block.pooled) {
20359
+ if (recovered || fed.length >= block.ssbLength - admitted) break;
20360
+ fed.push(rsId);
20361
+ recovered = decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
20362
+ }
20363
+ } catch {
20364
+ recovered = null;
20365
+ } finally {
20366
+ decoder.free?.();
20367
+ }
20368
+ if (recovered && this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
20369
+ fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
20370
+ fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20371
+ fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20372
+ })) {
20373
+ this.#unlistedAssistedRecoveries++;
20374
+ this.#assistBlocks.delete(key);
20375
+ return;
20376
+ }
20377
+ for (const rsId of fed) block.pooled.delete(rsId);
20378
+ const failed = (this.#assistFailures.get(key) ?? 0) + 1;
20379
+ this.#assistFailures.delete(key);
20380
+ this.#assistFailures.set(key, failed);
20381
+ for (const oldest of this.#assistFailures.keys()) {
20382
+ if (this.#assistFailures.size <= ASSIST_FAILURE_LEDGER_LIMIT) break;
20383
+ this.#assistFailures.delete(oldest);
20384
+ }
20385
+ }
20234
20386
  /**
20235
20387
  * The admission that makes this router protected under `flow` (7.2.7):
20236
20388
  * its FEC instances discard what they hold of the flow's listed tracks so
@@ -20406,8 +20558,9 @@ var MmtpRouter = class {
20406
20558
  );
20407
20559
  }
20408
20560
  if (ownerPacketId != null && !admitted) {
20409
- const admit = this.#admitUnicast(ownerPacketId, packet, () => this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName, true));
20561
+ const admit = this.#admitUnicast(repairPacketId, packet, () => this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName, true));
20410
20562
  if (admit === "held" || admit === "dropped") return;
20563
+ if (admit === "unlisted" && this.#fecManager && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackName, parsed)) return;
20411
20564
  }
20412
20565
  if (ownerPacketId != null && this.#fecManager) {
20413
20566
  this.#moqRepairPidCounts[repairPacketId] = (this.#moqRepairPidCounts[repairPacketId] || 0) + 1;
@@ -20527,6 +20680,7 @@ var MmtpRouter = class {
20527
20680
  const placement = fec.placeIsoRepair(ownerPacketId, catalogTrackName, repair.ssStart, repair.ssbLength);
20528
20681
  if (placement.status !== "placed") return;
20529
20682
  const blockStart = placement.block.start;
20683
+ this.#openAssistBlock(ownerPacketId, catalogTrackName, blockStart, repair.ssbLength);
20530
20684
  if (placement.span !== void 0) {
20531
20685
  this.#replayIsoSources(ownerPacketId, catalogTrackName, placement.span.block.start, placement.span.placed);
20532
20686
  }
@@ -20557,6 +20711,8 @@ var MmtpRouter = class {
20557
20711
  fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20558
20712
  fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20559
20713
  });
20714
+ } else if (!recovered) {
20715
+ this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { rsId: repair.rsId }, repair.symbolData);
20560
20716
  }
20561
20717
  }
20562
20718
  /**
@@ -20596,6 +20752,7 @@ var MmtpRouter = class {
20596
20752
  if (payload.oversized) continue;
20597
20753
  const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20598
20754
  const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, catalogTrackName);
20755
+ if (!recovered) this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { esi }, payload.symbolData);
20599
20756
  if (!recovered || alreadyCompleted) continue;
20600
20757
  this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
20601
20758
  fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
@@ -20666,6 +20823,9 @@ var MmtpRouter = class {
20666
20823
  }
20667
20824
  }
20668
20825
  this.#parser.resetTrack(packetId, catalogTrackName);
20826
+ const assistPrefix = assistKey(packetId, catalogTrackName, 0).slice(0, -1);
20827
+ for (const key of this.#assistBlocks.keys()) if (key.startsWith(assistPrefix)) this.#assistBlocks.delete(key);
20828
+ for (const key of this.#assistFailures.keys()) if (key.startsWith(assistPrefix)) this.#assistFailures.delete(key);
20669
20829
  const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);
20670
20830
  this.#trackSynced.delete(streamKey);
20671
20831
  this.#droppedPreSyncVideo.delete(streamKey);
@@ -21326,6 +21486,7 @@ var MmtpRouter = class {
21326
21486
  const ownerPacketId = this.#resolveRepairOwnerPacketId(info.packetId, ingress);
21327
21487
  if (ownerPacketId != null && this.#fecManager) {
21328
21488
  if (hasAmbiguousCatalogTrackName(ingress, ownerPacketId)) return;
21489
+ if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
21329
21490
  this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
21330
21491
  const count = this.#repairPidCounts[info.packetId];
21331
21492
  if (count <= 2 || count % 500 === 0) {
@@ -21493,6 +21654,15 @@ var MmtpRouter = class {
21493
21654
  sourceFec.symbolData,
21494
21655
  catalogTrackName
21495
21656
  );
21657
+ if (!recovered) {
21658
+ this.#noteAssistAdmitted(
21659
+ info.packetId,
21660
+ catalogTrackName,
21661
+ sourceFec.sbn,
21662
+ { esi: serial32Distance(sourceFec.sbn, sourceFec.ssId) },
21663
+ sourceFec.symbolData
21664
+ );
21665
+ }
21496
21666
  if (recovered && !alreadyCompleted) {
21497
21667
  const fec = this.#fecManager;
21498
21668
  const sbn = sourceFec.sbn;
@@ -28120,6 +28290,14 @@ var MulticastManager = class {
28120
28290
  }
28121
28291
  }
28122
28292
  }
28293
+ const factory = typeof wasmDecoderSource === "function" ? wasmDecoderSource : null;
28294
+ const fecTracks = fecTrackIndexes.map((i) => catalog.tracks[i]);
28295
+ this.#router.setScratchFecDecoderFactory(factory && ((ownerPacketId, catalogTrackName) => {
28296
+ const track = fecTracks.find((t) => t.packetId === ownerPacketId && t.name === catalogTrackName);
28297
+ if (!track) return null;
28298
+ const decoder = factory(track);
28299
+ return [...fecDecoders.values()].includes(decoder) ? null : decoder;
28300
+ }));
28123
28301
  this.#switchingSets.clear();
28124
28302
  this.#repairPacketIds.clear();
28125
28303
  this.#router.clearRepairPacketIdOwners();