@blockcast/mmt-container 2.2.0-main.986a21fc9782 → 2.2.0-main.a5b276c121d4
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 +696 -139
- package/dist/browser-runtime.js.map +3 -3
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +37 -11
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +181 -52
- 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 +21 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +114 -11
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +113 -12
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +114 -11
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.d.ts +18 -9
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +254 -44
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.d.ts +26 -3
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +517 -113
- 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 +114 -11
- 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 +114 -11
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +5 -5
- package/src/fec-manager.ts +156 -26
- package/src/index.ts +2 -0
- package/src/iso-block-locator.ts +114 -31
- package/src/mmtp-container-parser.ts +181 -10
- package/src/mmtp-router.ts +231 -58
- package/src/multicast-manager.ts +118 -14
- package/src/repair-fetch.ts +363 -49
- package/src/track-session.ts +3 -0
package/dist/browser-runtime.js
CHANGED
|
@@ -851,6 +851,17 @@ function readRepairFecPayloadIdFields(mmtpPacket, parsedHeader) {
|
|
|
851
851
|
dataOffset: off + REPAIR_FEC_PID_SIZE
|
|
852
852
|
};
|
|
853
853
|
}
|
|
854
|
+
function extractRepairFecPayloadId(mmtpPacket) {
|
|
855
|
+
const fields = readRepairFecPayloadIdFields(mmtpPacket);
|
|
856
|
+
if (!fields)
|
|
857
|
+
return null;
|
|
858
|
+
return {
|
|
859
|
+
ssStart: fields.ssStart,
|
|
860
|
+
rsbLength: fields.rsbLength,
|
|
861
|
+
rsId: fields.rsId,
|
|
862
|
+
ssbLength: fields.ssbLength
|
|
863
|
+
};
|
|
864
|
+
}
|
|
854
865
|
function parseMoqRepairFrame(frame) {
|
|
855
866
|
return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame));
|
|
856
867
|
}
|
|
@@ -8625,9 +8636,9 @@ async function openSubscription(rx, openedPromise, options, packetIds) {
|
|
|
8625
8636
|
} catch (error) {
|
|
8626
8637
|
throw new MulticastError("subscription-failed", "MulticastReceiver did not open", error);
|
|
8627
8638
|
}
|
|
8628
|
-
if (
|
|
8639
|
+
if (opened.integrityScheme !== INTEGRITY_SCHEME) {
|
|
8629
8640
|
await rx.close();
|
|
8630
|
-
throw unsupported(`integrity scheme ${String(
|
|
8641
|
+
throw unsupported(`integrity scheme ${String(opened.integrityScheme)} is not ${INTEGRITY_SCHEME}`);
|
|
8631
8642
|
}
|
|
8632
8643
|
const events = new EventTarget();
|
|
8633
8644
|
let state = "connected";
|
|
@@ -12435,6 +12446,7 @@ var TrackSession = class {
|
|
|
12435
12446
|
this.#state = "idle";
|
|
12436
12447
|
this.#subscription = null;
|
|
12437
12448
|
this.#disarmFec();
|
|
12449
|
+
this.#unsuppress();
|
|
12438
12450
|
}
|
|
12439
12451
|
// -----------------------------------------------------------------------
|
|
12440
12452
|
// Internal
|
|
@@ -12928,6 +12940,15 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
12928
12940
|
this.#heldEvicted = 0;
|
|
12929
12941
|
/** Pending holes (see `deferIncompleteGaps`): unstated, sources still held. */
|
|
12930
12942
|
this.#holes = [];
|
|
12943
|
+
/** Holes the horizon passed while pending: sources held until closed. */
|
|
12944
|
+
this.#expired = [];
|
|
12945
|
+
/** Of `#expired`, those `takeExpiredHoles` has not handed out yet. */
|
|
12946
|
+
this.#newlyExpired = [];
|
|
12947
|
+
/**
|
|
12948
|
+
* Spans closed from expired holes: behind the horizon, so kept only for
|
|
12949
|
+
* `locate` while their sources are fed, until the next retirement.
|
|
12950
|
+
*/
|
|
12951
|
+
this.#closedBehind = [];
|
|
12931
12952
|
/**
|
|
12932
12953
|
* Keep a gap that is missing a source as a pending hole rather than a span,
|
|
12933
12954
|
* so a repair FETCHed for it (draft-ramadan-moq-fec §7.5) can still state
|
|
@@ -12951,6 +12972,9 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
12951
12972
|
#newest;
|
|
12952
12973
|
#newestSource;
|
|
12953
12974
|
#holes;
|
|
12975
|
+
#expired;
|
|
12976
|
+
#newlyExpired;
|
|
12977
|
+
#closedBehind;
|
|
12954
12978
|
static #checkedRetention(retentionBlocks) {
|
|
12955
12979
|
if (!Number.isSafeInteger(retentionBlocks) || retentionBlocks <= 0) {
|
|
12956
12980
|
throw new Error(
|
|
@@ -13020,17 +13044,29 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
13020
13044
|
}
|
|
13021
13045
|
/** The known block or span containing `ssId`, with its ESI. */
|
|
13022
13046
|
locate(ssId) {
|
|
13023
|
-
for (
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13047
|
+
for (const blocks of [this.#blocks, this.#closedBehind]) {
|
|
13048
|
+
for (let i = blocks.length - 1; i >= 0; i--) {
|
|
13049
|
+
const block = blocks[i];
|
|
13050
|
+
if (isoBlockContains(block.start, block.length, ssId)) {
|
|
13051
|
+
return { block, esi: serial32Distance(block.start, ssId) };
|
|
13052
|
+
}
|
|
13027
13053
|
}
|
|
13028
13054
|
}
|
|
13029
13055
|
return null;
|
|
13030
13056
|
}
|
|
13031
|
-
/** Number of pending
|
|
13057
|
+
/** Number of holes not closed yet, pending or past the horizon. */
|
|
13032
13058
|
get holeCount() {
|
|
13033
|
-
return this.#holes.length;
|
|
13059
|
+
return this.#holes.length + this.#expired.length;
|
|
13060
|
+
}
|
|
13061
|
+
/**
|
|
13062
|
+
* The pending holes the horizon has passed since the last call, in the
|
|
13063
|
+
* order it passed them. Each is no longer pending: no repair states a block
|
|
13064
|
+
* in it (that repair is `stale`, as for a span the horizon retired) and the
|
|
13065
|
+
* release gate does not wait for it. Its owner must close it
|
|
13066
|
+
* (`closeHolesWithin`), which turns it into a span that is not kept here.
|
|
13067
|
+
*/
|
|
13068
|
+
takeExpiredHoles() {
|
|
13069
|
+
return this.#newlyExpired.splice(0);
|
|
13034
13070
|
}
|
|
13035
13071
|
/** The stated block (never a span) ending exactly at `end`. */
|
|
13036
13072
|
blockEndingAt(end) {
|
|
@@ -13086,9 +13122,11 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
13086
13122
|
/**
|
|
13087
13123
|
* Apply one repair symbol's `(SS_Start, SSB_length)`. The first repair of a
|
|
13088
13124
|
* block states its range and places every held source in it; later repair
|
|
13089
|
-
* symbols of the same block change nothing. A range
|
|
13090
|
-
*
|
|
13091
|
-
*
|
|
13125
|
+
* symbols of the same block change nothing. A repair whose range is exactly
|
|
13126
|
+
* a repair-less span states that span's one block. A range that contradicts
|
|
13127
|
+
* a known block (a different length, or an overlap with another block or
|
|
13128
|
+
* with part of a span) fails closed: the repair symbol is dropped and no
|
|
13129
|
+
* block changes.
|
|
13092
13130
|
* A newly stated block retires behind its SS_Start (see the class note).
|
|
13093
13131
|
* A block so far past `newest` that stating it would retire the position
|
|
13094
13132
|
* `newest` is refused as `ahead`. When `newest` is a placed block's SS_Start
|
|
@@ -13107,7 +13145,16 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
13107
13145
|
const known = this.blockAt(start);
|
|
13108
13146
|
if (known !== void 0) {
|
|
13109
13147
|
if (known.span) {
|
|
13110
|
-
|
|
13148
|
+
if (known.length === length) {
|
|
13149
|
+
const block2 = { start, length, span: false };
|
|
13150
|
+
this.#blocks[this.#blocks.indexOf(known)] = block2;
|
|
13151
|
+
return { status: "placed", block: block2, placed: [] };
|
|
13152
|
+
}
|
|
13153
|
+
return length < known.length ? {
|
|
13154
|
+
status: "overlap",
|
|
13155
|
+
reason: `repair block ${start}+${length} is only the first block of repair-less span ${known.start}+${known.length}`,
|
|
13156
|
+
ofSpan: known
|
|
13157
|
+
} : { status: "overlap", reason: `repair block ${start}+${length} runs past repair-less span ${known.start}+${known.length}` };
|
|
13111
13158
|
}
|
|
13112
13159
|
if (known.length !== length) {
|
|
13113
13160
|
return {
|
|
@@ -13175,18 +13222,28 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
13175
13222
|
return span === void 0 ? { status: "placed", block, placed } : { status: "placed", block, placed, span };
|
|
13176
13223
|
}
|
|
13177
13224
|
/**
|
|
13178
|
-
* Turn every
|
|
13179
|
-
*
|
|
13180
|
-
*
|
|
13225
|
+
* Turn every hole inside `[start, start + length)` into a repair-less span,
|
|
13226
|
+
* claiming its held sources: its range FETCH ended, so nothing more will
|
|
13227
|
+
* state its blocks. A hole the horizon passed (`takeExpiredHoles`) leaves a
|
|
13228
|
+
* span that is not kept among the blocks, as the horizon would retire it at
|
|
13229
|
+
* once; only `locate` finds it, until the next retirement, so its sources
|
|
13230
|
+
* can be fed. Returns the spans, in SS_ID order.
|
|
13181
13231
|
*/
|
|
13182
13232
|
closeHolesWithin(start, length) {
|
|
13183
13233
|
const closed = [];
|
|
13184
|
-
for (const
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13234
|
+
for (const holes of [this.#holes, this.#expired]) {
|
|
13235
|
+
for (const hole of [...holes]) {
|
|
13236
|
+
if (!isoBlockContains(start, length, hole.start)) continue;
|
|
13237
|
+
holes.splice(holes.indexOf(hole), 1);
|
|
13238
|
+
const spanBlock = { start: hole.start, length: hole.length, span: true };
|
|
13239
|
+
closed.push({ block: spanBlock, placed: this.#claim(spanBlock) });
|
|
13240
|
+
if (holes === this.#holes) {
|
|
13241
|
+
this.#blocks.push(spanBlock);
|
|
13242
|
+
} else {
|
|
13243
|
+
this.#closedBehind.push(spanBlock);
|
|
13244
|
+
if (this.#newlyExpired.includes(hole)) this.#newlyExpired.splice(this.#newlyExpired.indexOf(hole), 1);
|
|
13245
|
+
}
|
|
13246
|
+
}
|
|
13190
13247
|
}
|
|
13191
13248
|
return closed.sort((x, y) => serial32Distance(start, x.block.start) - serial32Distance(start, y.block.start));
|
|
13192
13249
|
}
|
|
@@ -13200,15 +13257,17 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
13200
13257
|
return void 0;
|
|
13201
13258
|
}
|
|
13202
13259
|
/**
|
|
13203
|
-
* Retire every block and span whose last SS_ID, and every held source
|
|
13204
|
-
* lies more than `retentionBlocks` blocks behind
|
|
13205
|
-
* lower-bound metric (`isoBlocksBehindLowerBound`)
|
|
13206
|
-
* horizon uses
|
|
13260
|
+
* Retire every block and span whose last SS_ID, and every held source
|
|
13261
|
+
* outside a hole, lies more than `retentionBlocks` blocks behind
|
|
13262
|
+
* `position`, with the shared lower-bound metric (`isoBlocksBehindLowerBound`)
|
|
13263
|
+
* every ISO interleave horizon uses; a pending hole so far behind expires
|
|
13264
|
+
* (`takeExpiredHoles`). Returns the retired block starts.
|
|
13207
13265
|
*/
|
|
13208
13266
|
retireBehind(position) {
|
|
13209
13267
|
this.observe(position);
|
|
13210
13268
|
const horizon = this.#retentionBlocks;
|
|
13211
13269
|
const retired = [];
|
|
13270
|
+
this.#closedBehind.length = 0;
|
|
13212
13271
|
for (let i = this.#blocks.length - 1; i >= 0; i--) {
|
|
13213
13272
|
const block = this.#blocks[i];
|
|
13214
13273
|
const last = serial32Add(block.start, block.length - 1);
|
|
@@ -13220,10 +13279,15 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
13220
13279
|
for (let i = this.#holes.length - 1; i >= 0; i--) {
|
|
13221
13280
|
const hole = this.#holes[i];
|
|
13222
13281
|
const last = serial32Add(hole.start, hole.length - 1);
|
|
13223
|
-
if (isoBlocksBehindLowerBound(last, position, this.#kMax)
|
|
13282
|
+
if (isoBlocksBehindLowerBound(last, position, this.#kMax) <= horizon) continue;
|
|
13283
|
+
this.#holes.splice(i, 1);
|
|
13284
|
+
this.#expired.push(hole);
|
|
13285
|
+
this.#newlyExpired.push(hole);
|
|
13224
13286
|
}
|
|
13225
13287
|
for (const ssId of [...this.#held.keys()]) {
|
|
13226
|
-
if (isoBlocksBehindLowerBound(ssId, position, this.#kMax) > horizon) this.#
|
|
13288
|
+
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))) {
|
|
13289
|
+
this.#held.delete(ssId);
|
|
13290
|
+
}
|
|
13227
13291
|
}
|
|
13228
13292
|
return retired;
|
|
13229
13293
|
}
|
|
@@ -13231,6 +13295,9 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
13231
13295
|
reset() {
|
|
13232
13296
|
this.#blocks.length = 0;
|
|
13233
13297
|
this.#holes.length = 0;
|
|
13298
|
+
this.#expired.length = 0;
|
|
13299
|
+
this.#newlyExpired.length = 0;
|
|
13300
|
+
this.#closedBehind.length = 0;
|
|
13234
13301
|
this.#held.clear();
|
|
13235
13302
|
this.#coverageEnd = void 0;
|
|
13236
13303
|
this.#newest = void 0;
|
|
@@ -13948,6 +14015,10 @@ var FECManager = class _FECManager {
|
|
|
13948
14015
|
// 'pending' forever, which would park every later frame of its stream in
|
|
13949
14016
|
// the in-order release gate. Each entry owns its handle; the callback
|
|
13950
14017
|
// checks it still owns the key, so a settled or reset key is never touched.
|
|
14018
|
+
/**
|
|
14019
|
+
* Per owed Worker cleanup: the bound on its acknowledgement, when its block
|
|
14020
|
+
* was due, and when its cleanup was asked for (see `onCleanupComplete`).
|
|
14021
|
+
*/
|
|
13951
14022
|
this.#pendingWorkerCleanupBounds = /* @__PURE__ */ new Map();
|
|
13952
14023
|
// Source symbol count per track-scoped block — marks complete when K symbols arrive.
|
|
13953
14024
|
this.#blockSourceCounts = /* @__PURE__ */ new Map();
|
|
@@ -13964,6 +14035,9 @@ var FECManager = class _FECManager {
|
|
|
13964
14035
|
// SSB_length source arrivals and recoverable from SSB_length received
|
|
13965
14036
|
// symbols; waiting for K fails every one of them.
|
|
13966
14037
|
this.#blockAuthoredLengths = /* @__PURE__ */ new Map();
|
|
14038
|
+
// Keys whose authored length is a repair-less span's (#openIsoSpan), until
|
|
14039
|
+
// a repair states the block there or the span resolves.
|
|
14040
|
+
this.#isoSpanKeys = /* @__PURE__ */ new Set();
|
|
13967
14041
|
this.#ssbLengthConflictCount = 0;
|
|
13968
14042
|
this.#blockRepairRsIds = /* @__PURE__ */ new Map();
|
|
13969
14043
|
// RSB_length per block (C.5.3 Repair FEC Payload ID): how many repair
|
|
@@ -14161,7 +14235,10 @@ var FECManager = class _FECManager {
|
|
|
14161
14235
|
// arrives via onBlockRecovered callback.
|
|
14162
14236
|
this.onBlockStarted = null;
|
|
14163
14237
|
this.onBlockComplete = null;
|
|
14164
|
-
/**
|
|
14238
|
+
/**
|
|
14239
|
+
* A block reached its fail bound. `blockClass` is its class at failure.
|
|
14240
|
+
* `span` is set when what failed is a repair-less span starting at `sbn`.
|
|
14241
|
+
*/
|
|
14165
14242
|
this.onBlockFailed = null;
|
|
14166
14243
|
/**
|
|
14167
14244
|
* Fired once per stream when its Worker decoder stops being routable.
|
|
@@ -14203,6 +14280,8 @@ var FECManager = class _FECManager {
|
|
|
14203
14280
|
* for it, which it must be.
|
|
14204
14281
|
*/
|
|
14205
14282
|
this.#onUndelimitedLoss = null;
|
|
14283
|
+
/** Set with the loss handler: true takes an expired hole to close (`closeIsoHoles`). */
|
|
14284
|
+
this.#onIsoHoleExpired = null;
|
|
14206
14285
|
/** A block every symbol of which vanished was created by SBN evidence. */
|
|
14207
14286
|
this.onBlockVanished = null;
|
|
14208
14287
|
/** A block reached a terminal state (completed, recovered, or failed). */
|
|
@@ -14247,6 +14326,7 @@ var FECManager = class _FECManager {
|
|
|
14247
14326
|
#blockSourceEsis;
|
|
14248
14327
|
#blockRebuiltEsis;
|
|
14249
14328
|
#blockAuthoredLengths;
|
|
14329
|
+
#isoSpanKeys;
|
|
14250
14330
|
#ssbLengthConflictCount;
|
|
14251
14331
|
#blockRepairRsIds;
|
|
14252
14332
|
#blockRsbLengths;
|
|
@@ -14313,6 +14393,7 @@ var FECManager = class _FECManager {
|
|
|
14313
14393
|
#decoderUnavailableEvents;
|
|
14314
14394
|
#repairSymbolSizeMismatchCount;
|
|
14315
14395
|
#onUndelimitedLoss;
|
|
14396
|
+
#onIsoHoleExpired;
|
|
14316
14397
|
#encodeTrackName(catalogTrackName) {
|
|
14317
14398
|
let encoded = this.#encodedTrackNames.get(catalogTrackName);
|
|
14318
14399
|
if (encoded === void 0) {
|
|
@@ -14784,7 +14865,10 @@ var FECManager = class _FECManager {
|
|
|
14784
14865
|
timer.handle = null;
|
|
14785
14866
|
this.#requestRepair(sbn, packetId, catalogTrackName);
|
|
14786
14867
|
};
|
|
14787
|
-
timer.handle = setTimeout(
|
|
14868
|
+
timer.handle = setTimeout(
|
|
14869
|
+
onFire,
|
|
14870
|
+
this.#receiptAtMs === void 0 ? Math.max(0, deadlineMs - performance.now()) : this.#receiptTimerDelayMs(deadlineMs, 0)
|
|
14871
|
+
);
|
|
14788
14872
|
this.#repairDeadlines.set(key, timer);
|
|
14789
14873
|
}
|
|
14790
14874
|
#requestRepair(sbn, packetId, catalogTrackName, settledByOrder = false) {
|
|
@@ -14928,8 +15012,14 @@ var FECManager = class _FECManager {
|
|
|
14928
15012
|
* Vanished blocks are not enumerated here: their starts are unknown until a
|
|
14929
15013
|
* repair states them. The SS_IDs between two stated blocks become one
|
|
14930
15014
|
* repair-less span when the later block's repair arrives (placeIsoRepair).
|
|
15015
|
+
*
|
|
15016
|
+
* A `held` source arrived before its block was known and is fed only now
|
|
15017
|
+
* that a repair, a span or a closed hole placed it: how far its block start
|
|
15018
|
+
* then lies behind the frontier measures how long the receiver held it, not
|
|
15019
|
+
* where the stream was, so only its SS_ID and packet_sequence_number judge
|
|
15020
|
+
* it (BLO-52085).
|
|
14931
15021
|
*/
|
|
14932
|
-
#noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber, ssId = sbn) {
|
|
15022
|
+
#noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber, ssId = sbn, held = false) {
|
|
14933
15023
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
14934
15024
|
const now = performance.now();
|
|
14935
15025
|
const previous = (symbol === "source" ? this.#sourceFrontiers : this.#highestSbn).get(streamKey);
|
|
@@ -14968,7 +15058,7 @@ var FECManager = class _FECManager {
|
|
|
14968
15058
|
return sent <= 2147483647 && serial32Distance(from.ssId, to.ssId) <= sent + config.k;
|
|
14969
15059
|
};
|
|
14970
15060
|
if (!this.#blockPrecedes(previous.sbn, sbn)) {
|
|
14971
|
-
if (symbol === "source" && window2 !== 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))) {
|
|
15061
|
+
if (symbol === "source" && window2 !== 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))) {
|
|
14972
15062
|
discontinuity();
|
|
14973
15063
|
return;
|
|
14974
15064
|
}
|
|
@@ -15230,17 +15320,21 @@ var FECManager = class _FECManager {
|
|
|
15230
15320
|
this.#blockWaitDeadlinesMs.delete(key);
|
|
15231
15321
|
this.#repairHoldUntilMs.delete(key);
|
|
15232
15322
|
this.#traceLifecycle("timeout-queued", sbn, packetId, catalogTrackName);
|
|
15233
|
-
this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName);
|
|
15323
|
+
this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName, currentDeadlineMs ?? firedAtMs);
|
|
15234
15324
|
};
|
|
15235
|
-
timer.handle = setTimeout(
|
|
15325
|
+
timer.handle = setTimeout(
|
|
15326
|
+
onFire,
|
|
15327
|
+
this.#receiptAtMs === void 0 ? Math.max(1, Math.ceil(deadlineMs - nowMs)) : this.#receiptTimerDelayMs(deadlineMs, 1)
|
|
15328
|
+
);
|
|
15236
15329
|
this.#activeBlocks.set(key, timer);
|
|
15237
15330
|
this.#indexBlockKey(key, sbn, packetId);
|
|
15238
15331
|
}
|
|
15239
15332
|
/**
|
|
15240
15333
|
* Hand a block whose delivery is over to its decoder for finalization: the
|
|
15241
|
-
* datagram deadline fired, or the ordered relay moved past it.
|
|
15334
|
+
* datagram deadline fired, or the ordered relay moved past it. `dueAtMs`
|
|
15335
|
+
* is when it was due: its deadline, or now.
|
|
15242
15336
|
*/
|
|
15243
|
-
#finalizeExpiredBlock(sbn, packetId, catalogTrackName) {
|
|
15337
|
+
#finalizeExpiredBlock(sbn, packetId, catalogTrackName, dueAtMs = performance.now()) {
|
|
15244
15338
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
15245
15339
|
if (this.#materializedBlocks.has(key) && !this.#blockPathCounts.has(key)) {
|
|
15246
15340
|
this.#failBlock(sbn, packetId, catalogTrackName);
|
|
@@ -15256,7 +15350,7 @@ var FECManager = class _FECManager {
|
|
|
15256
15350
|
if (workerClient) {
|
|
15257
15351
|
this.#pendingWorkerCleanups.add(key);
|
|
15258
15352
|
this.#pendingWorkerCleanupClients.set(key, workerClient);
|
|
15259
|
-
this.#boundOwedCleanup(key, packetId, workerClient);
|
|
15353
|
+
this.#boundOwedCleanup(key, packetId, workerClient, dueAtMs);
|
|
15260
15354
|
workerClient.cleanup(sbn);
|
|
15261
15355
|
} else {
|
|
15262
15356
|
const decoder = this.#decoderForPacket(packetId, catalogTrackName)?.client;
|
|
@@ -15570,6 +15664,7 @@ var FECManager = class _FECManager {
|
|
|
15570
15664
|
resetMap(this.#blockSourceEsis);
|
|
15571
15665
|
resetMap(this.#blockRebuiltEsis);
|
|
15572
15666
|
resetMap(this.#blockAuthoredLengths);
|
|
15667
|
+
resetSet(this.#isoSpanKeys);
|
|
15573
15668
|
resetMap(this.#blockRepairRsIds);
|
|
15574
15669
|
resetMap(this.#blockRsbLengths);
|
|
15575
15670
|
resetMap(this.#heldLayerStates);
|
|
@@ -15695,6 +15790,7 @@ var FECManager = class _FECManager {
|
|
|
15695
15790
|
dropMap(this.#blockSourceEsis);
|
|
15696
15791
|
dropMap(this.#blockRebuiltEsis);
|
|
15697
15792
|
dropMap(this.#blockAuthoredLengths);
|
|
15793
|
+
dropSet(this.#isoSpanKeys);
|
|
15698
15794
|
dropMap(this.#blockRepairRsIds);
|
|
15699
15795
|
dropMap(this.#blockRsbLengths);
|
|
15700
15796
|
dropMap(this.#blockPathCounts);
|
|
@@ -16037,8 +16133,9 @@ var FECManager = class _FECManager {
|
|
|
16037
16133
|
return true;
|
|
16038
16134
|
}
|
|
16039
16135
|
/**
|
|
16040
|
-
* Drop a block's repair hold: its FETCH ended without delivering
|
|
16041
|
-
*
|
|
16136
|
+
* Drop a block's repair hold: its FETCH ended without delivering, or brought
|
|
16137
|
+
* back only a repair that cannot complete it. The block falls back to its
|
|
16138
|
+
* multicast wait, which may already have passed.
|
|
16042
16139
|
*/
|
|
16043
16140
|
releaseRepairHold(sbn, packetId, catalogTrackName) {
|
|
16044
16141
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
@@ -16176,6 +16273,7 @@ var FECManager = class _FECManager {
|
|
|
16176
16273
|
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
16177
16274
|
const evictedBefore = locator.heldEvicted;
|
|
16178
16275
|
const held = locator.hold(ssId, source);
|
|
16276
|
+
this.#settleExpiredIsoHoles(locator, packetId, catalogTrackName);
|
|
16179
16277
|
if (held && source.source === "multicast") this.#noteMulticastSource(locator, ssId, packetId, catalogTrackName);
|
|
16180
16278
|
const evicted = locator.heldEvicted - evictedBefore;
|
|
16181
16279
|
if (evicted > 0) {
|
|
@@ -16197,17 +16295,20 @@ var FECManager = class _FECManager {
|
|
|
16197
16295
|
* contradicts a known block is refused (counted) and must be dropped.
|
|
16198
16296
|
*/
|
|
16199
16297
|
placeIsoRepair(packetId, catalogTrackName, ssStart, ssbLength) {
|
|
16200
|
-
const
|
|
16298
|
+
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
16299
|
+
const placement = locator.placeRepair(ssStart, ssbLength);
|
|
16300
|
+
this.#settleExpiredIsoHoles(locator, packetId, catalogTrackName);
|
|
16201
16301
|
if (placement.status !== "placed") {
|
|
16202
16302
|
this.#isoRepairRefusedCount++;
|
|
16203
16303
|
if (this.#isoRepairRefusedCount <= 5 || this.#isoRepairRefusedCount % 100 === 0) {
|
|
16204
16304
|
console.warn(`[FECManager] packetId=${packetId} iso-ssbg1-v1 repair refused (${placement.status}): ${placement.reason}`);
|
|
16205
16305
|
}
|
|
16306
|
+
if (placement.ofSpan !== void 0) this.releaseRepairHold(placement.ofSpan.start, packetId, catalogTrackName);
|
|
16206
16307
|
return placement;
|
|
16207
16308
|
}
|
|
16309
|
+
this.#isoSpanKeys.delete(this.#blockKey(placement.block.start, packetId, catalogTrackName));
|
|
16208
16310
|
if (placement.span !== void 0) this.#openIsoSpan(placement.span, packetId, catalogTrackName);
|
|
16209
16311
|
if (placement.hole !== void 0) {
|
|
16210
|
-
const locator = this.#isoLocatorFor(packetId, catalogTrackName);
|
|
16211
16312
|
const hole = placement.hole.block;
|
|
16212
16313
|
const previous = locator.blockEndingAt(hole.start);
|
|
16213
16314
|
const loss = {
|
|
@@ -16228,17 +16329,29 @@ var FECManager = class _FECManager {
|
|
|
16228
16329
|
* `handler`, instead of a repair-less span; null restores spans. Set by the
|
|
16229
16330
|
* range repair scheduler; applies to every stream from its next gap. The
|
|
16230
16331
|
* handler and the deferral are one setting, so no hole is ever created with
|
|
16231
|
-
* nobody to close it.
|
|
16332
|
+
* nobody to close it. `onExpired` is told of each pending hole the stream
|
|
16333
|
+
* runs past the locator's horizon (BLO-52085): returning true, it closes
|
|
16334
|
+
* the hole (`closeIsoHoles`), so the span it leaves still fails and is
|
|
16335
|
+
* reported; otherwise the hole is discarded with its sources.
|
|
16232
16336
|
*/
|
|
16233
|
-
setUndelimitedLossHandler(handler) {
|
|
16337
|
+
setUndelimitedLossHandler(handler, onExpired = null) {
|
|
16234
16338
|
this.#onUndelimitedLoss = handler;
|
|
16339
|
+
this.#onIsoHoleExpired = handler === null ? null : onExpired;
|
|
16235
16340
|
this.#deferIsoGaps = handler !== null;
|
|
16236
16341
|
for (const locator of this.#isoLocators.values()) locator.deferIncompleteGaps = this.#deferIsoGaps;
|
|
16237
16342
|
}
|
|
16343
|
+
/** Hand each hole the locator's horizon passed to its owner, or discard it (setUndelimitedLossHandler). */
|
|
16344
|
+
#settleExpiredIsoHoles(locator, packetId, catalogTrackName) {
|
|
16345
|
+
for (const hole of locator.takeExpiredHoles()) {
|
|
16346
|
+
const expired = { packetId, catalogTrackName, holeStart: hole.start, holeLength: hole.length };
|
|
16347
|
+
if (this.#onIsoHoleExpired?.(expired) !== true) locator.closeHolesWithin(hole.start, hole.length);
|
|
16348
|
+
}
|
|
16349
|
+
}
|
|
16238
16350
|
/**
|
|
16239
|
-
* The range repair for a hole ended: every hole
|
|
16240
|
-
*
|
|
16241
|
-
* with the held sources they placed, for the caller to feed
|
|
16351
|
+
* The range repair for a hole ended: every hole inside `[start, start +
|
|
16352
|
+
* length)`, pending or expired, becomes a repair-less span. Returns those
|
|
16353
|
+
* spans with the held sources they placed, for the caller to feed
|
|
16354
|
+
* (MmtpRouter).
|
|
16242
16355
|
*/
|
|
16243
16356
|
closeIsoHoles(packetId, catalogTrackName, start, length) {
|
|
16244
16357
|
const locator = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName));
|
|
@@ -16257,7 +16370,10 @@ var FECManager = class _FECManager {
|
|
|
16257
16370
|
*/
|
|
16258
16371
|
#openIsoSpan(span, packetId, catalogTrackName) {
|
|
16259
16372
|
const key = this.#blockKey(span.block.start, packetId, catalogTrackName);
|
|
16260
|
-
if (!this.#blockAuthoredLengths.has(key))
|
|
16373
|
+
if (!this.#blockAuthoredLengths.has(key)) {
|
|
16374
|
+
this.#blockAuthoredLengths.set(key, span.block.length);
|
|
16375
|
+
this.#isoSpanKeys.add(key);
|
|
16376
|
+
}
|
|
16261
16377
|
if (span.placed.length === 0) this.#materializeBlock(span.block.start, packetId, catalogTrackName, true);
|
|
16262
16378
|
}
|
|
16263
16379
|
/**
|
|
@@ -16598,11 +16714,14 @@ var FECManager = class _FECManager {
|
|
|
16598
16714
|
});
|
|
16599
16715
|
client.onCleanupComplete((sbn) => {
|
|
16600
16716
|
if (this.#releasedWorkerClients.has(client)) return;
|
|
16601
|
-
const
|
|
16717
|
+
const owed = [...this.#pendingWorkerCleanups].filter((key) => this.#pendingWorkerCleanupClients.get(key) === client);
|
|
16718
|
+
const cleanupKeys = owed.filter((key) => {
|
|
16602
16719
|
const block = this.#parseBlockKey(key);
|
|
16603
|
-
return block.packetId === packetId && block.sbn === sbn
|
|
16720
|
+
return block.packetId === packetId && block.sbn === sbn;
|
|
16604
16721
|
});
|
|
16605
|
-
|
|
16722
|
+
const askedAtMs = Math.max(...cleanupKeys.map((key) => this.#pendingWorkerCleanupBounds.get(key)?.askedAtMs ?? -Infinity));
|
|
16723
|
+
const due = owed.filter((key) => !cleanupKeys.includes(key) && (this.#pendingWorkerCleanupBounds.get(key)?.dueAtMs ?? Infinity) <= askedAtMs);
|
|
16724
|
+
for (const key of [...cleanupKeys, ...due]) this.#settleOwedCleanup(key, "cleanup-ack");
|
|
16606
16725
|
});
|
|
16607
16726
|
client.onError((msg, code) => {
|
|
16608
16727
|
const requestedKind = code ?? msg;
|
|
@@ -16672,9 +16791,11 @@ var FECManager = class _FECManager {
|
|
|
16672
16791
|
* fail deadline already passed, so failing it is the decision that deadline
|
|
16673
16792
|
* made. A late ack then finds nothing owed and is a no-op.
|
|
16674
16793
|
*/
|
|
16675
|
-
#boundOwedCleanup(key, packetId, client) {
|
|
16794
|
+
#boundOwedCleanup(key, packetId, client, dueAtMs) {
|
|
16676
16795
|
this.#clearOwedCleanupBound(key);
|
|
16677
16796
|
const bound = {
|
|
16797
|
+
dueAtMs,
|
|
16798
|
+
askedAtMs: performance.now(),
|
|
16678
16799
|
handle: setTimeout(() => {
|
|
16679
16800
|
if (this.#pendingWorkerCleanupBounds.get(key) !== bound) return;
|
|
16680
16801
|
this.#pendingWorkerCleanupBounds.delete(key);
|
|
@@ -17245,12 +17366,13 @@ var FECManager = class _FECManager {
|
|
|
17245
17366
|
* the inactivity deadline on each symbol, records the symbol's in-band
|
|
17246
17367
|
* classification evidence, and marks the block clean when all of its
|
|
17247
17368
|
* authored source symbols arrived: the SSB_length a repair symbol declared,
|
|
17248
|
-
* otherwise K.
|
|
17369
|
+
* otherwise K. `held` marks a source held unplaced since it arrived and
|
|
17370
|
+
* placed only now (see #noteSbnSeen).
|
|
17249
17371
|
*
|
|
17250
17372
|
* Throws for a packetId that was never configured, so a classified block
|
|
17251
17373
|
* never reads the manager-global K or timeout.
|
|
17252
17374
|
*/
|
|
17253
|
-
trackBlock(sbn, packetId, catalogTrackName, esi, evidence) {
|
|
17375
|
+
trackBlock(sbn, packetId, catalogTrackName, esi, evidence, held = false) {
|
|
17254
17376
|
const trackConfig = this.#trackConfigs.get(packetId);
|
|
17255
17377
|
if (trackConfig === void 0) {
|
|
17256
17378
|
throw new Error(
|
|
@@ -17266,7 +17388,8 @@ var FECManager = class _FECManager {
|
|
|
17266
17388
|
catalogTrackName,
|
|
17267
17389
|
"source",
|
|
17268
17390
|
evidence.packetSequenceNumber,
|
|
17269
|
-
Number.isInteger(esi) ? sbn + esi >>> 0 : sbn
|
|
17391
|
+
Number.isInteger(esi) ? sbn + esi >>> 0 : sbn,
|
|
17392
|
+
held
|
|
17270
17393
|
);
|
|
17271
17394
|
const authoredLength = this.#blockAuthoredLengths.get(key) ?? k;
|
|
17272
17395
|
if (Number.isInteger(esi) && esi >= authoredLength) {
|
|
@@ -17321,6 +17444,7 @@ var FECManager = class _FECManager {
|
|
|
17321
17444
|
this.#blockSourceEsis.delete(key);
|
|
17322
17445
|
this.#blockRebuiltEsis.delete(key);
|
|
17323
17446
|
this.#blockAuthoredLengths.delete(key);
|
|
17447
|
+
this.#isoSpanKeys.delete(key);
|
|
17324
17448
|
this.#blockRepairRsIds.delete(key);
|
|
17325
17449
|
this.#blockRsbLengths.delete(key);
|
|
17326
17450
|
this.#markTerminal(key, sbn, packetId, true);
|
|
@@ -17351,6 +17475,19 @@ var FECManager = class _FECManager {
|
|
|
17351
17475
|
#receiptNow() {
|
|
17352
17476
|
return this.#receiptAtMs ?? performance.now();
|
|
17353
17477
|
}
|
|
17478
|
+
/**
|
|
17479
|
+
* The whole-ms delay of a timer for `deadlineMs` armed inside a receipt
|
|
17480
|
+
* scope: counted from the receipt time, as the timer of a symbol fed when
|
|
17481
|
+
* it was received was, less the whole ms since. So it fires on the tick
|
|
17482
|
+
* that timer would have, never later than a delay counted from now, and
|
|
17483
|
+
* its handler still re-arms a timer that fires before its deadline.
|
|
17484
|
+
* Counted from now, a deadline set from the receipt time is rounded up
|
|
17485
|
+
* from another fraction of a ms (BLO-52085).
|
|
17486
|
+
*/
|
|
17487
|
+
#receiptTimerDelayMs(deadlineMs, minMs) {
|
|
17488
|
+
const receivedAtMs = this.#receiptAtMs;
|
|
17489
|
+
return Math.max(minMs, Math.ceil(deadlineMs - receivedAtMs) - Math.ceil(performance.now() - receivedAtMs));
|
|
17490
|
+
}
|
|
17354
17491
|
/**
|
|
17355
17492
|
* A held packet admitted late arms from its (earlier) receipt time; that
|
|
17356
17493
|
* never pulls in a deadline later activity already armed. Outside a
|
|
@@ -17399,6 +17536,7 @@ var FECManager = class _FECManager {
|
|
|
17399
17536
|
}
|
|
17400
17537
|
#failBlock(sbn, packetId, catalogTrackName) {
|
|
17401
17538
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
17539
|
+
const span = this.#isoSpanKeys.delete(key) ? { length: this.#blockAuthoredLengths.get(key) ?? 0, sources: this.#blockSourceCounts.get(key) ?? 0 } : void 0;
|
|
17402
17540
|
this.#traceLifecycle("failed", sbn, packetId, catalogTrackName);
|
|
17403
17541
|
this.#clearRepairDeadline(key);
|
|
17404
17542
|
this.#clearBlockTimer(key);
|
|
@@ -17418,7 +17556,8 @@ var FECManager = class _FECManager {
|
|
|
17418
17556
|
this.#markPathBlockComplete(sbn, packetId, catalogTrackName);
|
|
17419
17557
|
this.#fecStats.blocksFailed++;
|
|
17420
17558
|
this.#statsForPacket(packetId).blocksFailed++;
|
|
17421
|
-
this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
17559
|
+
if (span === void 0) this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
17560
|
+
else this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass, span);
|
|
17422
17561
|
this.onBlockResolved?.(sbn, packetId, catalogTrackName);
|
|
17423
17562
|
}
|
|
17424
17563
|
/**
|
|
@@ -17449,6 +17588,7 @@ var FECManager = class _FECManager {
|
|
|
17449
17588
|
this.#blockSourceEsis.delete(key);
|
|
17450
17589
|
this.#blockRebuiltEsis.delete(key);
|
|
17451
17590
|
this.#blockAuthoredLengths.delete(key);
|
|
17591
|
+
this.#isoSpanKeys.delete(key);
|
|
17452
17592
|
this.#blockRepairRsIds.delete(key);
|
|
17453
17593
|
this.#blockRsbLengths.delete(key);
|
|
17454
17594
|
this.#clearBlockTimer(key);
|
|
@@ -17541,6 +17681,7 @@ var FECManager = class _FECManager {
|
|
|
17541
17681
|
this.#blockSourceEsis.clear();
|
|
17542
17682
|
this.#blockRebuiltEsis.clear();
|
|
17543
17683
|
this.#blockAuthoredLengths.clear();
|
|
17684
|
+
this.#isoSpanKeys.clear();
|
|
17544
17685
|
this.#blockRepairRsIds.clear();
|
|
17545
17686
|
this.#blockRsbLengths.clear();
|
|
17546
17687
|
this.#heldLayerStates.clear();
|
|
@@ -18442,6 +18583,9 @@ function authoredGap(before, after) {
|
|
|
18442
18583
|
function ssIdAfter(a, b) {
|
|
18443
18584
|
return (a - b | 0) > 0;
|
|
18444
18585
|
}
|
|
18586
|
+
function holdsRebuilt(stream) {
|
|
18587
|
+
return stream.buf.some(({ meta }) => meta.fecSbnFirst !== void 0);
|
|
18588
|
+
}
|
|
18445
18589
|
var FecReleaseSequencer = class {
|
|
18446
18590
|
#streams = /* @__PURE__ */ new Map();
|
|
18447
18591
|
#gate;
|
|
@@ -18504,6 +18648,7 @@ var FecReleaseSequencer = class {
|
|
|
18504
18648
|
stream.lastSbn = void 0;
|
|
18505
18649
|
stream.discontinuity = false;
|
|
18506
18650
|
stream.rapSsId = void 0;
|
|
18651
|
+
stream.sourced = false;
|
|
18507
18652
|
}
|
|
18508
18653
|
if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {
|
|
18509
18654
|
this.#recordStat("droppedStale");
|
|
@@ -18530,6 +18675,7 @@ var FecReleaseSequencer = class {
|
|
|
18530
18675
|
if (compareFrameOrder(stream.buf[mid], frame) < 0) lo = mid + 1;
|
|
18531
18676
|
else hi = mid;
|
|
18532
18677
|
}
|
|
18678
|
+
if (stream.buf[lo]?.passed === true) frame.passed = true;
|
|
18533
18679
|
stream.buf.splice(lo, 0, frame);
|
|
18534
18680
|
this.#drainUnlessDeferred(stream);
|
|
18535
18681
|
}
|
|
@@ -18565,6 +18711,41 @@ var FecReleaseSequencer = class {
|
|
|
18565
18711
|
}
|
|
18566
18712
|
return false;
|
|
18567
18713
|
}
|
|
18714
|
+
/**
|
|
18715
|
+
* These streams' sink is being replaced by one that has seen no source
|
|
18716
|
+
* frame yet. First, from a stream that holds a frame the overlay rebuilt,
|
|
18717
|
+
* release to the sink being replaced the frames the unanchored path had
|
|
18718
|
+
* already released to it (#cursorPassed): there they waited in its queue,
|
|
18719
|
+
* which its replacement flushes. A stream that holds none is not drained:
|
|
18720
|
+
* it holds nothing the anchor kept back, and a drain would still force out
|
|
18721
|
+
* a frame held past the hold limit, which before the anchor waited for the
|
|
18722
|
+
* next push. Then treat the streams as unsourced until a source frame is
|
|
18723
|
+
* released.
|
|
18724
|
+
*/
|
|
18725
|
+
unsource(packetId, catalogTrackName) {
|
|
18726
|
+
for (const stream of this.#streams.values()) {
|
|
18727
|
+
if (stream.packetId === packetId && (catalogTrackName === void 0 || stream.catalogTrackName === catalogTrackName)) {
|
|
18728
|
+
if (stream.draining === 0 && holdsRebuilt(stream)) {
|
|
18729
|
+
if (this.#deferDepth > 0) this.#deferredStreams.add(stream);
|
|
18730
|
+
else this.#drain(stream, true);
|
|
18731
|
+
}
|
|
18732
|
+
stream.sourced = false;
|
|
18733
|
+
}
|
|
18734
|
+
}
|
|
18735
|
+
}
|
|
18736
|
+
/**
|
|
18737
|
+
* Whether a stream of `packetId` holds a frame the keyframe overlay rebuilt
|
|
18738
|
+
* (FrameMeta.fecSbnFirst). Without `catalogTrackName`, every stream of the
|
|
18739
|
+
* packetId.
|
|
18740
|
+
*/
|
|
18741
|
+
holdsRebuilt(packetId, catalogTrackName) {
|
|
18742
|
+
for (const stream of this.#streams.values()) {
|
|
18743
|
+
if (stream.packetId !== packetId) continue;
|
|
18744
|
+
if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
|
|
18745
|
+
if (holdsRebuilt(stream)) return true;
|
|
18746
|
+
}
|
|
18747
|
+
return false;
|
|
18748
|
+
}
|
|
18568
18749
|
/** Re-evaluate held gaps after FEC state changed. */
|
|
18569
18750
|
poke(streamKey) {
|
|
18570
18751
|
if (streamKey !== void 0) {
|
|
@@ -18656,16 +18837,32 @@ var FecReleaseSequencer = class {
|
|
|
18656
18837
|
last: null,
|
|
18657
18838
|
lastSbn: void 0,
|
|
18658
18839
|
discontinuity: false,
|
|
18659
|
-
rapSsId: void 0
|
|
18840
|
+
rapSsId: void 0,
|
|
18841
|
+
sourced: false,
|
|
18842
|
+
draining: 0
|
|
18660
18843
|
};
|
|
18661
18844
|
this.#streams.set(streamKey, stream);
|
|
18662
18845
|
}
|
|
18663
18846
|
return stream;
|
|
18664
18847
|
}
|
|
18665
|
-
#
|
|
18848
|
+
/** `asUnanchored`: release overlay-rebuilt frames when unanchored did (#cursorPassed). */
|
|
18849
|
+
#drain(stream, asUnanchored = false) {
|
|
18850
|
+
stream.draining++;
|
|
18851
|
+
try {
|
|
18852
|
+
this.#drainLoop(stream, asUnanchored);
|
|
18853
|
+
} finally {
|
|
18854
|
+
stream.draining--;
|
|
18855
|
+
}
|
|
18856
|
+
}
|
|
18857
|
+
#drainLoop(stream, asUnanchored) {
|
|
18666
18858
|
while (stream.buf.length > 0 && stream.last !== null) {
|
|
18667
18859
|
const head = stream.buf[0];
|
|
18668
18860
|
const last = stream.last;
|
|
18861
|
+
if (compareFrameOrder(head, last) <= 0) {
|
|
18862
|
+
stream.buf.shift();
|
|
18863
|
+
this.#release(stream, head, void 0);
|
|
18864
|
+
continue;
|
|
18865
|
+
}
|
|
18669
18866
|
if (stream.discontinuity) {
|
|
18670
18867
|
stream.discontinuity = false;
|
|
18671
18868
|
stream.buf.shift();
|
|
@@ -18679,9 +18876,10 @@ var FecReleaseSequencer = class {
|
|
|
18679
18876
|
}
|
|
18680
18877
|
const headSbn = head.meta.fecSbn;
|
|
18681
18878
|
const from = stream.lastSbn ?? headSbn;
|
|
18682
|
-
const to = headSbn ?? stream.lastSbn;
|
|
18879
|
+
const to = head.meta.fecSbnFirst ?? headSbn ?? stream.lastSbn;
|
|
18683
18880
|
let lost = [];
|
|
18684
18881
|
const unanchored = from === void 0 && to === void 0;
|
|
18882
|
+
let unresolved = false;
|
|
18685
18883
|
if (from !== void 0 && to !== void 0) {
|
|
18686
18884
|
const resolved = this.#gate.resolvedLoss(
|
|
18687
18885
|
stream.packetId,
|
|
@@ -18690,7 +18888,8 @@ var FecReleaseSequencer = class {
|
|
|
18690
18888
|
Math.max(from, to),
|
|
18691
18889
|
from
|
|
18692
18890
|
);
|
|
18693
|
-
|
|
18891
|
+
unresolved = resolved === null;
|
|
18892
|
+
if (unresolved && !this.#cursorPassed(stream, asUnanchored)) {
|
|
18694
18893
|
const heldMs = performance.now() - head.arrivedAtMs;
|
|
18695
18894
|
if (heldMs <= this.#gate.holdLimitMs(stream.packetId, stream.catalogTrackName)) return;
|
|
18696
18895
|
this.#recordStat("forcedWindowFlushes");
|
|
@@ -18698,22 +18897,56 @@ var FecReleaseSequencer = class {
|
|
|
18698
18897
|
this.#release(stream, head, this.#gate.decide(last, head, [], true));
|
|
18699
18898
|
continue;
|
|
18700
18899
|
}
|
|
18701
|
-
lost = resolved;
|
|
18900
|
+
lost = resolved ?? [];
|
|
18702
18901
|
}
|
|
18703
18902
|
stream.buf.shift();
|
|
18704
18903
|
this.#release(
|
|
18705
18904
|
stream,
|
|
18706
18905
|
head,
|
|
18707
|
-
lost.length > 0 || unanchored || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
|
|
18906
|
+
lost.length > 0 || unanchored || unresolved || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
|
|
18708
18907
|
);
|
|
18709
18908
|
}
|
|
18710
18909
|
}
|
|
18910
|
+
/**
|
|
18911
|
+
* An overlay-rebuilt frame (fecSbnFirst) waits no longer than the BLO-53342
|
|
18912
|
+
* source cursor holds it unanchored, and neither does a later frame behind
|
|
18913
|
+
* it. Unanchored, a frame that waits on no block (fecSbnFirst set, or no
|
|
18914
|
+
* fecSbn) left the gate once every frame ahead of it had: mark it `passed`,
|
|
18915
|
+
* like each frame that arrived in front of it since (stale there). The head
|
|
18916
|
+
* then goes, decided as it would be unanchored, once a frame the base path
|
|
18917
|
+
* delivered (a source frame, or one base FEC recovered) is buffered after a
|
|
18918
|
+
* marked one: the base path has passed that one, so a frame still missing
|
|
18919
|
+
* before it can only come from a repair. It also goes while no source frame
|
|
18920
|
+
* has been released, when the direct path shows a recovered frame at once.
|
|
18921
|
+
* And it goes at once while a staged switch decides on when frames arrive
|
|
18922
|
+
* (FecReleaseGate.releaseUnanchored), and when unsource releases into a
|
|
18923
|
+
* sink being replaced: unanchored, it had already reached the sink.
|
|
18924
|
+
* The scan also writes: the `passed` marks it sets stay on the frames,
|
|
18925
|
+
* and push marks a frame it inserts in front of a marked one from them.
|
|
18926
|
+
*/
|
|
18927
|
+
#cursorPassed(stream, asUnanchored) {
|
|
18928
|
+
let at2 = -1;
|
|
18929
|
+
for (let i = 0; i < stream.buf.length; i++) {
|
|
18930
|
+
const frame = stream.buf[i];
|
|
18931
|
+
if (frame.meta.fecSbnFirst !== void 0 || frame.meta.fecSbn === void 0) {
|
|
18932
|
+
frame.passed = true;
|
|
18933
|
+
if (at2 < 0) at2 = i;
|
|
18934
|
+
} else if (frame.passed !== true) break;
|
|
18935
|
+
}
|
|
18936
|
+
if (at2 < 0) return false;
|
|
18937
|
+
return asUnanchored || !stream.sourced || this.#gate.releaseUnanchored?.(stream.packetId, stream.catalogTrackName) === true || stream.buf.some(({ meta }, i) => i > at2 && meta.fecSbnFirst === void 0 && (meta.recovered !== true || meta.fecSbn !== void 0));
|
|
18938
|
+
}
|
|
18711
18939
|
#release(stream, frame, decision) {
|
|
18940
|
+
const stale = stream.last !== null && compareFrameOrder(frame, stream.last) <= 0;
|
|
18941
|
+
if (stale) this.#recordStat("droppedStale");
|
|
18712
18942
|
if (decision) frame.meta.gapBefore = decision;
|
|
18713
18943
|
stream.last = frame;
|
|
18714
|
-
if (frame.meta.fecSbn !== void 0
|
|
18944
|
+
if (frame.meta.fecSbn !== void 0 && (frame.meta.fecSbnFirst === void 0 || stream.lastSbn !== void 0)) {
|
|
18945
|
+
stream.lastSbn = frame.meta.fecSbn;
|
|
18946
|
+
}
|
|
18947
|
+
if (frame.meta.recovered !== true) stream.sourced = true;
|
|
18715
18948
|
this.#gate.onReleased?.(frame.data, frame.meta);
|
|
18716
|
-
this.#emit(frame.data, frame.meta);
|
|
18949
|
+
if (!stale) this.#emit(frame.data, frame.meta);
|
|
18717
18950
|
}
|
|
18718
18951
|
};
|
|
18719
18952
|
var FMPU_BOX_TYPES = /* @__PURE__ */ new Set(["ftyp", "moov", "moof"]);
|
|
@@ -19039,6 +19272,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19039
19272
|
fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
|
|
19040
19273
|
return this.#fecSequencer?.rapAbove(packetId, catalogTrackName, ssId) ?? false;
|
|
19041
19274
|
}
|
|
19275
|
+
/** See FecReleaseSequencer.unsource. */
|
|
19276
|
+
unsourceFecRelease(packetId, catalogTrackName) {
|
|
19277
|
+
this.#fecSequencer?.unsource(packetId, catalogTrackName);
|
|
19278
|
+
}
|
|
19279
|
+
/** See FecReleaseSequencer.holdsRebuilt. */
|
|
19280
|
+
fecReleaseHoldsRebuilt(packetId, catalogTrackName) {
|
|
19281
|
+
return this.#fecSequencer?.holdsRebuilt(packetId, catalogTrackName) ?? false;
|
|
19282
|
+
}
|
|
19042
19283
|
/** Re-evaluate held gaps after FEC block state changed. */
|
|
19043
19284
|
pokeFecRelease(packetId, catalogTrackName) {
|
|
19044
19285
|
if (!this.#fecSequencer) return;
|
|
@@ -19365,7 +19606,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19365
19606
|
info,
|
|
19366
19607
|
resolver,
|
|
19367
19608
|
options?.recovered === true,
|
|
19368
|
-
options?.fecSbn
|
|
19609
|
+
options?.fecSbn,
|
|
19610
|
+
options?.fecSbnFirst
|
|
19369
19611
|
);
|
|
19370
19612
|
if (deferred.record === void 0) this.#countDeferredOutcome(deferred.reason);
|
|
19371
19613
|
}
|
|
@@ -19458,7 +19700,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19458
19700
|
info,
|
|
19459
19701
|
resolver,
|
|
19460
19702
|
options?.recovered === true,
|
|
19461
|
-
options?.fecSbn
|
|
19703
|
+
options?.fecSbn,
|
|
19704
|
+
options?.fecSbnFirst
|
|
19462
19705
|
);
|
|
19463
19706
|
if (outcome.record !== void 0) return outcome.record;
|
|
19464
19707
|
return this.#drop(outcome.reason);
|
|
@@ -19478,7 +19721,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19478
19721
|
* attribution exists to keep readable. Leaving the choice at the call site
|
|
19479
19722
|
* is what keeps both true.
|
|
19480
19723
|
*/
|
|
19481
|
-
#emitCombinedSample(combined, info, resolver, recovered, fecSbn) {
|
|
19724
|
+
#emitCombinedSample(combined, info, resolver, recovered, fecSbn, fecSbnFirst) {
|
|
19482
19725
|
const timing = resolver.sampleTiming(
|
|
19483
19726
|
combined.mpuSequenceNumber,
|
|
19484
19727
|
combined.movieFragmentSequenceNumber,
|
|
@@ -19495,6 +19738,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19495
19738
|
);
|
|
19496
19739
|
if (!meta) return { reason: "combined-meta-build-failed" };
|
|
19497
19740
|
if (fecSbn !== void 0) meta.fecSbn = fecSbn;
|
|
19741
|
+
if (fecSbnFirst !== void 0) meta.fecSbnFirst = fecSbnFirst;
|
|
19498
19742
|
if (!resolver.consumeSample(
|
|
19499
19743
|
combined.mpuSequenceNumber,
|
|
19500
19744
|
combined.movieFragmentSequenceNumber,
|
|
@@ -23011,6 +23255,7 @@ function unstampedBaseSsId(offset, unstampedOffset, headerPsn) {
|
|
|
23011
23255
|
var OVERLAY_ROUTED_LIMIT = 1024;
|
|
23012
23256
|
var OVERLAY_PENDING_LIMIT = 256;
|
|
23013
23257
|
var OVERLAY_OFFSET_MPUS = 16;
|
|
23258
|
+
var OVERLAY_REBUILT_LIMIT = 256;
|
|
23014
23259
|
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
23015
23260
|
function assertStreamIdentity(role, stream) {
|
|
23016
23261
|
if (stream === void 0 || !Number.isInteger(stream.packetId) || stream.packetId <= MMTP_SIGNALING_PACKET_ID || typeof stream.catalogTrackName !== "string" || stream.catalogTrackName.length === 0) {
|
|
@@ -23187,6 +23432,17 @@ var MmtpRouter = class {
|
|
|
23187
23432
|
* an MPU no member of which arrived).
|
|
23188
23433
|
*/
|
|
23189
23434
|
#unstampedSsIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
23435
|
+
/**
|
|
23436
|
+
* The same, shown by the overlay alone, before any base recovery did: a
|
|
23437
|
+
* packet it rebuilt whose base copy carries its protected psn
|
|
23438
|
+
* (#feedOverlaySource), or whose protected psn passes overlayBaseSsId's
|
|
23439
|
+
* test at that offset (#routeOverlayRecovered). A base SS_ID placed by it
|
|
23440
|
+
* anchors the release gate only (BLO-52085 D3): fed to the base, it would
|
|
23441
|
+
* make the base drop a late base copy of a keyframe the overlay never
|
|
23442
|
+
* finished, and lift a block's late-source cut, which nothing before
|
|
23443
|
+
* this offset did.
|
|
23444
|
+
*/
|
|
23445
|
+
#overlayUnstampedSsIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
23190
23446
|
/**
|
|
23191
23447
|
* Streams whose multicast leg delivered a RAP-flagged source packet that was
|
|
23192
23448
|
* not parsed, typically because its overlap copy was parsed first
|
|
@@ -23464,13 +23720,18 @@ var MmtpRouter = class {
|
|
|
23464
23720
|
this.#onDeltaBlockCompleteCb?.(packetId, false);
|
|
23465
23721
|
}
|
|
23466
23722
|
};
|
|
23467
|
-
this.#fecManager.onBlockFailed = (sbn, packetId, catalogTrackName, blockClass) => {
|
|
23723
|
+
this.#fecManager.onBlockFailed = (sbn, packetId, catalogTrackName, blockClass, span) => {
|
|
23468
23724
|
this.#mseAssemblerFor(packetId, catalogTrackName)?.failBlock(sbn);
|
|
23469
23725
|
if (this.#isCatalogTrackUnambiguous(packetId, catalogTrackName) && this.#packetIdRoles.get(packetId) === "delta") {
|
|
23470
23726
|
this.#onDeltaBlockCompleteCb?.(packetId, true);
|
|
23471
23727
|
}
|
|
23472
|
-
|
|
23473
|
-
|
|
23728
|
+
if (span === void 0) {
|
|
23729
|
+
onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
23730
|
+
this.#onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
23731
|
+
} else {
|
|
23732
|
+
onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass, span);
|
|
23733
|
+
this.#onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass, span);
|
|
23734
|
+
}
|
|
23474
23735
|
};
|
|
23475
23736
|
}
|
|
23476
23737
|
}
|
|
@@ -23515,8 +23776,9 @@ var MmtpRouter = class {
|
|
|
23515
23776
|
}
|
|
23516
23777
|
/**
|
|
23517
23778
|
* FEC-recovered source symbols whose reconstructed header named a
|
|
23518
|
-
* packet_sequence_number other than the wire sequence their SS_ID maps to
|
|
23519
|
-
*
|
|
23779
|
+
* packet_sequence_number other than the wire sequence their SS_ID maps to,
|
|
23780
|
+
* or, rebuilt by the overlay, than their base copy carries: a hop
|
|
23781
|
+
* re-stamped the sequence after the sender protected the packet.
|
|
23520
23782
|
*/
|
|
23521
23783
|
get restampedRecoveredSequenceCount() {
|
|
23522
23784
|
return this.#restampedRecoveredSeqs;
|
|
@@ -23695,6 +23957,7 @@ var MmtpRouter = class {
|
|
|
23695
23957
|
enableMseAssembler(packetId, config, catalogTrackName) {
|
|
23696
23958
|
const assemblerKey = this.#mseAssemblerKey(packetId, catalogTrackName);
|
|
23697
23959
|
const previous = this.#mseAssemblers.get(assemblerKey);
|
|
23960
|
+
this.#parser.unsourceFecRelease(packetId, catalogTrackName);
|
|
23698
23961
|
if (previous) {
|
|
23699
23962
|
previous.flush();
|
|
23700
23963
|
previous.dispose();
|
|
@@ -23845,7 +24108,8 @@ var MmtpRouter = class {
|
|
|
23845
24108
|
fec,
|
|
23846
24109
|
k,
|
|
23847
24110
|
offsets: /* @__PURE__ */ new Map(),
|
|
23848
|
-
moqBlocks: /* @__PURE__ */ new Set()
|
|
24111
|
+
moqBlocks: /* @__PURE__ */ new Set(),
|
|
24112
|
+
rebuilt: /* @__PURE__ */ new Map()
|
|
23849
24113
|
};
|
|
23850
24114
|
this.#overlays.set(repairPacketId, inst);
|
|
23851
24115
|
return () => {
|
|
@@ -23886,6 +24150,7 @@ var MmtpRouter = class {
|
|
|
23886
24150
|
inst.last = void 0;
|
|
23887
24151
|
inst.offsets.clear();
|
|
23888
24152
|
inst.moqBlocks.clear();
|
|
24153
|
+
inst.rebuilt.clear();
|
|
23889
24154
|
}
|
|
23890
24155
|
#overlaysForSource(sourcePacketId, catalogTrackName) {
|
|
23891
24156
|
return [...this.#overlays.values()].filter(({ leg }) => leg.sourcePacketId === sourcePacketId && (catalogTrackName === void 0 || leg.sourceCatalogTrackName === void 0 || leg.sourceCatalogTrackName === catalogTrackName));
|
|
@@ -23938,13 +24203,23 @@ var MmtpRouter = class {
|
|
|
23938
24203
|
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
23939
24204
|
* included (7.1, 7.4.3). `baseSsId` is that trailer's SS_ID, when the base
|
|
23940
24205
|
* FEC reads it.
|
|
24206
|
+
*
|
|
24207
|
+
* The base copy of a packet an overlay rebuilt with no base SS_ID shows
|
|
24208
|
+
* what a base recovery of it would (#routeProcessedFecSymbols, BLO-52085):
|
|
24209
|
+
* its wire packet_sequence_number equal to the protected one rebuilt shows
|
|
24210
|
+
* the stream unstamped at the SS_ID offset it states, for the release gate
|
|
24211
|
+
* (#overlayUnstampedSsIdSeqOffsetByStream); any other shows a hop that
|
|
24212
|
+
* re-stamps it, which is counted (restampedRecoveredSequenceCount).
|
|
23941
24213
|
*/
|
|
23942
24214
|
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source, baseSsId) {
|
|
23943
24215
|
if (this.#overlays.size === 0) return;
|
|
23944
24216
|
const ssId = keyframeOverlaySsId(header);
|
|
23945
24217
|
if (ssId === void 0) return;
|
|
24218
|
+
let rebuiltPsn;
|
|
23946
24219
|
for (const inst of this.#overlaysForSource(packetId, catalogTrackName)) {
|
|
23947
24220
|
this.#noteOverlaySsId(inst, ssId, sequenceNumber);
|
|
24221
|
+
rebuiltPsn ??= inst.rebuilt.get(ssId);
|
|
24222
|
+
inst.rebuilt.delete(ssId);
|
|
23948
24223
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
23949
24224
|
if (identity !== null) {
|
|
23950
24225
|
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
@@ -23974,6 +24249,14 @@ var MmtpRouter = class {
|
|
|
23974
24249
|
const recovered = fec.feedSourceSymbol(pid, ssId, symbolData, name);
|
|
23975
24250
|
if (recovered && !alreadyCompleted) this.#routeOverlayRecovered(inst, coords.sbn, recovered);
|
|
23976
24251
|
}
|
|
24252
|
+
if (rebuiltPsn === void 0 || this.#classifyPacketId(packetId) === null) return;
|
|
24253
|
+
const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);
|
|
24254
|
+
if (rebuiltPsn !== sequenceNumber) {
|
|
24255
|
+
this.#restampedRecoveredSeqs++;
|
|
24256
|
+
} else if (baseSsId !== void 0) {
|
|
24257
|
+
const offset = this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
24258
|
+
if (offset === baseSsId - sequenceNumber >>> 0) this.#overlayUnstampedSsIdSeqOffsetByStream.set(streamKey, offset);
|
|
24259
|
+
}
|
|
23977
24260
|
}
|
|
23978
24261
|
#noteOverlayMoqBlock(inst, blockStart) {
|
|
23979
24262
|
inst.moqBlocks.add(blockStart);
|
|
@@ -24007,11 +24290,16 @@ var MmtpRouter = class {
|
|
|
24007
24290
|
}
|
|
24008
24291
|
/**
|
|
24009
24292
|
* Route an overlay-recovered block's packets as source media of the
|
|
24010
|
-
* overlay's source (7.2: an overlay coordinate yields no base SS_ID).
|
|
24011
|
-
*
|
|
24012
|
-
*
|
|
24013
|
-
*
|
|
24014
|
-
*
|
|
24293
|
+
* overlay's source (7.2: an overlay coordinate yields no base SS_ID). A
|
|
24294
|
+
* packet whose base SS_ID is determined independently (overlayBaseSsId,
|
|
24295
|
+
* unstampedBaseSsId) enters the release gate at that SS_ID, as a packet
|
|
24296
|
+
* base FEC recovered does, so it waits behind base frames still in flight,
|
|
24297
|
+
* though not for its own MPU's packets (FrameMeta.fecSbnFirst) and no
|
|
24298
|
+
* longer than unanchored; one whose base SS_ID is undetermined has no
|
|
24299
|
+
* release-gate anchor. No parse ledger, no base re-stamp, no block
|
|
24300
|
+
* advance. Each packet's wire sequence is restored from its keyframe's own
|
|
24301
|
+
* offset; without one, a stream the base never re-stamped keeps the
|
|
24302
|
+
* header's sequence and any other drops the packet.
|
|
24015
24303
|
*/
|
|
24016
24304
|
#routeOverlayRecovered(inst, blockStart, recovered) {
|
|
24017
24305
|
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
@@ -24032,40 +24320,65 @@ var MmtpRouter = class {
|
|
|
24032
24320
|
const restamped = this.#restampedStreams.has(streamKey);
|
|
24033
24321
|
const restored = /* @__PURE__ */ new Set();
|
|
24034
24322
|
const baseSsIds = /* @__PURE__ */ new Map();
|
|
24323
|
+
const gateOnly = /* @__PURE__ */ new Set();
|
|
24035
24324
|
const symbols = result.symbols.flatMap((sym) => {
|
|
24325
|
+
const overlaySsId = serial32Add(blockStart, sym.index);
|
|
24036
24326
|
const anchor = inst.offsets.get(sym.fragment.mpuSequenceNumber);
|
|
24037
24327
|
if (anchor !== void 0) {
|
|
24038
|
-
const overlaySsId = serial32Add(blockStart, sym.index);
|
|
24039
24328
|
const packetSequenceNumber = overlaySsId - anchor.psnOffset >>> 0;
|
|
24040
24329
|
restored.add(packetSequenceNumber);
|
|
24041
24330
|
const out = { ...sym, packetSequenceNumber };
|
|
24042
24331
|
const baseSsId = overlayBaseSsId(anchor, overlaySsId, packetSequenceNumber, sym.packetSequenceNumber);
|
|
24043
|
-
if (baseSsId !== void 0)
|
|
24332
|
+
if (baseSsId !== void 0) {
|
|
24333
|
+
baseSsIds.set(sym.index, baseSsId);
|
|
24334
|
+
const offset = this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
24335
|
+
if (!this.#sourceSeqReceived(sourcePacketId, packetSequenceNumber, sourceName) && offset === baseSsId - packetSequenceNumber >>> 0) {
|
|
24336
|
+
this.#overlayUnstampedSsIdSeqOffsetByStream.set(streamKey, offset);
|
|
24337
|
+
}
|
|
24338
|
+
}
|
|
24044
24339
|
return [out];
|
|
24045
24340
|
}
|
|
24046
24341
|
if (!restamped) {
|
|
24047
|
-
const
|
|
24048
|
-
|
|
24049
|
-
|
|
24050
|
-
|
|
24051
|
-
|
|
24052
|
-
|
|
24342
|
+
const offset = this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
24343
|
+
const fedSsId = unstampedBaseSsId(offset, this.#unstampedSsIdSeqOffsetByStream.get(streamKey), sym.packetSequenceNumber);
|
|
24344
|
+
const baseSsId = fedSsId ?? unstampedBaseSsId(offset, this.#overlayUnstampedSsIdSeqOffsetByStream.get(streamKey), sym.packetSequenceNumber);
|
|
24345
|
+
if (baseSsId !== void 0) {
|
|
24346
|
+
baseSsIds.set(sym.index, baseSsId);
|
|
24347
|
+
if (fedSsId === void 0) gateOnly.add(sym.index);
|
|
24348
|
+
} else {
|
|
24349
|
+
inst.rebuilt.set(overlaySsId, sym.packetSequenceNumber);
|
|
24350
|
+
if (inst.rebuilt.size > OVERLAY_REBUILT_LIMIT) inst.rebuilt.delete(inst.rebuilt.keys().next().value);
|
|
24351
|
+
}
|
|
24053
24352
|
return [sym];
|
|
24054
24353
|
}
|
|
24055
24354
|
this.#overlayStats.unsequencedDropped++;
|
|
24056
24355
|
return [];
|
|
24057
24356
|
});
|
|
24058
24357
|
this.#overlayStats.recovered++;
|
|
24358
|
+
const mpuFirst = /* @__PURE__ */ new Map();
|
|
24359
|
+
for (const sym of symbols) {
|
|
24360
|
+
const ssId = baseSsIds.get(sym.index);
|
|
24361
|
+
const mpu = sym.fragment.mpuSequenceNumber;
|
|
24362
|
+
const first = mpuFirst.get(mpu);
|
|
24363
|
+
if (ssId !== void 0 && (first === void 0 || serial32Precedes(ssId, first))) mpuFirst.set(mpu, ssId);
|
|
24364
|
+
}
|
|
24365
|
+
const firstSsIds = /* @__PURE__ */ new Map();
|
|
24366
|
+
for (const sym of symbols) {
|
|
24367
|
+
if (baseSsIds.has(sym.index)) firstSsIds.set(sym.index, mpuFirst.get(sym.fragment.mpuSequenceNumber));
|
|
24368
|
+
}
|
|
24059
24369
|
this.#routeProcessedFecSymbols(blockStart, { ...result, symbols }, sourcePacketId, sourceName, {
|
|
24060
|
-
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
24370
|
+
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast",
|
|
24371
|
+
baseSsIds,
|
|
24372
|
+
firstSsIds
|
|
24061
24373
|
});
|
|
24062
24374
|
this.#noteOverlayRecoveredRaps(sourcePacketId, sourceName, streamKey, symbols.filter((sym) => restored.has(sym.packetSequenceNumber)));
|
|
24063
|
-
this.#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds);
|
|
24375
|
+
this.#feedOverlayRecoveredToBase(inst, blockStart, symbols, gateOnly.size === 0 ? baseSsIds : new Map([...baseSsIds].filter(([index]) => !gateOnly.has(index))));
|
|
24064
24376
|
}
|
|
24065
24377
|
/**
|
|
24066
24378
|
* Joint decoding (BLO-52085). A packet the overlay rebuilt is a source of
|
|
24067
24379
|
* its base block too, once its base SS_ID is determined (overlayBaseSsId,
|
|
24068
|
-
* from a received member of its MPU, else unstampedBaseSsId
|
|
24380
|
+
* from a received member of its MPU, else unstampedBaseSsId at an offset a
|
|
24381
|
+
* base recovery showed; `baseSsIds` holds no other),
|
|
24069
24382
|
* so it is fed to the base decoder as a rebuilt source
|
|
24070
24383
|
* (FECManager.feedRebuiltSourceSymbol): the base block has one erasure
|
|
24071
24384
|
* fewer to solve, and its repair can recover the block's other packets.
|
|
@@ -24091,7 +24404,7 @@ var MmtpRouter = class {
|
|
|
24091
24404
|
const feeds = [];
|
|
24092
24405
|
for (const sym of symbols) {
|
|
24093
24406
|
if (duplicates.sourceSeqs.has(sym.packetSequenceNumber)) continue;
|
|
24094
|
-
const ssId = baseSsIds.get(sym);
|
|
24407
|
+
const ssId = baseSsIds.get(sym.index);
|
|
24095
24408
|
if (ssId === void 0) {
|
|
24096
24409
|
this.#overlayStats.baseUndetermined++;
|
|
24097
24410
|
continue;
|
|
@@ -24820,7 +25133,7 @@ var MmtpRouter = class {
|
|
|
24820
25133
|
catalogTrackName
|
|
24821
25134
|
)) continue;
|
|
24822
25135
|
if (fec.isTrackConfigured?.(ownerPacketId) !== false) {
|
|
24823
|
-
fec.trackBlock(blockStart, ownerPacketId, catalogTrackName, esi, payload.evidence);
|
|
25136
|
+
fec.trackBlock(blockStart, ownerPacketId, catalogTrackName, esi, payload.evidence, true);
|
|
24824
25137
|
this.#requestFecPoke(ownerPacketId, catalogTrackName);
|
|
24825
25138
|
}
|
|
24826
25139
|
if (payload.oversized) continue;
|
|
@@ -24908,6 +25221,7 @@ var MmtpRouter = class {
|
|
|
24908
25221
|
this.#latestVideoSeqByStream.delete(streamKey);
|
|
24909
25222
|
this.#ssIdSeqOffsetByStream.delete(streamKey);
|
|
24910
25223
|
this.#unstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
25224
|
+
this.#overlayUnstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
24911
25225
|
this.#multicastRapTwinByStream.delete(streamKey);
|
|
24912
25226
|
this.#lastDirectSourceVideoTsByStream.delete(streamKey);
|
|
24913
25227
|
this.#lastDirectEmittedVideoTsByStream.delete(streamKey);
|
|
@@ -25034,6 +25348,12 @@ var MmtpRouter = class {
|
|
|
25034
25348
|
* the outgoing stream stops. The hold is bounded by the incoming stream's
|
|
25035
25349
|
* own RAP cadence, so no timer is involved.
|
|
25036
25350
|
*
|
|
25351
|
+
* While the switch is staged, the FEC release gate lets a keyframe the
|
|
25352
|
+
* overlay rebuilt reach it, on either side, when the keyframe did before
|
|
25353
|
+
* the gate anchored it (FecReleaseGate.releaseUnanchored): the switch
|
|
25354
|
+
* decides on what each side delivered, and the commit discards the gate's
|
|
25355
|
+
* frames of the outgoing stream.
|
|
25356
|
+
*
|
|
25037
25357
|
* The commit is one synchronous transition inside the frame path: the
|
|
25038
25358
|
* outgoing stream's already-admitted frames are released (its queued FEC
|
|
25039
25359
|
* gap fills only up to the RAP, since recovery can queue a frame ahead of
|
|
@@ -25084,6 +25404,9 @@ var MmtpRouter = class {
|
|
|
25084
25404
|
droppedIncomingFrames: 0
|
|
25085
25405
|
};
|
|
25086
25406
|
this.#staged = state;
|
|
25407
|
+
if (this.#parser.fecReleaseHoldsRebuilt(from.packetId, from.catalogTrackName)) {
|
|
25408
|
+
this.#requestFecPoke(from.packetId, from.catalogTrackName);
|
|
25409
|
+
}
|
|
25087
25410
|
return {
|
|
25088
25411
|
cancel: () => {
|
|
25089
25412
|
if (this.#staged !== state) return false;
|
|
@@ -25166,7 +25489,17 @@ var MmtpRouter = class {
|
|
|
25166
25489
|
*/
|
|
25167
25490
|
#routeParsedFrame(data, meta) {
|
|
25168
25491
|
if (this.#classifyPacketId(meta.packetId) === null) return;
|
|
25169
|
-
if (this.#staged !== null
|
|
25492
|
+
if (this.#staged !== null) {
|
|
25493
|
+
let consumed = false;
|
|
25494
|
+
this.#fecPokeDepth++;
|
|
25495
|
+
try {
|
|
25496
|
+
consumed = this.#interceptSwitchFrame(data, meta);
|
|
25497
|
+
} finally {
|
|
25498
|
+
this.#fecPokeDepth--;
|
|
25499
|
+
this.#flushPendingFecPokes();
|
|
25500
|
+
}
|
|
25501
|
+
if (consumed) return;
|
|
25502
|
+
}
|
|
25170
25503
|
this.#deliverParsedFrame(data, meta);
|
|
25171
25504
|
}
|
|
25172
25505
|
#commitStreamSwitch(state, rap, reason) {
|
|
@@ -25318,12 +25651,16 @@ var MmtpRouter = class {
|
|
|
25318
25651
|
this.#parser.deferFecRelease(work);
|
|
25319
25652
|
} finally {
|
|
25320
25653
|
this.#fecPokeDepth--;
|
|
25321
|
-
|
|
25322
|
-
|
|
25323
|
-
|
|
25324
|
-
|
|
25325
|
-
|
|
25326
|
-
|
|
25654
|
+
this.#flushPendingFecPokes();
|
|
25655
|
+
}
|
|
25656
|
+
}
|
|
25657
|
+
/** Run the pokes requested while #fecPokeDepth was raised, once it is 0. */
|
|
25658
|
+
#flushPendingFecPokes() {
|
|
25659
|
+
if (this.#fecPokeDepth === 0 && this.#pendingFecPokes.size > 0 && !this.#disposed) {
|
|
25660
|
+
const pokes = [...this.#pendingFecPokes.values()];
|
|
25661
|
+
this.#pendingFecPokes.clear();
|
|
25662
|
+
for (const poke of pokes) {
|
|
25663
|
+
this.#parser.pokeFecRelease(poke.packetId, poke.catalogTrackName);
|
|
25327
25664
|
}
|
|
25328
25665
|
}
|
|
25329
25666
|
}
|
|
@@ -25401,6 +25738,14 @@ var MmtpRouter = class {
|
|
|
25401
25738
|
onRap: (packetId, catalogTrackName, ssId) => {
|
|
25402
25739
|
for (const observer of this.#fecRapObservers) observer(packetId, catalogTrackName, ssId);
|
|
25403
25740
|
},
|
|
25741
|
+
// A staged switch commits on when each side's frames reach it
|
|
25742
|
+
// (#interceptSwitchFrame): both sides' overlay keyframes reach it when
|
|
25743
|
+
// they did unanchored.
|
|
25744
|
+
releaseUnanchored: (packetId, catalogTrackName) => {
|
|
25745
|
+
const staged = this.#staged;
|
|
25746
|
+
const stream = { packetId, ...catalogTrackName === void 0 ? {} : { catalogTrackName } };
|
|
25747
|
+
return staged !== null && (isStream(stream, staged.from) || isStream(stream, staged.to));
|
|
25748
|
+
},
|
|
25404
25749
|
onReleased: (data, meta) => {
|
|
25405
25750
|
if (meta.trackType !== "video" || meta.mpuSequenceNumber === void 0) return;
|
|
25406
25751
|
const timing = meta.authoredTiming;
|
|
@@ -25851,7 +26196,8 @@ var MmtpRouter = class {
|
|
|
25851
26196
|
source: parserOptions?.source,
|
|
25852
26197
|
deliveryProvenance: parserOptions?.deliveryProvenance,
|
|
25853
26198
|
catalogTrackName: parserOptions?.catalogTrackName,
|
|
25854
|
-
...parserOptions?.fecSbn === void 0 ? {} : { fecSbn: parserOptions.fecSbn }
|
|
26199
|
+
...parserOptions?.fecSbn === void 0 ? {} : { fecSbn: parserOptions.fecSbn },
|
|
26200
|
+
...parserOptions?.fecSbnFirst === void 0 ? {} : { fecSbnFirst: parserOptions.fecSbnFirst }
|
|
25855
26201
|
};
|
|
25856
26202
|
if (parsed) this.#parser.feedParsed(packet, parsed, feedOptions);
|
|
25857
26203
|
else this.#parser.feed(packet, feedOptions);
|
|
@@ -26317,7 +26663,12 @@ var MmtpRouter = class {
|
|
|
26317
26663
|
#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, overlay) {
|
|
26318
26664
|
if (ownerPacketId == null) return;
|
|
26319
26665
|
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
26320
|
-
const fecSbnOf = (index) =>
|
|
26666
|
+
const fecSbnOf = (index) => {
|
|
26667
|
+
if (!overlay) return { fecSbn: this.#fecAnchorForRecovered(blockId, index) };
|
|
26668
|
+
const fecSbn = overlay.baseSsIds.get(index);
|
|
26669
|
+
const fecSbnFirst = overlay.firstSsIds.get(index);
|
|
26670
|
+
return fecSbn === void 0 || fecSbnFirst === void 0 ? {} : { fecSbn, fecSbnFirst };
|
|
26671
|
+
};
|
|
26321
26672
|
const streamKey = this.#mediaStreamKey(ownerPacketId, catalogTrackName);
|
|
26322
26673
|
const ssIdSeqOffset = overlay ? void 0 : this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
26323
26674
|
if (ssIdSeqOffset !== void 0) {
|
|
@@ -28113,6 +28464,12 @@ function isNonNegativeSafeInteger(value) {
|
|
|
28113
28464
|
function isSymbolCounts(value) {
|
|
28114
28465
|
return isRecord(value) && hasExactKeys(value, ["multicast", "moq"]) && isNonNegativeSafeInteger(value["multicast"]) && isNonNegativeSafeInteger(value["moq"]);
|
|
28115
28466
|
}
|
|
28467
|
+
function isSpanFailure(value) {
|
|
28468
|
+
if (!isRecord(value) || !hasExactKeys(value, ["length", "sources"])) return false;
|
|
28469
|
+
const length = value["length"];
|
|
28470
|
+
const sources = value["sources"];
|
|
28471
|
+
return Number.isSafeInteger(length) && length > 0 && isNonNegativeSafeInteger(sources) && sources < length;
|
|
28472
|
+
}
|
|
28116
28473
|
function isNullableNonNegative(value) {
|
|
28117
28474
|
return value === null || typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
28118
28475
|
}
|
|
@@ -28132,7 +28489,10 @@ function isRepairLifecycleEvent(value) {
|
|
|
28132
28489
|
case "recovered":
|
|
28133
28490
|
return hasExactKeys(value, ["version", "type", "block", "blockClass", "via", "symbols"]) && isRepairBlockIdentity(block) && BLOCK_CLASSES.has(value["blockClass"]) && (value["via"] === "inband" || value["via"] === "quic") && isSymbolCounts(value["symbols"]);
|
|
28134
28491
|
case "failed":
|
|
28135
|
-
return hasExactKeys(
|
|
28492
|
+
return hasExactKeys(
|
|
28493
|
+
value,
|
|
28494
|
+
"span" in value ? ["version", "type", "block", "blockClass", "symbols", "span"] : ["version", "type", "block", "blockClass", "symbols"]
|
|
28495
|
+
) && isRepairBlockIdentity(block) && BLOCK_CLASSES.has(value["blockClass"]) && isSymbolCounts(value["symbols"]) && (!("span" in value) || isSpanFailure(value["span"]));
|
|
28136
28496
|
case "repair-preempted":
|
|
28137
28497
|
case "repair-deferred":
|
|
28138
28498
|
return hasExactKeys(value, ["version", "type", "block"]) && isRepairBlockIdentity(block);
|
|
@@ -28341,6 +28701,8 @@ var MulticastManager = class {
|
|
|
28341
28701
|
/** Live joinRepairEndpoints() releases → the repair owners they registered,
|
|
28342
28702
|
* re-applied after configureMultiTrack() clears catalog-derived owners. */
|
|
28343
28703
|
#repairReleases = /* @__PURE__ */ new Map();
|
|
28704
|
+
/** Protected repair flows owned by each live repair-endpoint release. */
|
|
28705
|
+
#repairProvenanceFlows = /* @__PURE__ */ new Map();
|
|
28344
28706
|
/** Each source's catalog keyframe overlay, from its `ok` binding. */
|
|
28345
28707
|
#overlayBindings = /* @__PURE__ */ new Map();
|
|
28346
28708
|
/** Installed overlay decoders (`setKeyframeOverlayDecoder`), by source. */
|
|
@@ -28398,7 +28760,7 @@ var MulticastManager = class {
|
|
|
28398
28760
|
onPublisherRestart: (packetId, catalogTrackName) => {
|
|
28399
28761
|
this.#resetStreamOutsideRouter(packetId, catalogTrackName);
|
|
28400
28762
|
},
|
|
28401
|
-
onBlockFailed: (sbn, packetId, catalogTrackName, blockClass) => {
|
|
28763
|
+
onBlockFailed: (sbn, packetId, catalogTrackName, blockClass, span) => {
|
|
28402
28764
|
const pathCount = this.#fec.blockPathCount(
|
|
28403
28765
|
sbn,
|
|
28404
28766
|
packetId,
|
|
@@ -28416,7 +28778,8 @@ var MulticastManager = class {
|
|
|
28416
28778
|
symbols: {
|
|
28417
28779
|
multicast: pathCount?.multicastSymbols ?? 0,
|
|
28418
28780
|
moq: pathCount?.moqSymbols ?? 0
|
|
28419
|
-
}
|
|
28781
|
+
},
|
|
28782
|
+
...span === void 0 ? {} : { span }
|
|
28420
28783
|
});
|
|
28421
28784
|
},
|
|
28422
28785
|
onFrameGap: (decision) => {
|
|
@@ -29006,6 +29369,9 @@ var MulticastManager = class {
|
|
|
29006
29369
|
this.#transport.onFailback((reason) => {
|
|
29007
29370
|
const joined = this.#provenanceJoin;
|
|
29008
29371
|
if (joined !== null) this.#router.unprotectProvenanceFlow(joined.flow);
|
|
29372
|
+
for (const flows of this.#repairProvenanceFlows.values()) {
|
|
29373
|
+
for (const flow of flows) this.#router.unprotectProvenanceFlow(flow);
|
|
29374
|
+
}
|
|
29009
29375
|
this.#onFailback?.(reason);
|
|
29010
29376
|
});
|
|
29011
29377
|
}
|
|
@@ -29040,6 +29406,12 @@ var MulticastManager = class {
|
|
|
29040
29406
|
"[MulticastManager] join: provenance flow (S, G, port) must match sourceAddress ?? senderAddress, groupAddress and port"
|
|
29041
29407
|
);
|
|
29042
29408
|
}
|
|
29409
|
+
if (provenanceFlow !== void 0) {
|
|
29410
|
+
const flow = multicastProvenanceFlowKey(provenanceFlow);
|
|
29411
|
+
if ([...this.#repairProvenanceFlows.values()].some((flows) => flows.includes(flow))) {
|
|
29412
|
+
throw new TypeError(`[MulticastManager] join: provenance flow already registered by a repair endpoint: ${flow}`);
|
|
29413
|
+
}
|
|
29414
|
+
}
|
|
29043
29415
|
const staged = this.#renditionSwitch;
|
|
29044
29416
|
if (staged !== null) this.#endRenditionSwitch(staged, { reason: "superseded" }, false);
|
|
29045
29417
|
this.configureFec(config);
|
|
@@ -29047,8 +29419,8 @@ var MulticastManager = class {
|
|
|
29047
29419
|
const selectedPacketIds = config.packetIds?.length ? new Set(config.packetIds) : null;
|
|
29048
29420
|
const catalogTracks = config.tracks?.filter(({ packetId }) => selectedPacketIds === null || selectedPacketIds.has(packetId));
|
|
29049
29421
|
const ingressTracks = catalogTracks ? catalogTracks : selectedPacketIds === null ? void 0 : [...selectedPacketIds].map((packetId) => ({ name: "", packetId }));
|
|
29050
|
-
this.#releaseProvenanceJoin();
|
|
29051
29422
|
const provenance = config.provenance;
|
|
29423
|
+
this.#releaseProvenanceJoin();
|
|
29052
29424
|
const protectedTracks = ingressTracks ?? provenance?.flow.tracks;
|
|
29053
29425
|
const ingress = protectedTracks === void 0 ? void 0 : {
|
|
29054
29426
|
tracks: Object.freeze(
|
|
@@ -29101,8 +29473,13 @@ var MulticastManager = class {
|
|
|
29101
29473
|
* feeds the flow's receiver; media datagrams are held until one admits them.
|
|
29102
29474
|
*/
|
|
29103
29475
|
#joinProvenance(provenance, ingress) {
|
|
29476
|
+
this.#router.registerProvenanceFlow(ingress, this.#provenanceReceiver(provenance));
|
|
29477
|
+
this.#hookFailback();
|
|
29478
|
+
this.#provenanceJoin = { flow: ingress.flow };
|
|
29479
|
+
}
|
|
29480
|
+
#provenanceReceiver(provenance) {
|
|
29104
29481
|
const { flow, auth } = provenance;
|
|
29105
|
-
|
|
29482
|
+
return new BcProvenanceV2Receiver({
|
|
29106
29483
|
...provenance.receiver,
|
|
29107
29484
|
// The caller's ctx when it passes one, else the catalog's: an explicit
|
|
29108
29485
|
// `catalogCtx: undefined` must not switch off the 7.2.4 ctx check.
|
|
@@ -29119,9 +29496,6 @@ var MulticastManager = class {
|
|
|
29119
29496
|
maxAuthDelayMs: flow.maxAuthDelayMs,
|
|
29120
29497
|
manifestTimerMs: flow.manifestTimerMs
|
|
29121
29498
|
});
|
|
29122
|
-
this.#router.registerProvenanceFlow(ingress, receiver);
|
|
29123
|
-
this.#hookFailback();
|
|
29124
|
-
this.#provenanceJoin = { flow: ingress.flow };
|
|
29125
29499
|
}
|
|
29126
29500
|
/** Un-protect: drop the flow's hold queues (no expiry counted). */
|
|
29127
29501
|
#releaseProvenanceJoin() {
|
|
@@ -29152,8 +29526,25 @@ var MulticastManager = class {
|
|
|
29152
29526
|
* callback delivers must come from a subscription opened on
|
|
29153
29527
|
* `options.gateway`. A callback that also merges another source (a MoQ or
|
|
29154
29528
|
* WebSocket repair feed) must not name a native provider here.
|
|
29529
|
+
*
|
|
29530
|
+
* An endpoint's `provenance` makes its group a protected flow of its own
|
|
29531
|
+
* (multicast-02 7.2.7), registered here and released with the group; its
|
|
29532
|
+
* records ride in band on the group's port. On the user-agent path a
|
|
29533
|
+
* flow-less repair group of a protected track is set aside, never read, so
|
|
29534
|
+
* a player joining a protected catalog's repair groups passes it.
|
|
29155
29535
|
*/
|
|
29156
29536
|
joinRepairEndpoints(endpoints, provider, options) {
|
|
29537
|
+
for (const { provenance, flow, source, group, port: port2 } of endpoints) {
|
|
29538
|
+
if (provenance === void 0) continue;
|
|
29539
|
+
if (flow !== void 0) {
|
|
29540
|
+
throw new TypeError("[MulticastManager] joinRepairEndpoints: an endpoint names provenance or flow, never both");
|
|
29541
|
+
}
|
|
29542
|
+
if (!sameIpAddress(source, provenance.flow.source) || !sameIpAddress(group, provenance.flow.groupAddress) || port2 !== provenance.flow.port) {
|
|
29543
|
+
throw new RangeError(
|
|
29544
|
+
"[MulticastManager] joinRepairEndpoints: provenance flow (S, G, port) must match the endpoint's source, group and port"
|
|
29545
|
+
);
|
|
29546
|
+
}
|
|
29547
|
+
}
|
|
29157
29548
|
const releases = [];
|
|
29158
29549
|
let released = false;
|
|
29159
29550
|
const safely = (each) => {
|
|
@@ -29163,17 +29554,36 @@ var MulticastManager = class {
|
|
|
29163
29554
|
console.warn("[MulticastManager] repair group release failed", error);
|
|
29164
29555
|
}
|
|
29165
29556
|
};
|
|
29557
|
+
const usedFlows = /* @__PURE__ */ new Set();
|
|
29558
|
+
const preparedProvenance = endpoints.map((endpoint) => {
|
|
29559
|
+
if (endpoint.provenance === void 0) return void 0;
|
|
29560
|
+
const flow = multicastProvenanceFlowKey(endpoint.provenance.flow);
|
|
29561
|
+
if (usedFlows.has(flow) || this.#provenanceJoin?.flow === flow || [...this.#repairProvenanceFlows.values()].some((flows) => flows.includes(flow))) {
|
|
29562
|
+
throw new TypeError(`[MulticastManager] joinRepairEndpoints: provenance flow already registered: ${flow}`);
|
|
29563
|
+
}
|
|
29564
|
+
usedFlows.add(flow);
|
|
29565
|
+
return { flow, receiver: this.#provenanceReceiver(endpoint.provenance) };
|
|
29566
|
+
});
|
|
29567
|
+
const provenanceFlows = [];
|
|
29166
29568
|
const repairOwners = endpoints.flatMap((endpoint) => endpoint.repairOwners.map((owner) => owner.instance !== "keyframe" ? owner : {
|
|
29167
29569
|
...owner,
|
|
29168
29570
|
ownerTrackName: endpoint.tracks.find((t) => t.packetId === owner.ownerPacketId)?.name,
|
|
29169
29571
|
repairTrackName: endpoint.tracks.find((t) => t.packetId === owner.packetId)?.name
|
|
29170
29572
|
}));
|
|
29171
29573
|
this.#applyRepairOwners(repairOwners);
|
|
29172
|
-
for (const endpoint of endpoints) {
|
|
29574
|
+
for (const [index, endpoint] of endpoints.entries()) {
|
|
29575
|
+
const flow = endpoint.provenance === void 0 ? endpoint.flow : multicastProvenanceFlowKey(endpoint.provenance.flow);
|
|
29173
29576
|
const ingress = {
|
|
29174
29577
|
tracks: Object.freeze(endpoint.tracks.map(({ name, packetId }) => Object.freeze({ name, packetId }))),
|
|
29175
|
-
...
|
|
29578
|
+
...flow === void 0 ? {} : { flow }
|
|
29176
29579
|
};
|
|
29580
|
+
if (endpoint.provenance !== void 0) {
|
|
29581
|
+
const prepared = preparedProvenance[index];
|
|
29582
|
+
if (prepared === void 0) throw new Error("[MulticastManager] repair provenance preparation missing");
|
|
29583
|
+
this.#router.registerProvenanceFlow(ingress, prepared.receiver);
|
|
29584
|
+
provenanceFlows.push(prepared.flow);
|
|
29585
|
+
releases.push(() => this.#router.releaseProvenanceFlow(flow));
|
|
29586
|
+
}
|
|
29177
29587
|
let sender;
|
|
29178
29588
|
const feed = (packet, sourceAddress, ingressToFeed) => {
|
|
29179
29589
|
if (released) return;
|
|
@@ -29203,7 +29613,7 @@ var MulticastManager = class {
|
|
|
29203
29613
|
packetIds: endpoint.packetIds,
|
|
29204
29614
|
tracks: endpoint.tracks.map(({ name, packetId }) => ({ name, packetId })),
|
|
29205
29615
|
// A protected repair group's records ride in band on its port (7.2.3).
|
|
29206
|
-
...
|
|
29616
|
+
...flow === void 0 ? {} : { protected: true }
|
|
29207
29617
|
}, ingress).then((joined) => {
|
|
29208
29618
|
if (!joined && !released) fail(transport.lastJoinOutcome);
|
|
29209
29619
|
});
|
|
@@ -29212,9 +29622,12 @@ var MulticastManager = class {
|
|
|
29212
29622
|
if (released) return;
|
|
29213
29623
|
released = true;
|
|
29214
29624
|
this.#repairReleases.delete(release);
|
|
29625
|
+
this.#repairProvenanceFlows.delete(release);
|
|
29215
29626
|
for (const each of releases.splice(0)) safely(each);
|
|
29216
29627
|
};
|
|
29217
29628
|
this.#repairReleases.set(release, repairOwners);
|
|
29629
|
+
this.#repairProvenanceFlows.set(release, provenanceFlows);
|
|
29630
|
+
if (provenanceFlows.length) this.#hookFailback();
|
|
29218
29631
|
return release;
|
|
29219
29632
|
}
|
|
29220
29633
|
#applyRepairOwners(owners) {
|
|
@@ -29796,10 +30209,12 @@ var MulticastManager = class {
|
|
|
29796
30209
|
* Register the range repair scheduler (draft-ramadan-moq-fec §7.5): from now
|
|
29797
30210
|
* on an iso-ssbg1-v1 gap missing a source and every repair is kept as a
|
|
29798
30211
|
* pending hole and reported here, and `closeIsoHoles` must be called for it
|
|
29799
|
-
* once its range FETCH ends (or none is sent).
|
|
30212
|
+
* once its range FETCH ends (or none is sent). `onExpired` is told of a
|
|
30213
|
+
* pending hole the stream runs past the locator's horizon: returning true,
|
|
30214
|
+
* the caller closes it (`closeIsoHoles`); otherwise it is discarded.
|
|
29800
30215
|
*/
|
|
29801
|
-
onUndelimitedLoss(cb) {
|
|
29802
|
-
this.#fec.setUndelimitedLossHandler(cb);
|
|
30216
|
+
onUndelimitedLoss(cb, onExpired) {
|
|
30217
|
+
this.#fec.setUndelimitedLossHandler(cb, onExpired ?? null);
|
|
29803
30218
|
}
|
|
29804
30219
|
/**
|
|
29805
30220
|
* Report each stream whose FEC fails closed at runtime (fec-01 6.3.9), e.g.
|
|
@@ -30035,7 +30450,8 @@ var MulticastManager = class {
|
|
|
30035
30450
|
"stream" in event ? { ...event, stream: Object.freeze({ ...event.stream }) } : {
|
|
30036
30451
|
...event,
|
|
30037
30452
|
block: Object.freeze({ ...event.block }),
|
|
30038
|
-
..."symbols" in event ? { symbols: Object.freeze({ ...event.symbols }) } : {}
|
|
30453
|
+
..."symbols" in event ? { symbols: Object.freeze({ ...event.symbols }) } : {},
|
|
30454
|
+
..."span" in event && event.span !== void 0 ? { span: Object.freeze({ ...event.span }) } : {}
|
|
30039
30455
|
}
|
|
30040
30456
|
);
|
|
30041
30457
|
if (this.#logRepairLifecycle) {
|
|
@@ -32707,6 +33123,7 @@ var RepairLayerSubscriptions = class {
|
|
|
32707
33123
|
|
|
32708
33124
|
// src/repair-fetch.ts
|
|
32709
33125
|
var MOQT_PRIORITY_MAX = 255;
|
|
33126
|
+
var WALK_LEAD_MS = 3;
|
|
32710
33127
|
var SOURCE_MEDIA_PRIORITY_MAX = REPAIR_PRIORITY_MIN - 1;
|
|
32711
33128
|
function isMoqtPriority(value) {
|
|
32712
33129
|
return Number.isSafeInteger(value) && value >= 0 && value <= MOQT_PRIORITY_MAX;
|
|
@@ -32757,8 +33174,17 @@ function wireRepairFetch(mc, opts) {
|
|
|
32757
33174
|
mc.noteRepairScheduling(type, entry.sbn, entry.packetId, entry.catalogTrackName);
|
|
32758
33175
|
};
|
|
32759
33176
|
const clearEntry = (entry) => {
|
|
32760
|
-
|
|
32761
|
-
|
|
33177
|
+
const { range } = entry;
|
|
33178
|
+
if (range) {
|
|
33179
|
+
atReport(range, () => mc.closeIsoHoles(entry.packetId, entry.catalogTrackName, entry.sbn, range.holeLength));
|
|
33180
|
+
} else {
|
|
33181
|
+
clearRepair(entry.sbn, entry.packetId, entry.catalogTrackName);
|
|
33182
|
+
}
|
|
33183
|
+
};
|
|
33184
|
+
const atReport = (range, work) => {
|
|
33185
|
+
const fec = mc.fecManager;
|
|
33186
|
+
if (range.groups !== null || fec.withReceiptTime === void 0) work();
|
|
33187
|
+
else fec.withReceiptTime(range.reportedAt, work);
|
|
32762
33188
|
};
|
|
32763
33189
|
const releaseHold = (entry) => {
|
|
32764
33190
|
if (entry.range) return;
|
|
@@ -32825,21 +33251,42 @@ function wireRepairFetch(mc, opts) {
|
|
|
32825
33251
|
const admit = (entry, now) => {
|
|
32826
33252
|
const budgetMs = entry.deadlineAt - now;
|
|
32827
33253
|
const rttMs = readRtt();
|
|
32828
|
-
const reason =
|
|
33254
|
+
const reason = refusal(entry, now, rttMs);
|
|
32829
33255
|
if (reason === null) return rttMs;
|
|
32830
33256
|
skip(entry, reason, rttMs, budgetMs);
|
|
32831
33257
|
return null;
|
|
32832
33258
|
};
|
|
33259
|
+
const refusal = (entry, now, rttMs) => !entry.range && !groupCanComplete(entry.sbn, entry.packetId, entry.catalogTrackName) ? "group-cannot-complete" : rttMs === null ? "rtt-unmeasured" : rttMs < entry.deadlineAt - now ? null : "rtt-exceeds-budget";
|
|
32833
33260
|
const retire = (entry) => {
|
|
32834
33261
|
if (entries.get(entry.key) === entry) entries.delete(entry.key);
|
|
32835
33262
|
if (entry.expiry !== void 0) clearTimeout(entry.expiry);
|
|
32836
33263
|
};
|
|
32837
|
-
const
|
|
33264
|
+
const holdsBack = (entry, priority, videoWalks = true) => {
|
|
33265
|
+
if (entry.priority >= priority) return false;
|
|
33266
|
+
const { walk } = entry;
|
|
33267
|
+
if (walk === void 0) return true;
|
|
33268
|
+
if (entry.priority !== 0 || !videoWalks) return false;
|
|
33269
|
+
return entry.running === null || walk.group !== null && !walk.group.controller.signal.aborted;
|
|
33270
|
+
};
|
|
33271
|
+
const outstandingBelow = (priority, videoWalks = true) => {
|
|
32838
33272
|
for (const entry of entries.values()) {
|
|
32839
|
-
if (entry
|
|
33273
|
+
if (holdsBack(entry, priority, videoWalks)) return true;
|
|
32840
33274
|
}
|
|
32841
33275
|
return false;
|
|
32842
33276
|
};
|
|
33277
|
+
const yieldVideoWalks = (queued) => {
|
|
33278
|
+
const now = performance.now();
|
|
33279
|
+
const rttMs = readRtt();
|
|
33280
|
+
const due = queued.some((entry) => entry.priority !== 0 && entry.walk === void 0 && !outstandingBelow(entry.priority, false) && refusal(entry, now, rttMs) === null);
|
|
33281
|
+
if (!due) return;
|
|
33282
|
+
for (const entry of entries.values()) {
|
|
33283
|
+
const walk = entry.walk;
|
|
33284
|
+
if (entry.priority !== 0 || walk === void 0 || walk.group === null) continue;
|
|
33285
|
+
if (walk.group.controller.signal.aborted) continue;
|
|
33286
|
+
walk.yielded = true;
|
|
33287
|
+
walk.group.controller.abort();
|
|
33288
|
+
}
|
|
33289
|
+
};
|
|
32843
33290
|
const preemptBestEffort = () => {
|
|
32844
33291
|
for (const entry of entries.values()) {
|
|
32845
33292
|
if (entry.priority === 0 || !entry.running) continue;
|
|
@@ -32868,7 +33315,8 @@ function wireRepairFetch(mc, opts) {
|
|
|
32868
33315
|
}
|
|
32869
33316
|
const rttMs = admit(entry, now);
|
|
32870
33317
|
if (rttMs === null) return;
|
|
32871
|
-
if (entry.priority === 0) preemptBestEffort();
|
|
33318
|
+
if (entry.priority === 0 && entry.walk === void 0) preemptBestEffort();
|
|
33319
|
+
if (entry.range === void 0) leaveWalkedGroup(entry);
|
|
32872
33320
|
if (counted) entry.attempts++;
|
|
32873
33321
|
mc.noteRepairFetchSent?.(entry.priority, entry.sbn, entry.packetId, entry.catalogTrackName, {
|
|
32874
33322
|
rttMs,
|
|
@@ -32890,6 +33338,14 @@ function wireRepairFetch(mc, opts) {
|
|
|
32890
33338
|
}
|
|
32891
33339
|
void execute(entry, run);
|
|
32892
33340
|
};
|
|
33341
|
+
const leaveWalkedGroup = (block) => {
|
|
33342
|
+
for (const entry of entries.values()) {
|
|
33343
|
+
const group = entry.walk?.group;
|
|
33344
|
+
if (group?.sbn === block.sbn && entry.packetId === block.packetId && entry.catalogTrackName === block.catalogTrackName) {
|
|
33345
|
+
group.controller.abort();
|
|
33346
|
+
}
|
|
33347
|
+
}
|
|
33348
|
+
};
|
|
32893
33349
|
const dispatch = () => {
|
|
32894
33350
|
if (disposed) return;
|
|
32895
33351
|
const now = performance.now();
|
|
@@ -32914,6 +33370,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
32914
33370
|
for (const entry of queued) {
|
|
32915
33371
|
if (entry.priority === 0) start(entry);
|
|
32916
33372
|
}
|
|
33373
|
+
yieldVideoWalks(queued);
|
|
32917
33374
|
if (outstandingBelow(1)) return;
|
|
32918
33375
|
for (const entry of queued) {
|
|
32919
33376
|
if (entry.priority === 1) start(entry);
|
|
@@ -32940,6 +33397,16 @@ function wireRepairFetch(mc, opts) {
|
|
|
32940
33397
|
}
|
|
32941
33398
|
return counts.sources + counts.repairGroupLength >= counts.authoredLength;
|
|
32942
33399
|
};
|
|
33400
|
+
const groupServed = (sbn, packetId, catalogTrackName) => {
|
|
33401
|
+
if (mc.fecManager.blockSymbolCounts === void 0) return false;
|
|
33402
|
+
const counts = symbolCounts(sbn, packetId, catalogTrackName);
|
|
33403
|
+
if (counts === void 0 || counts.authoredLength === void 0) return true;
|
|
33404
|
+
return counts.sources + counts.repairs >= counts.authoredLength || !groupCanComplete(sbn, packetId, catalogTrackName);
|
|
33405
|
+
};
|
|
33406
|
+
const statedEnd = (sbn, packetId, catalogTrackName) => {
|
|
33407
|
+
const length = symbolCounts(sbn, packetId, catalogTrackName)?.authoredLength;
|
|
33408
|
+
return length === void 0 ? void 0 : serial32Add(sbn, length);
|
|
33409
|
+
};
|
|
32943
33410
|
const finish = (entry, run, fed, error) => {
|
|
32944
33411
|
if (run.stopped !== null || entry.running !== run || entries.get(entry.key) !== entry) {
|
|
32945
33412
|
return;
|
|
@@ -32984,27 +33451,81 @@ function wireRepairFetch(mc, opts) {
|
|
|
32984
33451
|
signal: run.controller.signal,
|
|
32985
33452
|
subscriberPriority: entry.subscriberPriority
|
|
32986
33453
|
};
|
|
33454
|
+
const { range } = entry;
|
|
33455
|
+
const feed = (packet) => {
|
|
33456
|
+
if (entry.catalogTrackName === void 0) {
|
|
33457
|
+
mc.processRepairFromMoQ(packet, entry.packetId);
|
|
33458
|
+
} else {
|
|
33459
|
+
mc.processRepairFromMoQ(packet, entry.packetId, entry.catalogTrackName);
|
|
33460
|
+
}
|
|
33461
|
+
};
|
|
32987
33462
|
try {
|
|
32988
|
-
const packets = entry.
|
|
33463
|
+
const packets = range === void 0 ? opts.transport({ ...common, sbn: entry.sbn }) : entry.walk !== void 0 ? walkHole(entry, entry.walk, range, common) : opts.rangeTransport({
|
|
32989
33464
|
...common,
|
|
32990
|
-
startGroup:
|
|
32991
|
-
endGroup:
|
|
32992
|
-
lostSsId:
|
|
32993
|
-
})
|
|
33465
|
+
startGroup: range.groups.startGroup,
|
|
33466
|
+
endGroup: range.groups.endGroup,
|
|
33467
|
+
lostSsId: range.lostSsId
|
|
33468
|
+
});
|
|
32994
33469
|
for await (const packet of packets) {
|
|
32995
33470
|
if (run.stopped !== null || entry.running !== run) break;
|
|
32996
33471
|
fed++;
|
|
32997
|
-
if (
|
|
32998
|
-
|
|
32999
|
-
} else {
|
|
33000
|
-
mc.processRepairFromMoQ(packet, entry.packetId, entry.catalogTrackName);
|
|
33001
|
-
}
|
|
33472
|
+
if (range === void 0) feed(packet);
|
|
33473
|
+
else atReport(range, () => feed(packet));
|
|
33002
33474
|
}
|
|
33003
33475
|
finish(entry, run, fed, void 0);
|
|
33004
33476
|
} catch (error) {
|
|
33005
33477
|
finish(entry, run, fed, error ?? new Error("repair transport rejected"));
|
|
33006
33478
|
}
|
|
33007
33479
|
};
|
|
33480
|
+
async function* walkHole(entry, walk, hole, common) {
|
|
33481
|
+
const lostAt = serial32Distance(entry.sbn, hole.lostSsId);
|
|
33482
|
+
for (let first = true; !common.signal.aborted; first = false) {
|
|
33483
|
+
if (!first && entry.priority !== 0 && outstandingBelow(1)) {
|
|
33484
|
+
stop(entry, "preempted");
|
|
33485
|
+
note("repair-preempted", entry);
|
|
33486
|
+
return;
|
|
33487
|
+
}
|
|
33488
|
+
if (!first && entry.priority === 0) {
|
|
33489
|
+
dispatch();
|
|
33490
|
+
if (common.signal.aborted) return;
|
|
33491
|
+
}
|
|
33492
|
+
const sbn = walk.next;
|
|
33493
|
+
let next;
|
|
33494
|
+
if (entries.get(requestKey(sbn, entry.packetId, entry.catalogTrackName))?.running == null) {
|
|
33495
|
+
const rsIds = /* @__PURE__ */ new Set();
|
|
33496
|
+
const group = new AbortController();
|
|
33497
|
+
const cancel = () => group.abort();
|
|
33498
|
+
common.signal.addEventListener("abort", cancel, { once: true });
|
|
33499
|
+
walk.group = { sbn, controller: group };
|
|
33500
|
+
try {
|
|
33501
|
+
for await (const packet of opts.transport({ ...common, sbn, signal: group.signal })) {
|
|
33502
|
+
yield packet;
|
|
33503
|
+
const id = extractRepairFecPayloadId(packet);
|
|
33504
|
+
if (id === null || id.ssStart !== sbn || id.ssbLength === 0) continue;
|
|
33505
|
+
next ??= serial32Add(sbn, id.ssbLength);
|
|
33506
|
+
rsIds.add(id.rsId);
|
|
33507
|
+
if (rsIds.size >= id.rsbLength || groupServed(sbn, entry.packetId, entry.catalogTrackName)) {
|
|
33508
|
+
group.abort();
|
|
33509
|
+
break;
|
|
33510
|
+
}
|
|
33511
|
+
}
|
|
33512
|
+
} catch (error) {
|
|
33513
|
+
if (!group.signal.aborted || common.signal.aborted) throw error;
|
|
33514
|
+
} finally {
|
|
33515
|
+
common.signal.removeEventListener("abort", cancel);
|
|
33516
|
+
walk.group = null;
|
|
33517
|
+
}
|
|
33518
|
+
if (walk.yielded) {
|
|
33519
|
+
walk.yielded = false;
|
|
33520
|
+
continue;
|
|
33521
|
+
}
|
|
33522
|
+
}
|
|
33523
|
+
next ??= statedEnd(sbn, entry.packetId, entry.catalogTrackName);
|
|
33524
|
+
if (next === void 0) return;
|
|
33525
|
+
if (serial32Distance(entry.sbn, next) > lostAt) return;
|
|
33526
|
+
walk.next = next;
|
|
33527
|
+
}
|
|
33528
|
+
}
|
|
33008
33529
|
const removeResetObserver = mc.addTrackResetObserver((packetId, catalogTrackName) => {
|
|
33009
33530
|
for (const entry of [...entries.values()]) {
|
|
33010
33531
|
if (entry.packetId !== packetId || entry.catalogTrackName !== catalogTrackName) continue;
|
|
@@ -33200,7 +33721,37 @@ function wireRepairFetch(mc, opts) {
|
|
|
33200
33721
|
throw error;
|
|
33201
33722
|
}
|
|
33202
33723
|
};
|
|
33203
|
-
|
|
33724
|
+
const trackKind = (packetId) => {
|
|
33725
|
+
try {
|
|
33726
|
+
return mc.fecManager.trackKindFor?.(packetId);
|
|
33727
|
+
} catch {
|
|
33728
|
+
return void 0;
|
|
33729
|
+
}
|
|
33730
|
+
};
|
|
33731
|
+
const walkDeadlineAt = (packetId, now, deadlineAt) => {
|
|
33732
|
+
let repairDeadlineMs;
|
|
33733
|
+
try {
|
|
33734
|
+
repairDeadlineMs = mc.fecManager.repairDeadlineMsForPacket?.(packetId);
|
|
33735
|
+
} catch {
|
|
33736
|
+
repairDeadlineMs = void 0;
|
|
33737
|
+
}
|
|
33738
|
+
if (repairDeadlineMs === void 0 || !Number.isFinite(repairDeadlineMs)) return deadlineAt;
|
|
33739
|
+
return Math.min(deadlineAt, now + repairDeadlineMs - WALK_LEAD_MS);
|
|
33740
|
+
};
|
|
33741
|
+
const onHoleExpired = (hole) => {
|
|
33742
|
+
if (disposed) return false;
|
|
33743
|
+
const entry = [...entries.values()].find((candidate) => candidate.walk !== void 0 && candidate.packetId === hole.packetId && candidate.catalogTrackName === hole.catalogTrackName && serial32Distance(candidate.sbn, hole.holeStart) < candidate.range.holeLength);
|
|
33744
|
+
if (entry === void 0) return false;
|
|
33745
|
+
queueMicrotask(() => {
|
|
33746
|
+
if (entries.get(entry.key) !== entry) return;
|
|
33747
|
+
retire(entry);
|
|
33748
|
+
stop(entry, "expired");
|
|
33749
|
+
clearEntry(entry);
|
|
33750
|
+
dispatch();
|
|
33751
|
+
});
|
|
33752
|
+
return true;
|
|
33753
|
+
};
|
|
33754
|
+
if (mc.onUndelimitedLoss !== void 0 && mc.closeIsoHoles !== void 0) {
|
|
33204
33755
|
mc.onUndelimitedLoss((loss) => {
|
|
33205
33756
|
const { packetId, catalogTrackName, holeStart, holeLength } = loss;
|
|
33206
33757
|
const close = () => mc.closeIsoHoles(packetId, catalogTrackName, holeStart, holeLength);
|
|
@@ -33208,9 +33759,11 @@ function wireRepairFetch(mc, opts) {
|
|
|
33208
33759
|
const blockProfile = blockProfileOf(packetId);
|
|
33209
33760
|
const target = opts.resolveTarget(packetId, catalogTrackName);
|
|
33210
33761
|
if (!target) return close();
|
|
33211
|
-
const
|
|
33212
|
-
const
|
|
33213
|
-
|
|
33762
|
+
const walked = opts.rangeTransport === void 0;
|
|
33763
|
+
const holeClass = walked && trackKind(packetId) === "audio" ? "audio" : "unknown";
|
|
33764
|
+
const priority = repairPriorityForClass(holeClass);
|
|
33765
|
+
const groups = walked ? null : rangeFor(loss, target.repairTrack);
|
|
33766
|
+
if (!walked && groups === null) {
|
|
33214
33767
|
close();
|
|
33215
33768
|
mc.noteRepairSkipped("range-no-anchor", { rttMs: readRtt(), budgetMs: 0, priority }, holeStart, packetId, catalogTrackName);
|
|
33216
33769
|
return;
|
|
@@ -33221,8 +33774,10 @@ function wireRepairFetch(mc, opts) {
|
|
|
33221
33774
|
packetId,
|
|
33222
33775
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
33223
33776
|
};
|
|
33224
|
-
const wire = wirePriority(identity,
|
|
33225
|
-
|
|
33777
|
+
const wire = wirePriority(identity, holeClass);
|
|
33778
|
+
if (wire === null) return close();
|
|
33779
|
+
const now = performance.now();
|
|
33780
|
+
const deadline = presentationDeadlineAt(holeStart, packetId, catalogTrackName, now, holeClass);
|
|
33226
33781
|
if (deadline === null) {
|
|
33227
33782
|
close();
|
|
33228
33783
|
mc.noteRepairSkipped(
|
|
@@ -33234,6 +33789,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
33234
33789
|
);
|
|
33235
33790
|
return;
|
|
33236
33791
|
}
|
|
33792
|
+
const deadlineAt = walked ? walkDeadlineAt(packetId, now, deadline.deadlineAt) : deadline.deadlineAt;
|
|
33237
33793
|
const entry = {
|
|
33238
33794
|
key: JSON.stringify(["range", packetId, catalogTrackName ?? null, holeStart]),
|
|
33239
33795
|
...identity,
|
|
@@ -33241,15 +33797,16 @@ function wireRepairFetch(mc, opts) {
|
|
|
33241
33797
|
blockProfile,
|
|
33242
33798
|
priority,
|
|
33243
33799
|
subscriberPriority: wire.subscriberPriority,
|
|
33244
|
-
deadlineAt
|
|
33245
|
-
playoutDeadlineAt:
|
|
33800
|
+
deadlineAt,
|
|
33801
|
+
playoutDeadlineAt: deadlineAt - deadline.extensionMs,
|
|
33246
33802
|
anchorAt: deadline.anchorAt,
|
|
33247
33803
|
delayMs: deadline.delayMs,
|
|
33248
33804
|
bound: deadline.bound,
|
|
33249
33805
|
order: enqueueOrder++,
|
|
33250
33806
|
attempts: 0,
|
|
33251
33807
|
running: null,
|
|
33252
|
-
range: {
|
|
33808
|
+
range: { lostSsId: loss.lostSsId, holeLength, groups, reportedAt: now },
|
|
33809
|
+
...walked ? { walk: { next: holeStart, group: null, yielded: false } } : {}
|
|
33253
33810
|
};
|
|
33254
33811
|
const previous = entries.get(entry.key);
|
|
33255
33812
|
if (previous) {
|
|
@@ -33259,7 +33816,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
33259
33816
|
entries.set(entry.key, entry);
|
|
33260
33817
|
entry.expiry = setTimeout(dispatch, Math.max(0, entry.deadlineAt - performance.now()) + 1);
|
|
33261
33818
|
dispatch();
|
|
33262
|
-
});
|
|
33819
|
+
}, onHoleExpired);
|
|
33263
33820
|
}
|
|
33264
33821
|
const removeRapObserver = mc.addRapCompleteObserver?.((packetId) => {
|
|
33265
33822
|
queueMicrotask(() => {
|