@blockcast/mmt-container 2.2.0-main.da2e7cd1dd5d → 2.2.0-main.e75bbd7576a6
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 +472 -36
- package/dist/browser-runtime.js.map +2 -2
- package/dist/delivery-catalog.js +2 -1
- package/dist/delivery-catalog.js.map +2 -2
- package/dist/fec-manager.d.ts +30 -0
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +196 -2
- package/dist/fec-manager.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.d.ts +9 -0
- package/dist/iso-block-locator.d.ts.map +1 -1
- package/dist/iso-block-locator.js +17 -0
- package/dist/iso-block-locator.js.map +2 -2
- package/dist/iso-bmff-timing.d.ts +10 -1
- package/dist/iso-bmff-timing.d.ts.map +1 -1
- package/dist/iso-bmff-timing.js +36 -2
- package/dist/iso-bmff-timing.js.map +2 -2
- package/dist/mmtp-container-parser.d.ts +35 -2
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +82 -9
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +85 -11
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +81 -9
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.d.ts +18 -1
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +276 -34
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.js +472 -36
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.js +2 -1
- package/dist/multicast-transport.js.map +2 -2
- package/dist/no-modules.js +81 -9
- package/dist/no-modules.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +81 -9
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +5 -5
- package/src/fec-manager.ts +251 -2
- package/src/index.ts +1 -0
- package/src/iso-block-locator.ts +19 -0
- package/src/iso-bmff-timing.ts +40 -1
- package/src/mmtp-container-parser.ts +84 -8
- package/src/mmtp-router.ts +346 -30
|
@@ -1210,6 +1210,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
1210
1210
|
#retentionBlocks;
|
|
1211
1211
|
#heldEvicted;
|
|
1212
1212
|
#newest;
|
|
1213
|
+
#newestSource;
|
|
1213
1214
|
#holes;
|
|
1214
1215
|
static #checkedRetention(retentionBlocks) {
|
|
1215
1216
|
if (!Number.isSafeInteger(retentionBlocks) || retentionBlocks <= 0) {
|
|
@@ -1247,6 +1248,21 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
1247
1248
|
observe(position) {
|
|
1248
1249
|
if (this.#newest === void 0 || serial32Precedes(this.#newest, position)) this.#newest = position;
|
|
1249
1250
|
}
|
|
1251
|
+
/**
|
|
1252
|
+
* The newest source SS_ID noted with `noteSource` (RFC 1982 order). Unlike
|
|
1253
|
+
* `newest`, a repair never moves it: a repair can run ahead of its sources,
|
|
1254
|
+
* a source cannot run ahead of the sources sent before it except by
|
|
1255
|
+
* reordering.
|
|
1256
|
+
*/
|
|
1257
|
+
get newestSource() {
|
|
1258
|
+
return this.#newestSource;
|
|
1259
|
+
}
|
|
1260
|
+
/** Note a received source's SS_ID; true when it is the newest one yet. */
|
|
1261
|
+
noteSource(ssId) {
|
|
1262
|
+
if (this.#newestSource !== void 0 && !serial32Precedes(this.#newestSource, ssId)) return false;
|
|
1263
|
+
this.#newestSource = ssId;
|
|
1264
|
+
return true;
|
|
1265
|
+
}
|
|
1250
1266
|
/** Held sources evicted by `heldCapacity` (never by the horizon itself). */
|
|
1251
1267
|
get heldEvicted() {
|
|
1252
1268
|
return this.#heldEvicted;
|
|
@@ -1479,6 +1495,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
1479
1495
|
this.#held.clear();
|
|
1480
1496
|
this.#coverageEnd = void 0;
|
|
1481
1497
|
this.#newest = void 0;
|
|
1498
|
+
this.#newestSource = void 0;
|
|
1482
1499
|
}
|
|
1483
1500
|
#claim(block) {
|
|
1484
1501
|
const placed = [];
|
|
@@ -2067,6 +2084,7 @@ var WORKER_ERROR_LOG_CAP = 5;
|
|
|
2067
2084
|
var WORKER_ERROR_KIND_CAP = 32;
|
|
2068
2085
|
var WORKER_ERROR_OVERFLOW_KIND = "other";
|
|
2069
2086
|
var STARTUP_REPAIR_WAIT_SOURCE_BOUNDARIES = 2;
|
|
2087
|
+
var NOT_TAKEN = Object.freeze({ taken: false, recovered: null });
|
|
2070
2088
|
function isMulticastSymbolDelivery(value) {
|
|
2071
2089
|
return value === "datagram" || value === "ordered-relay";
|
|
2072
2090
|
}
|
|
@@ -2164,6 +2182,10 @@ var FECManager = class _FECManager {
|
|
|
2164
2182
|
// Source symbol count per track-scoped block — marks complete when K symbols arrive.
|
|
2165
2183
|
this.#blockSourceCounts = /* @__PURE__ */ new Map();
|
|
2166
2184
|
this.#blockSourceEsis = /* @__PURE__ */ new Map();
|
|
2185
|
+
// Source ESIs per block that another FEC instance rebuilt and fed in
|
|
2186
|
+
// (feedRebuiltSourceSymbol). Kept apart from #blockSourceEsis: they are
|
|
2187
|
+
// not network arrivals and never make a block clean.
|
|
2188
|
+
this.#blockRebuiltEsis = /* @__PURE__ */ new Map();
|
|
2167
2189
|
// Authored SSB_length per block (ISO/IEC 23008-1 §C.5.3), learned from the
|
|
2168
2190
|
// first repair symbol. The FFmpeg moq_mmt publisher closes a block every D
|
|
2169
2191
|
// media units, usually before it holds K symbols; under iso-ssbg1-v1
|
|
@@ -2453,6 +2475,7 @@ var FECManager = class _FECManager {
|
|
|
2453
2475
|
#pendingWorkerCleanupBounds;
|
|
2454
2476
|
#blockSourceCounts;
|
|
2455
2477
|
#blockSourceEsis;
|
|
2478
|
+
#blockRebuiltEsis;
|
|
2456
2479
|
#blockAuthoredLengths;
|
|
2457
2480
|
#ssbLengthConflictCount;
|
|
2458
2481
|
#blockRepairRsIds;
|
|
@@ -3775,6 +3798,7 @@ var FECManager = class _FECManager {
|
|
|
3775
3798
|
}
|
|
3776
3799
|
resetMap(this.#blockSourceCounts);
|
|
3777
3800
|
resetMap(this.#blockSourceEsis);
|
|
3801
|
+
resetMap(this.#blockRebuiltEsis);
|
|
3778
3802
|
resetMap(this.#blockAuthoredLengths);
|
|
3779
3803
|
resetMap(this.#blockRepairRsIds);
|
|
3780
3804
|
resetMap(this.#blockRsbLengths);
|
|
@@ -3899,6 +3923,7 @@ var FECManager = class _FECManager {
|
|
|
3899
3923
|
dropMap(this.#pendingWorkerCleanupClients);
|
|
3900
3924
|
dropMap(this.#blockSourceCounts);
|
|
3901
3925
|
dropMap(this.#blockSourceEsis);
|
|
3926
|
+
dropMap(this.#blockRebuiltEsis);
|
|
3902
3927
|
dropMap(this.#blockAuthoredLengths);
|
|
3903
3928
|
dropMap(this.#blockRepairRsIds);
|
|
3904
3929
|
dropMap(this.#blockRsbLengths);
|
|
@@ -4061,6 +4086,9 @@ var FECManager = class _FECManager {
|
|
|
4061
4086
|
this.#unindexBlockKey(key);
|
|
4062
4087
|
}
|
|
4063
4088
|
}
|
|
4089
|
+
if (isSourceSymbol && source === "multicast") {
|
|
4090
|
+
this.#noteMulticastSource(this.#isoLocatorFor(packetId, catalogTrackName), sbn + esi >>> 0, packetId, catalogTrackName);
|
|
4091
|
+
}
|
|
4064
4092
|
return true;
|
|
4065
4093
|
}
|
|
4066
4094
|
#observeBlockCatalogTrackName(blockKey, sbn, packetId, catalogTrackName) {
|
|
@@ -4378,6 +4406,7 @@ var FECManager = class _FECManager {
|
|
|
4378
4406
|
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
4379
4407
|
const evictedBefore = locator.heldEvicted;
|
|
4380
4408
|
const held = locator.hold(ssId, source);
|
|
4409
|
+
if (held && source.source === "multicast") this.#noteMulticastSource(locator, ssId, packetId, catalogTrackName);
|
|
4381
4410
|
const evicted = locator.heldEvicted - evictedBefore;
|
|
4382
4411
|
if (evicted > 0) {
|
|
4383
4412
|
const before = this.#isoHeldEvictedCount;
|
|
@@ -4973,7 +5002,15 @@ var FECManager = class _FECManager {
|
|
|
4973
5002
|
this.#isoSourceOversizeCount++;
|
|
4974
5003
|
return null;
|
|
4975
5004
|
}
|
|
4976
|
-
|
|
5005
|
+
return this.#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, located.block.start);
|
|
5006
|
+
}
|
|
5007
|
+
/**
|
|
5008
|
+
* Hand one placed source symbol to the stream's decoder: its Worker, which
|
|
5009
|
+
* answers through onFrame (null here), or its synchronous decoder, whose
|
|
5010
|
+
* recovered block is returned. `sbn` is the SS_Start of the block a repair
|
|
5011
|
+
* stated for `ssId`.
|
|
5012
|
+
*/
|
|
5013
|
+
#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, sbn) {
|
|
4977
5014
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
4978
5015
|
const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey);
|
|
4979
5016
|
const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName);
|
|
@@ -5002,6 +5039,74 @@ var FECManager = class _FECManager {
|
|
|
5002
5039
|
}
|
|
5003
5040
|
return recovered;
|
|
5004
5041
|
}
|
|
5042
|
+
/**
|
|
5043
|
+
* Feed a source symbol of this stream that another FEC instance over the
|
|
5044
|
+
* same packets rebuilt: a keyframe overlay recovery whose base SS_ID the
|
|
5045
|
+
* caller determined (draft-ramadan-moq-mmt-02 7.1, 7.4.4; joint decoding,
|
|
5046
|
+
* BLO-52085). It joins its block's decoder as a received source would, so
|
|
5047
|
+
* it reduces the erasures the block still has to solve, but it is not a
|
|
5048
|
+
* network arrival: no transport accounting, no classification evidence,
|
|
5049
|
+
* no deadline refresh, and it never makes a block clean.
|
|
5050
|
+
*
|
|
5051
|
+
* Only a block a repair stated takes one (a repair-less span has no
|
|
5052
|
+
* decoder block). A block that is terminal, or already holds that source,
|
|
5053
|
+
* does not. The symbol is the exact protected packet, unpadded, as
|
|
5054
|
+
* feedSourceSymbol takes it.
|
|
5055
|
+
*
|
|
5056
|
+
* `recovered` is the block the synchronous decoder completed with it; the
|
|
5057
|
+
* caller routes it and completes the block, as for any recovery. When
|
|
5058
|
+
* every source of the block is then present, received or rebuilt, and no
|
|
5059
|
+
* decoder returned it (a Worker answers asynchronously, and reports such a
|
|
5060
|
+
* block as clean on its own side), the block is completed here as
|
|
5061
|
+
* recovered, with nothing left to route.
|
|
5062
|
+
*/
|
|
5063
|
+
feedRebuiltSourceSymbol(packetId, ssId, symbolData, catalogTrackName) {
|
|
5064
|
+
if (!this.#hasBlockProfile(packetId)) return NOT_TAKEN;
|
|
5065
|
+
const located = this.#isoLocatorFor(packetId, catalogTrackName).locate(ssId);
|
|
5066
|
+
if (located === null || located.block.span) return NOT_TAKEN;
|
|
5067
|
+
if (!ssbg1FitsSymbol(symbolData.byteLength, this.#trackConfig(packetId).symbolSize)) return NOT_TAKEN;
|
|
5068
|
+
const sbn = located.block.start;
|
|
5069
|
+
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
5070
|
+
if (this.#completedBlocks.has(key) || this.#blockSourceEsis.get(key)?.has(located.esi)) return NOT_TAKEN;
|
|
5071
|
+
let rebuilt = this.#blockRebuiltEsis.get(key);
|
|
5072
|
+
if (rebuilt?.has(located.esi)) return NOT_TAKEN;
|
|
5073
|
+
if (rebuilt === void 0) {
|
|
5074
|
+
rebuilt = /* @__PURE__ */ new Set();
|
|
5075
|
+
this.#blockRebuiltEsis.set(key, rebuilt);
|
|
5076
|
+
}
|
|
5077
|
+
const config = this.#trackConfigs.get(packetId);
|
|
5078
|
+
const wasShort = config !== void 0 && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) !== void 0;
|
|
5079
|
+
rebuilt.add(located.esi);
|
|
5080
|
+
const recovered = this.#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, sbn);
|
|
5081
|
+
if (recovered === null && !this.#completeWhenSourcesPresent(key, sbn, located.block.length, packetId, catalogTrackName)) {
|
|
5082
|
+
if (wasShort && this.#activeBlocks.has(key) && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) === void 0) {
|
|
5083
|
+
this.#armBlockTimeout(sbn, packetId, catalogTrackName, this.#multicastWaitMs(config));
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
return { taken: true, recovered };
|
|
5087
|
+
}
|
|
5088
|
+
/**
|
|
5089
|
+
* A block that holds every one of its `length` sources, received or
|
|
5090
|
+
* rebuilt by another instance, with at least one rebuilt, is recovered:
|
|
5091
|
+
* every packet of it was delivered, by its own arrival or by the instance
|
|
5092
|
+
* that rebuilt it, so nothing is routed. Returns whether it completed it.
|
|
5093
|
+
*/
|
|
5094
|
+
#completeWhenSourcesPresent(key, sbn, length, packetId, catalogTrackName) {
|
|
5095
|
+
const rebuilt = this.#blockRebuiltEsis.get(key);
|
|
5096
|
+
if (rebuilt === void 0 || this.#completedBlocks.has(key)) return false;
|
|
5097
|
+
let present = rebuilt.size;
|
|
5098
|
+
for (const esi of this.#blockSourceEsis.get(key) ?? []) if (!rebuilt.has(esi)) present++;
|
|
5099
|
+
if (present < length) return false;
|
|
5100
|
+
this.#traceLifecycle("recovered", sbn, packetId, catalogTrackName);
|
|
5101
|
+
this.countRecovery(packetId, sbn, catalogTrackName);
|
|
5102
|
+
this.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
5103
|
+
if (catalogTrackName === void 0) {
|
|
5104
|
+
this.onBlockComplete?.(sbn, packetId);
|
|
5105
|
+
} else {
|
|
5106
|
+
this.onBlockComplete?.(sbn, packetId, catalogTrackName);
|
|
5107
|
+
}
|
|
5108
|
+
return true;
|
|
5109
|
+
}
|
|
5005
5110
|
/**
|
|
5006
5111
|
* fec-01 4.5: a reed-solomon repair packet whose SSB_length + RSB_length
|
|
5007
5112
|
* exceeds 255, or whose RS_ID is not below RSB_length, is discarded with the
|
|
@@ -5204,8 +5309,75 @@ var FECManager = class _FECManager {
|
|
|
5204
5309
|
this.#completeCleanBlock(blockKey, sbn, packetId, catalogTrackName);
|
|
5205
5310
|
return null;
|
|
5206
5311
|
}
|
|
5312
|
+
if (recovered === null) this.#shortenUnrecoverableWait(blockKey, sbn, packetId, catalogTrackName);
|
|
5207
5313
|
return recovered;
|
|
5208
5314
|
}
|
|
5315
|
+
/**
|
|
5316
|
+
* A first-arrival multicast source, held or placed. A datagram flow keeps
|
|
5317
|
+
* its sources in send order, so one that moves the stream's newest source
|
|
5318
|
+
* position past a block leaves the sources that block still misses
|
|
5319
|
+
* reordered behind it at best: it shortens the wait of every block it
|
|
5320
|
+
* passed that only a late source can complete. MoQ is left out: its streams
|
|
5321
|
+
* can overtake one another, so a MoQ source shows nothing about the sources
|
|
5322
|
+
* sent before it.
|
|
5323
|
+
*/
|
|
5324
|
+
#noteMulticastSource(locator, ssId, packetId, catalogTrackName) {
|
|
5325
|
+
const previous = locator.newestSource;
|
|
5326
|
+
if (!locator.noteSource(ssId) || previous === void 0) return;
|
|
5327
|
+
for (const block of locator.blocksBetween(previous - 1 >>> 0, ssId)) {
|
|
5328
|
+
if (block.span || !serial32Precedes(serial32Add(block.start, block.length - 1), ssId)) continue;
|
|
5329
|
+
this.#shortenUnrecoverableWait(
|
|
5330
|
+
this.#blockKey(block.start, packetId, catalogTrackName),
|
|
5331
|
+
block.start,
|
|
5332
|
+
packetId,
|
|
5333
|
+
catalogTrackName
|
|
5334
|
+
);
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
/**
|
|
5338
|
+
* The wait of a block that only a late source can complete (BLO-52085): a
|
|
5339
|
+
* repair stated it, its received sources plus every symbol its repair group
|
|
5340
|
+
* carries (RSB_length) are short of K' (the repair scheduler's
|
|
5341
|
+
* group-cannot-complete), and a later source of the stream has arrived, so
|
|
5342
|
+
* a source it misses is lost or reordered behind that one. The span, which
|
|
5343
|
+
* covers repair symbols paced after its sources, buys it nothing: it waits
|
|
5344
|
+
* the late-symbol allowance alone, the multicast wait less the span (the
|
|
5345
|
+
* catalog tolerance or the measured delivery gap). Undefined for any other
|
|
5346
|
+
* block.
|
|
5347
|
+
*/
|
|
5348
|
+
#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) {
|
|
5349
|
+
const authoredLength = this.#blockAuthoredLengths.get(key);
|
|
5350
|
+
const rsbLength = this.#blockRsbLengths.get(key);
|
|
5351
|
+
if (authoredLength === void 0 || rsbLength === void 0) return void 0;
|
|
5352
|
+
let present = this.#blockSourceCounts.get(key) ?? 0;
|
|
5353
|
+
const received = this.#blockSourceEsis.get(key);
|
|
5354
|
+
for (const esi of this.#blockRebuiltEsis.get(key) ?? []) if (!received?.has(esi)) present++;
|
|
5355
|
+
if (present + rsbLength >= authoredLength) return void 0;
|
|
5356
|
+
const newestSource = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newestSource;
|
|
5357
|
+
if (newestSource === void 0 || !serial32Precedes(serial32Add(sbn, authoredLength - 1), newestSource)) {
|
|
5358
|
+
return void 0;
|
|
5359
|
+
}
|
|
5360
|
+
return this.#multicastWaitMs(config) - config.interleaveDepthMs;
|
|
5361
|
+
}
|
|
5362
|
+
/**
|
|
5363
|
+
* Bring an open block's wait in to its late-source wait, when it has one.
|
|
5364
|
+
* Never later than it is armed: only a source can complete such a block,
|
|
5365
|
+
* so a repair of its group (paced across the span) must not keep it open.
|
|
5366
|
+
*/
|
|
5367
|
+
#shortenUnrecoverableWait(key, sbn, packetId, catalogTrackName) {
|
|
5368
|
+
if (!this.#activeBlocks.has(key)) return;
|
|
5369
|
+
const config = this.#trackConfigs.get(packetId);
|
|
5370
|
+
if (config === void 0) return;
|
|
5371
|
+
const waitMs = this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config);
|
|
5372
|
+
if (waitMs === void 0) return;
|
|
5373
|
+
const armedAtMs = this.#blockWaitDeadlinesMs.get(key);
|
|
5374
|
+
this.#armBlockTimeout(
|
|
5375
|
+
sbn,
|
|
5376
|
+
packetId,
|
|
5377
|
+
catalogTrackName,
|
|
5378
|
+
armedAtMs === void 0 ? waitMs : Math.min(waitMs, armedAtMs - this.#receiptNow())
|
|
5379
|
+
);
|
|
5380
|
+
}
|
|
5209
5381
|
/**
|
|
5210
5382
|
* Record a block's authored SSB_length from a repair symbol. Returns false,
|
|
5211
5383
|
* leaving the block unchanged, when the symbol contradicts the block: a
|
|
@@ -5360,6 +5532,15 @@ var FECManager = class _FECManager {
|
|
|
5360
5532
|
this.#completeCleanBlock(key, sbn, packetId, catalogTrackName);
|
|
5361
5533
|
return;
|
|
5362
5534
|
}
|
|
5535
|
+
if (this.#blockRebuiltEsis.has(key) && this.#completeWhenSourcesPresent(
|
|
5536
|
+
key,
|
|
5537
|
+
sbn,
|
|
5538
|
+
this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? authoredLength,
|
|
5539
|
+
packetId,
|
|
5540
|
+
catalogTrackName
|
|
5541
|
+
)) {
|
|
5542
|
+
return;
|
|
5543
|
+
}
|
|
5363
5544
|
this.noteBlockActivity(sbn, packetId, catalogTrackName, true);
|
|
5364
5545
|
}
|
|
5365
5546
|
#completeCleanBlock(key, sbn, packetId, catalogTrackName) {
|
|
@@ -5368,6 +5549,7 @@ var FECManager = class _FECManager {
|
|
|
5368
5549
|
this.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
5369
5550
|
this.#blockSourceCounts.delete(key);
|
|
5370
5551
|
this.#blockSourceEsis.delete(key);
|
|
5552
|
+
this.#blockRebuiltEsis.delete(key);
|
|
5371
5553
|
this.#blockAuthoredLengths.delete(key);
|
|
5372
5554
|
this.#blockRepairRsIds.delete(key);
|
|
5373
5555
|
this.#blockRsbLengths.delete(key);
|
|
@@ -5434,7 +5616,16 @@ var FECManager = class _FECManager {
|
|
|
5434
5616
|
this.#repairDeadlineMs(trackConfig)
|
|
5435
5617
|
);
|
|
5436
5618
|
}
|
|
5437
|
-
this.#
|
|
5619
|
+
if (!sourceProgress && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, trackConfig) !== void 0) {
|
|
5620
|
+
this.#shortenUnrecoverableWait(key, sbn, packetId, catalogTrackName);
|
|
5621
|
+
return;
|
|
5622
|
+
}
|
|
5623
|
+
this.#armBlockTimeout(
|
|
5624
|
+
sbn,
|
|
5625
|
+
packetId,
|
|
5626
|
+
catalogTrackName,
|
|
5627
|
+
this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, trackConfig) ?? timeoutMs
|
|
5628
|
+
);
|
|
5438
5629
|
}
|
|
5439
5630
|
#failBlock(sbn, packetId, catalogTrackName) {
|
|
5440
5631
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
@@ -5447,6 +5638,7 @@ var FECManager = class _FECManager {
|
|
|
5447
5638
|
}
|
|
5448
5639
|
this.#blockSourceCounts.delete(key);
|
|
5449
5640
|
this.#blockSourceEsis.delete(key);
|
|
5641
|
+
this.#blockRebuiltEsis.delete(key);
|
|
5450
5642
|
this.#blockAuthoredLengths.delete(key);
|
|
5451
5643
|
this.#blockRepairRsIds.delete(key);
|
|
5452
5644
|
this.#blockRsbLengths.delete(key);
|
|
@@ -5485,6 +5677,7 @@ var FECManager = class _FECManager {
|
|
|
5485
5677
|
this.#retireBlockClass(key, block.sbn, block.packetId, block.catalogTrackName);
|
|
5486
5678
|
this.#blockSourceCounts.delete(key);
|
|
5487
5679
|
this.#blockSourceEsis.delete(key);
|
|
5680
|
+
this.#blockRebuiltEsis.delete(key);
|
|
5488
5681
|
this.#blockAuthoredLengths.delete(key);
|
|
5489
5682
|
this.#blockRepairRsIds.delete(key);
|
|
5490
5683
|
this.#blockRsbLengths.delete(key);
|
|
@@ -5576,6 +5769,7 @@ var FECManager = class _FECManager {
|
|
|
5576
5769
|
this.#blockKeysBySbn.clear();
|
|
5577
5770
|
this.#blockSourceCounts.clear();
|
|
5578
5771
|
this.#blockSourceEsis.clear();
|
|
5772
|
+
this.#blockRebuiltEsis.clear();
|
|
5579
5773
|
this.#blockAuthoredLengths.clear();
|
|
5580
5774
|
this.#blockRepairRsIds.clear();
|
|
5581
5775
|
this.#blockRsbLengths.clear();
|
|
@@ -7076,6 +7270,7 @@ var MOQT_PUBLISHER_PRIORITY = Object.freeze({
|
|
|
7076
7270
|
});
|
|
7077
7271
|
|
|
7078
7272
|
// node_modules/@blockcast/mmt-transport/dist/amt-gateway.js
|
|
7273
|
+
var MAX_PENDING_PACKETS = 256;
|
|
7079
7274
|
var AMTGatewayManager = class {
|
|
7080
7275
|
subscriptions = /* @__PURE__ */ new Map();
|
|
7081
7276
|
messageHandler;
|
|
@@ -7199,7 +7394,7 @@ var AMTGatewayManager = class {
|
|
|
7199
7394
|
if (!subscription.handshakeComplete) {
|
|
7200
7395
|
const queue = this.pendingPackets.get(subscriptionId);
|
|
7201
7396
|
const packetData2 = data;
|
|
7202
|
-
if (queue) {
|
|
7397
|
+
if (queue && queue.length < MAX_PENDING_PACKETS) {
|
|
7203
7398
|
queue.push({
|
|
7204
7399
|
data: packetData2.data,
|
|
7205
7400
|
remoteAddress: packetData2.remoteAddress,
|
|
@@ -13142,9 +13337,21 @@ var IsoBmffTimingResolver = class {
|
|
|
13142
13337
|
}
|
|
13143
13338
|
#track = null;
|
|
13144
13339
|
#fragments = /* @__PURE__ */ new Map();
|
|
13340
|
+
/** First sample of each pending fragment, kept in `#delivered` with its sample count. */
|
|
13341
|
+
#firstSamples = /* @__PURE__ */ new Map();
|
|
13342
|
+
/**
|
|
13343
|
+
* First sample and sample count of the newest fragments whose every slot was
|
|
13344
|
+
* cleared. Abandonment counts: `releaseSample` clears a slot too, which
|
|
13345
|
+
* matches the combiner, since it retires an abandoned sample as well.
|
|
13346
|
+
*/
|
|
13347
|
+
#delivered = /* @__PURE__ */ new Map();
|
|
13145
13348
|
#pendingSamples = 0;
|
|
13146
13349
|
#evictedSamples = 0;
|
|
13147
13350
|
#lastMovieFragmentRejection = null;
|
|
13351
|
+
/** True once an FT=0 has bound the track that FT=1 timing is read against. */
|
|
13352
|
+
get initialized() {
|
|
13353
|
+
return this.#track !== null;
|
|
13354
|
+
}
|
|
13148
13355
|
get pendingSampleCount() {
|
|
13149
13356
|
return this.#pendingSamples;
|
|
13150
13357
|
}
|
|
@@ -13168,7 +13375,14 @@ var IsoBmffTimingResolver = class {
|
|
|
13168
13375
|
this.#track = timing;
|
|
13169
13376
|
return true;
|
|
13170
13377
|
}
|
|
13171
|
-
|
|
13378
|
+
/**
|
|
13379
|
+
* `recovered` marks an FEC-recovered copy. A recovered block hands back
|
|
13380
|
+
* every FT=1 it holds, the received ones included, so a recovered copy of a
|
|
13381
|
+
* fragment whose samples are all done is a duplicate: admitting it again
|
|
13382
|
+
* would charge stale timing and could displace the live MPU's. A source
|
|
13383
|
+
* copy is still admitted again (a rendition switch re-arms timing that way).
|
|
13384
|
+
*/
|
|
13385
|
+
addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
|
|
13172
13386
|
this.#lastMovieFragmentRejection = null;
|
|
13173
13387
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
13174
13388
|
this.#lastMovieFragmentRejection = "invalid";
|
|
@@ -13194,6 +13408,8 @@ var IsoBmffTimingResolver = class {
|
|
|
13194
13408
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
13195
13409
|
);
|
|
13196
13410
|
}
|
|
13411
|
+
const delivered = recovered ? this.#delivered.get(key) : void 0;
|
|
13412
|
+
if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
|
|
13197
13413
|
let freed = 0;
|
|
13198
13414
|
while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
13199
13415
|
freed += this.#evictOldestFragment();
|
|
@@ -13207,6 +13423,7 @@ var IsoBmffTimingResolver = class {
|
|
|
13207
13423
|
return false;
|
|
13208
13424
|
}
|
|
13209
13425
|
this.#fragments.set(key, [...parsed.samples]);
|
|
13426
|
+
this.#firstSamples.set(key, parsed.samples[0]);
|
|
13210
13427
|
this.#pendingSamples += parsed.samples.length;
|
|
13211
13428
|
return true;
|
|
13212
13429
|
}
|
|
@@ -13264,6 +13481,7 @@ var IsoBmffTimingResolver = class {
|
|
|
13264
13481
|
let live = 0;
|
|
13265
13482
|
for (const sample of samples) if (sample !== null) live++;
|
|
13266
13483
|
this.#fragments.delete(oldest);
|
|
13484
|
+
this.#firstSamples.delete(oldest);
|
|
13267
13485
|
this.#pendingSamples -= live;
|
|
13268
13486
|
this.#evictedSamples += live;
|
|
13269
13487
|
return live;
|
|
@@ -13279,12 +13497,23 @@ var IsoBmffTimingResolver = class {
|
|
|
13279
13497
|
if (!samples || !samples[index]) return false;
|
|
13280
13498
|
samples[index] = null;
|
|
13281
13499
|
this.#pendingSamples--;
|
|
13282
|
-
if (samples.every((value) => value === null))
|
|
13500
|
+
if (samples.every((value) => value === null)) {
|
|
13501
|
+
this.#fragments.delete(key);
|
|
13502
|
+
const first = this.#firstSamples.get(key);
|
|
13503
|
+
this.#firstSamples.delete(key);
|
|
13504
|
+
this.#delivered.delete(key);
|
|
13505
|
+
this.#delivered.set(key, { first, sampleCount: samples.length });
|
|
13506
|
+
if (this.#delivered.size > this.maxPendingSamples) {
|
|
13507
|
+
this.#delivered.delete(this.#delivered.keys().next().value);
|
|
13508
|
+
}
|
|
13509
|
+
}
|
|
13283
13510
|
return true;
|
|
13284
13511
|
}
|
|
13285
13512
|
reset() {
|
|
13286
13513
|
this.#track = null;
|
|
13287
13514
|
this.#fragments.clear();
|
|
13515
|
+
this.#firstSamples.clear();
|
|
13516
|
+
this.#delivered.clear();
|
|
13288
13517
|
this.#pendingSamples = 0;
|
|
13289
13518
|
}
|
|
13290
13519
|
};
|
|
@@ -13766,6 +13995,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
13766
13995
|
this.#reclaimedTimingSamples = 0;
|
|
13767
13996
|
this.#evictedTimingSamples = 0;
|
|
13768
13997
|
this.#rejectedMovieFragments = 0;
|
|
13998
|
+
this.#recoveredHeadsAwaitingTiming = {
|
|
13999
|
+
held: 0,
|
|
14000
|
+
expired: 0,
|
|
14001
|
+
discarded: 0,
|
|
14002
|
+
superseded: 0
|
|
14003
|
+
};
|
|
13769
14004
|
this.#sequencers = /* @__PURE__ */ new Map();
|
|
13770
14005
|
this.#fecGate = null;
|
|
13771
14006
|
this.#fecSequencer = null;
|
|
@@ -13874,6 +14109,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
13874
14109
|
#reclaimedTimingSamples;
|
|
13875
14110
|
#evictedTimingSamples;
|
|
13876
14111
|
#rejectedMovieFragments;
|
|
14112
|
+
#recoveredHeadsAwaitingTiming;
|
|
13877
14113
|
#sequencers;
|
|
13878
14114
|
#fecGate;
|
|
13879
14115
|
#fecSequencer;
|
|
@@ -13994,6 +14230,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
13994
14230
|
rejectedMovieFragments: this.#rejectedMovieFragments
|
|
13995
14231
|
};
|
|
13996
14232
|
}
|
|
14233
|
+
/** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
|
|
14234
|
+
get recoveredHeadsAwaitingTiming() {
|
|
14235
|
+
let pending = 0;
|
|
14236
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
14237
|
+
for (const head of awaitingTiming) if (head.options?.recovered) pending++;
|
|
14238
|
+
}
|
|
14239
|
+
return { ...this.#recoveredHeadsAwaitingTiming, pending };
|
|
14240
|
+
}
|
|
13997
14241
|
get reorderStats() {
|
|
13998
14242
|
let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
|
|
13999
14243
|
for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
|
|
@@ -14167,7 +14411,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14167
14411
|
const now = Date.now();
|
|
14168
14412
|
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
14169
14413
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
14170
|
-
awaitingTiming.shift();
|
|
14414
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
14171
14415
|
}
|
|
14172
14416
|
}
|
|
14173
14417
|
}
|
|
@@ -14285,11 +14529,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14285
14529
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
14286
14530
|
return this.#drop("fragment-unexpected-fragmentation");
|
|
14287
14531
|
const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
|
|
14288
|
-
if (!resolver2) return this.#drop("fragment-no-resolver");
|
|
14532
|
+
if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
|
|
14289
14533
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
14290
14534
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
14291
14535
|
const remaining = this.#maxPendingTimingSamples - this.#pendingTimingSamples;
|
|
14292
|
-
const admitted = resolver2.addMovieFragment(
|
|
14536
|
+
const admitted = resolver2.addMovieFragment(
|
|
14537
|
+
mpu.mpuSequenceNumber,
|
|
14538
|
+
data,
|
|
14539
|
+
remaining,
|
|
14540
|
+
options?.recovered === true
|
|
14541
|
+
);
|
|
14293
14542
|
this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
|
|
14294
14543
|
this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
|
|
14295
14544
|
if (!admitted) {
|
|
@@ -14309,7 +14558,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14309
14558
|
};
|
|
14310
14559
|
}
|
|
14311
14560
|
if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
|
|
14312
|
-
const resolver = this.#timingResolver(streamKey, info.packetId,
|
|
14561
|
+
const resolver = this.#timingResolver(streamKey, info.packetId, true);
|
|
14313
14562
|
if (!resolver) return this.#drop("mfu-no-resolver");
|
|
14314
14563
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
14315
14564
|
return this.#drop("mfu-empty-payload");
|
|
@@ -14330,6 +14579,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14330
14579
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
14331
14580
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
14332
14581
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
14582
|
+
sequenceNumber: info.sequenceNumber,
|
|
14333
14583
|
arrivedAt: Date.now()
|
|
14334
14584
|
});
|
|
14335
14585
|
return this.#drop("mfu-sample-timing-missing");
|
|
@@ -14718,6 +14968,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14718
14968
|
for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
|
|
14719
14969
|
this.#sequencers.clear();
|
|
14720
14970
|
this.#fecSequencer?.discard();
|
|
14971
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
14972
|
+
for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
14973
|
+
}
|
|
14721
14974
|
this.#timingStreams.clear();
|
|
14722
14975
|
this.#pendingTimingSamples = 0;
|
|
14723
14976
|
this.#reliableMoqStartupGate.clear();
|
|
@@ -14736,10 +14989,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14736
14989
|
return resolver;
|
|
14737
14990
|
}
|
|
14738
14991
|
#awaitTiming(streamKey, head) {
|
|
14739
|
-
const
|
|
14740
|
-
if (!
|
|
14992
|
+
const state = this.#timingStreams.get(streamKey);
|
|
14993
|
+
if (!state) return;
|
|
14994
|
+
if (!head.options?.recovered) {
|
|
14995
|
+
const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
|
|
14996
|
+
this.#recoveredHeadsAwaitingTiming.superseded += state.awaitingTiming.length - kept.length;
|
|
14997
|
+
state.awaitingTiming = kept;
|
|
14998
|
+
}
|
|
14999
|
+
const awaitingTiming = state.awaitingTiming;
|
|
14741
15000
|
awaitingTiming.push(head);
|
|
14742
|
-
if (
|
|
15001
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming.held++;
|
|
15002
|
+
if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
|
|
15003
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
15004
|
+
}
|
|
15005
|
+
}
|
|
15006
|
+
/** Count a recovered head leaving the queue other than by replay or its source copy. */
|
|
15007
|
+
#leaveAwaitingTiming(head, outcome) {
|
|
15008
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming[outcome]++;
|
|
14743
15009
|
}
|
|
14744
15010
|
/**
|
|
14745
15011
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -14767,6 +15033,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14767
15033
|
const state = this.#timingStreams.get(streamKey);
|
|
14768
15034
|
if (!state) return;
|
|
14769
15035
|
this.#timingStreams.delete(streamKey);
|
|
15036
|
+
for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
14770
15037
|
this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
|
|
14771
15038
|
if (this.#pendingTimingSamples < 0) {
|
|
14772
15039
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
@@ -17972,6 +18239,16 @@ function keyframeOverlaySsId(header) {
|
|
|
17972
18239
|
if (ext?.type !== KEYFRAME_OVERLAY_EXTENSION_TYPE || ext.data.length !== 4) return void 0;
|
|
17973
18240
|
return new DataView(ext.data.buffer, ext.data.byteOffset, 4).getUint32(0);
|
|
17974
18241
|
}
|
|
18242
|
+
function overlayBaseSsId(anchor, overlaySsId, restoredPsn, headerPsn) {
|
|
18243
|
+
if (anchor.baseSsId === void 0 || headerPsn !== restoredPsn) return void 0;
|
|
18244
|
+
return serial32Add(anchor.baseSsId, overlaySsId - anchor.overlaySsId | 0);
|
|
18245
|
+
}
|
|
18246
|
+
function unstampedBaseSsId(offset, unstampedOffset, headerPsn) {
|
|
18247
|
+
if (offset === void 0 || offset !== unstampedOffset) return void 0;
|
|
18248
|
+
return headerPsn + offset >>> 0;
|
|
18249
|
+
}
|
|
18250
|
+
var OVERLAY_ROUTED_LIMIT = 1024;
|
|
18251
|
+
var OVERLAY_PENDING_LIMIT = 256;
|
|
17975
18252
|
var OVERLAY_OFFSET_MPUS = 16;
|
|
17976
18253
|
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
17977
18254
|
function assertStreamIdentity(role, stream) {
|
|
@@ -18083,6 +18360,7 @@ var MmtpRouter = class {
|
|
|
18083
18360
|
#provenance = /* @__PURE__ */ new Map();
|
|
18084
18361
|
#onFlowProtected = null;
|
|
18085
18362
|
#unadmittedListedDrops = 0;
|
|
18363
|
+
#admittedSourceDatagrams = 0;
|
|
18086
18364
|
#unlistedRepairSetAside = 0;
|
|
18087
18365
|
#assistBlocks = /* @__PURE__ */ new Map();
|
|
18088
18366
|
#assistFailures = /* @__PURE__ */ new Map();
|
|
@@ -18139,6 +18417,15 @@ var MmtpRouter = class {
|
|
|
18139
18417
|
* sequence is its SS_ID minus this; see #routeProcessedFecSymbols.
|
|
18140
18418
|
*/
|
|
18141
18419
|
#ssIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
18420
|
+
/**
|
|
18421
|
+
* Per stream, the SS_ID offset at which a base recovery last showed the
|
|
18422
|
+
* sender's own packet_sequence_number: a recovered packet's header psn
|
|
18423
|
+
* equal to its SS_ID minus that offset. While it equals the stream's
|
|
18424
|
+
* current offset, no hop re-stamps the stream, and a packet's protected psn
|
|
18425
|
+
* names its base SS_ID within unstampedBaseSsId's limits (joint decoding of
|
|
18426
|
+
* an MPU no member of which arrived).
|
|
18427
|
+
*/
|
|
18428
|
+
#unstampedSsIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
18142
18429
|
/**
|
|
18143
18430
|
* Streams whose multicast leg delivered a RAP-flagged source packet that was
|
|
18144
18431
|
* not parsed, typically because its overlap copy was parsed first
|
|
@@ -18208,7 +18495,9 @@ var MmtpRouter = class {
|
|
|
18208
18495
|
recovered: 0,
|
|
18209
18496
|
restarts: 0,
|
|
18210
18497
|
unsequencedDropped: 0,
|
|
18211
|
-
protectedDropped: 0
|
|
18498
|
+
protectedDropped: 0,
|
|
18499
|
+
baseFed: 0,
|
|
18500
|
+
baseUndetermined: 0
|
|
18212
18501
|
};
|
|
18213
18502
|
/** Callback fired when a delta-track FEC block completes or fails. */
|
|
18214
18503
|
#onDeltaBlockCompleteCb = null;
|
|
@@ -18381,7 +18670,7 @@ var MmtpRouter = class {
|
|
|
18381
18670
|
const cb = this.#getFrameCallback(packetId);
|
|
18382
18671
|
if (isVideo && !assembler) {
|
|
18383
18672
|
if (frameMeta.recovered) {
|
|
18384
|
-
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb);
|
|
18673
|
+
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb, meta.fecSbn);
|
|
18385
18674
|
} else {
|
|
18386
18675
|
this.#routeDirectSourceVideo(packetId, data, frameMeta, cb);
|
|
18387
18676
|
}
|
|
@@ -18771,6 +19060,10 @@ var MmtpRouter = class {
|
|
|
18771
19060
|
get keyframeOverlayStats() {
|
|
18772
19061
|
return { ...this.#overlayStats };
|
|
18773
19062
|
}
|
|
19063
|
+
/** Recovered FT=2 heads held for their FT=1, and how each left the wait (BLO-53369). */
|
|
19064
|
+
get recoveredHeadsAwaitingTiming() {
|
|
19065
|
+
return this.#parser.recoveredHeadsAwaitingTiming;
|
|
19066
|
+
}
|
|
18774
19067
|
#isOverlayRepair(repairPacketId, repairTrackName) {
|
|
18775
19068
|
return this.#overlayLegs.has(repairPacketId) || repairTrackName !== void 0 && this.#overlayRepairNames.has(repairTrackName);
|
|
18776
19069
|
}
|
|
@@ -18821,6 +19114,11 @@ var MmtpRouter = class {
|
|
|
18821
19114
|
*/
|
|
18822
19115
|
resetKeyframeOverlays(sourcePacketId, catalogTrackName) {
|
|
18823
19116
|
for (const inst of this.#overlaysForSource(sourcePacketId, catalogTrackName)) this.#resetOverlay(inst);
|
|
19117
|
+
for (const [name, stream] of this.#streams.get(sourcePacketId) ?? []) {
|
|
19118
|
+
if (catalogTrackName !== void 0 && name !== catalogTrackName) continue;
|
|
19119
|
+
stream.overlayToBase.routed.clear();
|
|
19120
|
+
stream.overlayToBase.pending.clear();
|
|
19121
|
+
}
|
|
18824
19122
|
}
|
|
18825
19123
|
#resetOverlay(inst) {
|
|
18826
19124
|
inst.fec.resetTrack(inst.repairPacketId, inst.streamName);
|
|
@@ -18877,9 +19175,10 @@ var MmtpRouter = class {
|
|
|
18877
19175
|
* A source packet that carries a 0x8B01 overlay SS_ID is also a symbol of
|
|
18878
19176
|
* every overlay installed for its source, at that SS_ID, with the bytes the
|
|
18879
19177
|
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
18880
|
-
* included (7.1, 7.4.3).
|
|
19178
|
+
* included (7.1, 7.4.3). `baseSsId` is that trailer's SS_ID, when the base
|
|
19179
|
+
* FEC reads it.
|
|
18881
19180
|
*/
|
|
18882
|
-
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source) {
|
|
19181
|
+
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source, baseSsId) {
|
|
18883
19182
|
if (this.#overlays.size === 0) return;
|
|
18884
19183
|
const ssId = keyframeOverlaySsId(header);
|
|
18885
19184
|
if (ssId === void 0) return;
|
|
@@ -18888,7 +19187,11 @@ var MmtpRouter = class {
|
|
|
18888
19187
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
18889
19188
|
if (identity !== null) {
|
|
18890
19189
|
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
18891
|
-
inst.offsets.set(identity.mpuSequenceNumber,
|
|
19190
|
+
inst.offsets.set(identity.mpuSequenceNumber, {
|
|
19191
|
+
psnOffset: ssId - sequenceNumber >>> 0,
|
|
19192
|
+
overlaySsId: ssId,
|
|
19193
|
+
...baseSsId === void 0 ? {} : { baseSsId }
|
|
19194
|
+
});
|
|
18892
19195
|
if (inst.offsets.size > OVERLAY_OFFSET_MPUS) inst.offsets.delete(inst.offsets.keys().next().value);
|
|
18893
19196
|
}
|
|
18894
19197
|
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
@@ -18967,14 +19270,27 @@ var MmtpRouter = class {
|
|
|
18967
19270
|
const streamKey = this.#mediaStreamKey(sourcePacketId, sourceName);
|
|
18968
19271
|
const restamped = this.#restampedStreams.has(streamKey);
|
|
18969
19272
|
const restored = /* @__PURE__ */ new Set();
|
|
19273
|
+
const baseSsIds = /* @__PURE__ */ new Map();
|
|
18970
19274
|
const symbols = result.symbols.flatMap((sym) => {
|
|
18971
|
-
const
|
|
18972
|
-
if (
|
|
18973
|
-
const
|
|
19275
|
+
const anchor = inst.offsets.get(sym.fragment.mpuSequenceNumber);
|
|
19276
|
+
if (anchor !== void 0) {
|
|
19277
|
+
const overlaySsId = serial32Add(blockStart, sym.index);
|
|
19278
|
+
const packetSequenceNumber = overlaySsId - anchor.psnOffset >>> 0;
|
|
18974
19279
|
restored.add(packetSequenceNumber);
|
|
18975
|
-
|
|
19280
|
+
const out = { ...sym, packetSequenceNumber };
|
|
19281
|
+
const baseSsId = overlayBaseSsId(anchor, overlaySsId, packetSequenceNumber, sym.packetSequenceNumber);
|
|
19282
|
+
if (baseSsId !== void 0) baseSsIds.set(out, baseSsId);
|
|
19283
|
+
return [out];
|
|
19284
|
+
}
|
|
19285
|
+
if (!restamped) {
|
|
19286
|
+
const baseSsId = unstampedBaseSsId(
|
|
19287
|
+
this.#ssIdSeqOffsetByStream.get(streamKey),
|
|
19288
|
+
this.#unstampedSsIdSeqOffsetByStream.get(streamKey),
|
|
19289
|
+
sym.packetSequenceNumber
|
|
19290
|
+
);
|
|
19291
|
+
if (baseSsId !== void 0) baseSsIds.set(sym, baseSsId);
|
|
19292
|
+
return [sym];
|
|
18976
19293
|
}
|
|
18977
|
-
if (!restamped) return [sym];
|
|
18978
19294
|
this.#overlayStats.unsequencedDropped++;
|
|
18979
19295
|
return [];
|
|
18980
19296
|
});
|
|
@@ -18983,6 +19299,85 @@ var MmtpRouter = class {
|
|
|
18983
19299
|
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
18984
19300
|
});
|
|
18985
19301
|
this.#noteOverlayRecoveredRaps(sourcePacketId, sourceName, streamKey, symbols.filter((sym) => restored.has(sym.packetSequenceNumber)));
|
|
19302
|
+
this.#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds);
|
|
19303
|
+
}
|
|
19304
|
+
/**
|
|
19305
|
+
* Joint decoding (BLO-52085). A packet the overlay rebuilt is a source of
|
|
19306
|
+
* its base block too, once its base SS_ID is determined (overlayBaseSsId,
|
|
19307
|
+
* from a received member of its MPU, else unstampedBaseSsId),
|
|
19308
|
+
* so it is fed to the base decoder as a rebuilt source
|
|
19309
|
+
* (FECManager.feedRebuiltSourceSymbol): the base block has one erasure
|
|
19310
|
+
* fewer to solve, and its repair can recover the block's other packets.
|
|
19311
|
+
* A base block no repair has stated yet takes it when one does
|
|
19312
|
+
* (#feedPendingRebuilt).
|
|
19313
|
+
*
|
|
19314
|
+
* The overlay route already handled each packet fed (delivered it, or
|
|
19315
|
+
* held an untimed head for its FT=1), so a base recovery routes none of
|
|
19316
|
+
* them again (#routeVerifiedFecSymbols): a second recovered copy of a
|
|
19317
|
+
* held head would wait in the parser for timing its first copy consumed.
|
|
19318
|
+
* Only an overlay block decoded from multicast symbols alone is fed: with
|
|
19319
|
+
* a MoQ symbol in it, the base block's recovery would depend on bytes
|
|
19320
|
+
* that crossed no multicast socket, which the provenance counted from the
|
|
19321
|
+
* block's own arrivals cannot show.
|
|
19322
|
+
*/
|
|
19323
|
+
#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds) {
|
|
19324
|
+
const fec = this.#fecManager;
|
|
19325
|
+
const owner = inst.leg.sourcePacketId;
|
|
19326
|
+
const name = inst.leg.sourceCatalogTrackName;
|
|
19327
|
+
if (!fec?.feedRebuiltSourceSymbol || inst.moqBlocks.has(blockStart)) return;
|
|
19328
|
+
if (this.#classifyPacketId(owner) === null || fec.isTrackConfigured?.(owner) === false) return;
|
|
19329
|
+
const { duplicates, overlayToBase: { routed } } = this.#stream(owner, name);
|
|
19330
|
+
const feeds = [];
|
|
19331
|
+
for (const sym of symbols) {
|
|
19332
|
+
if (duplicates.sourceSeqs.has(sym.packetSequenceNumber)) continue;
|
|
19333
|
+
const ssId = baseSsIds.get(sym);
|
|
19334
|
+
if (ssId === void 0) {
|
|
19335
|
+
this.#overlayStats.baseUndetermined++;
|
|
19336
|
+
continue;
|
|
19337
|
+
}
|
|
19338
|
+
routed.delete(ssId);
|
|
19339
|
+
routed.add(ssId);
|
|
19340
|
+
if (routed.size > OVERLAY_ROUTED_LIMIT) routed.delete(routed.values().next().value);
|
|
19341
|
+
feeds.push([ssId, sym.rawSymbol]);
|
|
19342
|
+
}
|
|
19343
|
+
for (const [ssId, symbolData] of feeds) {
|
|
19344
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
19345
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
19346
|
+
}
|
|
19347
|
+
}
|
|
19348
|
+
/** Feed one rebuilt source to its base block, or keep it until a repair states that block. */
|
|
19349
|
+
#feedRebuiltToBase(owner, name, ssId, symbolData) {
|
|
19350
|
+
const fec = this.#fecManager;
|
|
19351
|
+
if (!fec?.feedRebuiltSourceSymbol) return;
|
|
19352
|
+
const coords = fec.locateIsoSource?.(owner, name, ssId);
|
|
19353
|
+
if (coords == null) {
|
|
19354
|
+
const pending = this.#stream(owner, name).overlayToBase.pending;
|
|
19355
|
+
pending.delete(ssId);
|
|
19356
|
+
pending.set(ssId, symbolData.slice());
|
|
19357
|
+
if (pending.size > OVERLAY_PENDING_LIMIT) pending.delete(pending.keys().next().value);
|
|
19358
|
+
return;
|
|
19359
|
+
}
|
|
19360
|
+
const fed = fec.feedRebuiltSourceSymbol(owner, ssId, symbolData, name);
|
|
19361
|
+
if (!fed.taken) return;
|
|
19362
|
+
this.#overlayStats.baseFed++;
|
|
19363
|
+
if (fed.recovered === null) return;
|
|
19364
|
+
const sbn = coords.sbn;
|
|
19365
|
+
this.#routeRecoveredBlock(sbn, fed.recovered, owner, name, () => {
|
|
19366
|
+
fec.cancelBlockTimeout(sbn, owner, name);
|
|
19367
|
+
fec.countRecovery(owner, sbn, name);
|
|
19368
|
+
fec.markBlockCompleted(sbn, owner, name);
|
|
19369
|
+
});
|
|
19370
|
+
}
|
|
19371
|
+
/** A repair stated `block`: the rebuilt sources that waited for it join it now. */
|
|
19372
|
+
#feedPendingRebuilt(owner, name, block) {
|
|
19373
|
+
const pending = this.#streams.get(owner)?.get(name)?.overlayToBase.pending;
|
|
19374
|
+
if (pending === void 0 || pending.size === 0) return;
|
|
19375
|
+
for (const [ssId, symbolData] of [...pending]) {
|
|
19376
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
19377
|
+
if (serial32Distance(block.start, ssId) >= block.length) continue;
|
|
19378
|
+
pending.delete(ssId);
|
|
19379
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
19380
|
+
}
|
|
18986
19381
|
}
|
|
18987
19382
|
/** fec-multicast-00 §6.6: report each base block whose RAP MFU fragments
|
|
18988
19383
|
* the overlay restored, when the keyframe's FT=0 and FT=1 are held too. A
|
|
@@ -19122,6 +19517,10 @@ var MmtpRouter = class {
|
|
|
19122
19517
|
get unadmittedListedDropCount() {
|
|
19123
19518
|
return this.#unadmittedListedDrops;
|
|
19124
19519
|
}
|
|
19520
|
+
/** Source datagrams (multicast or MoQ) a protected flow's bc-provenance v2 receiver released: admitted at once or held, then admitted by a record. Repair symbols and user-agent-admitted datagrams are not counted. Counted at release, before this router's own track checks: a released datagram those drop, which only a track change between hold and release can cause, still counts, so this is not a count of routed media. */
|
|
19521
|
+
get admittedSourceDatagramCount() {
|
|
19522
|
+
return this.#admittedSourceDatagrams;
|
|
19523
|
+
}
|
|
19125
19524
|
/** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
|
|
19126
19525
|
get unlistedRepairSetAsideCount() {
|
|
19127
19526
|
return this.#unlistedRepairSetAside;
|
|
@@ -19608,6 +20007,8 @@ var MmtpRouter = class {
|
|
|
19608
20007
|
)) return;
|
|
19609
20008
|
fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);
|
|
19610
20009
|
fec.noteRepairGroupLength?.(blockStart, ownerPacketId, repair.rsbLength, catalogTrackName);
|
|
20010
|
+
this.#feedPendingRebuilt(ownerPacketId, catalogTrackName, placement.block);
|
|
20011
|
+
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
19611
20012
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
19612
20013
|
const recovered = fec.feedRepairSymbol(
|
|
19613
20014
|
ownerPacketId,
|
|
@@ -19745,6 +20146,7 @@ var MmtpRouter = class {
|
|
|
19745
20146
|
this.#droppedPreSyncVideo.delete(streamKey);
|
|
19746
20147
|
this.#latestVideoSeqByStream.delete(streamKey);
|
|
19747
20148
|
this.#ssIdSeqOffsetByStream.delete(streamKey);
|
|
20149
|
+
this.#unstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
19748
20150
|
this.#multicastRapTwinByStream.delete(streamKey);
|
|
19749
20151
|
this.#lastDirectSourceVideoTsByStream.delete(streamKey);
|
|
19750
20152
|
this.#lastDirectEmittedVideoTsByStream.delete(streamKey);
|
|
@@ -20379,6 +20781,7 @@ var MmtpRouter = class {
|
|
|
20379
20781
|
const parsed = this.#parser.parsePacket(packet);
|
|
20380
20782
|
if (!parsed) return;
|
|
20381
20783
|
const info = parsed.info;
|
|
20784
|
+
if (manifestAdmitted && !info.isRepair && ingress?.admittedBy !== USER_AGENT_VERIFIED) this.#admittedSourceDatagrams++;
|
|
20382
20785
|
if (source === "multicast" && !manifestAdmitted) {
|
|
20383
20786
|
this.#fecManager?.noteMulticastArrival?.(info.packetId, info.sequenceNumber);
|
|
20384
20787
|
}
|
|
@@ -20660,7 +21063,8 @@ var MmtpRouter = class {
|
|
|
20660
21063
|
parsed.header,
|
|
20661
21064
|
info.sequenceNumber,
|
|
20662
21065
|
info.rapFlag,
|
|
20663
|
-
source
|
|
21066
|
+
source,
|
|
21067
|
+
fecPayloadId?.ssId
|
|
20664
21068
|
);
|
|
20665
21069
|
}
|
|
20666
21070
|
}
|
|
@@ -20810,7 +21214,8 @@ var MmtpRouter = class {
|
|
|
20810
21214
|
if (stream === void 0) {
|
|
20811
21215
|
stream = {
|
|
20812
21216
|
key: JSON.stringify([packetId, catalogTrackName ?? null]),
|
|
20813
|
-
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId))
|
|
21217
|
+
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId)),
|
|
21218
|
+
overlayToBase: { routed: /* @__PURE__ */ new Set(), pending: /* @__PURE__ */ new Map() }
|
|
20814
21219
|
};
|
|
20815
21220
|
byName.set(catalogTrackName, stream);
|
|
20816
21221
|
}
|
|
@@ -20925,8 +21330,16 @@ var MmtpRouter = class {
|
|
|
20925
21330
|
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
20926
21331
|
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
20927
21332
|
}
|
|
20928
|
-
#routeDirectRecoveredVideo(packetId, data, meta, cb) {
|
|
21333
|
+
#routeDirectRecoveredVideo(packetId, data, meta, cb, fecSbn) {
|
|
20929
21334
|
const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);
|
|
21335
|
+
if (this.#isFecTrack(packetId)) {
|
|
21336
|
+
const high = this.#deliveredVideoHighWaterByStream.get(streamKey);
|
|
21337
|
+
if (fecSbn !== void 0 || high !== void 0 && authoredAdjacent(high, meta)) {
|
|
21338
|
+
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
21339
|
+
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
21340
|
+
return;
|
|
21341
|
+
}
|
|
21342
|
+
}
|
|
20930
21343
|
this.#enqueueDirectRecoveredVideo(packetId, data, meta);
|
|
20931
21344
|
const lastSourceTs = this.#lastDirectSourceVideoTsByStream.get(streamKey);
|
|
20932
21345
|
this.#drainDirectRecoveredVideo(
|
|
@@ -21080,6 +21493,13 @@ var MmtpRouter = class {
|
|
|
21080
21493
|
* frames by epoch only if pre-protection input ever matters.
|
|
21081
21494
|
*/
|
|
21082
21495
|
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
21496
|
+
const overlayRouted = ownerPacketId == null ? void 0 : this.#streams.get(ownerPacketId)?.get(catalogTrackName)?.overlayToBase.routed;
|
|
21497
|
+
if (overlayRouted !== void 0 && overlayRouted.size > 0) {
|
|
21498
|
+
result = {
|
|
21499
|
+
...result,
|
|
21500
|
+
symbols: result.symbols.filter((sym) => !overlayRouted.has(this.#fecAnchorForRecovered(blockId, sym.index)))
|
|
21501
|
+
};
|
|
21502
|
+
}
|
|
21083
21503
|
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
21084
21504
|
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
21085
21505
|
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
@@ -21144,7 +21564,10 @@ var MmtpRouter = class {
|
|
|
21144
21564
|
...result,
|
|
21145
21565
|
symbols: result.symbols.map((sym) => {
|
|
21146
21566
|
const wire = this.#fecAnchorForRecovered(blockId, sym.index) - ssIdSeqOffset >>> 0;
|
|
21147
|
-
if (wire === sym.packetSequenceNumber)
|
|
21567
|
+
if (wire === sym.packetSequenceNumber) {
|
|
21568
|
+
this.#unstampedSsIdSeqOffsetByStream.set(streamKey, ssIdSeqOffset);
|
|
21569
|
+
return sym;
|
|
21570
|
+
}
|
|
21148
21571
|
this.#restampedRecoveredSeqs++;
|
|
21149
21572
|
this.#restampedStreams.add(streamKey);
|
|
21150
21573
|
return { ...sym, packetSequenceNumber: wire };
|
|
@@ -21180,16 +21603,29 @@ var MmtpRouter = class {
|
|
|
21180
21603
|
}
|
|
21181
21604
|
);
|
|
21182
21605
|
}
|
|
21183
|
-
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
|
|
21188
|
-
|
|
21189
|
-
|
|
21190
|
-
|
|
21191
|
-
))
|
|
21192
|
-
|
|
21606
|
+
const untimedHead = (sym) => sym.fragmentType === 2 && sym.fragment.fragmentationIndicator === 1 && sym.fragment.movieFragmentSequenceNumber !== void 0 && sym.fragment.sampleNumber !== void 0 && !this.#parser.hasAuthoredTimingFor(
|
|
21607
|
+
ownerPacketId,
|
|
21608
|
+
sym.fragment.mpuSequenceNumber,
|
|
21609
|
+
sym.fragment.movieFragmentSequenceNumber,
|
|
21610
|
+
sym.fragment.sampleNumber,
|
|
21611
|
+
catalogTrackName
|
|
21612
|
+
);
|
|
21613
|
+
for (const sym of result.symbols) {
|
|
21614
|
+
if (!untimedHead(sym) || this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) continue;
|
|
21615
|
+
this.#feedViaParser(
|
|
21616
|
+
sym.rawSymbol,
|
|
21617
|
+
{ packetId: ownerPacketId, sequenceNumber: sym.packetSequenceNumber },
|
|
21618
|
+
void 0,
|
|
21619
|
+
{
|
|
21620
|
+
skipFecPayloadId: true,
|
|
21621
|
+
recovered: true,
|
|
21622
|
+
deliveryProvenance,
|
|
21623
|
+
...fecSbnOf(sym.index),
|
|
21624
|
+
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
21625
|
+
}
|
|
21626
|
+
);
|
|
21627
|
+
}
|
|
21628
|
+
result = { ...result, symbols: result.symbols.filter((sym) => !untimedHead(sym)) };
|
|
21193
21629
|
const gated = this.#isFecTrack(ownerPacketId);
|
|
21194
21630
|
const assembler = this.#mseAssemblerFor(ownerPacketId, catalogTrackName);
|
|
21195
21631
|
if (assembler) {
|