@blockcast/mmt-container 2.2.0-main.f480a7ede8b1 → 2.2.0-main.fb5c7dfbe2de
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 +891 -141
- package/dist/browser-runtime.js.map +3 -3
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +67 -11
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +283 -54
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-worker-setup.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.d.ts +39 -13
- package/dist/iso-block-locator.d.ts.map +1 -1
- package/dist/iso-block-locator.js +80 -25
- package/dist/iso-block-locator.js.map +2 -2
- package/dist/media-router.js.map +1 -1
- package/dist/mmtp-container-parser.d.ts +42 -8
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +121 -17
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +120 -18
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +121 -17
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.d.ts +28 -8
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +407 -52
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.d.ts +13 -3
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +712 -115
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.js +2 -2
- package/dist/multicast-transport.js.map +2 -2
- package/dist/no-modules.js +121 -17
- package/dist/no-modules.js.map +2 -2
- package/dist/repair-fetch.d.ts +36 -8
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +285 -26
- package/dist/repair-fetch.js.map +3 -3
- package/dist/track-session.d.ts.map +1 -1
- package/dist/track-session.js +1 -0
- package/dist/track-session.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +121 -17
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +5 -5
- package/src/fec-manager.ts +304 -28
- package/src/index.ts +2 -0
- package/src/iso-block-locator.ts +114 -31
- package/src/mmtp-container-parser.ts +210 -21
- package/src/mmtp-router.ts +488 -45
- package/src/multicast-manager.ts +44 -6
- package/src/repair-fetch.ts +363 -49
- package/src/track-session.ts +3 -0
package/dist/fec-manager.js
CHANGED
|
@@ -327,6 +327,15 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
327
327
|
this.#heldEvicted = 0;
|
|
328
328
|
/** Pending holes (see `deferIncompleteGaps`): unstated, sources still held. */
|
|
329
329
|
this.#holes = [];
|
|
330
|
+
/** Holes the horizon passed while pending: sources held until closed. */
|
|
331
|
+
this.#expired = [];
|
|
332
|
+
/** Of `#expired`, those `takeExpiredHoles` has not handed out yet. */
|
|
333
|
+
this.#newlyExpired = [];
|
|
334
|
+
/**
|
|
335
|
+
* Spans closed from expired holes: behind the horizon, so kept only for
|
|
336
|
+
* `locate` while their sources are fed, until the next retirement.
|
|
337
|
+
*/
|
|
338
|
+
this.#closedBehind = [];
|
|
330
339
|
/**
|
|
331
340
|
* Keep a gap that is missing a source as a pending hole rather than a span,
|
|
332
341
|
* so a repair FETCHed for it (draft-ramadan-moq-fec §7.5) can still state
|
|
@@ -350,6 +359,9 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
350
359
|
#newest;
|
|
351
360
|
#newestSource;
|
|
352
361
|
#holes;
|
|
362
|
+
#expired;
|
|
363
|
+
#newlyExpired;
|
|
364
|
+
#closedBehind;
|
|
353
365
|
static #checkedRetention(retentionBlocks) {
|
|
354
366
|
if (!Number.isSafeInteger(retentionBlocks) || retentionBlocks <= 0) {
|
|
355
367
|
throw new Error(
|
|
@@ -419,17 +431,29 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
419
431
|
}
|
|
420
432
|
/** The known block or span containing `ssId`, with its ESI. */
|
|
421
433
|
locate(ssId) {
|
|
422
|
-
for (
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
434
|
+
for (const blocks of [this.#blocks, this.#closedBehind]) {
|
|
435
|
+
for (let i = blocks.length - 1; i >= 0; i--) {
|
|
436
|
+
const block = blocks[i];
|
|
437
|
+
if (isoBlockContains(block.start, block.length, ssId)) {
|
|
438
|
+
return { block, esi: serial32Distance(block.start, ssId) };
|
|
439
|
+
}
|
|
426
440
|
}
|
|
427
441
|
}
|
|
428
442
|
return null;
|
|
429
443
|
}
|
|
430
|
-
/** Number of pending
|
|
444
|
+
/** Number of holes not closed yet, pending or past the horizon. */
|
|
431
445
|
get holeCount() {
|
|
432
|
-
return this.#holes.length;
|
|
446
|
+
return this.#holes.length + this.#expired.length;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* The pending holes the horizon has passed since the last call, in the
|
|
450
|
+
* order it passed them. Each is no longer pending: no repair states a block
|
|
451
|
+
* in it (that repair is `stale`, as for a span the horizon retired) and the
|
|
452
|
+
* release gate does not wait for it. Its owner must close it
|
|
453
|
+
* (`closeHolesWithin`), which turns it into a span that is not kept here.
|
|
454
|
+
*/
|
|
455
|
+
takeExpiredHoles() {
|
|
456
|
+
return this.#newlyExpired.splice(0);
|
|
433
457
|
}
|
|
434
458
|
/** The stated block (never a span) ending exactly at `end`. */
|
|
435
459
|
blockEndingAt(end) {
|
|
@@ -485,9 +509,11 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
485
509
|
/**
|
|
486
510
|
* Apply one repair symbol's `(SS_Start, SSB_length)`. The first repair of a
|
|
487
511
|
* block states its range and places every held source in it; later repair
|
|
488
|
-
* symbols of the same block change nothing. A range
|
|
489
|
-
*
|
|
490
|
-
*
|
|
512
|
+
* symbols of the same block change nothing. A repair whose range is exactly
|
|
513
|
+
* a repair-less span states that span's one block. A range that contradicts
|
|
514
|
+
* a known block (a different length, or an overlap with another block or
|
|
515
|
+
* with part of a span) fails closed: the repair symbol is dropped and no
|
|
516
|
+
* block changes.
|
|
491
517
|
* A newly stated block retires behind its SS_Start (see the class note).
|
|
492
518
|
* A block so far past `newest` that stating it would retire the position
|
|
493
519
|
* `newest` is refused as `ahead`. When `newest` is a placed block's SS_Start
|
|
@@ -506,7 +532,16 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
506
532
|
const known = this.blockAt(start);
|
|
507
533
|
if (known !== void 0) {
|
|
508
534
|
if (known.span) {
|
|
509
|
-
|
|
535
|
+
if (known.length === length) {
|
|
536
|
+
const block2 = { start, length, span: false };
|
|
537
|
+
this.#blocks[this.#blocks.indexOf(known)] = block2;
|
|
538
|
+
return { status: "placed", block: block2, placed: [] };
|
|
539
|
+
}
|
|
540
|
+
return length < known.length ? {
|
|
541
|
+
status: "overlap",
|
|
542
|
+
reason: `repair block ${start}+${length} is only the first block of repair-less span ${known.start}+${known.length}`,
|
|
543
|
+
ofSpan: known
|
|
544
|
+
} : { status: "overlap", reason: `repair block ${start}+${length} runs past repair-less span ${known.start}+${known.length}` };
|
|
510
545
|
}
|
|
511
546
|
if (known.length !== length) {
|
|
512
547
|
return {
|
|
@@ -574,18 +609,28 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
574
609
|
return span === void 0 ? { status: "placed", block, placed } : { status: "placed", block, placed, span };
|
|
575
610
|
}
|
|
576
611
|
/**
|
|
577
|
-
* Turn every
|
|
578
|
-
*
|
|
579
|
-
*
|
|
612
|
+
* Turn every hole inside `[start, start + length)` into a repair-less span,
|
|
613
|
+
* claiming its held sources: its range FETCH ended, so nothing more will
|
|
614
|
+
* state its blocks. A hole the horizon passed (`takeExpiredHoles`) leaves a
|
|
615
|
+
* span that is not kept among the blocks, as the horizon would retire it at
|
|
616
|
+
* once; only `locate` finds it, until the next retirement, so its sources
|
|
617
|
+
* can be fed. Returns the spans, in SS_ID order.
|
|
580
618
|
*/
|
|
581
619
|
closeHolesWithin(start, length) {
|
|
582
620
|
const closed = [];
|
|
583
|
-
for (const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
621
|
+
for (const holes of [this.#holes, this.#expired]) {
|
|
622
|
+
for (const hole of [...holes]) {
|
|
623
|
+
if (!isoBlockContains(start, length, hole.start)) continue;
|
|
624
|
+
holes.splice(holes.indexOf(hole), 1);
|
|
625
|
+
const spanBlock = { start: hole.start, length: hole.length, span: true };
|
|
626
|
+
closed.push({ block: spanBlock, placed: this.#claim(spanBlock) });
|
|
627
|
+
if (holes === this.#holes) {
|
|
628
|
+
this.#blocks.push(spanBlock);
|
|
629
|
+
} else {
|
|
630
|
+
this.#closedBehind.push(spanBlock);
|
|
631
|
+
if (this.#newlyExpired.includes(hole)) this.#newlyExpired.splice(this.#newlyExpired.indexOf(hole), 1);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
589
634
|
}
|
|
590
635
|
return closed.sort((x, y) => serial32Distance(start, x.block.start) - serial32Distance(start, y.block.start));
|
|
591
636
|
}
|
|
@@ -599,15 +644,17 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
599
644
|
return void 0;
|
|
600
645
|
}
|
|
601
646
|
/**
|
|
602
|
-
* Retire every block and span whose last SS_ID, and every held source
|
|
603
|
-
* lies more than `retentionBlocks` blocks behind
|
|
604
|
-
* lower-bound metric (`isoBlocksBehindLowerBound`)
|
|
605
|
-
* horizon uses
|
|
647
|
+
* Retire every block and span whose last SS_ID, and every held source
|
|
648
|
+
* outside a hole, lies more than `retentionBlocks` blocks behind
|
|
649
|
+
* `position`, with the shared lower-bound metric (`isoBlocksBehindLowerBound`)
|
|
650
|
+
* every ISO interleave horizon uses; a pending hole so far behind expires
|
|
651
|
+
* (`takeExpiredHoles`). Returns the retired block starts.
|
|
606
652
|
*/
|
|
607
653
|
retireBehind(position) {
|
|
608
654
|
this.observe(position);
|
|
609
655
|
const horizon = this.#retentionBlocks;
|
|
610
656
|
const retired = [];
|
|
657
|
+
this.#closedBehind.length = 0;
|
|
611
658
|
for (let i = this.#blocks.length - 1; i >= 0; i--) {
|
|
612
659
|
const block = this.#blocks[i];
|
|
613
660
|
const last = serial32Add(block.start, block.length - 1);
|
|
@@ -619,10 +666,15 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
619
666
|
for (let i = this.#holes.length - 1; i >= 0; i--) {
|
|
620
667
|
const hole = this.#holes[i];
|
|
621
668
|
const last = serial32Add(hole.start, hole.length - 1);
|
|
622
|
-
if (isoBlocksBehindLowerBound(last, position, this.#kMax)
|
|
669
|
+
if (isoBlocksBehindLowerBound(last, position, this.#kMax) <= horizon) continue;
|
|
670
|
+
this.#holes.splice(i, 1);
|
|
671
|
+
this.#expired.push(hole);
|
|
672
|
+
this.#newlyExpired.push(hole);
|
|
623
673
|
}
|
|
624
674
|
for (const ssId of [...this.#held.keys()]) {
|
|
625
|
-
if (isoBlocksBehindLowerBound(ssId, position, this.#kMax) > horizon) this.#
|
|
675
|
+
if (isoBlocksBehindLowerBound(ssId, position, this.#kMax) > horizon && !this.#holes.some((hole) => isoBlockContains(hole.start, hole.length, ssId)) && !this.#expired.some((hole) => isoBlockContains(hole.start, hole.length, ssId))) {
|
|
676
|
+
this.#held.delete(ssId);
|
|
677
|
+
}
|
|
626
678
|
}
|
|
627
679
|
return retired;
|
|
628
680
|
}
|
|
@@ -630,6 +682,9 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
630
682
|
reset() {
|
|
631
683
|
this.#blocks.length = 0;
|
|
632
684
|
this.#holes.length = 0;
|
|
685
|
+
this.#expired.length = 0;
|
|
686
|
+
this.#newlyExpired.length = 0;
|
|
687
|
+
this.#closedBehind.length = 0;
|
|
633
688
|
this.#held.clear();
|
|
634
689
|
this.#coverageEnd = void 0;
|
|
635
690
|
this.#newest = void 0;
|
|
@@ -1098,6 +1153,7 @@ var WORKER_ERROR_LOG_CAP = 5;
|
|
|
1098
1153
|
var WORKER_ERROR_KIND_CAP = 32;
|
|
1099
1154
|
var WORKER_ERROR_OVERFLOW_KIND = "other";
|
|
1100
1155
|
var STARTUP_REPAIR_WAIT_SOURCE_BOUNDARIES = 2;
|
|
1156
|
+
var NOT_TAKEN = Object.freeze({ taken: false, recovered: null });
|
|
1101
1157
|
function isMulticastSymbolDelivery(value) {
|
|
1102
1158
|
return value === "datagram" || value === "ordered-relay";
|
|
1103
1159
|
}
|
|
@@ -1191,10 +1247,18 @@ var FECManager = class _FECManager {
|
|
|
1191
1247
|
// 'pending' forever, which would park every later frame of its stream in
|
|
1192
1248
|
// the in-order release gate. Each entry owns its handle; the callback
|
|
1193
1249
|
// checks it still owns the key, so a settled or reset key is never touched.
|
|
1250
|
+
/**
|
|
1251
|
+
* Per owed Worker cleanup: the bound on its acknowledgement, when its block
|
|
1252
|
+
* was due, and when its cleanup was asked for (see `onCleanupComplete`).
|
|
1253
|
+
*/
|
|
1194
1254
|
this.#pendingWorkerCleanupBounds = /* @__PURE__ */ new Map();
|
|
1195
1255
|
// Source symbol count per track-scoped block — marks complete when K symbols arrive.
|
|
1196
1256
|
this.#blockSourceCounts = /* @__PURE__ */ new Map();
|
|
1197
1257
|
this.#blockSourceEsis = /* @__PURE__ */ new Map();
|
|
1258
|
+
// Source ESIs per block that another FEC instance rebuilt and fed in
|
|
1259
|
+
// (feedRebuiltSourceSymbol). Kept apart from #blockSourceEsis: they are
|
|
1260
|
+
// not network arrivals and never make a block clean.
|
|
1261
|
+
this.#blockRebuiltEsis = /* @__PURE__ */ new Map();
|
|
1198
1262
|
// Authored SSB_length per block (ISO/IEC 23008-1 §C.5.3), learned from the
|
|
1199
1263
|
// first repair symbol. The FFmpeg moq_mmt publisher closes a block every D
|
|
1200
1264
|
// media units, usually before it holds K symbols; under iso-ssbg1-v1
|
|
@@ -1203,6 +1267,9 @@ var FECManager = class _FECManager {
|
|
|
1203
1267
|
// SSB_length source arrivals and recoverable from SSB_length received
|
|
1204
1268
|
// symbols; waiting for K fails every one of them.
|
|
1205
1269
|
this.#blockAuthoredLengths = /* @__PURE__ */ new Map();
|
|
1270
|
+
// Keys whose authored length is a repair-less span's (#openIsoSpan), until
|
|
1271
|
+
// a repair states the block there or the span resolves.
|
|
1272
|
+
this.#isoSpanKeys = /* @__PURE__ */ new Set();
|
|
1206
1273
|
this.#ssbLengthConflictCount = 0;
|
|
1207
1274
|
this.#blockRepairRsIds = /* @__PURE__ */ new Map();
|
|
1208
1275
|
// RSB_length per block (C.5.3 Repair FEC Payload ID): how many repair
|
|
@@ -1400,7 +1467,10 @@ var FECManager = class _FECManager {
|
|
|
1400
1467
|
// arrives via onBlockRecovered callback.
|
|
1401
1468
|
this.onBlockStarted = null;
|
|
1402
1469
|
this.onBlockComplete = null;
|
|
1403
|
-
/**
|
|
1470
|
+
/**
|
|
1471
|
+
* A block reached its fail bound. `blockClass` is its class at failure.
|
|
1472
|
+
* `span` is set when what failed is a repair-less span starting at `sbn`.
|
|
1473
|
+
*/
|
|
1404
1474
|
this.onBlockFailed = null;
|
|
1405
1475
|
/**
|
|
1406
1476
|
* Fired once per stream when its Worker decoder stops being routable.
|
|
@@ -1442,6 +1512,8 @@ var FECManager = class _FECManager {
|
|
|
1442
1512
|
* for it, which it must be.
|
|
1443
1513
|
*/
|
|
1444
1514
|
this.#onUndelimitedLoss = null;
|
|
1515
|
+
/** Set with the loss handler: true takes an expired hole to close (`closeIsoHoles`). */
|
|
1516
|
+
this.#onIsoHoleExpired = null;
|
|
1445
1517
|
/** A block every symbol of which vanished was created by SBN evidence. */
|
|
1446
1518
|
this.onBlockVanished = null;
|
|
1447
1519
|
/** A block reached a terminal state (completed, recovered, or failed). */
|
|
@@ -1484,7 +1556,9 @@ var FECManager = class _FECManager {
|
|
|
1484
1556
|
#pendingWorkerCleanupBounds;
|
|
1485
1557
|
#blockSourceCounts;
|
|
1486
1558
|
#blockSourceEsis;
|
|
1559
|
+
#blockRebuiltEsis;
|
|
1487
1560
|
#blockAuthoredLengths;
|
|
1561
|
+
#isoSpanKeys;
|
|
1488
1562
|
#ssbLengthConflictCount;
|
|
1489
1563
|
#blockRepairRsIds;
|
|
1490
1564
|
#blockRsbLengths;
|
|
@@ -1551,6 +1625,7 @@ var FECManager = class _FECManager {
|
|
|
1551
1625
|
#decoderUnavailableEvents;
|
|
1552
1626
|
#repairSymbolSizeMismatchCount;
|
|
1553
1627
|
#onUndelimitedLoss;
|
|
1628
|
+
#onIsoHoleExpired;
|
|
1554
1629
|
#encodeTrackName(catalogTrackName) {
|
|
1555
1630
|
let encoded = this.#encodedTrackNames.get(catalogTrackName);
|
|
1556
1631
|
if (encoded === void 0) {
|
|
@@ -2022,7 +2097,10 @@ var FECManager = class _FECManager {
|
|
|
2022
2097
|
timer.handle = null;
|
|
2023
2098
|
this.#requestRepair(sbn, packetId, catalogTrackName);
|
|
2024
2099
|
};
|
|
2025
|
-
timer.handle = setTimeout(
|
|
2100
|
+
timer.handle = setTimeout(
|
|
2101
|
+
onFire,
|
|
2102
|
+
this.#receiptAtMs === void 0 ? Math.max(0, deadlineMs - performance.now()) : this.#receiptTimerDelayMs(deadlineMs, 0)
|
|
2103
|
+
);
|
|
2026
2104
|
this.#repairDeadlines.set(key, timer);
|
|
2027
2105
|
}
|
|
2028
2106
|
#requestRepair(sbn, packetId, catalogTrackName, settledByOrder = false) {
|
|
@@ -2166,8 +2244,14 @@ var FECManager = class _FECManager {
|
|
|
2166
2244
|
* Vanished blocks are not enumerated here: their starts are unknown until a
|
|
2167
2245
|
* repair states them. The SS_IDs between two stated blocks become one
|
|
2168
2246
|
* repair-less span when the later block's repair arrives (placeIsoRepair).
|
|
2247
|
+
*
|
|
2248
|
+
* A `held` source arrived before its block was known and is fed only now
|
|
2249
|
+
* that a repair, a span or a closed hole placed it: how far its block start
|
|
2250
|
+
* then lies behind the frontier measures how long the receiver held it, not
|
|
2251
|
+
* where the stream was, so only its SS_ID and packet_sequence_number judge
|
|
2252
|
+
* it (BLO-52085).
|
|
2169
2253
|
*/
|
|
2170
|
-
#noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber, ssId = sbn) {
|
|
2254
|
+
#noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber, ssId = sbn, held = false) {
|
|
2171
2255
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
2172
2256
|
const now = performance.now();
|
|
2173
2257
|
const previous = (symbol === "source" ? this.#sourceFrontiers : this.#highestSbn).get(streamKey);
|
|
@@ -2206,7 +2290,7 @@ var FECManager = class _FECManager {
|
|
|
2206
2290
|
return sent <= 2147483647 && serial32Distance(from.ssId, to.ssId) <= sent + config.k;
|
|
2207
2291
|
};
|
|
2208
2292
|
if (!this.#blockPrecedes(previous.sbn, sbn)) {
|
|
2209
|
-
if (symbol === "source" && window !== null && this.#blockPrecedes(sbn, previous.sbn) && (this.#blocksBehind(sbn, previous.sbn, packetId) > this.#isoRetentionBlocks(packetId) || psn !== void 0 && anchor !== void 0 && serial32Precedes(ssId, anchor.ssId) && psn !== anchor.psn && !sentInOrder({ ssId, psn }, anchor))) {
|
|
2293
|
+
if (symbol === "source" && window !== null && this.#blockPrecedes(sbn, previous.sbn) && (!held && this.#blocksBehind(sbn, previous.sbn, packetId) > this.#isoRetentionBlocks(packetId) || psn !== void 0 && anchor !== void 0 && serial32Precedes(ssId, anchor.ssId) && psn !== anchor.psn && !sentInOrder({ ssId, psn }, anchor))) {
|
|
2210
2294
|
discontinuity();
|
|
2211
2295
|
return;
|
|
2212
2296
|
}
|
|
@@ -2468,17 +2552,21 @@ var FECManager = class _FECManager {
|
|
|
2468
2552
|
this.#blockWaitDeadlinesMs.delete(key);
|
|
2469
2553
|
this.#repairHoldUntilMs.delete(key);
|
|
2470
2554
|
this.#traceLifecycle("timeout-queued", sbn, packetId, catalogTrackName);
|
|
2471
|
-
this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName);
|
|
2555
|
+
this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName, currentDeadlineMs ?? firedAtMs);
|
|
2472
2556
|
};
|
|
2473
|
-
timer.handle = setTimeout(
|
|
2557
|
+
timer.handle = setTimeout(
|
|
2558
|
+
onFire,
|
|
2559
|
+
this.#receiptAtMs === void 0 ? Math.max(1, Math.ceil(deadlineMs - nowMs)) : this.#receiptTimerDelayMs(deadlineMs, 1)
|
|
2560
|
+
);
|
|
2474
2561
|
this.#activeBlocks.set(key, timer);
|
|
2475
2562
|
this.#indexBlockKey(key, sbn, packetId);
|
|
2476
2563
|
}
|
|
2477
2564
|
/**
|
|
2478
2565
|
* Hand a block whose delivery is over to its decoder for finalization: the
|
|
2479
|
-
* datagram deadline fired, or the ordered relay moved past it.
|
|
2566
|
+
* datagram deadline fired, or the ordered relay moved past it. `dueAtMs`
|
|
2567
|
+
* is when it was due: its deadline, or now.
|
|
2480
2568
|
*/
|
|
2481
|
-
#finalizeExpiredBlock(sbn, packetId, catalogTrackName) {
|
|
2569
|
+
#finalizeExpiredBlock(sbn, packetId, catalogTrackName, dueAtMs = performance.now()) {
|
|
2482
2570
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
2483
2571
|
if (this.#materializedBlocks.has(key) && !this.#blockPathCounts.has(key)) {
|
|
2484
2572
|
this.#failBlock(sbn, packetId, catalogTrackName);
|
|
@@ -2494,7 +2582,7 @@ var FECManager = class _FECManager {
|
|
|
2494
2582
|
if (workerClient) {
|
|
2495
2583
|
this.#pendingWorkerCleanups.add(key);
|
|
2496
2584
|
this.#pendingWorkerCleanupClients.set(key, workerClient);
|
|
2497
|
-
this.#boundOwedCleanup(key, packetId, workerClient);
|
|
2585
|
+
this.#boundOwedCleanup(key, packetId, workerClient, dueAtMs);
|
|
2498
2586
|
workerClient.cleanup(sbn);
|
|
2499
2587
|
} else {
|
|
2500
2588
|
const decoder = this.#decoderForPacket(packetId, catalogTrackName)?.client;
|
|
@@ -2806,7 +2894,9 @@ var FECManager = class _FECManager {
|
|
|
2806
2894
|
}
|
|
2807
2895
|
resetMap(this.#blockSourceCounts);
|
|
2808
2896
|
resetMap(this.#blockSourceEsis);
|
|
2897
|
+
resetMap(this.#blockRebuiltEsis);
|
|
2809
2898
|
resetMap(this.#blockAuthoredLengths);
|
|
2899
|
+
resetSet(this.#isoSpanKeys);
|
|
2810
2900
|
resetMap(this.#blockRepairRsIds);
|
|
2811
2901
|
resetMap(this.#blockRsbLengths);
|
|
2812
2902
|
resetMap(this.#heldLayerStates);
|
|
@@ -2930,7 +3020,9 @@ var FECManager = class _FECManager {
|
|
|
2930
3020
|
dropMap(this.#pendingWorkerCleanupClients);
|
|
2931
3021
|
dropMap(this.#blockSourceCounts);
|
|
2932
3022
|
dropMap(this.#blockSourceEsis);
|
|
3023
|
+
dropMap(this.#blockRebuiltEsis);
|
|
2933
3024
|
dropMap(this.#blockAuthoredLengths);
|
|
3025
|
+
dropSet(this.#isoSpanKeys);
|
|
2934
3026
|
dropMap(this.#blockRepairRsIds);
|
|
2935
3027
|
dropMap(this.#blockRsbLengths);
|
|
2936
3028
|
dropMap(this.#blockPathCounts);
|
|
@@ -3273,8 +3365,9 @@ var FECManager = class _FECManager {
|
|
|
3273
3365
|
return true;
|
|
3274
3366
|
}
|
|
3275
3367
|
/**
|
|
3276
|
-
* Drop a block's repair hold: its FETCH ended without delivering
|
|
3277
|
-
*
|
|
3368
|
+
* Drop a block's repair hold: its FETCH ended without delivering, or brought
|
|
3369
|
+
* back only a repair that cannot complete it. The block falls back to its
|
|
3370
|
+
* multicast wait, which may already have passed.
|
|
3278
3371
|
*/
|
|
3279
3372
|
releaseRepairHold(sbn, packetId, catalogTrackName) {
|
|
3280
3373
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
@@ -3412,6 +3505,7 @@ var FECManager = class _FECManager {
|
|
|
3412
3505
|
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
3413
3506
|
const evictedBefore = locator.heldEvicted;
|
|
3414
3507
|
const held = locator.hold(ssId, source);
|
|
3508
|
+
this.#settleExpiredIsoHoles(locator, packetId, catalogTrackName);
|
|
3415
3509
|
if (held && source.source === "multicast") this.#noteMulticastSource(locator, ssId, packetId, catalogTrackName);
|
|
3416
3510
|
const evicted = locator.heldEvicted - evictedBefore;
|
|
3417
3511
|
if (evicted > 0) {
|
|
@@ -3433,17 +3527,20 @@ var FECManager = class _FECManager {
|
|
|
3433
3527
|
* contradicts a known block is refused (counted) and must be dropped.
|
|
3434
3528
|
*/
|
|
3435
3529
|
placeIsoRepair(packetId, catalogTrackName, ssStart, ssbLength) {
|
|
3436
|
-
const
|
|
3530
|
+
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
3531
|
+
const placement = locator.placeRepair(ssStart, ssbLength);
|
|
3532
|
+
this.#settleExpiredIsoHoles(locator, packetId, catalogTrackName);
|
|
3437
3533
|
if (placement.status !== "placed") {
|
|
3438
3534
|
this.#isoRepairRefusedCount++;
|
|
3439
3535
|
if (this.#isoRepairRefusedCount <= 5 || this.#isoRepairRefusedCount % 100 === 0) {
|
|
3440
3536
|
console.warn(`[FECManager] packetId=${packetId} iso-ssbg1-v1 repair refused (${placement.status}): ${placement.reason}`);
|
|
3441
3537
|
}
|
|
3538
|
+
if (placement.ofSpan !== void 0) this.releaseRepairHold(placement.ofSpan.start, packetId, catalogTrackName);
|
|
3442
3539
|
return placement;
|
|
3443
3540
|
}
|
|
3541
|
+
this.#isoSpanKeys.delete(this.#blockKey(placement.block.start, packetId, catalogTrackName));
|
|
3444
3542
|
if (placement.span !== void 0) this.#openIsoSpan(placement.span, packetId, catalogTrackName);
|
|
3445
3543
|
if (placement.hole !== void 0) {
|
|
3446
|
-
const locator = this.#isoLocatorFor(packetId, catalogTrackName);
|
|
3447
3544
|
const hole = placement.hole.block;
|
|
3448
3545
|
const previous = locator.blockEndingAt(hole.start);
|
|
3449
3546
|
const loss = {
|
|
@@ -3464,17 +3561,29 @@ var FECManager = class _FECManager {
|
|
|
3464
3561
|
* `handler`, instead of a repair-less span; null restores spans. Set by the
|
|
3465
3562
|
* range repair scheduler; applies to every stream from its next gap. The
|
|
3466
3563
|
* handler and the deferral are one setting, so no hole is ever created with
|
|
3467
|
-
* nobody to close it.
|
|
3564
|
+
* nobody to close it. `onExpired` is told of each pending hole the stream
|
|
3565
|
+
* runs past the locator's horizon (BLO-52085): returning true, it closes
|
|
3566
|
+
* the hole (`closeIsoHoles`), so the span it leaves still fails and is
|
|
3567
|
+
* reported; otherwise the hole is discarded with its sources.
|
|
3468
3568
|
*/
|
|
3469
|
-
setUndelimitedLossHandler(handler) {
|
|
3569
|
+
setUndelimitedLossHandler(handler, onExpired = null) {
|
|
3470
3570
|
this.#onUndelimitedLoss = handler;
|
|
3571
|
+
this.#onIsoHoleExpired = handler === null ? null : onExpired;
|
|
3471
3572
|
this.#deferIsoGaps = handler !== null;
|
|
3472
3573
|
for (const locator of this.#isoLocators.values()) locator.deferIncompleteGaps = this.#deferIsoGaps;
|
|
3473
3574
|
}
|
|
3575
|
+
/** Hand each hole the locator's horizon passed to its owner, or discard it (setUndelimitedLossHandler). */
|
|
3576
|
+
#settleExpiredIsoHoles(locator, packetId, catalogTrackName) {
|
|
3577
|
+
for (const hole of locator.takeExpiredHoles()) {
|
|
3578
|
+
const expired = { packetId, catalogTrackName, holeStart: hole.start, holeLength: hole.length };
|
|
3579
|
+
if (this.#onIsoHoleExpired?.(expired) !== true) locator.closeHolesWithin(hole.start, hole.length);
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3474
3582
|
/**
|
|
3475
|
-
* The range repair for a hole ended: every hole
|
|
3476
|
-
*
|
|
3477
|
-
* with the held sources they placed, for the caller to feed
|
|
3583
|
+
* The range repair for a hole ended: every hole inside `[start, start +
|
|
3584
|
+
* length)`, pending or expired, becomes a repair-less span. Returns those
|
|
3585
|
+
* spans with the held sources they placed, for the caller to feed
|
|
3586
|
+
* (MmtpRouter).
|
|
3478
3587
|
*/
|
|
3479
3588
|
closeIsoHoles(packetId, catalogTrackName, start, length) {
|
|
3480
3589
|
const locator = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName));
|
|
@@ -3493,7 +3602,10 @@ var FECManager = class _FECManager {
|
|
|
3493
3602
|
*/
|
|
3494
3603
|
#openIsoSpan(span, packetId, catalogTrackName) {
|
|
3495
3604
|
const key = this.#blockKey(span.block.start, packetId, catalogTrackName);
|
|
3496
|
-
if (!this.#blockAuthoredLengths.has(key))
|
|
3605
|
+
if (!this.#blockAuthoredLengths.has(key)) {
|
|
3606
|
+
this.#blockAuthoredLengths.set(key, span.block.length);
|
|
3607
|
+
this.#isoSpanKeys.add(key);
|
|
3608
|
+
}
|
|
3497
3609
|
if (span.placed.length === 0) this.#materializeBlock(span.block.start, packetId, catalogTrackName, true);
|
|
3498
3610
|
}
|
|
3499
3611
|
/**
|
|
@@ -3834,11 +3946,14 @@ var FECManager = class _FECManager {
|
|
|
3834
3946
|
});
|
|
3835
3947
|
client.onCleanupComplete((sbn) => {
|
|
3836
3948
|
if (this.#releasedWorkerClients.has(client)) return;
|
|
3837
|
-
const
|
|
3949
|
+
const owed = [...this.#pendingWorkerCleanups].filter((key) => this.#pendingWorkerCleanupClients.get(key) === client);
|
|
3950
|
+
const cleanupKeys = owed.filter((key) => {
|
|
3838
3951
|
const block = this.#parseBlockKey(key);
|
|
3839
|
-
return block.packetId === packetId && block.sbn === sbn
|
|
3952
|
+
return block.packetId === packetId && block.sbn === sbn;
|
|
3840
3953
|
});
|
|
3841
|
-
|
|
3954
|
+
const askedAtMs = Math.max(...cleanupKeys.map((key) => this.#pendingWorkerCleanupBounds.get(key)?.askedAtMs ?? -Infinity));
|
|
3955
|
+
const due = owed.filter((key) => !cleanupKeys.includes(key) && (this.#pendingWorkerCleanupBounds.get(key)?.dueAtMs ?? Infinity) <= askedAtMs);
|
|
3956
|
+
for (const key of [...cleanupKeys, ...due]) this.#settleOwedCleanup(key, "cleanup-ack");
|
|
3842
3957
|
});
|
|
3843
3958
|
client.onError((msg, code) => {
|
|
3844
3959
|
const requestedKind = code ?? msg;
|
|
@@ -3908,9 +4023,11 @@ var FECManager = class _FECManager {
|
|
|
3908
4023
|
* fail deadline already passed, so failing it is the decision that deadline
|
|
3909
4024
|
* made. A late ack then finds nothing owed and is a no-op.
|
|
3910
4025
|
*/
|
|
3911
|
-
#boundOwedCleanup(key, packetId, client) {
|
|
4026
|
+
#boundOwedCleanup(key, packetId, client, dueAtMs) {
|
|
3912
4027
|
this.#clearOwedCleanupBound(key);
|
|
3913
4028
|
const bound = {
|
|
4029
|
+
dueAtMs,
|
|
4030
|
+
askedAtMs: performance.now(),
|
|
3914
4031
|
handle: setTimeout(() => {
|
|
3915
4032
|
if (this.#pendingWorkerCleanupBounds.get(key) !== bound) return;
|
|
3916
4033
|
this.#pendingWorkerCleanupBounds.delete(key);
|
|
@@ -4008,7 +4125,15 @@ var FECManager = class _FECManager {
|
|
|
4008
4125
|
this.#isoSourceOversizeCount++;
|
|
4009
4126
|
return null;
|
|
4010
4127
|
}
|
|
4011
|
-
|
|
4128
|
+
return this.#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, located.block.start);
|
|
4129
|
+
}
|
|
4130
|
+
/**
|
|
4131
|
+
* Hand one placed source symbol to the stream's decoder: its Worker, which
|
|
4132
|
+
* answers through onFrame (null here), or its synchronous decoder, whose
|
|
4133
|
+
* recovered block is returned. `sbn` is the SS_Start of the block a repair
|
|
4134
|
+
* stated for `ssId`.
|
|
4135
|
+
*/
|
|
4136
|
+
#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, sbn) {
|
|
4012
4137
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
4013
4138
|
const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey);
|
|
4014
4139
|
const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName);
|
|
@@ -4037,6 +4162,74 @@ var FECManager = class _FECManager {
|
|
|
4037
4162
|
}
|
|
4038
4163
|
return recovered;
|
|
4039
4164
|
}
|
|
4165
|
+
/**
|
|
4166
|
+
* Feed a source symbol of this stream that another FEC instance over the
|
|
4167
|
+
* same packets rebuilt: a keyframe overlay recovery whose base SS_ID the
|
|
4168
|
+
* caller determined (draft-ramadan-moq-mmt-02 7.1, 7.4.4; joint decoding,
|
|
4169
|
+
* BLO-52085). It joins its block's decoder as a received source would, so
|
|
4170
|
+
* it reduces the erasures the block still has to solve, but it is not a
|
|
4171
|
+
* network arrival: no transport accounting, no classification evidence,
|
|
4172
|
+
* no deadline refresh, and it never makes a block clean.
|
|
4173
|
+
*
|
|
4174
|
+
* Only a block a repair stated takes one (a repair-less span has no
|
|
4175
|
+
* decoder block). A block that is terminal, or already holds that source,
|
|
4176
|
+
* does not. The symbol is the exact protected packet, unpadded, as
|
|
4177
|
+
* feedSourceSymbol takes it.
|
|
4178
|
+
*
|
|
4179
|
+
* `recovered` is the block the synchronous decoder completed with it; the
|
|
4180
|
+
* caller routes it and completes the block, as for any recovery. When
|
|
4181
|
+
* every source of the block is then present, received or rebuilt, and no
|
|
4182
|
+
* decoder returned it (a Worker answers asynchronously, and reports such a
|
|
4183
|
+
* block as clean on its own side), the block is completed here as
|
|
4184
|
+
* recovered, with nothing left to route.
|
|
4185
|
+
*/
|
|
4186
|
+
feedRebuiltSourceSymbol(packetId, ssId, symbolData, catalogTrackName) {
|
|
4187
|
+
if (!this.#hasBlockProfile(packetId)) return NOT_TAKEN;
|
|
4188
|
+
const located = this.#isoLocatorFor(packetId, catalogTrackName).locate(ssId);
|
|
4189
|
+
if (located === null || located.block.span) return NOT_TAKEN;
|
|
4190
|
+
if (!ssbg1FitsSymbol(symbolData.byteLength, this.#trackConfig(packetId).symbolSize)) return NOT_TAKEN;
|
|
4191
|
+
const sbn = located.block.start;
|
|
4192
|
+
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
4193
|
+
if (this.#completedBlocks.has(key) || this.#blockSourceEsis.get(key)?.has(located.esi)) return NOT_TAKEN;
|
|
4194
|
+
let rebuilt = this.#blockRebuiltEsis.get(key);
|
|
4195
|
+
if (rebuilt?.has(located.esi)) return NOT_TAKEN;
|
|
4196
|
+
if (rebuilt === void 0) {
|
|
4197
|
+
rebuilt = /* @__PURE__ */ new Set();
|
|
4198
|
+
this.#blockRebuiltEsis.set(key, rebuilt);
|
|
4199
|
+
}
|
|
4200
|
+
const config = this.#trackConfigs.get(packetId);
|
|
4201
|
+
const wasShort = config !== void 0 && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) !== void 0;
|
|
4202
|
+
rebuilt.add(located.esi);
|
|
4203
|
+
const recovered = this.#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, sbn);
|
|
4204
|
+
if (recovered === null && !this.#completeWhenSourcesPresent(key, sbn, located.block.length, packetId, catalogTrackName)) {
|
|
4205
|
+
if (wasShort && this.#activeBlocks.has(key) && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) === void 0) {
|
|
4206
|
+
this.#armBlockTimeout(sbn, packetId, catalogTrackName, this.#multicastWaitMs(config));
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
return { taken: true, recovered };
|
|
4210
|
+
}
|
|
4211
|
+
/**
|
|
4212
|
+
* A block that holds every one of its `length` sources, received or
|
|
4213
|
+
* rebuilt by another instance, with at least one rebuilt, is recovered:
|
|
4214
|
+
* every packet of it was delivered, by its own arrival or by the instance
|
|
4215
|
+
* that rebuilt it, so nothing is routed. Returns whether it completed it.
|
|
4216
|
+
*/
|
|
4217
|
+
#completeWhenSourcesPresent(key, sbn, length, packetId, catalogTrackName) {
|
|
4218
|
+
const rebuilt = this.#blockRebuiltEsis.get(key);
|
|
4219
|
+
if (rebuilt === void 0 || this.#completedBlocks.has(key)) return false;
|
|
4220
|
+
let present = rebuilt.size;
|
|
4221
|
+
for (const esi of this.#blockSourceEsis.get(key) ?? []) if (!rebuilt.has(esi)) present++;
|
|
4222
|
+
if (present < length) return false;
|
|
4223
|
+
this.#traceLifecycle("recovered", sbn, packetId, catalogTrackName);
|
|
4224
|
+
this.countRecovery(packetId, sbn, catalogTrackName);
|
|
4225
|
+
this.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
4226
|
+
if (catalogTrackName === void 0) {
|
|
4227
|
+
this.onBlockComplete?.(sbn, packetId);
|
|
4228
|
+
} else {
|
|
4229
|
+
this.onBlockComplete?.(sbn, packetId, catalogTrackName);
|
|
4230
|
+
}
|
|
4231
|
+
return true;
|
|
4232
|
+
}
|
|
4040
4233
|
/**
|
|
4041
4234
|
* fec-01 4.5: a reed-solomon repair packet whose SSB_length + RSB_length
|
|
4042
4235
|
* exceeds 255, or whose RS_ID is not below RSB_length, is discarded with the
|
|
@@ -4279,7 +4472,10 @@ var FECManager = class _FECManager {
|
|
|
4279
4472
|
const authoredLength = this.#blockAuthoredLengths.get(key);
|
|
4280
4473
|
const rsbLength = this.#blockRsbLengths.get(key);
|
|
4281
4474
|
if (authoredLength === void 0 || rsbLength === void 0) return void 0;
|
|
4282
|
-
|
|
4475
|
+
let present = this.#blockSourceCounts.get(key) ?? 0;
|
|
4476
|
+
const received = this.#blockSourceEsis.get(key);
|
|
4477
|
+
for (const esi of this.#blockRebuiltEsis.get(key) ?? []) if (!received?.has(esi)) present++;
|
|
4478
|
+
if (present + rsbLength >= authoredLength) return void 0;
|
|
4283
4479
|
const newestSource = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newestSource;
|
|
4284
4480
|
if (newestSource === void 0 || !serial32Precedes(serial32Add(sbn, authoredLength - 1), newestSource)) {
|
|
4285
4481
|
return void 0;
|
|
@@ -4402,12 +4598,13 @@ var FECManager = class _FECManager {
|
|
|
4402
4598
|
* the inactivity deadline on each symbol, records the symbol's in-band
|
|
4403
4599
|
* classification evidence, and marks the block clean when all of its
|
|
4404
4600
|
* authored source symbols arrived: the SSB_length a repair symbol declared,
|
|
4405
|
-
* otherwise K.
|
|
4601
|
+
* otherwise K. `held` marks a source held unplaced since it arrived and
|
|
4602
|
+
* placed only now (see #noteSbnSeen).
|
|
4406
4603
|
*
|
|
4407
4604
|
* Throws for a packetId that was never configured, so a classified block
|
|
4408
4605
|
* never reads the manager-global K or timeout.
|
|
4409
4606
|
*/
|
|
4410
|
-
trackBlock(sbn, packetId, catalogTrackName, esi, evidence) {
|
|
4607
|
+
trackBlock(sbn, packetId, catalogTrackName, esi, evidence, held = false) {
|
|
4411
4608
|
const trackConfig = this.#trackConfigs.get(packetId);
|
|
4412
4609
|
if (trackConfig === void 0) {
|
|
4413
4610
|
throw new Error(
|
|
@@ -4423,7 +4620,8 @@ var FECManager = class _FECManager {
|
|
|
4423
4620
|
catalogTrackName,
|
|
4424
4621
|
"source",
|
|
4425
4622
|
evidence.packetSequenceNumber,
|
|
4426
|
-
Number.isInteger(esi) ? sbn + esi >>> 0 : sbn
|
|
4623
|
+
Number.isInteger(esi) ? sbn + esi >>> 0 : sbn,
|
|
4624
|
+
held
|
|
4427
4625
|
);
|
|
4428
4626
|
const authoredLength = this.#blockAuthoredLengths.get(key) ?? k;
|
|
4429
4627
|
if (Number.isInteger(esi) && esi >= authoredLength) {
|
|
@@ -4459,6 +4657,15 @@ var FECManager = class _FECManager {
|
|
|
4459
4657
|
this.#completeCleanBlock(key, sbn, packetId, catalogTrackName);
|
|
4460
4658
|
return;
|
|
4461
4659
|
}
|
|
4660
|
+
if (this.#blockRebuiltEsis.has(key) && this.#completeWhenSourcesPresent(
|
|
4661
|
+
key,
|
|
4662
|
+
sbn,
|
|
4663
|
+
this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? authoredLength,
|
|
4664
|
+
packetId,
|
|
4665
|
+
catalogTrackName
|
|
4666
|
+
)) {
|
|
4667
|
+
return;
|
|
4668
|
+
}
|
|
4462
4669
|
this.noteBlockActivity(sbn, packetId, catalogTrackName, true);
|
|
4463
4670
|
}
|
|
4464
4671
|
#completeCleanBlock(key, sbn, packetId, catalogTrackName) {
|
|
@@ -4467,7 +4674,9 @@ var FECManager = class _FECManager {
|
|
|
4467
4674
|
this.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
4468
4675
|
this.#blockSourceCounts.delete(key);
|
|
4469
4676
|
this.#blockSourceEsis.delete(key);
|
|
4677
|
+
this.#blockRebuiltEsis.delete(key);
|
|
4470
4678
|
this.#blockAuthoredLengths.delete(key);
|
|
4679
|
+
this.#isoSpanKeys.delete(key);
|
|
4471
4680
|
this.#blockRepairRsIds.delete(key);
|
|
4472
4681
|
this.#blockRsbLengths.delete(key);
|
|
4473
4682
|
this.#markTerminal(key, sbn, packetId, true);
|
|
@@ -4498,6 +4707,19 @@ var FECManager = class _FECManager {
|
|
|
4498
4707
|
#receiptNow() {
|
|
4499
4708
|
return this.#receiptAtMs ?? performance.now();
|
|
4500
4709
|
}
|
|
4710
|
+
/**
|
|
4711
|
+
* The whole-ms delay of a timer for `deadlineMs` armed inside a receipt
|
|
4712
|
+
* scope: counted from the receipt time, as the timer of a symbol fed when
|
|
4713
|
+
* it was received was, less the whole ms since. So it fires on the tick
|
|
4714
|
+
* that timer would have, never later than a delay counted from now, and
|
|
4715
|
+
* its handler still re-arms a timer that fires before its deadline.
|
|
4716
|
+
* Counted from now, a deadline set from the receipt time is rounded up
|
|
4717
|
+
* from another fraction of a ms (BLO-52085).
|
|
4718
|
+
*/
|
|
4719
|
+
#receiptTimerDelayMs(deadlineMs, minMs) {
|
|
4720
|
+
const receivedAtMs = this.#receiptAtMs;
|
|
4721
|
+
return Math.max(minMs, Math.ceil(deadlineMs - receivedAtMs) - Math.ceil(performance.now() - receivedAtMs));
|
|
4722
|
+
}
|
|
4501
4723
|
/**
|
|
4502
4724
|
* A held packet admitted late arms from its (earlier) receipt time; that
|
|
4503
4725
|
* never pulls in a deadline later activity already armed. Outside a
|
|
@@ -4546,6 +4768,7 @@ var FECManager = class _FECManager {
|
|
|
4546
4768
|
}
|
|
4547
4769
|
#failBlock(sbn, packetId, catalogTrackName) {
|
|
4548
4770
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
4771
|
+
const span = this.#isoSpanKeys.delete(key) ? { length: this.#blockAuthoredLengths.get(key) ?? 0, sources: this.#blockSourceCounts.get(key) ?? 0 } : void 0;
|
|
4549
4772
|
this.#traceLifecycle("failed", sbn, packetId, catalogTrackName);
|
|
4550
4773
|
this.#clearRepairDeadline(key);
|
|
4551
4774
|
this.#clearBlockTimer(key);
|
|
@@ -4555,6 +4778,7 @@ var FECManager = class _FECManager {
|
|
|
4555
4778
|
}
|
|
4556
4779
|
this.#blockSourceCounts.delete(key);
|
|
4557
4780
|
this.#blockSourceEsis.delete(key);
|
|
4781
|
+
this.#blockRebuiltEsis.delete(key);
|
|
4558
4782
|
this.#blockAuthoredLengths.delete(key);
|
|
4559
4783
|
this.#blockRepairRsIds.delete(key);
|
|
4560
4784
|
this.#blockRsbLengths.delete(key);
|
|
@@ -4564,7 +4788,8 @@ var FECManager = class _FECManager {
|
|
|
4564
4788
|
this.#markPathBlockComplete(sbn, packetId, catalogTrackName);
|
|
4565
4789
|
this.#fecStats.blocksFailed++;
|
|
4566
4790
|
this.#statsForPacket(packetId).blocksFailed++;
|
|
4567
|
-
this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
4791
|
+
if (span === void 0) this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
4792
|
+
else this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass, span);
|
|
4568
4793
|
this.onBlockResolved?.(sbn, packetId, catalogTrackName);
|
|
4569
4794
|
}
|
|
4570
4795
|
/**
|
|
@@ -4593,7 +4818,9 @@ var FECManager = class _FECManager {
|
|
|
4593
4818
|
this.#retireBlockClass(key, block.sbn, block.packetId, block.catalogTrackName);
|
|
4594
4819
|
this.#blockSourceCounts.delete(key);
|
|
4595
4820
|
this.#blockSourceEsis.delete(key);
|
|
4821
|
+
this.#blockRebuiltEsis.delete(key);
|
|
4596
4822
|
this.#blockAuthoredLengths.delete(key);
|
|
4823
|
+
this.#isoSpanKeys.delete(key);
|
|
4597
4824
|
this.#blockRepairRsIds.delete(key);
|
|
4598
4825
|
this.#blockRsbLengths.delete(key);
|
|
4599
4826
|
this.#clearBlockTimer(key);
|
|
@@ -4684,7 +4911,9 @@ var FECManager = class _FECManager {
|
|
|
4684
4911
|
this.#blockKeysBySbn.clear();
|
|
4685
4912
|
this.#blockSourceCounts.clear();
|
|
4686
4913
|
this.#blockSourceEsis.clear();
|
|
4914
|
+
this.#blockRebuiltEsis.clear();
|
|
4687
4915
|
this.#blockAuthoredLengths.clear();
|
|
4916
|
+
this.#isoSpanKeys.clear();
|
|
4688
4917
|
this.#blockRepairRsIds.clear();
|
|
4689
4918
|
this.#blockRsbLengths.clear();
|
|
4690
4919
|
this.#heldLayerStates.clear();
|