@blockcast/mmt-container 0.5.0-main.dd44e35d4e29 → 0.5.0-main.e7704f492663
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 +356 -29
- package/dist/browser-runtime.js.map +2 -2
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +20 -0
- package/dist/fec-manager.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +2 -2
- package/dist/mmtp-container-parser.d.ts +10 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +31 -1
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +31 -1
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +31 -1
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.d.ts +26 -1
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +232 -9
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.d.ts +30 -2
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +298 -10
- package/dist/multicast-manager.js.map +2 -2
- package/dist/no-modules.js +31 -1
- package/dist/no-modules.js.map +2 -2
- package/dist/repair-fetch.d.ts +41 -10
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +58 -19
- package/dist/repair-fetch.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +31 -1
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +7 -6
- package/src/fec-manager.ts +23 -0
- package/src/index.ts +1 -0
- package/src/mmtp-container-parser.ts +48 -0
- package/src/mmtp-router.ts +286 -11
- package/src/multicast-manager.ts +85 -1
- package/src/repair-fetch.ts +131 -31
- package/test-vectors/repair-policy-v1.json +113 -0
package/dist/browser-runtime.js
CHANGED
|
@@ -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();
|
|
@@ -17635,6 +17655,9 @@ function authoredGap(before, after) {
|
|
|
17635
17655
|
if (!a || !b || a.mediaTimescale !== b.mediaTimescale) return false;
|
|
17636
17656
|
return b.decodeTime > a.decodeTime + BigInt(a.durationTicks);
|
|
17637
17657
|
}
|
|
17658
|
+
function ssIdAfter(a, b) {
|
|
17659
|
+
return (a - b | 0) > 0;
|
|
17660
|
+
}
|
|
17638
17661
|
var FecReleaseSequencer = class {
|
|
17639
17662
|
#streams = /* @__PURE__ */ new Map();
|
|
17640
17663
|
#gate;
|
|
@@ -17696,11 +17719,17 @@ var FecReleaseSequencer = class {
|
|
|
17696
17719
|
stream.last = null;
|
|
17697
17720
|
stream.lastSbn = void 0;
|
|
17698
17721
|
stream.discontinuity = false;
|
|
17722
|
+
stream.rapSsId = void 0;
|
|
17699
17723
|
}
|
|
17700
17724
|
if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {
|
|
17701
17725
|
this.#recordStat("droppedStale");
|
|
17702
17726
|
return;
|
|
17703
17727
|
}
|
|
17728
|
+
const ssId = meta.fecSbn;
|
|
17729
|
+
if (meta.trackType === "video" && ssId !== void 0 && this.#gate.isRap?.(data, meta) === true) {
|
|
17730
|
+
if (stream.rapSsId === void 0 || ssIdAfter(ssId, stream.rapSsId)) stream.rapSsId = ssId;
|
|
17731
|
+
this.#gate.onRap?.(stream.packetId, stream.catalogTrackName, ssId);
|
|
17732
|
+
}
|
|
17704
17733
|
if (stream.last === null) {
|
|
17705
17734
|
if (meta.trackType === "video" && !meta.keyframe) {
|
|
17706
17735
|
this.#recordStat("droppedBeforeStartupKeyframe");
|
|
@@ -17736,6 +17765,22 @@ var FecReleaseSequencer = class {
|
|
|
17736
17765
|
}
|
|
17737
17766
|
return earliest;
|
|
17738
17767
|
}
|
|
17768
|
+
/**
|
|
17769
|
+
* Whether a complete RAP above SS_ID `ssId` entered the gate of a stream of
|
|
17770
|
+
* `packetId` (draft-ramadan-moq-fec 11.4.2: repair of a block below it is
|
|
17771
|
+
* moot). Its SS_ID is the one its frame completed in; a frame's packets
|
|
17772
|
+
* hold consecutive SS_IDs in authored order, so a complete RAP with a
|
|
17773
|
+
* packet above `ssId` has every packet of it at or above the first SS_ID
|
|
17774
|
+
* any loss at or below `ssId` could have taken.
|
|
17775
|
+
*/
|
|
17776
|
+
rapAbove(packetId, catalogTrackName, ssId) {
|
|
17777
|
+
for (const stream of this.#streams.values()) {
|
|
17778
|
+
if (stream.packetId !== packetId) continue;
|
|
17779
|
+
if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
|
|
17780
|
+
if (stream.rapSsId !== void 0 && ssIdAfter(stream.rapSsId, ssId)) return true;
|
|
17781
|
+
}
|
|
17782
|
+
return false;
|
|
17783
|
+
}
|
|
17739
17784
|
/** Re-evaluate held gaps after FEC state changed. */
|
|
17740
17785
|
poke(streamKey) {
|
|
17741
17786
|
if (streamKey !== void 0) {
|
|
@@ -17826,7 +17871,8 @@ var FecReleaseSequencer = class {
|
|
|
17826
17871
|
buf: [],
|
|
17827
17872
|
last: null,
|
|
17828
17873
|
lastSbn: void 0,
|
|
17829
|
-
discontinuity: false
|
|
17874
|
+
discontinuity: false,
|
|
17875
|
+
rapSsId: void 0
|
|
17830
17876
|
};
|
|
17831
17877
|
this.#streams.set(streamKey, stream);
|
|
17832
17878
|
}
|
|
@@ -18189,6 +18235,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18189
18235
|
fecReleaseEarliestHeldAtMs(packetId, catalogTrackName) {
|
|
18190
18236
|
return this.#fecSequencer?.earliestHeldArrivalMs(packetId, catalogTrackName);
|
|
18191
18237
|
}
|
|
18238
|
+
/** See FecReleaseSequencer.rapAbove; false when no stream is FEC-gated. */
|
|
18239
|
+
fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
|
|
18240
|
+
return this.#fecSequencer?.rapAbove(packetId, catalogTrackName, ssId) ?? false;
|
|
18241
|
+
}
|
|
18192
18242
|
/** Re-evaluate held gaps after FEC block state changed. */
|
|
18193
18243
|
pokeFecRelease(packetId, catalogTrackName) {
|
|
18194
18244
|
if (!this.#fecSequencer) return;
|
|
@@ -19459,6 +19509,10 @@ var NO_BLOCK_SPAN_HORIZON = Object.freeze({
|
|
|
19459
19509
|
reason: "no-block-span"
|
|
19460
19510
|
});
|
|
19461
19511
|
var DIRECT_RECOVERED_VIDEO_QUEUE_LIMIT = 512;
|
|
19512
|
+
var ASSIST_BLOCK_LIMIT = 64;
|
|
19513
|
+
var ASSIST_FAILURE_LEDGER_LIMIT = 4 * ASSIST_BLOCK_LIMIT;
|
|
19514
|
+
var ASSIST_FAILED_DECODES_MAX = 2;
|
|
19515
|
+
var assistKey = (ownerPacketId, catalogTrackName, blockStart) => `${ownerPacketId}/${catalogTrackName ?? ""}/${blockStart}`;
|
|
19462
19516
|
var DIRECT_RECOVERED_AUDIO_QUEUE_LIMIT = 512;
|
|
19463
19517
|
function sourceSymbolEvidence(packet, header, rapFlag, packetSequenceNumber) {
|
|
19464
19518
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
@@ -19483,6 +19537,12 @@ var MmtpRouter = class {
|
|
|
19483
19537
|
#provenance = /* @__PURE__ */ new Map();
|
|
19484
19538
|
#onFlowProtected = null;
|
|
19485
19539
|
#unadmittedListedDrops = 0;
|
|
19540
|
+
#unlistedRepairSetAside = 0;
|
|
19541
|
+
#assistBlocks = /* @__PURE__ */ new Map();
|
|
19542
|
+
#assistFailures = /* @__PURE__ */ new Map();
|
|
19543
|
+
#scratchDecoderFactory = null;
|
|
19544
|
+
#unlistedAssistedRecoveries = 0;
|
|
19545
|
+
#unlistedAssistedRefusals = 0;
|
|
19486
19546
|
/** The ingress a held datagram arrived with; the receiver returns the same bytes on release. */
|
|
19487
19547
|
#heldIngress = /* @__PURE__ */ new WeakMap();
|
|
19488
19548
|
#unicastRepair = false;
|
|
@@ -19575,6 +19635,7 @@ var MmtpRouter = class {
|
|
|
19575
19635
|
#onBlockFailed = null;
|
|
19576
19636
|
/** Typed observer of every gap decision the release gate makes. */
|
|
19577
19637
|
#onFrameGap;
|
|
19638
|
+
#fecRapObservers = /* @__PURE__ */ new Set();
|
|
19578
19639
|
// Pokes requested while a packet is being processed are deferred to its
|
|
19579
19640
|
// end: a synchronous recovery completes its block BEFORE routing the
|
|
19580
19641
|
// recovered frames, and releasing a held gap in between would strand them.
|
|
@@ -20198,14 +20259,13 @@ var MmtpRouter = class {
|
|
|
20198
20259
|
updateProvenanceFlowIngress(ingress) {
|
|
20199
20260
|
const entry = this.#provenance.get(ingress.flow);
|
|
20200
20261
|
if (!entry) return;
|
|
20262
|
+
const added = entry.protected ? ingress.tracks.filter(({ name, packetId }) => name && !entry.ingress.tracks.some((t) => t.name === name && t.packetId === packetId)) : [];
|
|
20263
|
+
const governed = added.map(({ packetId }) => [...this.#repairOwnersByPacketId.get(packetId) ?? [packetId]].some((owner) => this.#governs(owner)));
|
|
20201
20264
|
this.#provenance.set(ingress.flow, { ...entry, ingress });
|
|
20202
|
-
|
|
20203
|
-
for (const
|
|
20204
|
-
|
|
20205
|
-
|
|
20206
|
-
}
|
|
20207
|
-
}
|
|
20208
|
-
}
|
|
20265
|
+
added.forEach(({ name, packetId }, i) => {
|
|
20266
|
+
if (governed[i]) for (const block of this.#assistBlocks.values()) block.pooled.clear();
|
|
20267
|
+
else this.#fecManager?.resetTrack?.(packetId, name);
|
|
20268
|
+
});
|
|
20209
20269
|
}
|
|
20210
20270
|
/**
|
|
20211
20271
|
* Called once per protection of a flow: at the admission that makes this
|
|
@@ -20226,11 +20286,154 @@ var MmtpRouter = class {
|
|
|
20226
20286
|
if (!entry) return;
|
|
20227
20287
|
entry.receiver.release();
|
|
20228
20288
|
entry.protected = false;
|
|
20289
|
+
this.#assistBlocks.clear();
|
|
20290
|
+
this.#assistFailures.clear();
|
|
20229
20291
|
}
|
|
20230
20292
|
/** MoQ packets of a track a protected flow lists, dropped on its unicast path: duplicate, overflow, or listed by more than one flow. */
|
|
20231
20293
|
get unadmittedListedDropCount() {
|
|
20232
20294
|
return this.#unadmittedListedDrops;
|
|
20233
20295
|
}
|
|
20296
|
+
/** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
|
|
20297
|
+
get unlistedRepairSetAsideCount() {
|
|
20298
|
+
return this.#unlistedRepairSetAside;
|
|
20299
|
+
}
|
|
20300
|
+
/** Governed blocks recovered with the help of unlisted repair (7.2.7 step 2). */
|
|
20301
|
+
get unlistedAssistedRecoveryCount() {
|
|
20302
|
+
return this.#unlistedAssistedRecoveries;
|
|
20303
|
+
}
|
|
20304
|
+
/** Unlisted-assisted decodes refused because two already failed for the block (7.2.7 step 2). */
|
|
20305
|
+
get unlistedAssistedRefusalCount() {
|
|
20306
|
+
return this.#unlistedAssistedRefusals;
|
|
20307
|
+
}
|
|
20308
|
+
/** Unlisted repair symbols pooled now, across every governed block (7.2.7 step 2). */
|
|
20309
|
+
get unlistedPoolSymbolCount() {
|
|
20310
|
+
let n = 0;
|
|
20311
|
+
for (const block of this.#assistBlocks.values()) n += block.pooled.size;
|
|
20312
|
+
return n;
|
|
20313
|
+
}
|
|
20314
|
+
/**
|
|
20315
|
+
* Wire the decoder an unlisted-assisted decode runs in: a fresh, configured
|
|
20316
|
+
* decoder of the owner's track, used once and discarded, never the live one.
|
|
20317
|
+
* Without it unlisted repair on a governed instance is set aside and dropped.
|
|
20318
|
+
*/
|
|
20319
|
+
setScratchFecDecoderFactory(factory) {
|
|
20320
|
+
this.#scratchDecoderFactory = factory;
|
|
20321
|
+
if (!factory) {
|
|
20322
|
+
this.#assistBlocks.clear();
|
|
20323
|
+
this.#assistFailures.clear();
|
|
20324
|
+
}
|
|
20325
|
+
}
|
|
20326
|
+
/** Whether a protected flow lists `ownerPacketId`: its FEC instance is governed (7.2.7). */
|
|
20327
|
+
#governs(ownerPacketId) {
|
|
20328
|
+
for (const entry of this.#provenance.values()) {
|
|
20329
|
+
if (entry.protected && ingressHasPacketId(entry.ingress, ownerPacketId)) return true;
|
|
20330
|
+
}
|
|
20331
|
+
return false;
|
|
20332
|
+
}
|
|
20333
|
+
/**
|
|
20334
|
+
* A MoQ repair symbol no protected flow lists, owned by a source track one
|
|
20335
|
+
* does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
|
|
20336
|
+
* symbols, so the symbol is set aside: pooled for an unlisted-assisted
|
|
20337
|
+
* decode of a block admitted repair already delimits, else dropped. It
|
|
20338
|
+
* never states a block, so the live locator and decoder are unchanged.
|
|
20339
|
+
* Un-listing needs no hook: an unprotected or released flow lists nothing.
|
|
20340
|
+
*/
|
|
20341
|
+
#setAsideUnlistedRepair(ownerPacketId, catalogTrackName, repair) {
|
|
20342
|
+
if (!this.#governs(ownerPacketId)) return false;
|
|
20343
|
+
this.#unlistedRepairSetAside++;
|
|
20344
|
+
const key = assistKey(ownerPacketId, catalogTrackName, repair.ssStart);
|
|
20345
|
+
const block = this.#assistBlocks.get(key);
|
|
20346
|
+
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) {
|
|
20347
|
+
block.pooled.set(repair.rsId, repair.symbolData);
|
|
20348
|
+
this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, repair.ssStart);
|
|
20349
|
+
}
|
|
20350
|
+
return true;
|
|
20351
|
+
}
|
|
20352
|
+
/**
|
|
20353
|
+
* An admitted repair placed a governed block (its SSB_length delimits it):
|
|
20354
|
+
* open the block's record, which the symbols its live decoder takes join.
|
|
20355
|
+
*/
|
|
20356
|
+
#openAssistBlock(ownerPacketId, catalogTrackName, blockStart, ssbLength) {
|
|
20357
|
+
const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
|
|
20358
|
+
if (!this.#scratchDecoderFactory || this.#assistBlocks.has(key) || !this.#governs(ownerPacketId)) return;
|
|
20359
|
+
this.#assistBlocks.set(key, { ssbLength, sources: /* @__PURE__ */ new Map(), repairs: /* @__PURE__ */ new Map(), pooled: /* @__PURE__ */ new Map() });
|
|
20360
|
+
for (const oldest of this.#assistBlocks.keys()) {
|
|
20361
|
+
if (this.#assistBlocks.size <= ASSIST_BLOCK_LIMIT) break;
|
|
20362
|
+
this.#assistBlocks.delete(oldest);
|
|
20363
|
+
}
|
|
20364
|
+
}
|
|
20365
|
+
/** A symbol fed to an open governed block's live decoder that did not recover it. */
|
|
20366
|
+
#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, symbol, data) {
|
|
20367
|
+
const key = assistKey(ownerPacketId, catalogTrackName, blockStart);
|
|
20368
|
+
const block = this.#assistBlocks.get(key);
|
|
20369
|
+
if (block === void 0) return;
|
|
20370
|
+
if ("rsId" in symbol) {
|
|
20371
|
+
block.repairs.set(symbol.rsId, data);
|
|
20372
|
+
block.pooled.delete(symbol.rsId);
|
|
20373
|
+
} else {
|
|
20374
|
+
block.sources.set(symbol.esi, data);
|
|
20375
|
+
}
|
|
20376
|
+
this.#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart);
|
|
20377
|
+
}
|
|
20378
|
+
/**
|
|
20379
|
+
* 7.2.7 step 2, phase two: a governed block its admitted symbols cannot
|
|
20380
|
+
* decode (the live decoder already had them all) is decoded in a scratch
|
|
20381
|
+
* decoder from its admitted symbols plus pooled unlisted repair, only as
|
|
20382
|
+
* many as close the shortfall. What it recovers is released only through
|
|
20383
|
+
* admission (#routeRecoveredBlock), so the live decoder is never touched and
|
|
20384
|
+
* a refused recovery leaves the block to fail at its FEC deadline. At most
|
|
20385
|
+
* two such decodes may fail per block; a third is refused.
|
|
20386
|
+
*/
|
|
20387
|
+
#tryAssistedDecode(key, block, ownerPacketId, catalogTrackName, blockStart) {
|
|
20388
|
+
const fec = this.#fecManager;
|
|
20389
|
+
const admitted = block.sources.size + block.repairs.size;
|
|
20390
|
+
if (!fec || block.pooled.size === 0) return;
|
|
20391
|
+
if (admitted >= block.ssbLength || admitted + block.pooled.size < block.ssbLength) return;
|
|
20392
|
+
if (fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName)) {
|
|
20393
|
+
this.#assistBlocks.delete(key);
|
|
20394
|
+
return;
|
|
20395
|
+
}
|
|
20396
|
+
if ((this.#assistFailures.get(key) ?? 0) >= ASSIST_FAILED_DECODES_MAX) {
|
|
20397
|
+
this.#unlistedAssistedRefusals++;
|
|
20398
|
+
return;
|
|
20399
|
+
}
|
|
20400
|
+
const decoder = this.#scratchDecoderFactory?.(ownerPacketId, catalogTrackName);
|
|
20401
|
+
if (!decoder) return;
|
|
20402
|
+
let recovered = null;
|
|
20403
|
+
const fed = [];
|
|
20404
|
+
try {
|
|
20405
|
+
for (const [esi, data] of block.sources) decoder.add_source(serial32Add(blockStart, esi), data, 0);
|
|
20406
|
+
for (const [rsId, data] of block.repairs) {
|
|
20407
|
+
recovered ??= decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
|
|
20408
|
+
}
|
|
20409
|
+
for (const [rsId, data] of block.pooled) {
|
|
20410
|
+
if (recovered || fed.length >= block.ssbLength - admitted) break;
|
|
20411
|
+
fed.push(rsId);
|
|
20412
|
+
recovered = decoder.add_repair(blockStart, block.ssbLength, rsId, data, 0);
|
|
20413
|
+
}
|
|
20414
|
+
} catch {
|
|
20415
|
+
recovered = null;
|
|
20416
|
+
} finally {
|
|
20417
|
+
decoder.free?.();
|
|
20418
|
+
}
|
|
20419
|
+
if (recovered && this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
|
|
20420
|
+
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
20421
|
+
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
20422
|
+
fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
20423
|
+
})) {
|
|
20424
|
+
this.#unlistedAssistedRecoveries++;
|
|
20425
|
+
this.#assistBlocks.delete(key);
|
|
20426
|
+
return;
|
|
20427
|
+
}
|
|
20428
|
+
for (const rsId of fed) block.pooled.delete(rsId);
|
|
20429
|
+
const failed = (this.#assistFailures.get(key) ?? 0) + 1;
|
|
20430
|
+
this.#assistFailures.delete(key);
|
|
20431
|
+
this.#assistFailures.set(key, failed);
|
|
20432
|
+
for (const oldest of this.#assistFailures.keys()) {
|
|
20433
|
+
if (this.#assistFailures.size <= ASSIST_FAILURE_LEDGER_LIMIT) break;
|
|
20434
|
+
this.#assistFailures.delete(oldest);
|
|
20435
|
+
}
|
|
20436
|
+
}
|
|
20234
20437
|
/**
|
|
20235
20438
|
* The admission that makes this router protected under `flow` (7.2.7):
|
|
20236
20439
|
* its FEC instances discard what they hold of the flow's listed tracks so
|
|
@@ -20406,8 +20609,9 @@ var MmtpRouter = class {
|
|
|
20406
20609
|
);
|
|
20407
20610
|
}
|
|
20408
20611
|
if (ownerPacketId != null && !admitted) {
|
|
20409
|
-
const admit = this.#admitUnicast(
|
|
20612
|
+
const admit = this.#admitUnicast(repairPacketId, packet, () => this.#feedRepairPacket(packet, ownerPacketIdHint, catalogTrackName, repairTrackName, true));
|
|
20410
20613
|
if (admit === "held" || admit === "dropped") return;
|
|
20614
|
+
if (admit === "unlisted" && this.#fecManager && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackName, parsed)) return;
|
|
20411
20615
|
}
|
|
20412
20616
|
if (ownerPacketId != null && this.#fecManager) {
|
|
20413
20617
|
this.#moqRepairPidCounts[repairPacketId] = (this.#moqRepairPidCounts[repairPacketId] || 0) + 1;
|
|
@@ -20527,6 +20731,7 @@ var MmtpRouter = class {
|
|
|
20527
20731
|
const placement = fec.placeIsoRepair(ownerPacketId, catalogTrackName, repair.ssStart, repair.ssbLength);
|
|
20528
20732
|
if (placement.status !== "placed") return;
|
|
20529
20733
|
const blockStart = placement.block.start;
|
|
20734
|
+
this.#openAssistBlock(ownerPacketId, catalogTrackName, blockStart, repair.ssbLength);
|
|
20530
20735
|
if (placement.span !== void 0) {
|
|
20531
20736
|
this.#replayIsoSources(ownerPacketId, catalogTrackName, placement.span.block.start, placement.span.placed);
|
|
20532
20737
|
}
|
|
@@ -20557,6 +20762,8 @@ var MmtpRouter = class {
|
|
|
20557
20762
|
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
20558
20763
|
fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
20559
20764
|
});
|
|
20765
|
+
} else if (!recovered) {
|
|
20766
|
+
this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { rsId: repair.rsId }, repair.symbolData);
|
|
20560
20767
|
}
|
|
20561
20768
|
}
|
|
20562
20769
|
/**
|
|
@@ -20596,6 +20803,7 @@ var MmtpRouter = class {
|
|
|
20596
20803
|
if (payload.oversized) continue;
|
|
20597
20804
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
20598
20805
|
const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, catalogTrackName);
|
|
20806
|
+
if (!recovered) this.#noteAssistAdmitted(ownerPacketId, catalogTrackName, blockStart, { esi }, payload.symbolData);
|
|
20599
20807
|
if (!recovered || alreadyCompleted) continue;
|
|
20600
20808
|
this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
|
|
20601
20809
|
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
@@ -20666,6 +20874,9 @@ var MmtpRouter = class {
|
|
|
20666
20874
|
}
|
|
20667
20875
|
}
|
|
20668
20876
|
this.#parser.resetTrack(packetId, catalogTrackName);
|
|
20877
|
+
const assistPrefix = assistKey(packetId, catalogTrackName, 0).slice(0, -1);
|
|
20878
|
+
for (const key of this.#assistBlocks.keys()) if (key.startsWith(assistPrefix)) this.#assistBlocks.delete(key);
|
|
20879
|
+
for (const key of this.#assistFailures.keys()) if (key.startsWith(assistPrefix)) this.#assistFailures.delete(key);
|
|
20669
20880
|
const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);
|
|
20670
20881
|
this.#trackSynced.delete(streamKey);
|
|
20671
20882
|
this.#droppedPreSyncVideo.delete(streamKey);
|
|
@@ -21083,6 +21294,24 @@ var MmtpRouter = class {
|
|
|
21083
21294
|
fecReleaseEarliestHeldAtMs(packetId, catalogTrackName) {
|
|
21084
21295
|
return this.#parser.fecReleaseEarliestHeldAtMs(packetId, catalogTrackName);
|
|
21085
21296
|
}
|
|
21297
|
+
/**
|
|
21298
|
+
* Whether a complete RAP above SS_ID `ssId` entered this stream's FEC
|
|
21299
|
+
* release gate (see FecReleaseSequencer.rapAbove).
|
|
21300
|
+
*/
|
|
21301
|
+
fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
|
|
21302
|
+
return this.#parser.fecReleaseRapAbove(packetId, catalogTrackName, ssId);
|
|
21303
|
+
}
|
|
21304
|
+
/**
|
|
21305
|
+
* Observe each complete RAP entering an FEC release gate, with the SS_ID
|
|
21306
|
+
* its frame completed in. Called synchronously from the frame push: an
|
|
21307
|
+
* observer that drives FEC state must defer that work.
|
|
21308
|
+
*/
|
|
21309
|
+
addFecRapObserver(observer) {
|
|
21310
|
+
this.#fecRapObservers.add(observer);
|
|
21311
|
+
return () => {
|
|
21312
|
+
this.#fecRapObservers.delete(observer);
|
|
21313
|
+
};
|
|
21314
|
+
}
|
|
21086
21315
|
#requestFecPoke(packetId, catalogTrackName) {
|
|
21087
21316
|
if (this.#disposed) return;
|
|
21088
21317
|
if (this.#fecPokeDepth > 0) {
|
|
@@ -21128,6 +21357,10 @@ var MmtpRouter = class {
|
|
|
21128
21357
|
this.#onFrameGap?.(decision);
|
|
21129
21358
|
return decision;
|
|
21130
21359
|
},
|
|
21360
|
+
isRap: (data, meta) => this.#isRapForGap(data, meta),
|
|
21361
|
+
onRap: (packetId, catalogTrackName, ssId) => {
|
|
21362
|
+
for (const observer of this.#fecRapObservers) observer(packetId, catalogTrackName, ssId);
|
|
21363
|
+
},
|
|
21131
21364
|
onReleased: (data, meta) => {
|
|
21132
21365
|
if (meta.trackType !== "video" || meta.mpuSequenceNumber === void 0) return;
|
|
21133
21366
|
const timing = meta.authoredTiming;
|
|
@@ -21326,6 +21559,7 @@ var MmtpRouter = class {
|
|
|
21326
21559
|
const ownerPacketId = this.#resolveRepairOwnerPacketId(info.packetId, ingress);
|
|
21327
21560
|
if (ownerPacketId != null && this.#fecManager) {
|
|
21328
21561
|
if (hasAmbiguousCatalogTrackName(ingress, ownerPacketId)) return;
|
|
21562
|
+
if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
21329
21563
|
this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
|
|
21330
21564
|
const count = this.#repairPidCounts[info.packetId];
|
|
21331
21565
|
if (count <= 2 || count % 500 === 0) {
|
|
@@ -21493,6 +21727,15 @@ var MmtpRouter = class {
|
|
|
21493
21727
|
sourceFec.symbolData,
|
|
21494
21728
|
catalogTrackName
|
|
21495
21729
|
);
|
|
21730
|
+
if (!recovered) {
|
|
21731
|
+
this.#noteAssistAdmitted(
|
|
21732
|
+
info.packetId,
|
|
21733
|
+
catalogTrackName,
|
|
21734
|
+
sourceFec.sbn,
|
|
21735
|
+
{ esi: serial32Distance(sourceFec.sbn, sourceFec.ssId) },
|
|
21736
|
+
sourceFec.symbolData
|
|
21737
|
+
);
|
|
21738
|
+
}
|
|
21496
21739
|
if (recovered && !alreadyCompleted) {
|
|
21497
21740
|
const fec = this.#fecManager;
|
|
21498
21741
|
const sbn = sourceFec.sbn;
|
|
@@ -26566,6 +26809,8 @@ function newBlockPolicyStats(packetId, catalogTrackName, epoch) {
|
|
|
26566
26809
|
repairsSkippedPresentationDelayUnavailable: 0,
|
|
26567
26810
|
repairsSkippedRangeNoAnchor: 0,
|
|
26568
26811
|
repairsSkippedGroupCannotComplete: 0,
|
|
26812
|
+
repairsSkippedMootRap: 0,
|
|
26813
|
+
repairStops: { preempted: 0, reprioritized: 0, expired: 0, resolved: 0, reset: 0, disposed: 0, moot: 0 },
|
|
26569
26814
|
lastRepairSkip: null,
|
|
26570
26815
|
keyframeRepairFetchesSent: 0,
|
|
26571
26816
|
rangeRepairFetchesSent: 0,
|
|
@@ -26652,7 +26897,17 @@ var REPAIR_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
|
26652
26897
|
"rtt-exceeds-budget",
|
|
26653
26898
|
"presentation-delay-unavailable",
|
|
26654
26899
|
"range-no-anchor",
|
|
26655
|
-
"group-cannot-complete"
|
|
26900
|
+
"group-cannot-complete",
|
|
26901
|
+
"moot-rap"
|
|
26902
|
+
]);
|
|
26903
|
+
var REPAIR_STOP_REASONS = /* @__PURE__ */ new Set([
|
|
26904
|
+
"preempted",
|
|
26905
|
+
"reprioritized",
|
|
26906
|
+
"expired",
|
|
26907
|
+
"resolved",
|
|
26908
|
+
"reset",
|
|
26909
|
+
"disposed",
|
|
26910
|
+
"moot"
|
|
26656
26911
|
]);
|
|
26657
26912
|
var REPAIR_DEADLINE_BOUNDS = /* @__PURE__ */ new Set(["wait", "presentation", "keyframe", "retention"]);
|
|
26658
26913
|
var GAP_REASONS = {
|
|
@@ -26711,6 +26966,8 @@ function isRepairLifecycleEvent(value) {
|
|
|
26711
26966
|
]) && isRepairBlockIdentity(block) && (value["priority"] === 0 || value["priority"] === 1 || value["priority"] === 2) && isFiniteNumber(value["rttMs"]) && value["rttMs"] > 0 && isFiniteNumber(value["budgetMs"]) && value["budgetMs"] > value["rttMs"] && isNullableNonNegative(value["delayMs"]) && isNullableNonNegative(value["blockAgeMs"]) && value["delayMs"] === null === (value["blockAgeMs"] === null) && REPAIR_DEADLINE_BOUNDS.has(value["bound"]);
|
|
26712
26967
|
case "repair-skipped":
|
|
26713
26968
|
return hasExactKeys(value, ["version", "type", "block", "reason", "rttMs", "budgetMs"]) && isRepairBlockIdentity(block) && REPAIR_SKIP_REASONS.has(value["reason"]) && isNullableNonNegative(value["rttMs"]) && isFiniteNumber(value["budgetMs"]);
|
|
26969
|
+
case "repair-stopped":
|
|
26970
|
+
return hasExactKeys(value, ["version", "type", "block", "reason"]) && isRepairBlockIdentity(block) && REPAIR_STOP_REASONS.has(value["reason"]);
|
|
26714
26971
|
case "repair-priority": {
|
|
26715
26972
|
const blockClass = value["blockClass"];
|
|
26716
26973
|
const action = value["action"];
|
|
@@ -28120,6 +28377,14 @@ var MulticastManager = class {
|
|
|
28120
28377
|
}
|
|
28121
28378
|
}
|
|
28122
28379
|
}
|
|
28380
|
+
const factory = typeof wasmDecoderSource === "function" ? wasmDecoderSource : null;
|
|
28381
|
+
const fecTracks = fecTrackIndexes.map((i) => catalog.tracks[i]);
|
|
28382
|
+
this.#router.setScratchFecDecoderFactory(factory && ((ownerPacketId, catalogTrackName) => {
|
|
28383
|
+
const track = fecTracks.find((t) => t.packetId === ownerPacketId && t.name === catalogTrackName);
|
|
28384
|
+
if (!track) return null;
|
|
28385
|
+
const decoder = factory(track);
|
|
28386
|
+
return [...fecDecoders.values()].includes(decoder) ? null : decoder;
|
|
28387
|
+
}));
|
|
28123
28388
|
this.#switchingSets.clear();
|
|
28124
28389
|
this.#repairPacketIds.clear();
|
|
28125
28390
|
this.#router.clearRepairPacketIdOwners();
|
|
@@ -28293,6 +28558,7 @@ var MulticastManager = class {
|
|
|
28293
28558
|
else if (reason === "rtt-exceeds-budget") stats.repairsSkippedRttExceedsBudget++;
|
|
28294
28559
|
else if (reason === "range-no-anchor") stats.repairsSkippedRangeNoAnchor++;
|
|
28295
28560
|
else if (reason === "group-cannot-complete") stats.repairsSkippedGroupCannotComplete++;
|
|
28561
|
+
else if (reason === "moot-rap") stats.repairsSkippedMootRap++;
|
|
28296
28562
|
else stats.repairsSkippedPresentationDelayUnavailable++;
|
|
28297
28563
|
stats.lastRepairSkip = {
|
|
28298
28564
|
reason,
|
|
@@ -28353,6 +28619,27 @@ var MulticastManager = class {
|
|
|
28353
28619
|
block: repairBlockIdentity(packetId, sbn, catalogTrackName)
|
|
28354
28620
|
});
|
|
28355
28621
|
}
|
|
28622
|
+
/** Record a running repair FETCH the scheduler stopped, and why. */
|
|
28623
|
+
noteRepairStopped(reason, sbn, packetId, catalogTrackName) {
|
|
28624
|
+
this.#statsFor(packetId, catalogTrackName).repairStops[reason]++;
|
|
28625
|
+
this.#emitRepairLifecycle({
|
|
28626
|
+
version: 2,
|
|
28627
|
+
type: "repair-stopped",
|
|
28628
|
+
block: repairBlockIdentity(packetId, sbn, catalogTrackName),
|
|
28629
|
+
reason
|
|
28630
|
+
});
|
|
28631
|
+
}
|
|
28632
|
+
/**
|
|
28633
|
+
* Whether a complete RAP above SS_ID `ssId` reached the stream's in-order
|
|
28634
|
+
* release gate (draft-ramadan-moq-fec 11.4.2: repair below it is moot).
|
|
28635
|
+
*/
|
|
28636
|
+
rapCompleteAbove(packetId, catalogTrackName, ssId) {
|
|
28637
|
+
return this.#router.fecReleaseRapAbove(packetId, catalogTrackName, ssId);
|
|
28638
|
+
}
|
|
28639
|
+
/** Observe each complete RAP reaching a release gate (synchronous: defer FEC work). */
|
|
28640
|
+
addRapCompleteObserver(observer) {
|
|
28641
|
+
return this.#router.addFecRapObserver(observer);
|
|
28642
|
+
}
|
|
28356
28643
|
/** Catalog/reset epoch of a packet's streams; gap decisions carry it. */
|
|
28357
28644
|
epochFor(packetId) {
|
|
28358
28645
|
return this.#fec.epochFor(packetId);
|
|
@@ -28368,6 +28655,7 @@ var MulticastManager = class {
|
|
|
28368
28655
|
if (stats.epoch !== this.#fec.epochFor(stats.packetId)) continue;
|
|
28369
28656
|
out[key] = {
|
|
28370
28657
|
...stats,
|
|
28658
|
+
repairStops: { ...stats.repairStops },
|
|
28371
28659
|
lastRepairSkip: stats.lastRepairSkip === null ? null : { ...stats.lastRepairSkip }
|
|
28372
28660
|
};
|
|
28373
28661
|
}
|
|
@@ -30731,6 +31019,34 @@ function wireRepairFetch(mc, opts) {
|
|
|
30731
31019
|
run.stopped = reason;
|
|
30732
31020
|
run.controller.abort();
|
|
30733
31021
|
if (reason === "preempted") releaseHold(entry);
|
|
31022
|
+
mc.noteRepairStopped?.(reason, entry.sbn, entry.packetId, entry.catalogTrackName);
|
|
31023
|
+
};
|
|
31024
|
+
const lastSsId = (entry) => {
|
|
31025
|
+
if (entry.range) return entry.sbn + entry.range.holeLength - 1;
|
|
31026
|
+
const length = symbolCounts(entry.sbn, entry.packetId, entry.catalogTrackName)?.authoredLength;
|
|
31027
|
+
return length === void 0 ? void 0 : entry.sbn + length - 1;
|
|
31028
|
+
};
|
|
31029
|
+
const moot = (entry) => {
|
|
31030
|
+
if (entry.priority !== 0) return false;
|
|
31031
|
+
const last = lastSsId(entry);
|
|
31032
|
+
if (last === void 0) return false;
|
|
31033
|
+
try {
|
|
31034
|
+
return mc.rapCompleteAbove?.(entry.packetId, entry.catalogTrackName, last) === true;
|
|
31035
|
+
} catch {
|
|
31036
|
+
return false;
|
|
31037
|
+
}
|
|
31038
|
+
};
|
|
31039
|
+
const skip = (entry, reason, rttMs, budgetMs) => {
|
|
31040
|
+
retire(entry);
|
|
31041
|
+
releaseHold(entry);
|
|
31042
|
+
clearEntry(entry);
|
|
31043
|
+
mc.noteRepairSkipped(
|
|
31044
|
+
reason,
|
|
31045
|
+
{ rttMs, budgetMs, priority: entry.priority },
|
|
31046
|
+
entry.sbn,
|
|
31047
|
+
entry.packetId,
|
|
31048
|
+
entry.catalogTrackName
|
|
31049
|
+
);
|
|
30734
31050
|
};
|
|
30735
31051
|
const readKeyframeBudget = (packetId, catalogTrackName) => {
|
|
30736
31052
|
const reader = opts.keyframeRepairBudgetMs;
|
|
@@ -30755,31 +31071,22 @@ function wireRepairFetch(mc, opts) {
|
|
|
30755
31071
|
const rttMs = readRtt();
|
|
30756
31072
|
const reason = !entry.range && !groupCanComplete(entry.sbn, entry.packetId, entry.catalogTrackName) ? "group-cannot-complete" : rttMs === null ? "rtt-unmeasured" : rttMs < budgetMs ? null : "rtt-exceeds-budget";
|
|
30757
31073
|
if (reason === null) return rttMs;
|
|
30758
|
-
|
|
30759
|
-
releaseHold(entry);
|
|
30760
|
-
clearEntry(entry);
|
|
30761
|
-
mc.noteRepairSkipped(
|
|
30762
|
-
reason,
|
|
30763
|
-
{ rttMs, budgetMs, priority: entry.priority },
|
|
30764
|
-
entry.sbn,
|
|
30765
|
-
entry.packetId,
|
|
30766
|
-
entry.catalogTrackName
|
|
30767
|
-
);
|
|
31074
|
+
skip(entry, reason, rttMs, budgetMs);
|
|
30768
31075
|
return null;
|
|
30769
31076
|
};
|
|
30770
31077
|
const retire = (entry) => {
|
|
30771
31078
|
if (entries.get(entry.key) === entry) entries.delete(entry.key);
|
|
30772
31079
|
if (entry.expiry !== void 0) clearTimeout(entry.expiry);
|
|
30773
31080
|
};
|
|
30774
|
-
const
|
|
31081
|
+
const outstandingBelow = (priority) => {
|
|
30775
31082
|
for (const entry of entries.values()) {
|
|
30776
|
-
if (entry.priority <
|
|
31083
|
+
if (entry.priority < priority) return true;
|
|
30777
31084
|
}
|
|
30778
31085
|
return false;
|
|
30779
31086
|
};
|
|
30780
|
-
const
|
|
31087
|
+
const preemptBestEffort = () => {
|
|
30781
31088
|
for (const entry of entries.values()) {
|
|
30782
|
-
if (entry.priority
|
|
31089
|
+
if (entry.priority === 0 || !entry.running) continue;
|
|
30783
31090
|
stop(entry, "preempted");
|
|
30784
31091
|
note("repair-preempted", entry);
|
|
30785
31092
|
}
|
|
@@ -30799,8 +31106,13 @@ function wireRepairFetch(mc, opts) {
|
|
|
30799
31106
|
};
|
|
30800
31107
|
const start = (entry, counted = true) => {
|
|
30801
31108
|
const now = performance.now();
|
|
31109
|
+
if (moot(entry)) {
|
|
31110
|
+
skip(entry, "moot-rap", readRtt(), entry.deadlineAt - now);
|
|
31111
|
+
return;
|
|
31112
|
+
}
|
|
30802
31113
|
const rttMs = admit(entry, now);
|
|
30803
31114
|
if (rttMs === null) return;
|
|
31115
|
+
if (entry.priority === 0) preemptBestEffort();
|
|
30804
31116
|
if (counted) entry.attempts++;
|
|
30805
31117
|
mc.noteRepairFetchSent?.(entry.priority, entry.sbn, entry.packetId, entry.catalogTrackName, {
|
|
30806
31118
|
rttMs,
|
|
@@ -30844,9 +31156,13 @@ function wireRepairFetch(mc, opts) {
|
|
|
30844
31156
|
queued.push(entry);
|
|
30845
31157
|
}
|
|
30846
31158
|
for (const entry of queued) {
|
|
30847
|
-
if (entry.priority
|
|
31159
|
+
if (entry.priority === 0) start(entry);
|
|
31160
|
+
}
|
|
31161
|
+
if (outstandingBelow(1)) return;
|
|
31162
|
+
for (const entry of queued) {
|
|
31163
|
+
if (entry.priority === 1) start(entry);
|
|
30848
31164
|
}
|
|
30849
|
-
if (
|
|
31165
|
+
if (outstandingBelow(2)) return;
|
|
30850
31166
|
queued.filter((entry) => entry.priority === 2).sort((a, b) => a.deadlineAt - b.deadlineAt || a.order - b.order).forEach((entry) => start(entry));
|
|
30851
31167
|
};
|
|
30852
31168
|
const symbolCounts = (sbn, packetId, catalogTrackName) => {
|
|
@@ -30974,7 +31290,6 @@ function wireRepairFetch(mc, opts) {
|
|
|
30974
31290
|
stop(entry2, "reprioritized");
|
|
30975
31291
|
start(entry2, false);
|
|
30976
31292
|
}
|
|
30977
|
-
preemptDeltas();
|
|
30978
31293
|
dispatch();
|
|
30979
31294
|
return;
|
|
30980
31295
|
}
|
|
@@ -31106,7 +31421,6 @@ function wireRepairFetch(mc, opts) {
|
|
|
31106
31421
|
};
|
|
31107
31422
|
entries.set(key, entry);
|
|
31108
31423
|
noteBudget(entry, deadline.extensionMs);
|
|
31109
|
-
if (entry.priority < 2) preemptDeltas();
|
|
31110
31424
|
dispatch();
|
|
31111
31425
|
});
|
|
31112
31426
|
const rangeFor = (loss, repairTrack) => {
|
|
@@ -31182,15 +31496,28 @@ function wireRepairFetch(mc, opts) {
|
|
|
31182
31496
|
}
|
|
31183
31497
|
entries.set(entry.key, entry);
|
|
31184
31498
|
entry.expiry = setTimeout(dispatch, Math.max(0, entry.deadlineAt - performance.now()) + 1);
|
|
31185
|
-
preemptDeltas();
|
|
31186
31499
|
dispatch();
|
|
31187
31500
|
});
|
|
31188
31501
|
}
|
|
31502
|
+
const removeRapObserver = mc.addRapCompleteObserver?.((packetId) => {
|
|
31503
|
+
queueMicrotask(() => {
|
|
31504
|
+
if (disposed) return;
|
|
31505
|
+
for (const entry of [...entries.values()]) {
|
|
31506
|
+
if (entry.packetId !== packetId || !entry.running || !moot(entry)) continue;
|
|
31507
|
+
stop(entry, "moot");
|
|
31508
|
+
retire(entry);
|
|
31509
|
+
releaseHold(entry);
|
|
31510
|
+
clearEntry(entry);
|
|
31511
|
+
}
|
|
31512
|
+
dispatch();
|
|
31513
|
+
});
|
|
31514
|
+
});
|
|
31189
31515
|
return () => {
|
|
31190
31516
|
disposed = true;
|
|
31191
31517
|
mc.setUnicastRepair?.(false);
|
|
31192
31518
|
removeResetObserver();
|
|
31193
31519
|
removeLifecycleObserver();
|
|
31520
|
+
removeRapObserver?.();
|
|
31194
31521
|
for (const entry of [...entries.values()]) {
|
|
31195
31522
|
clearEntry(entry);
|
|
31196
31523
|
if (entry.running) releaseHold(entry);
|