@blockcast/mmt-container 0.5.0-main.dd44e35d4e29 → 0.5.0-main.ed11aec88d91

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.
@@ -13555,6 +13555,11 @@ var FECManager = class _FECManager {
13555
13555
  // Worker-recovered blocks a recovered packet of which admission refused,
13556
13556
  // until the decode's final symbol (7.2.7: the block is unrecovered).
13557
13557
  this.#refusedRecoveries = /* @__PURE__ */ new Set();
13558
+ // Worker-recovered blocks: the next ESI the decode's ordered emit owes. The
13559
+ // Worker emits every ESI 0..SSB_length-1 in order and drops only a symbol it
13560
+ // refuses (a malformed ssbg_mode1 symbol), so a skipped ESI is a dropped
13561
+ // recovered packet and the decode recovered nothing.
13562
+ this.#workerRecoveredNextEsi = /* @__PURE__ */ new Map();
13558
13563
  // How the multicast leg delivers symbols (see MulticastSymbolDelivery). The
13559
13564
  // provider declares it per transport join; absence keeps datagram semantics.
13560
13565
  this.#multicastSymbolDelivery = "datagram";
@@ -13764,6 +13769,7 @@ var FECManager = class _FECManager {
13764
13769
  #repairDeadlineAtMs;
13765
13770
  #workerRecoveredBlocks;
13766
13771
  #refusedRecoveries;
13772
+ #workerRecoveredNextEsi;
13767
13773
  #multicastSymbolDelivery;
13768
13774
  #orderedFrontiers;
13769
13775
  #orderedPendingBlocks;
@@ -13904,6 +13910,7 @@ var FECManager = class _FECManager {
13904
13910
  this.#cleanlyCompletedBlocks.delete(record.key);
13905
13911
  this.#workerRecoveredBlocks.delete(record.key);
13906
13912
  this.#refusedRecoveries.delete(record.key);
13913
+ this.#workerRecoveredNextEsi.delete(record.key);
13907
13914
  this.#failedBlocks.delete(record.key);
13908
13915
  this.#resolvedBlockClasses.delete(record.key);
13909
13916
  this.#repairedBlocks.delete(record.key);
@@ -15059,6 +15066,7 @@ var FECManager = class _FECManager {
15059
15066
  resetSet(this.#repairRequestedBlocks);
15060
15067
  resetSet(this.#workerRecoveredBlocks);
15061
15068
  resetSet(this.#refusedRecoveries);
15069
+ resetMap(this.#workerRecoveredNextEsi);
15062
15070
  resetSet(this.#startupAlignedPackets);
15063
15071
  resetSet(this.#startupPartialBlocks);
15064
15072
  resetSet(this.#startupRepairReadyPackets);
@@ -15186,6 +15194,7 @@ var FECManager = class _FECManager {
15186
15194
  dropSet(this.#repairRequestedBlocks);
15187
15195
  dropSet(this.#workerRecoveredBlocks);
15188
15196
  dropSet(this.#refusedRecoveries);
15197
+ dropMap(this.#workerRecoveredNextEsi);
15189
15198
  dropSet(this.#startupAlignedPackets);
15190
15199
  dropSet(this.#startupPartialBlocks);
15191
15200
  dropSet(this.#startupRepairReadyPackets);
@@ -15973,6 +15982,16 @@ var FECManager = class _FECManager {
15973
15982
  if (!this.#claimDecoderBlock(client, meta.sbn, recoveredCatalogTrackName)) return;
15974
15983
  const blockKey = this.#blockKey(meta.sbn, packetId, recoveredCatalogTrackName);
15975
15984
  const cleanlyCompleted = this.#cleanlyCompletedBlocks.has(blockKey);
15985
+ if (esi !== (this.#workerRecoveredNextEsi.get(blockKey) ?? 0)) {
15986
+ this.#refusedRecoveries.add(blockKey);
15987
+ if (this.#refusedRecoveries.size > 1e3) dropOldest(this.#refusedRecoveries, 500);
15988
+ }
15989
+ if (esi >= meta.ssbLength - 1) {
15990
+ this.#workerRecoveredNextEsi.delete(blockKey);
15991
+ } else {
15992
+ this.#workerRecoveredNextEsi.set(blockKey, esi + 1);
15993
+ if (this.#workerRecoveredNextEsi.size > 1e3) dropOldest(this.#workerRecoveredNextEsi, 500);
15994
+ }
15976
15995
  if (!cleanlyCompleted) {
15977
15996
  const symbolKey = `${blockKey}:${esi}`;
15978
15997
  if (!this.#seenSymbols.has(symbolKey)) {
@@ -16782,6 +16801,7 @@ var FECManager = class _FECManager {
16782
16801
  this.#repairRequestedBlocks.clear();
16783
16802
  this.#workerRecoveredBlocks.clear();
16784
16803
  this.#refusedRecoveries.clear();
16804
+ this.#workerRecoveredNextEsi.clear();
16785
16805
  this.#syncDecoderBlockOwners.clear();
16786
16806
  this.#startupAlignedPackets.clear();
16787
16807
  this.#startupPartialBlocks.clear();
@@ -19459,6 +19479,10 @@ var NO_BLOCK_SPAN_HORIZON = Object.freeze({
19459
19479
  reason: "no-block-span"
19460
19480
  });
19461
19481
  var DIRECT_RECOVERED_VIDEO_QUEUE_LIMIT = 512;
19482
+ var ASSIST_BLOCK_LIMIT = 64;
19483
+ var ASSIST_FAILURE_LEDGER_LIMIT = 4 * ASSIST_BLOCK_LIMIT;
19484
+ var ASSIST_FAILED_DECODES_MAX = 2;
19485
+ var assistKey = (ownerPacketId, catalogTrackName, blockStart) => `${ownerPacketId}/${catalogTrackName ?? ""}/${blockStart}`;
19462
19486
  var DIRECT_RECOVERED_AUDIO_QUEUE_LIMIT = 512;
19463
19487
  function sourceSymbolEvidence(packet, header, rapFlag, packetSequenceNumber) {
19464
19488
  const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
@@ -19483,6 +19507,12 @@ var MmtpRouter = class {
19483
19507
  #provenance = /* @__PURE__ */ new Map();
19484
19508
  #onFlowProtected = null;
19485
19509
  #unadmittedListedDrops = 0;
19510
+ #unlistedRepairSetAside = 0;
19511
+ #assistBlocks = /* @__PURE__ */ new Map();
19512
+ #assistFailures = /* @__PURE__ */ new Map();
19513
+ #scratchDecoderFactory = null;
19514
+ #unlistedAssistedRecoveries = 0;
19515
+ #unlistedAssistedRefusals = 0;
19486
19516
  /** The ingress a held datagram arrived with; the receiver returns the same bytes on release. */
19487
19517
  #heldIngress = /* @__PURE__ */ new WeakMap();
19488
19518
  #unicastRepair = false;
@@ -20198,14 +20228,13 @@ var MmtpRouter = class {
20198
20228
  updateProvenanceFlowIngress(ingress) {
20199
20229
  const entry = this.#provenance.get(ingress.flow);
20200
20230
  if (!entry) return;
20231
+ const added = entry.protected ? ingress.tracks.filter(({ name, packetId }) => name && !entry.ingress.tracks.some((t) => t.name === name && t.packetId === packetId)) : [];
20232
+ const governed = added.map(({ packetId }) => [...this.#repairOwnersByPacketId.get(packetId) ?? [packetId]].some((owner) => this.#governs(owner)));
20201
20233
  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
- }
20234
+ added.forEach(({ name, packetId }, i) => {
20235
+ if (governed[i]) for (const block of this.#assistBlocks.values()) block.pooled.clear();
20236
+ else this.#fecManager?.resetTrack?.(packetId, name);
20237
+ });
20209
20238
  }
20210
20239
  /**
20211
20240
  * Called once per protection of a flow: at the admission that makes this
@@ -20226,11 +20255,154 @@ var MmtpRouter = class {
20226
20255
  if (!entry) return;
20227
20256
  entry.receiver.release();
20228
20257
  entry.protected = false;
20258
+ this.#assistBlocks.clear();
20259
+ this.#assistFailures.clear();
20229
20260
  }
20230
20261
  /** MoQ packets of a track a protected flow lists, dropped on its unicast path: duplicate, overflow, or listed by more than one flow. */
20231
20262
  get unadmittedListedDropCount() {
20232
20263
  return this.#unadmittedListedDrops;
20233
20264
  }
20265
+ /** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
20266
+ get unlistedRepairSetAsideCount() {
20267
+ return this.#unlistedRepairSetAside;
20268
+ }
20269
+ /** Governed blocks recovered with the help of unlisted repair (7.2.7 step 2). */
20270
+ get unlistedAssistedRecoveryCount() {
20271
+ return this.#unlistedAssistedRecoveries;
20272
+ }
20273
+ /** Unlisted-assisted decodes refused because two already failed for the block (7.2.7 step 2). */
20274
+ get unlistedAssistedRefusalCount() {
20275
+ return this.#unlistedAssistedRefusals;
20276
+ }
20277
+ /** Unlisted repair symbols pooled now, across every governed block (7.2.7 step 2). */
20278
+ get unlistedPoolSymbolCount() {
20279
+ let n = 0;
20280
+ for (const block of this.#assistBlocks.values()) n += block.pooled.size;
20281
+ return n;
20282
+ }
20283
+ /**
20284
+ * Wire the decoder an unlisted-assisted decode runs in: a fresh, configured
20285
+ * decoder of the owner's track, used once and discarded, never the live one.
20286
+ * Without it unlisted repair on a governed instance is set aside and dropped.
20287
+ */
20288
+ setScratchFecDecoderFactory(factory) {
20289
+ this.#scratchDecoderFactory = factory;
20290
+ if (!factory) {
20291
+ this.#assistBlocks.clear();
20292
+ this.#assistFailures.clear();
20293
+ }
20294
+ }
20295
+ /** Whether a protected flow lists `ownerPacketId`: its FEC instance is governed (7.2.7). */
20296
+ #governs(ownerPacketId) {
20297
+ for (const entry of this.#provenance.values()) {
20298
+ if (entry.protected && ingressHasPacketId(entry.ingress, ownerPacketId)) return true;
20299
+ }
20300
+ return false;
20301
+ }
20302
+ /**
20303
+ * A MoQ repair symbol no protected flow lists, owned by a source track one
20304
+ * does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
20305
+ * symbols, so the symbol is set aside: pooled for an unlisted-assisted
20306
+ * decode of a block admitted repair already delimits, else dropped. It
20307
+ * never states a block, so the live locator and decoder are unchanged.
20308
+ * Un-listing needs no hook: an unprotected or released flow lists nothing.
20309
+ */
20310
+ #setAsideUnlistedRepair(ownerPacketId, catalogTrackName, repair) {
20311
+ if (!this.#governs(ownerPacketId)) return false;
20312
+ this.#unlistedRepairSetAside++;
20313
+ const key = assistKey(ownerPacketId, catalogTrackName, repair.ssStart);
20314
+ const block = this.#assistBlocks.get(key);
20315
+ 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) {
20316
+ block.pooled.set(repair.rsId, repair.symbolData);
20317
+ this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, repair.ssStart);
20318
+ }
20319
+ return true;
20320
+ }
20321
+ /**
20322
+ * An admitted repair placed a governed block (its SSB_length delimits it):
20323
+ * open the block's record, which the symbols its live decoder takes join.
20324
+ */
20325
+ #openAssistBlock(ownerPacketId, catalogTrackName, blockStart, ssbLength) {
20326
+ const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
20327
+ if (!this.#scratchDecoderFactory || this.#assistBlocks.has(key) || !this.#governs(ownerPacketId)) return;
20328
+ this.#assistBlocks.set(key, { ssbLength, sources: /* @__PURE__ */ new Map(), repairs: /* @__PURE__ */ new Map(), pooled: /* @__PURE__ */ new Map() });
20329
+ for (const oldest of this.#assistBlocks.keys()) {
20330
+ if (this.#assistBlocks.size <= ASSIST_BLOCK_LIMIT) break;
20331
+ this.#assistBlocks.delete(oldest);
20332
+ }
20333
+ }
20334
+ /** A symbol fed to an open governed block's live decoder that did not recover it. */
20335
+ #noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, symbol, data) {
20336
+ const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
20337
+ const block = this.#assistBlocks.get(key);
20338
+ if (block === void 0) return;
20339
+ if ("rsId" in symbol) {
20340
+ block.repairs.set(symbol.rsId, data);
20341
+ block.pooled.delete(symbol.rsId);
20342
+ } else {
20343
+ block.sources.set(symbol.esi, data);
20344
+ }
20345
+ this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart);
20346
+ }
20347
+ /**
20348
+ * 7.2.7 step 2, phase two: a governed block its admitted symbols cannot
20349
+ * decode (the live decoder already had them all) is decoded in a scratch
20350
+ * decoder from its admitted symbols plus pooled unlisted repair, only as
20351
+ * many as close the shortfall. What it recovers is released only through
20352
+ * admission (#routeRecoveredBlock), so the live decoder is never touched and
20353
+ * a refused recovery leaves the block to fail at its FEC deadline. At most
20354
+ * two such decodes may fail per block; a third is refused.
20355
+ */
20356
+ #tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart) {
20357
+ const fec = this.#fecManager;
20358
+ const admitted = block.sources.size + block.repairs.size;
20359
+ if (!fec || block.pooled.size === 0) return;
20360
+ if (admitted >= block.ssbLength || admitted + block.pooled.size < block.ssbLength) return;
20361
+ if (fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName)) {
20362
+ this.#assistBlocks.delete(key);
20363
+ return;
20364
+ }
20365
+ if ((this.#assistFailures.get(key) ?? 0) >= ASSIST_FAILED_DECODES_MAX) {
20366
+ this.#unlistedAssistedRefusals++;
20367
+ return;
20368
+ }
20369
+ const decoder = this.#scratchDecoderFactory?.(ownerPacketId, catalogTrackName);
20370
+ if (!decoder) return;
20371
+ let recovered = null;
20372
+ const fed = [];
20373
+ try {
20374
+ for (const [esi, data] of block.sources) decoder.add_source(serial32Add(blockStart, esi), data, 0);
20375
+ for (const [rsId, data] of block.repairs) {
20376
+ recovered ??= decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
20377
+ }
20378
+ for (const [rsId, data] of block.pooled) {
20379
+ if (recovered || fed.length >= block.ssbLength - admitted) break;
20380
+ fed.push(rsId);
20381
+ recovered = decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
20382
+ }
20383
+ } catch {
20384
+ recovered = null;
20385
+ } finally {
20386
+ decoder.free?.();
20387
+ }
20388
+ if (recovered && this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
20389
+ fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
20390
+ fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20391
+ fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20392
+ })) {
20393
+ this.#unlistedAssistedRecoveries++;
20394
+ this.#assistBlocks.delete(key);
20395
+ return;
20396
+ }
20397
+ for (const rsId of fed) block.pooled.delete(rsId);
20398
+ const failed = (this.#assistFailures.get(key) ?? 0) + 1;
20399
+ this.#assistFailures.delete(key);
20400
+ this.#assistFailures.set(key, failed);
20401
+ for (const oldest of this.#assistFailures.keys()) {
20402
+ if (this.#assistFailures.size <= ASSIST_FAILURE_LEDGER_LIMIT) break;
20403
+ this.#assistFailures.delete(oldest);
20404
+ }
20405
+ }
20234
20406
  /**
20235
20407
  * The admission that makes this router protected under `flow` (7.2.7):
20236
20408
  * its FEC instances discard what they hold of the flow's listed tracks so
@@ -20406,8 +20578,9 @@ var MmtpRouter = class {
20406
20578
  );
20407
20579
  }
20408
20580
  if (ownerPacketId != null && !admitted) {
20409
- const admit = this.#admitUnicast(ownerPacketId, packet, () => this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName, true));
20581
+ const admit = this.#admitUnicast(repairPacketId, packet, () => this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName, true));
20410
20582
  if (admit === "held" || admit === "dropped") return;
20583
+ if (admit === "unlisted" && this.#fecManager && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackName, parsed)) return;
20411
20584
  }
20412
20585
  if (ownerPacketId != null && this.#fecManager) {
20413
20586
  this.#moqRepairPidCounts[repairPacketId] = (this.#moqRepairPidCounts[repairPacketId] || 0) + 1;
@@ -20527,6 +20700,7 @@ var MmtpRouter = class {
20527
20700
  const placement = fec.placeIsoRepair(ownerPacketId, catalogTrackName, repair.ssStart, repair.ssbLength);
20528
20701
  if (placement.status !== "placed") return;
20529
20702
  const blockStart = placement.block.start;
20703
+ this.#openAssistBlock(ownerPacketId, catalogTrackName, blockStart, repair.ssbLength);
20530
20704
  if (placement.span !== void 0) {
20531
20705
  this.#replayIsoSources(ownerPacketId, catalogTrackName, placement.span.block.start, placement.span.placed);
20532
20706
  }
@@ -20557,6 +20731,8 @@ var MmtpRouter = class {
20557
20731
  fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20558
20732
  fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20559
20733
  });
20734
+ } else if (!recovered) {
20735
+ this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { rsId: repair.rsId }, repair.symbolData);
20560
20736
  }
20561
20737
  }
20562
20738
  /**
@@ -20596,6 +20772,7 @@ var MmtpRouter = class {
20596
20772
  if (payload.oversized) continue;
20597
20773
  const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20598
20774
  const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, catalogTrackName);
20775
+ if (!recovered) this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { esi }, payload.symbolData);
20599
20776
  if (!recovered || alreadyCompleted) continue;
20600
20777
  this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
20601
20778
  fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
@@ -20666,6 +20843,9 @@ var MmtpRouter = class {
20666
20843
  }
20667
20844
  }
20668
20845
  this.#parser.resetTrack(packetId, catalogTrackName);
20846
+ const assistPrefix = assistKey(packetId, catalogTrackName, 0).slice(0, -1);
20847
+ for (const key of this.#assistBlocks.keys()) if (key.startsWith(assistPrefix)) this.#assistBlocks.delete(key);
20848
+ for (const key of this.#assistFailures.keys()) if (key.startsWith(assistPrefix)) this.#assistFailures.delete(key);
20669
20849
  const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);
20670
20850
  this.#trackSynced.delete(streamKey);
20671
20851
  this.#droppedPreSyncVideo.delete(streamKey);
@@ -21326,6 +21506,7 @@ var MmtpRouter = class {
21326
21506
  const ownerPacketId = this.#resolveRepairOwnerPacketId(info.packetId, ingress);
21327
21507
  if (ownerPacketId != null && this.#fecManager) {
21328
21508
  if (hasAmbiguousCatalogTrackName(ingress, ownerPacketId)) return;
21509
+ if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
21329
21510
  this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
21330
21511
  const count = this.#repairPidCounts[info.packetId];
21331
21512
  if (count <= 2 || count % 500 === 0) {
@@ -21493,6 +21674,15 @@ var MmtpRouter = class {
21493
21674
  sourceFec.symbolData,
21494
21675
  catalogTrackName
21495
21676
  );
21677
+ if (!recovered) {
21678
+ this.#noteAssistAdmitted(
21679
+ info.packetId,
21680
+ catalogTrackName,
21681
+ sourceFec.sbn,
21682
+ { esi: serial32Distance(sourceFec.sbn, sourceFec.ssId) },
21683
+ sourceFec.symbolData
21684
+ );
21685
+ }
21496
21686
  if (recovered && !alreadyCompleted) {
21497
21687
  const fec = this.#fecManager;
21498
21688
  const sbn = sourceFec.sbn;
@@ -28120,6 +28310,14 @@ var MulticastManager = class {
28120
28310
  }
28121
28311
  }
28122
28312
  }
28313
+ const factory = typeof wasmDecoderSource === "function" ? wasmDecoderSource : null;
28314
+ const fecTracks = fecTrackIndexes.map((i) => catalog.tracks[i]);
28315
+ this.#router.setScratchFecDecoderFactory(factory && ((ownerPacketId, catalogTrackName) => {
28316
+ const track = fecTracks.find((t) => t.packetId === ownerPacketId && t.name === catalogTrackName);
28317
+ if (!track) return null;
28318
+ const decoder = factory(track);
28319
+ return [...fecDecoders.values()].includes(decoder) ? null : decoder;
28320
+ }));
28123
28321
  this.#switchingSets.clear();
28124
28322
  this.#repairPacketIds.clear();
28125
28323
  this.#router.clearRepairPacketIdOwners();