@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
package/dist/browser-runtime.js
CHANGED
|
@@ -8746,6 +8746,7 @@ var MOQT_PUBLISHER_PRIORITY = Object.freeze({
|
|
|
8746
8746
|
});
|
|
8747
8747
|
|
|
8748
8748
|
// node_modules/@blockcast/mmt-transport/dist/amt-gateway.js
|
|
8749
|
+
var MAX_PENDING_PACKETS = 256;
|
|
8749
8750
|
var AMTGatewayManager = class {
|
|
8750
8751
|
subscriptions = /* @__PURE__ */ new Map();
|
|
8751
8752
|
messageHandler;
|
|
@@ -8869,7 +8870,7 @@ var AMTGatewayManager = class {
|
|
|
8869
8870
|
if (!subscription.handshakeComplete) {
|
|
8870
8871
|
const queue = this.pendingPackets.get(subscriptionId);
|
|
8871
8872
|
const packetData2 = data;
|
|
8872
|
-
if (queue) {
|
|
8873
|
+
if (queue && queue.length < MAX_PENDING_PACKETS) {
|
|
8873
8874
|
queue.push({
|
|
8874
8875
|
data: packetData2.data,
|
|
8875
8876
|
remoteAddress: packetData2.remoteAddress,
|
|
@@ -12948,6 +12949,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
12948
12949
|
#retentionBlocks;
|
|
12949
12950
|
#heldEvicted;
|
|
12950
12951
|
#newest;
|
|
12952
|
+
#newestSource;
|
|
12951
12953
|
#holes;
|
|
12952
12954
|
static #checkedRetention(retentionBlocks) {
|
|
12953
12955
|
if (!Number.isSafeInteger(retentionBlocks) || retentionBlocks <= 0) {
|
|
@@ -12985,6 +12987,21 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
12985
12987
|
observe(position) {
|
|
12986
12988
|
if (this.#newest === void 0 || serial32Precedes(this.#newest, position)) this.#newest = position;
|
|
12987
12989
|
}
|
|
12990
|
+
/**
|
|
12991
|
+
* The newest source SS_ID noted with `noteSource` (RFC 1982 order). Unlike
|
|
12992
|
+
* `newest`, a repair never moves it: a repair can run ahead of its sources,
|
|
12993
|
+
* a source cannot run ahead of the sources sent before it except by
|
|
12994
|
+
* reordering.
|
|
12995
|
+
*/
|
|
12996
|
+
get newestSource() {
|
|
12997
|
+
return this.#newestSource;
|
|
12998
|
+
}
|
|
12999
|
+
/** Note a received source's SS_ID; true when it is the newest one yet. */
|
|
13000
|
+
noteSource(ssId) {
|
|
13001
|
+
if (this.#newestSource !== void 0 && !serial32Precedes(this.#newestSource, ssId)) return false;
|
|
13002
|
+
this.#newestSource = ssId;
|
|
13003
|
+
return true;
|
|
13004
|
+
}
|
|
12988
13005
|
/** Held sources evicted by `heldCapacity` (never by the horizon itself). */
|
|
12989
13006
|
get heldEvicted() {
|
|
12990
13007
|
return this.#heldEvicted;
|
|
@@ -13217,6 +13234,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
13217
13234
|
this.#held.clear();
|
|
13218
13235
|
this.#coverageEnd = void 0;
|
|
13219
13236
|
this.#newest = void 0;
|
|
13237
|
+
this.#newestSource = void 0;
|
|
13220
13238
|
}
|
|
13221
13239
|
#claim(block) {
|
|
13222
13240
|
const placed = [];
|
|
@@ -13836,6 +13854,7 @@ var WORKER_ERROR_LOG_CAP = 5;
|
|
|
13836
13854
|
var WORKER_ERROR_KIND_CAP = 32;
|
|
13837
13855
|
var WORKER_ERROR_OVERFLOW_KIND = "other";
|
|
13838
13856
|
var STARTUP_REPAIR_WAIT_SOURCE_BOUNDARIES = 2;
|
|
13857
|
+
var NOT_TAKEN = Object.freeze({ taken: false, recovered: null });
|
|
13839
13858
|
function isMulticastSymbolDelivery(value) {
|
|
13840
13859
|
return value === "datagram" || value === "ordered-relay";
|
|
13841
13860
|
}
|
|
@@ -13933,6 +13952,10 @@ var FECManager = class _FECManager {
|
|
|
13933
13952
|
// Source symbol count per track-scoped block — marks complete when K symbols arrive.
|
|
13934
13953
|
this.#blockSourceCounts = /* @__PURE__ */ new Map();
|
|
13935
13954
|
this.#blockSourceEsis = /* @__PURE__ */ new Map();
|
|
13955
|
+
// Source ESIs per block that another FEC instance rebuilt and fed in
|
|
13956
|
+
// (feedRebuiltSourceSymbol). Kept apart from #blockSourceEsis: they are
|
|
13957
|
+
// not network arrivals and never make a block clean.
|
|
13958
|
+
this.#blockRebuiltEsis = /* @__PURE__ */ new Map();
|
|
13936
13959
|
// Authored SSB_length per block (ISO/IEC 23008-1 §C.5.3), learned from the
|
|
13937
13960
|
// first repair symbol. The FFmpeg moq_mmt publisher closes a block every D
|
|
13938
13961
|
// media units, usually before it holds K symbols; under iso-ssbg1-v1
|
|
@@ -14222,6 +14245,7 @@ var FECManager = class _FECManager {
|
|
|
14222
14245
|
#pendingWorkerCleanupBounds;
|
|
14223
14246
|
#blockSourceCounts;
|
|
14224
14247
|
#blockSourceEsis;
|
|
14248
|
+
#blockRebuiltEsis;
|
|
14225
14249
|
#blockAuthoredLengths;
|
|
14226
14250
|
#ssbLengthConflictCount;
|
|
14227
14251
|
#blockRepairRsIds;
|
|
@@ -15544,6 +15568,7 @@ var FECManager = class _FECManager {
|
|
|
15544
15568
|
}
|
|
15545
15569
|
resetMap(this.#blockSourceCounts);
|
|
15546
15570
|
resetMap(this.#blockSourceEsis);
|
|
15571
|
+
resetMap(this.#blockRebuiltEsis);
|
|
15547
15572
|
resetMap(this.#blockAuthoredLengths);
|
|
15548
15573
|
resetMap(this.#blockRepairRsIds);
|
|
15549
15574
|
resetMap(this.#blockRsbLengths);
|
|
@@ -15668,6 +15693,7 @@ var FECManager = class _FECManager {
|
|
|
15668
15693
|
dropMap(this.#pendingWorkerCleanupClients);
|
|
15669
15694
|
dropMap(this.#blockSourceCounts);
|
|
15670
15695
|
dropMap(this.#blockSourceEsis);
|
|
15696
|
+
dropMap(this.#blockRebuiltEsis);
|
|
15671
15697
|
dropMap(this.#blockAuthoredLengths);
|
|
15672
15698
|
dropMap(this.#blockRepairRsIds);
|
|
15673
15699
|
dropMap(this.#blockRsbLengths);
|
|
@@ -15830,6 +15856,9 @@ var FECManager = class _FECManager {
|
|
|
15830
15856
|
this.#unindexBlockKey(key);
|
|
15831
15857
|
}
|
|
15832
15858
|
}
|
|
15859
|
+
if (isSourceSymbol && source === "multicast") {
|
|
15860
|
+
this.#noteMulticastSource(this.#isoLocatorFor(packetId, catalogTrackName), sbn + esi >>> 0, packetId, catalogTrackName);
|
|
15861
|
+
}
|
|
15833
15862
|
return true;
|
|
15834
15863
|
}
|
|
15835
15864
|
#observeBlockCatalogTrackName(blockKey, sbn, packetId, catalogTrackName) {
|
|
@@ -16147,6 +16176,7 @@ var FECManager = class _FECManager {
|
|
|
16147
16176
|
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
16148
16177
|
const evictedBefore = locator.heldEvicted;
|
|
16149
16178
|
const held = locator.hold(ssId, source);
|
|
16179
|
+
if (held && source.source === "multicast") this.#noteMulticastSource(locator, ssId, packetId, catalogTrackName);
|
|
16150
16180
|
const evicted = locator.heldEvicted - evictedBefore;
|
|
16151
16181
|
if (evicted > 0) {
|
|
16152
16182
|
const before = this.#isoHeldEvictedCount;
|
|
@@ -16742,7 +16772,15 @@ var FECManager = class _FECManager {
|
|
|
16742
16772
|
this.#isoSourceOversizeCount++;
|
|
16743
16773
|
return null;
|
|
16744
16774
|
}
|
|
16745
|
-
|
|
16775
|
+
return this.#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, located.block.start);
|
|
16776
|
+
}
|
|
16777
|
+
/**
|
|
16778
|
+
* Hand one placed source symbol to the stream's decoder: its Worker, which
|
|
16779
|
+
* answers through onFrame (null here), or its synchronous decoder, whose
|
|
16780
|
+
* recovered block is returned. `sbn` is the SS_Start of the block a repair
|
|
16781
|
+
* stated for `ssId`.
|
|
16782
|
+
*/
|
|
16783
|
+
#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, sbn) {
|
|
16746
16784
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
16747
16785
|
const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey);
|
|
16748
16786
|
const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName);
|
|
@@ -16771,6 +16809,74 @@ var FECManager = class _FECManager {
|
|
|
16771
16809
|
}
|
|
16772
16810
|
return recovered;
|
|
16773
16811
|
}
|
|
16812
|
+
/**
|
|
16813
|
+
* Feed a source symbol of this stream that another FEC instance over the
|
|
16814
|
+
* same packets rebuilt: a keyframe overlay recovery whose base SS_ID the
|
|
16815
|
+
* caller determined (draft-ramadan-moq-mmt-02 7.1, 7.4.4; joint decoding,
|
|
16816
|
+
* BLO-52085). It joins its block's decoder as a received source would, so
|
|
16817
|
+
* it reduces the erasures the block still has to solve, but it is not a
|
|
16818
|
+
* network arrival: no transport accounting, no classification evidence,
|
|
16819
|
+
* no deadline refresh, and it never makes a block clean.
|
|
16820
|
+
*
|
|
16821
|
+
* Only a block a repair stated takes one (a repair-less span has no
|
|
16822
|
+
* decoder block). A block that is terminal, or already holds that source,
|
|
16823
|
+
* does not. The symbol is the exact protected packet, unpadded, as
|
|
16824
|
+
* feedSourceSymbol takes it.
|
|
16825
|
+
*
|
|
16826
|
+
* `recovered` is the block the synchronous decoder completed with it; the
|
|
16827
|
+
* caller routes it and completes the block, as for any recovery. When
|
|
16828
|
+
* every source of the block is then present, received or rebuilt, and no
|
|
16829
|
+
* decoder returned it (a Worker answers asynchronously, and reports such a
|
|
16830
|
+
* block as clean on its own side), the block is completed here as
|
|
16831
|
+
* recovered, with nothing left to route.
|
|
16832
|
+
*/
|
|
16833
|
+
feedRebuiltSourceSymbol(packetId, ssId, symbolData, catalogTrackName) {
|
|
16834
|
+
if (!this.#hasBlockProfile(packetId)) return NOT_TAKEN;
|
|
16835
|
+
const located = this.#isoLocatorFor(packetId, catalogTrackName).locate(ssId);
|
|
16836
|
+
if (located === null || located.block.span) return NOT_TAKEN;
|
|
16837
|
+
if (!ssbg1FitsSymbol(symbolData.byteLength, this.#trackConfig(packetId).symbolSize)) return NOT_TAKEN;
|
|
16838
|
+
const sbn = located.block.start;
|
|
16839
|
+
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
16840
|
+
if (this.#completedBlocks.has(key) || this.#blockSourceEsis.get(key)?.has(located.esi)) return NOT_TAKEN;
|
|
16841
|
+
let rebuilt = this.#blockRebuiltEsis.get(key);
|
|
16842
|
+
if (rebuilt?.has(located.esi)) return NOT_TAKEN;
|
|
16843
|
+
if (rebuilt === void 0) {
|
|
16844
|
+
rebuilt = /* @__PURE__ */ new Set();
|
|
16845
|
+
this.#blockRebuiltEsis.set(key, rebuilt);
|
|
16846
|
+
}
|
|
16847
|
+
const config = this.#trackConfigs.get(packetId);
|
|
16848
|
+
const wasShort = config !== void 0 && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) !== void 0;
|
|
16849
|
+
rebuilt.add(located.esi);
|
|
16850
|
+
const recovered = this.#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, sbn);
|
|
16851
|
+
if (recovered === null && !this.#completeWhenSourcesPresent(key, sbn, located.block.length, packetId, catalogTrackName)) {
|
|
16852
|
+
if (wasShort && this.#activeBlocks.has(key) && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) === void 0) {
|
|
16853
|
+
this.#armBlockTimeout(sbn, packetId, catalogTrackName, this.#multicastWaitMs(config));
|
|
16854
|
+
}
|
|
16855
|
+
}
|
|
16856
|
+
return { taken: true, recovered };
|
|
16857
|
+
}
|
|
16858
|
+
/**
|
|
16859
|
+
* A block that holds every one of its `length` sources, received or
|
|
16860
|
+
* rebuilt by another instance, with at least one rebuilt, is recovered:
|
|
16861
|
+
* every packet of it was delivered, by its own arrival or by the instance
|
|
16862
|
+
* that rebuilt it, so nothing is routed. Returns whether it completed it.
|
|
16863
|
+
*/
|
|
16864
|
+
#completeWhenSourcesPresent(key, sbn, length, packetId, catalogTrackName) {
|
|
16865
|
+
const rebuilt = this.#blockRebuiltEsis.get(key);
|
|
16866
|
+
if (rebuilt === void 0 || this.#completedBlocks.has(key)) return false;
|
|
16867
|
+
let present = rebuilt.size;
|
|
16868
|
+
for (const esi of this.#blockSourceEsis.get(key) ?? []) if (!rebuilt.has(esi)) present++;
|
|
16869
|
+
if (present < length) return false;
|
|
16870
|
+
this.#traceLifecycle("recovered", sbn, packetId, catalogTrackName);
|
|
16871
|
+
this.countRecovery(packetId, sbn, catalogTrackName);
|
|
16872
|
+
this.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
16873
|
+
if (catalogTrackName === void 0) {
|
|
16874
|
+
this.onBlockComplete?.(sbn, packetId);
|
|
16875
|
+
} else {
|
|
16876
|
+
this.onBlockComplete?.(sbn, packetId, catalogTrackName);
|
|
16877
|
+
}
|
|
16878
|
+
return true;
|
|
16879
|
+
}
|
|
16774
16880
|
/**
|
|
16775
16881
|
* fec-01 4.5: a reed-solomon repair packet whose SSB_length + RSB_length
|
|
16776
16882
|
* exceeds 255, or whose RS_ID is not below RSB_length, is discarded with the
|
|
@@ -16973,8 +17079,75 @@ var FECManager = class _FECManager {
|
|
|
16973
17079
|
this.#completeCleanBlock(blockKey, sbn, packetId, catalogTrackName);
|
|
16974
17080
|
return null;
|
|
16975
17081
|
}
|
|
17082
|
+
if (recovered === null) this.#shortenUnrecoverableWait(blockKey, sbn, packetId, catalogTrackName);
|
|
16976
17083
|
return recovered;
|
|
16977
17084
|
}
|
|
17085
|
+
/**
|
|
17086
|
+
* A first-arrival multicast source, held or placed. A datagram flow keeps
|
|
17087
|
+
* its sources in send order, so one that moves the stream's newest source
|
|
17088
|
+
* position past a block leaves the sources that block still misses
|
|
17089
|
+
* reordered behind it at best: it shortens the wait of every block it
|
|
17090
|
+
* passed that only a late source can complete. MoQ is left out: its streams
|
|
17091
|
+
* can overtake one another, so a MoQ source shows nothing about the sources
|
|
17092
|
+
* sent before it.
|
|
17093
|
+
*/
|
|
17094
|
+
#noteMulticastSource(locator, ssId, packetId, catalogTrackName) {
|
|
17095
|
+
const previous = locator.newestSource;
|
|
17096
|
+
if (!locator.noteSource(ssId) || previous === void 0) return;
|
|
17097
|
+
for (const block of locator.blocksBetween(previous - 1 >>> 0, ssId)) {
|
|
17098
|
+
if (block.span || !serial32Precedes(serial32Add(block.start, block.length - 1), ssId)) continue;
|
|
17099
|
+
this.#shortenUnrecoverableWait(
|
|
17100
|
+
this.#blockKey(block.start, packetId, catalogTrackName),
|
|
17101
|
+
block.start,
|
|
17102
|
+
packetId,
|
|
17103
|
+
catalogTrackName
|
|
17104
|
+
);
|
|
17105
|
+
}
|
|
17106
|
+
}
|
|
17107
|
+
/**
|
|
17108
|
+
* The wait of a block that only a late source can complete (BLO-52085): a
|
|
17109
|
+
* repair stated it, its received sources plus every symbol its repair group
|
|
17110
|
+
* carries (RSB_length) are short of K' (the repair scheduler's
|
|
17111
|
+
* group-cannot-complete), and a later source of the stream has arrived, so
|
|
17112
|
+
* a source it misses is lost or reordered behind that one. The span, which
|
|
17113
|
+
* covers repair symbols paced after its sources, buys it nothing: it waits
|
|
17114
|
+
* the late-symbol allowance alone, the multicast wait less the span (the
|
|
17115
|
+
* catalog tolerance or the measured delivery gap). Undefined for any other
|
|
17116
|
+
* block.
|
|
17117
|
+
*/
|
|
17118
|
+
#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) {
|
|
17119
|
+
const authoredLength = this.#blockAuthoredLengths.get(key);
|
|
17120
|
+
const rsbLength = this.#blockRsbLengths.get(key);
|
|
17121
|
+
if (authoredLength === void 0 || rsbLength === void 0) return void 0;
|
|
17122
|
+
let present = this.#blockSourceCounts.get(key) ?? 0;
|
|
17123
|
+
const received = this.#blockSourceEsis.get(key);
|
|
17124
|
+
for (const esi of this.#blockRebuiltEsis.get(key) ?? []) if (!received?.has(esi)) present++;
|
|
17125
|
+
if (present + rsbLength >= authoredLength) return void 0;
|
|
17126
|
+
const newestSource = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newestSource;
|
|
17127
|
+
if (newestSource === void 0 || !serial32Precedes(serial32Add(sbn, authoredLength - 1), newestSource)) {
|
|
17128
|
+
return void 0;
|
|
17129
|
+
}
|
|
17130
|
+
return this.#multicastWaitMs(config) - config.interleaveDepthMs;
|
|
17131
|
+
}
|
|
17132
|
+
/**
|
|
17133
|
+
* Bring an open block's wait in to its late-source wait, when it has one.
|
|
17134
|
+
* Never later than it is armed: only a source can complete such a block,
|
|
17135
|
+
* so a repair of its group (paced across the span) must not keep it open.
|
|
17136
|
+
*/
|
|
17137
|
+
#shortenUnrecoverableWait(key, sbn, packetId, catalogTrackName) {
|
|
17138
|
+
if (!this.#activeBlocks.has(key)) return;
|
|
17139
|
+
const config = this.#trackConfigs.get(packetId);
|
|
17140
|
+
if (config === void 0) return;
|
|
17141
|
+
const waitMs = this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config);
|
|
17142
|
+
if (waitMs === void 0) return;
|
|
17143
|
+
const armedAtMs = this.#blockWaitDeadlinesMs.get(key);
|
|
17144
|
+
this.#armBlockTimeout(
|
|
17145
|
+
sbn,
|
|
17146
|
+
packetId,
|
|
17147
|
+
catalogTrackName,
|
|
17148
|
+
armedAtMs === void 0 ? waitMs : Math.min(waitMs, armedAtMs - this.#receiptNow())
|
|
17149
|
+
);
|
|
17150
|
+
}
|
|
16978
17151
|
/**
|
|
16979
17152
|
* Record a block's authored SSB_length from a repair symbol. Returns false,
|
|
16980
17153
|
* leaving the block unchanged, when the symbol contradicts the block: a
|
|
@@ -17129,6 +17302,15 @@ var FECManager = class _FECManager {
|
|
|
17129
17302
|
this.#completeCleanBlock(key, sbn, packetId, catalogTrackName);
|
|
17130
17303
|
return;
|
|
17131
17304
|
}
|
|
17305
|
+
if (this.#blockRebuiltEsis.has(key) && this.#completeWhenSourcesPresent(
|
|
17306
|
+
key,
|
|
17307
|
+
sbn,
|
|
17308
|
+
this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? authoredLength,
|
|
17309
|
+
packetId,
|
|
17310
|
+
catalogTrackName
|
|
17311
|
+
)) {
|
|
17312
|
+
return;
|
|
17313
|
+
}
|
|
17132
17314
|
this.noteBlockActivity(sbn, packetId, catalogTrackName, true);
|
|
17133
17315
|
}
|
|
17134
17316
|
#completeCleanBlock(key, sbn, packetId, catalogTrackName) {
|
|
@@ -17137,6 +17319,7 @@ var FECManager = class _FECManager {
|
|
|
17137
17319
|
this.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
17138
17320
|
this.#blockSourceCounts.delete(key);
|
|
17139
17321
|
this.#blockSourceEsis.delete(key);
|
|
17322
|
+
this.#blockRebuiltEsis.delete(key);
|
|
17140
17323
|
this.#blockAuthoredLengths.delete(key);
|
|
17141
17324
|
this.#blockRepairRsIds.delete(key);
|
|
17142
17325
|
this.#blockRsbLengths.delete(key);
|
|
@@ -17203,7 +17386,16 @@ var FECManager = class _FECManager {
|
|
|
17203
17386
|
this.#repairDeadlineMs(trackConfig)
|
|
17204
17387
|
);
|
|
17205
17388
|
}
|
|
17206
|
-
this.#
|
|
17389
|
+
if (!sourceProgress && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, trackConfig) !== void 0) {
|
|
17390
|
+
this.#shortenUnrecoverableWait(key, sbn, packetId, catalogTrackName);
|
|
17391
|
+
return;
|
|
17392
|
+
}
|
|
17393
|
+
this.#armBlockTimeout(
|
|
17394
|
+
sbn,
|
|
17395
|
+
packetId,
|
|
17396
|
+
catalogTrackName,
|
|
17397
|
+
this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, trackConfig) ?? timeoutMs
|
|
17398
|
+
);
|
|
17207
17399
|
}
|
|
17208
17400
|
#failBlock(sbn, packetId, catalogTrackName) {
|
|
17209
17401
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
@@ -17216,6 +17408,7 @@ var FECManager = class _FECManager {
|
|
|
17216
17408
|
}
|
|
17217
17409
|
this.#blockSourceCounts.delete(key);
|
|
17218
17410
|
this.#blockSourceEsis.delete(key);
|
|
17411
|
+
this.#blockRebuiltEsis.delete(key);
|
|
17219
17412
|
this.#blockAuthoredLengths.delete(key);
|
|
17220
17413
|
this.#blockRepairRsIds.delete(key);
|
|
17221
17414
|
this.#blockRsbLengths.delete(key);
|
|
@@ -17254,6 +17447,7 @@ var FECManager = class _FECManager {
|
|
|
17254
17447
|
this.#retireBlockClass(key, block.sbn, block.packetId, block.catalogTrackName);
|
|
17255
17448
|
this.#blockSourceCounts.delete(key);
|
|
17256
17449
|
this.#blockSourceEsis.delete(key);
|
|
17450
|
+
this.#blockRebuiltEsis.delete(key);
|
|
17257
17451
|
this.#blockAuthoredLengths.delete(key);
|
|
17258
17452
|
this.#blockRepairRsIds.delete(key);
|
|
17259
17453
|
this.#blockRsbLengths.delete(key);
|
|
@@ -17345,6 +17539,7 @@ var FECManager = class _FECManager {
|
|
|
17345
17539
|
this.#blockKeysBySbn.clear();
|
|
17346
17540
|
this.#blockSourceCounts.clear();
|
|
17347
17541
|
this.#blockSourceEsis.clear();
|
|
17542
|
+
this.#blockRebuiltEsis.clear();
|
|
17348
17543
|
this.#blockAuthoredLengths.clear();
|
|
17349
17544
|
this.#blockRepairRsIds.clear();
|
|
17350
17545
|
this.#blockRsbLengths.clear();
|
|
@@ -17901,9 +18096,21 @@ var IsoBmffTimingResolver = class {
|
|
|
17901
18096
|
}
|
|
17902
18097
|
#track = null;
|
|
17903
18098
|
#fragments = /* @__PURE__ */ new Map();
|
|
18099
|
+
/** First sample of each pending fragment, kept in `#delivered` with its sample count. */
|
|
18100
|
+
#firstSamples = /* @__PURE__ */ new Map();
|
|
18101
|
+
/**
|
|
18102
|
+
* First sample and sample count of the newest fragments whose every slot was
|
|
18103
|
+
* cleared. Abandonment counts: `releaseSample` clears a slot too, which
|
|
18104
|
+
* matches the combiner, since it retires an abandoned sample as well.
|
|
18105
|
+
*/
|
|
18106
|
+
#delivered = /* @__PURE__ */ new Map();
|
|
17904
18107
|
#pendingSamples = 0;
|
|
17905
18108
|
#evictedSamples = 0;
|
|
17906
18109
|
#lastMovieFragmentRejection = null;
|
|
18110
|
+
/** True once an FT=0 has bound the track that FT=1 timing is read against. */
|
|
18111
|
+
get initialized() {
|
|
18112
|
+
return this.#track !== null;
|
|
18113
|
+
}
|
|
17907
18114
|
get pendingSampleCount() {
|
|
17908
18115
|
return this.#pendingSamples;
|
|
17909
18116
|
}
|
|
@@ -17927,7 +18134,14 @@ var IsoBmffTimingResolver = class {
|
|
|
17927
18134
|
this.#track = timing;
|
|
17928
18135
|
return true;
|
|
17929
18136
|
}
|
|
17930
|
-
|
|
18137
|
+
/**
|
|
18138
|
+
* `recovered` marks an FEC-recovered copy. A recovered block hands back
|
|
18139
|
+
* every FT=1 it holds, the received ones included, so a recovered copy of a
|
|
18140
|
+
* fragment whose samples are all done is a duplicate: admitting it again
|
|
18141
|
+
* would charge stale timing and could displace the live MPU's. A source
|
|
18142
|
+
* copy is still admitted again (a rendition switch re-arms timing that way).
|
|
18143
|
+
*/
|
|
18144
|
+
addMovieFragment(mpuSequenceNumber2, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
|
|
17931
18145
|
this.#lastMovieFragmentRejection = null;
|
|
17932
18146
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
17933
18147
|
this.#lastMovieFragmentRejection = "invalid";
|
|
@@ -17953,6 +18167,8 @@ var IsoBmffTimingResolver = class {
|
|
|
17953
18167
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
17954
18168
|
);
|
|
17955
18169
|
}
|
|
18170
|
+
const delivered = recovered ? this.#delivered.get(key) : void 0;
|
|
18171
|
+
if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
|
|
17956
18172
|
let freed = 0;
|
|
17957
18173
|
while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
17958
18174
|
freed += this.#evictOldestFragment();
|
|
@@ -17966,6 +18182,7 @@ var IsoBmffTimingResolver = class {
|
|
|
17966
18182
|
return false;
|
|
17967
18183
|
}
|
|
17968
18184
|
this.#fragments.set(key, [...parsed.samples]);
|
|
18185
|
+
this.#firstSamples.set(key, parsed.samples[0]);
|
|
17969
18186
|
this.#pendingSamples += parsed.samples.length;
|
|
17970
18187
|
return true;
|
|
17971
18188
|
}
|
|
@@ -18023,6 +18240,7 @@ var IsoBmffTimingResolver = class {
|
|
|
18023
18240
|
let live = 0;
|
|
18024
18241
|
for (const sample of samples) if (sample !== null) live++;
|
|
18025
18242
|
this.#fragments.delete(oldest);
|
|
18243
|
+
this.#firstSamples.delete(oldest);
|
|
18026
18244
|
this.#pendingSamples -= live;
|
|
18027
18245
|
this.#evictedSamples += live;
|
|
18028
18246
|
return live;
|
|
@@ -18038,12 +18256,23 @@ var IsoBmffTimingResolver = class {
|
|
|
18038
18256
|
if (!samples || !samples[index]) return false;
|
|
18039
18257
|
samples[index] = null;
|
|
18040
18258
|
this.#pendingSamples--;
|
|
18041
|
-
if (samples.every((value) => value === null))
|
|
18259
|
+
if (samples.every((value) => value === null)) {
|
|
18260
|
+
this.#fragments.delete(key);
|
|
18261
|
+
const first = this.#firstSamples.get(key);
|
|
18262
|
+
this.#firstSamples.delete(key);
|
|
18263
|
+
this.#delivered.delete(key);
|
|
18264
|
+
this.#delivered.set(key, { first, sampleCount: samples.length });
|
|
18265
|
+
if (this.#delivered.size > this.maxPendingSamples) {
|
|
18266
|
+
this.#delivered.delete(this.#delivered.keys().next().value);
|
|
18267
|
+
}
|
|
18268
|
+
}
|
|
18042
18269
|
return true;
|
|
18043
18270
|
}
|
|
18044
18271
|
reset() {
|
|
18045
18272
|
this.#track = null;
|
|
18046
18273
|
this.#fragments.clear();
|
|
18274
|
+
this.#firstSamples.clear();
|
|
18275
|
+
this.#delivered.clear();
|
|
18047
18276
|
this.#pendingSamples = 0;
|
|
18048
18277
|
}
|
|
18049
18278
|
};
|
|
@@ -18525,6 +18754,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18525
18754
|
this.#reclaimedTimingSamples = 0;
|
|
18526
18755
|
this.#evictedTimingSamples = 0;
|
|
18527
18756
|
this.#rejectedMovieFragments = 0;
|
|
18757
|
+
this.#recoveredHeadsAwaitingTiming = {
|
|
18758
|
+
held: 0,
|
|
18759
|
+
expired: 0,
|
|
18760
|
+
discarded: 0,
|
|
18761
|
+
superseded: 0
|
|
18762
|
+
};
|
|
18528
18763
|
this.#sequencers = /* @__PURE__ */ new Map();
|
|
18529
18764
|
this.#fecGate = null;
|
|
18530
18765
|
this.#fecSequencer = null;
|
|
@@ -18633,6 +18868,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18633
18868
|
#reclaimedTimingSamples;
|
|
18634
18869
|
#evictedTimingSamples;
|
|
18635
18870
|
#rejectedMovieFragments;
|
|
18871
|
+
#recoveredHeadsAwaitingTiming;
|
|
18636
18872
|
#sequencers;
|
|
18637
18873
|
#fecGate;
|
|
18638
18874
|
#fecSequencer;
|
|
@@ -18753,6 +18989,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18753
18989
|
rejectedMovieFragments: this.#rejectedMovieFragments
|
|
18754
18990
|
};
|
|
18755
18991
|
}
|
|
18992
|
+
/** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
|
|
18993
|
+
get recoveredHeadsAwaitingTiming() {
|
|
18994
|
+
let pending = 0;
|
|
18995
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
18996
|
+
for (const head of awaitingTiming) if (head.options?.recovered) pending++;
|
|
18997
|
+
}
|
|
18998
|
+
return { ...this.#recoveredHeadsAwaitingTiming, pending };
|
|
18999
|
+
}
|
|
18756
19000
|
get reorderStats() {
|
|
18757
19001
|
let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
|
|
18758
19002
|
for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
|
|
@@ -18926,7 +19170,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18926
19170
|
const now = Date.now();
|
|
18927
19171
|
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
18928
19172
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
18929
|
-
awaitingTiming.shift();
|
|
19173
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
18930
19174
|
}
|
|
18931
19175
|
}
|
|
18932
19176
|
}
|
|
@@ -19044,11 +19288,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19044
19288
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
19045
19289
|
return this.#drop("fragment-unexpected-fragmentation");
|
|
19046
19290
|
const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
|
|
19047
|
-
if (!resolver2) return this.#drop("fragment-no-resolver");
|
|
19291
|
+
if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
|
|
19048
19292
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
19049
19293
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
19050
19294
|
const remaining = this.#maxPendingTimingSamples - this.#pendingTimingSamples;
|
|
19051
|
-
const admitted = resolver2.addMovieFragment(
|
|
19295
|
+
const admitted = resolver2.addMovieFragment(
|
|
19296
|
+
mpu.mpuSequenceNumber,
|
|
19297
|
+
data,
|
|
19298
|
+
remaining,
|
|
19299
|
+
options?.recovered === true
|
|
19300
|
+
);
|
|
19052
19301
|
this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
|
|
19053
19302
|
this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
|
|
19054
19303
|
if (!admitted) {
|
|
@@ -19068,7 +19317,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19068
19317
|
};
|
|
19069
19318
|
}
|
|
19070
19319
|
if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
|
|
19071
|
-
const resolver = this.#timingResolver(streamKey, info.packetId,
|
|
19320
|
+
const resolver = this.#timingResolver(streamKey, info.packetId, true);
|
|
19072
19321
|
if (!resolver) return this.#drop("mfu-no-resolver");
|
|
19073
19322
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
19074
19323
|
return this.#drop("mfu-empty-payload");
|
|
@@ -19089,6 +19338,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19089
19338
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
19090
19339
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
19091
19340
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
19341
|
+
sequenceNumber: info.sequenceNumber,
|
|
19092
19342
|
arrivedAt: Date.now()
|
|
19093
19343
|
});
|
|
19094
19344
|
return this.#drop("mfu-sample-timing-missing");
|
|
@@ -19477,6 +19727,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19477
19727
|
for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
|
|
19478
19728
|
this.#sequencers.clear();
|
|
19479
19729
|
this.#fecSequencer?.discard();
|
|
19730
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
19731
|
+
for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
19732
|
+
}
|
|
19480
19733
|
this.#timingStreams.clear();
|
|
19481
19734
|
this.#pendingTimingSamples = 0;
|
|
19482
19735
|
this.#reliableMoqStartupGate.clear();
|
|
@@ -19495,10 +19748,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19495
19748
|
return resolver;
|
|
19496
19749
|
}
|
|
19497
19750
|
#awaitTiming(streamKey, head) {
|
|
19498
|
-
const
|
|
19499
|
-
if (!
|
|
19751
|
+
const state = this.#timingStreams.get(streamKey);
|
|
19752
|
+
if (!state) return;
|
|
19753
|
+
if (!head.options?.recovered) {
|
|
19754
|
+
const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
|
|
19755
|
+
this.#recoveredHeadsAwaitingTiming.superseded += state.awaitingTiming.length - kept.length;
|
|
19756
|
+
state.awaitingTiming = kept;
|
|
19757
|
+
}
|
|
19758
|
+
const awaitingTiming = state.awaitingTiming;
|
|
19500
19759
|
awaitingTiming.push(head);
|
|
19501
|
-
if (
|
|
19760
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming.held++;
|
|
19761
|
+
if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
|
|
19762
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
19763
|
+
}
|
|
19764
|
+
}
|
|
19765
|
+
/** Count a recovered head leaving the queue other than by replay or its source copy. */
|
|
19766
|
+
#leaveAwaitingTiming(head, outcome) {
|
|
19767
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming[outcome]++;
|
|
19502
19768
|
}
|
|
19503
19769
|
/**
|
|
19504
19770
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -19526,6 +19792,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19526
19792
|
const state = this.#timingStreams.get(streamKey);
|
|
19527
19793
|
if (!state) return;
|
|
19528
19794
|
this.#timingStreams.delete(streamKey);
|
|
19795
|
+
for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
19529
19796
|
this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
|
|
19530
19797
|
if (this.#pendingTimingSamples < 0) {
|
|
19531
19798
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
@@ -22732,6 +22999,16 @@ function keyframeOverlaySsId(header) {
|
|
|
22732
22999
|
if (ext?.type !== KEYFRAME_OVERLAY_EXTENSION_TYPE || ext.data.length !== 4) return void 0;
|
|
22733
23000
|
return new DataView(ext.data.buffer, ext.data.byteOffset, 4).getUint32(0);
|
|
22734
23001
|
}
|
|
23002
|
+
function overlayBaseSsId(anchor, overlaySsId, restoredPsn, headerPsn) {
|
|
23003
|
+
if (anchor.baseSsId === void 0 || headerPsn !== restoredPsn) return void 0;
|
|
23004
|
+
return serial32Add(anchor.baseSsId, overlaySsId - anchor.overlaySsId | 0);
|
|
23005
|
+
}
|
|
23006
|
+
function unstampedBaseSsId(offset, unstampedOffset, headerPsn) {
|
|
23007
|
+
if (offset === void 0 || offset !== unstampedOffset) return void 0;
|
|
23008
|
+
return headerPsn + offset >>> 0;
|
|
23009
|
+
}
|
|
23010
|
+
var OVERLAY_ROUTED_LIMIT = 1024;
|
|
23011
|
+
var OVERLAY_PENDING_LIMIT = 256;
|
|
22735
23012
|
var OVERLAY_OFFSET_MPUS = 16;
|
|
22736
23013
|
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
22737
23014
|
function assertStreamIdentity(role, stream) {
|
|
@@ -22843,6 +23120,7 @@ var MmtpRouter = class {
|
|
|
22843
23120
|
#provenance = /* @__PURE__ */ new Map();
|
|
22844
23121
|
#onFlowProtected = null;
|
|
22845
23122
|
#unadmittedListedDrops = 0;
|
|
23123
|
+
#admittedSourceDatagrams = 0;
|
|
22846
23124
|
#unlistedRepairSetAside = 0;
|
|
22847
23125
|
#assistBlocks = /* @__PURE__ */ new Map();
|
|
22848
23126
|
#assistFailures = /* @__PURE__ */ new Map();
|
|
@@ -22899,6 +23177,15 @@ var MmtpRouter = class {
|
|
|
22899
23177
|
* sequence is its SS_ID minus this; see #routeProcessedFecSymbols.
|
|
22900
23178
|
*/
|
|
22901
23179
|
#ssIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
23180
|
+
/**
|
|
23181
|
+
* Per stream, the SS_ID offset at which a base recovery last showed the
|
|
23182
|
+
* sender's own packet_sequence_number: a recovered packet's header psn
|
|
23183
|
+
* equal to its SS_ID minus that offset. While it equals the stream's
|
|
23184
|
+
* current offset, no hop re-stamps the stream, and a packet's protected psn
|
|
23185
|
+
* names its base SS_ID within unstampedBaseSsId's limits (joint decoding of
|
|
23186
|
+
* an MPU no member of which arrived).
|
|
23187
|
+
*/
|
|
23188
|
+
#unstampedSsIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
22902
23189
|
/**
|
|
22903
23190
|
* Streams whose multicast leg delivered a RAP-flagged source packet that was
|
|
22904
23191
|
* not parsed, typically because its overlap copy was parsed first
|
|
@@ -22968,7 +23255,9 @@ var MmtpRouter = class {
|
|
|
22968
23255
|
recovered: 0,
|
|
22969
23256
|
restarts: 0,
|
|
22970
23257
|
unsequencedDropped: 0,
|
|
22971
|
-
protectedDropped: 0
|
|
23258
|
+
protectedDropped: 0,
|
|
23259
|
+
baseFed: 0,
|
|
23260
|
+
baseUndetermined: 0
|
|
22972
23261
|
};
|
|
22973
23262
|
/** Callback fired when a delta-track FEC block completes or fails. */
|
|
22974
23263
|
#onDeltaBlockCompleteCb = null;
|
|
@@ -23141,7 +23430,7 @@ var MmtpRouter = class {
|
|
|
23141
23430
|
const cb = this.#getFrameCallback(packetId);
|
|
23142
23431
|
if (isVideo && !assembler) {
|
|
23143
23432
|
if (frameMeta.recovered) {
|
|
23144
|
-
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb);
|
|
23433
|
+
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb, meta.fecSbn);
|
|
23145
23434
|
} else {
|
|
23146
23435
|
this.#routeDirectSourceVideo(packetId, data, frameMeta, cb);
|
|
23147
23436
|
}
|
|
@@ -23531,6 +23820,10 @@ var MmtpRouter = class {
|
|
|
23531
23820
|
get keyframeOverlayStats() {
|
|
23532
23821
|
return { ...this.#overlayStats };
|
|
23533
23822
|
}
|
|
23823
|
+
/** Recovered FT=2 heads held for their FT=1, and how each left the wait (BLO-53369). */
|
|
23824
|
+
get recoveredHeadsAwaitingTiming() {
|
|
23825
|
+
return this.#parser.recoveredHeadsAwaitingTiming;
|
|
23826
|
+
}
|
|
23534
23827
|
#isOverlayRepair(repairPacketId, repairTrackName) {
|
|
23535
23828
|
return this.#overlayLegs.has(repairPacketId) || repairTrackName !== void 0 && this.#overlayRepairNames.has(repairTrackName);
|
|
23536
23829
|
}
|
|
@@ -23581,6 +23874,11 @@ var MmtpRouter = class {
|
|
|
23581
23874
|
*/
|
|
23582
23875
|
resetKeyframeOverlays(sourcePacketId, catalogTrackName) {
|
|
23583
23876
|
for (const inst of this.#overlaysForSource(sourcePacketId, catalogTrackName)) this.#resetOverlay(inst);
|
|
23877
|
+
for (const [name, stream] of this.#streams.get(sourcePacketId) ?? []) {
|
|
23878
|
+
if (catalogTrackName !== void 0 && name !== catalogTrackName) continue;
|
|
23879
|
+
stream.overlayToBase.routed.clear();
|
|
23880
|
+
stream.overlayToBase.pending.clear();
|
|
23881
|
+
}
|
|
23584
23882
|
}
|
|
23585
23883
|
#resetOverlay(inst) {
|
|
23586
23884
|
inst.fec.resetTrack(inst.repairPacketId, inst.streamName);
|
|
@@ -23637,9 +23935,10 @@ var MmtpRouter = class {
|
|
|
23637
23935
|
* A source packet that carries a 0x8B01 overlay SS_ID is also a symbol of
|
|
23638
23936
|
* every overlay installed for its source, at that SS_ID, with the bytes the
|
|
23639
23937
|
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
23640
|
-
* included (7.1, 7.4.3).
|
|
23938
|
+
* included (7.1, 7.4.3). `baseSsId` is that trailer's SS_ID, when the base
|
|
23939
|
+
* FEC reads it.
|
|
23641
23940
|
*/
|
|
23642
|
-
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source) {
|
|
23941
|
+
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source, baseSsId) {
|
|
23643
23942
|
if (this.#overlays.size === 0) return;
|
|
23644
23943
|
const ssId = keyframeOverlaySsId(header);
|
|
23645
23944
|
if (ssId === void 0) return;
|
|
@@ -23648,7 +23947,11 @@ var MmtpRouter = class {
|
|
|
23648
23947
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
23649
23948
|
if (identity !== null) {
|
|
23650
23949
|
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
23651
|
-
inst.offsets.set(identity.mpuSequenceNumber,
|
|
23950
|
+
inst.offsets.set(identity.mpuSequenceNumber, {
|
|
23951
|
+
psnOffset: ssId - sequenceNumber >>> 0,
|
|
23952
|
+
overlaySsId: ssId,
|
|
23953
|
+
...baseSsId === void 0 ? {} : { baseSsId }
|
|
23954
|
+
});
|
|
23652
23955
|
if (inst.offsets.size > OVERLAY_OFFSET_MPUS) inst.offsets.delete(inst.offsets.keys().next().value);
|
|
23653
23956
|
}
|
|
23654
23957
|
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
@@ -23727,14 +24030,27 @@ var MmtpRouter = class {
|
|
|
23727
24030
|
const streamKey = this.#mediaStreamKey(sourcePacketId, sourceName);
|
|
23728
24031
|
const restamped = this.#restampedStreams.has(streamKey);
|
|
23729
24032
|
const restored = /* @__PURE__ */ new Set();
|
|
24033
|
+
const baseSsIds = /* @__PURE__ */ new Map();
|
|
23730
24034
|
const symbols = result.symbols.flatMap((sym) => {
|
|
23731
|
-
const
|
|
23732
|
-
if (
|
|
23733
|
-
const
|
|
24035
|
+
const anchor = inst.offsets.get(sym.fragment.mpuSequenceNumber);
|
|
24036
|
+
if (anchor !== void 0) {
|
|
24037
|
+
const overlaySsId = serial32Add(blockStart, sym.index);
|
|
24038
|
+
const packetSequenceNumber = overlaySsId - anchor.psnOffset >>> 0;
|
|
23734
24039
|
restored.add(packetSequenceNumber);
|
|
23735
|
-
|
|
24040
|
+
const out = { ...sym, packetSequenceNumber };
|
|
24041
|
+
const baseSsId = overlayBaseSsId(anchor, overlaySsId, packetSequenceNumber, sym.packetSequenceNumber);
|
|
24042
|
+
if (baseSsId !== void 0) baseSsIds.set(out, baseSsId);
|
|
24043
|
+
return [out];
|
|
24044
|
+
}
|
|
24045
|
+
if (!restamped) {
|
|
24046
|
+
const baseSsId = unstampedBaseSsId(
|
|
24047
|
+
this.#ssIdSeqOffsetByStream.get(streamKey),
|
|
24048
|
+
this.#unstampedSsIdSeqOffsetByStream.get(streamKey),
|
|
24049
|
+
sym.packetSequenceNumber
|
|
24050
|
+
);
|
|
24051
|
+
if (baseSsId !== void 0) baseSsIds.set(sym, baseSsId);
|
|
24052
|
+
return [sym];
|
|
23736
24053
|
}
|
|
23737
|
-
if (!restamped) return [sym];
|
|
23738
24054
|
this.#overlayStats.unsequencedDropped++;
|
|
23739
24055
|
return [];
|
|
23740
24056
|
});
|
|
@@ -23743,6 +24059,85 @@ var MmtpRouter = class {
|
|
|
23743
24059
|
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
23744
24060
|
});
|
|
23745
24061
|
this.#noteOverlayRecoveredRaps(sourcePacketId, sourceName, streamKey, symbols.filter((sym) => restored.has(sym.packetSequenceNumber)));
|
|
24062
|
+
this.#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds);
|
|
24063
|
+
}
|
|
24064
|
+
/**
|
|
24065
|
+
* Joint decoding (BLO-52085). A packet the overlay rebuilt is a source of
|
|
24066
|
+
* its base block too, once its base SS_ID is determined (overlayBaseSsId,
|
|
24067
|
+
* from a received member of its MPU, else unstampedBaseSsId),
|
|
24068
|
+
* so it is fed to the base decoder as a rebuilt source
|
|
24069
|
+
* (FECManager.feedRebuiltSourceSymbol): the base block has one erasure
|
|
24070
|
+
* fewer to solve, and its repair can recover the block's other packets.
|
|
24071
|
+
* A base block no repair has stated yet takes it when one does
|
|
24072
|
+
* (#feedPendingRebuilt).
|
|
24073
|
+
*
|
|
24074
|
+
* The overlay route already handled each packet fed (delivered it, or
|
|
24075
|
+
* held an untimed head for its FT=1), so a base recovery routes none of
|
|
24076
|
+
* them again (#routeVerifiedFecSymbols): a second recovered copy of a
|
|
24077
|
+
* held head would wait in the parser for timing its first copy consumed.
|
|
24078
|
+
* Only an overlay block decoded from multicast symbols alone is fed: with
|
|
24079
|
+
* a MoQ symbol in it, the base block's recovery would depend on bytes
|
|
24080
|
+
* that crossed no multicast socket, which the provenance counted from the
|
|
24081
|
+
* block's own arrivals cannot show.
|
|
24082
|
+
*/
|
|
24083
|
+
#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds) {
|
|
24084
|
+
const fec = this.#fecManager;
|
|
24085
|
+
const owner = inst.leg.sourcePacketId;
|
|
24086
|
+
const name = inst.leg.sourceCatalogTrackName;
|
|
24087
|
+
if (!fec?.feedRebuiltSourceSymbol || inst.moqBlocks.has(blockStart)) return;
|
|
24088
|
+
if (this.#classifyPacketId(owner) === null || fec.isTrackConfigured?.(owner) === false) return;
|
|
24089
|
+
const { duplicates, overlayToBase: { routed } } = this.#stream(owner, name);
|
|
24090
|
+
const feeds = [];
|
|
24091
|
+
for (const sym of symbols) {
|
|
24092
|
+
if (duplicates.sourceSeqs.has(sym.packetSequenceNumber)) continue;
|
|
24093
|
+
const ssId = baseSsIds.get(sym);
|
|
24094
|
+
if (ssId === void 0) {
|
|
24095
|
+
this.#overlayStats.baseUndetermined++;
|
|
24096
|
+
continue;
|
|
24097
|
+
}
|
|
24098
|
+
routed.delete(ssId);
|
|
24099
|
+
routed.add(ssId);
|
|
24100
|
+
if (routed.size > OVERLAY_ROUTED_LIMIT) routed.delete(routed.values().next().value);
|
|
24101
|
+
feeds.push([ssId, sym.rawSymbol]);
|
|
24102
|
+
}
|
|
24103
|
+
for (const [ssId, symbolData] of feeds) {
|
|
24104
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
24105
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
24106
|
+
}
|
|
24107
|
+
}
|
|
24108
|
+
/** Feed one rebuilt source to its base block, or keep it until a repair states that block. */
|
|
24109
|
+
#feedRebuiltToBase(owner, name, ssId, symbolData) {
|
|
24110
|
+
const fec = this.#fecManager;
|
|
24111
|
+
if (!fec?.feedRebuiltSourceSymbol) return;
|
|
24112
|
+
const coords = fec.locateIsoSource?.(owner, name, ssId);
|
|
24113
|
+
if (coords == null) {
|
|
24114
|
+
const pending = this.#stream(owner, name).overlayToBase.pending;
|
|
24115
|
+
pending.delete(ssId);
|
|
24116
|
+
pending.set(ssId, symbolData.slice());
|
|
24117
|
+
if (pending.size > OVERLAY_PENDING_LIMIT) pending.delete(pending.keys().next().value);
|
|
24118
|
+
return;
|
|
24119
|
+
}
|
|
24120
|
+
const fed = fec.feedRebuiltSourceSymbol(owner, ssId, symbolData, name);
|
|
24121
|
+
if (!fed.taken) return;
|
|
24122
|
+
this.#overlayStats.baseFed++;
|
|
24123
|
+
if (fed.recovered === null) return;
|
|
24124
|
+
const sbn = coords.sbn;
|
|
24125
|
+
this.#routeRecoveredBlock(sbn, fed.recovered, owner, name, () => {
|
|
24126
|
+
fec.cancelBlockTimeout(sbn, owner, name);
|
|
24127
|
+
fec.countRecovery(owner, sbn, name);
|
|
24128
|
+
fec.markBlockCompleted(sbn, owner, name);
|
|
24129
|
+
});
|
|
24130
|
+
}
|
|
24131
|
+
/** A repair stated `block`: the rebuilt sources that waited for it join it now. */
|
|
24132
|
+
#feedPendingRebuilt(owner, name, block) {
|
|
24133
|
+
const pending = this.#streams.get(owner)?.get(name)?.overlayToBase.pending;
|
|
24134
|
+
if (pending === void 0 || pending.size === 0) return;
|
|
24135
|
+
for (const [ssId, symbolData] of [...pending]) {
|
|
24136
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
24137
|
+
if (serial32Distance(block.start, ssId) >= block.length) continue;
|
|
24138
|
+
pending.delete(ssId);
|
|
24139
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
24140
|
+
}
|
|
23746
24141
|
}
|
|
23747
24142
|
/** fec-multicast-00 §6.6: report each base block whose RAP MFU fragments
|
|
23748
24143
|
* the overlay restored, when the keyframe's FT=0 and FT=1 are held too. A
|
|
@@ -23882,6 +24277,10 @@ var MmtpRouter = class {
|
|
|
23882
24277
|
get unadmittedListedDropCount() {
|
|
23883
24278
|
return this.#unadmittedListedDrops;
|
|
23884
24279
|
}
|
|
24280
|
+
/** 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. */
|
|
24281
|
+
get admittedSourceDatagramCount() {
|
|
24282
|
+
return this.#admittedSourceDatagrams;
|
|
24283
|
+
}
|
|
23885
24284
|
/** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
|
|
23886
24285
|
get unlistedRepairSetAsideCount() {
|
|
23887
24286
|
return this.#unlistedRepairSetAside;
|
|
@@ -24368,6 +24767,8 @@ var MmtpRouter = class {
|
|
|
24368
24767
|
)) return;
|
|
24369
24768
|
fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);
|
|
24370
24769
|
fec.noteRepairGroupLength?.(blockStart, ownerPacketId, repair.rsbLength, catalogTrackName);
|
|
24770
|
+
this.#feedPendingRebuilt(ownerPacketId, catalogTrackName, placement.block);
|
|
24771
|
+
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
24371
24772
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
24372
24773
|
const recovered = fec.feedRepairSymbol(
|
|
24373
24774
|
ownerPacketId,
|
|
@@ -24505,6 +24906,7 @@ var MmtpRouter = class {
|
|
|
24505
24906
|
this.#droppedPreSyncVideo.delete(streamKey);
|
|
24506
24907
|
this.#latestVideoSeqByStream.delete(streamKey);
|
|
24507
24908
|
this.#ssIdSeqOffsetByStream.delete(streamKey);
|
|
24909
|
+
this.#unstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
24508
24910
|
this.#multicastRapTwinByStream.delete(streamKey);
|
|
24509
24911
|
this.#lastDirectSourceVideoTsByStream.delete(streamKey);
|
|
24510
24912
|
this.#lastDirectEmittedVideoTsByStream.delete(streamKey);
|
|
@@ -25139,6 +25541,7 @@ var MmtpRouter = class {
|
|
|
25139
25541
|
const parsed = this.#parser.parsePacket(packet);
|
|
25140
25542
|
if (!parsed) return;
|
|
25141
25543
|
const info = parsed.info;
|
|
25544
|
+
if (manifestAdmitted && !info.isRepair && ingress?.admittedBy !== USER_AGENT_VERIFIED) this.#admittedSourceDatagrams++;
|
|
25142
25545
|
if (source === "multicast" && !manifestAdmitted) {
|
|
25143
25546
|
this.#fecManager?.noteMulticastArrival?.(info.packetId, info.sequenceNumber);
|
|
25144
25547
|
}
|
|
@@ -25420,7 +25823,8 @@ var MmtpRouter = class {
|
|
|
25420
25823
|
parsed.header,
|
|
25421
25824
|
info.sequenceNumber,
|
|
25422
25825
|
info.rapFlag,
|
|
25423
|
-
source
|
|
25826
|
+
source,
|
|
25827
|
+
fecPayloadId?.ssId
|
|
25424
25828
|
);
|
|
25425
25829
|
}
|
|
25426
25830
|
}
|
|
@@ -25570,7 +25974,8 @@ var MmtpRouter = class {
|
|
|
25570
25974
|
if (stream === void 0) {
|
|
25571
25975
|
stream = {
|
|
25572
25976
|
key: JSON.stringify([packetId, catalogTrackName ?? null]),
|
|
25573
|
-
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId))
|
|
25977
|
+
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId)),
|
|
25978
|
+
overlayToBase: { routed: /* @__PURE__ */ new Set(), pending: /* @__PURE__ */ new Map() }
|
|
25574
25979
|
};
|
|
25575
25980
|
byName.set(catalogTrackName, stream);
|
|
25576
25981
|
}
|
|
@@ -25685,8 +26090,16 @@ var MmtpRouter = class {
|
|
|
25685
26090
|
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
25686
26091
|
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
25687
26092
|
}
|
|
25688
|
-
#routeDirectRecoveredVideo(packetId, data, meta, cb) {
|
|
26093
|
+
#routeDirectRecoveredVideo(packetId, data, meta, cb, fecSbn) {
|
|
25689
26094
|
const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);
|
|
26095
|
+
if (this.#isFecTrack(packetId)) {
|
|
26096
|
+
const high = this.#deliveredVideoHighWaterByStream.get(streamKey);
|
|
26097
|
+
if (fecSbn !== void 0 || high !== void 0 && authoredAdjacent(high, meta)) {
|
|
26098
|
+
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
26099
|
+
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
26100
|
+
return;
|
|
26101
|
+
}
|
|
26102
|
+
}
|
|
25690
26103
|
this.#enqueueDirectRecoveredVideo(packetId, data, meta);
|
|
25691
26104
|
const lastSourceTs = this.#lastDirectSourceVideoTsByStream.get(streamKey);
|
|
25692
26105
|
this.#drainDirectRecoveredVideo(
|
|
@@ -25840,6 +26253,13 @@ var MmtpRouter = class {
|
|
|
25840
26253
|
* frames by epoch only if pre-protection input ever matters.
|
|
25841
26254
|
*/
|
|
25842
26255
|
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
26256
|
+
const overlayRouted = ownerPacketId == null ? void 0 : this.#streams.get(ownerPacketId)?.get(catalogTrackName)?.overlayToBase.routed;
|
|
26257
|
+
if (overlayRouted !== void 0 && overlayRouted.size > 0) {
|
|
26258
|
+
result = {
|
|
26259
|
+
...result,
|
|
26260
|
+
symbols: result.symbols.filter((sym) => !overlayRouted.has(this.#fecAnchorForRecovered(blockId, sym.index)))
|
|
26261
|
+
};
|
|
26262
|
+
}
|
|
25843
26263
|
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
25844
26264
|
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
25845
26265
|
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
@@ -25904,7 +26324,10 @@ var MmtpRouter = class {
|
|
|
25904
26324
|
...result,
|
|
25905
26325
|
symbols: result.symbols.map((sym) => {
|
|
25906
26326
|
const wire = this.#fecAnchorForRecovered(blockId, sym.index) - ssIdSeqOffset >>> 0;
|
|
25907
|
-
if (wire === sym.packetSequenceNumber)
|
|
26327
|
+
if (wire === sym.packetSequenceNumber) {
|
|
26328
|
+
this.#unstampedSsIdSeqOffsetByStream.set(streamKey, ssIdSeqOffset);
|
|
26329
|
+
return sym;
|
|
26330
|
+
}
|
|
25908
26331
|
this.#restampedRecoveredSeqs++;
|
|
25909
26332
|
this.#restampedStreams.add(streamKey);
|
|
25910
26333
|
return { ...sym, packetSequenceNumber: wire };
|
|
@@ -25940,16 +26363,29 @@ var MmtpRouter = class {
|
|
|
25940
26363
|
}
|
|
25941
26364
|
);
|
|
25942
26365
|
}
|
|
25943
|
-
|
|
25944
|
-
|
|
25945
|
-
|
|
25946
|
-
|
|
25947
|
-
|
|
25948
|
-
|
|
25949
|
-
|
|
25950
|
-
|
|
25951
|
-
))
|
|
25952
|
-
|
|
26366
|
+
const untimedHead = (sym) => sym.fragmentType === 2 && sym.fragment.fragmentationIndicator === 1 && sym.fragment.movieFragmentSequenceNumber !== void 0 && sym.fragment.sampleNumber !== void 0 && !this.#parser.hasAuthoredTimingFor(
|
|
26367
|
+
ownerPacketId,
|
|
26368
|
+
sym.fragment.mpuSequenceNumber,
|
|
26369
|
+
sym.fragment.movieFragmentSequenceNumber,
|
|
26370
|
+
sym.fragment.sampleNumber,
|
|
26371
|
+
catalogTrackName
|
|
26372
|
+
);
|
|
26373
|
+
for (const sym of result.symbols) {
|
|
26374
|
+
if (!untimedHead(sym) || this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) continue;
|
|
26375
|
+
this.#feedViaParser(
|
|
26376
|
+
sym.rawSymbol,
|
|
26377
|
+
{ packetId: ownerPacketId, sequenceNumber: sym.packetSequenceNumber },
|
|
26378
|
+
void 0,
|
|
26379
|
+
{
|
|
26380
|
+
skipFecPayloadId: true,
|
|
26381
|
+
recovered: true,
|
|
26382
|
+
deliveryProvenance,
|
|
26383
|
+
...fecSbnOf(sym.index),
|
|
26384
|
+
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
26385
|
+
}
|
|
26386
|
+
);
|
|
26387
|
+
}
|
|
26388
|
+
result = { ...result, symbols: result.symbols.filter((sym) => !untimedHead(sym)) };
|
|
25953
26389
|
const gated = this.#isFecTrack(ownerPacketId);
|
|
25954
26390
|
const assembler = this.#mseAssemblerFor(ownerPacketId, catalogTrackName);
|
|
25955
26391
|
if (assembler) {
|