@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.
- package/dist/browser-runtime.js +186 -8
- package/dist/browser-runtime.js.map +2 -2
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js.map +2 -2
- package/dist/mmtp-router.d.ts +15 -1
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +178 -8
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.d.ts +5 -1
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +186 -8
- package/dist/multicast-manager.js.map +2 -2
- package/package.json +5 -5
- package/src/fec-manager.ts +4 -0
- package/src/mmtp-router.ts +257 -11
- package/src/multicast-manager.ts +17 -1
package/dist/browser-runtime.js
CHANGED
|
@@ -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
|
-
|
|
20203
|
-
for (const
|
|
20204
|
-
|
|
20205
|
-
|
|
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(
|
|
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();
|