@blockcast/mmt-container 2.2.0-main.c4fa878f4ff4 → 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/README.md +29 -17
- package/dist/browser-runtime.js +703 -175
- package/dist/browser-runtime.js.map +3 -3
- package/dist/catalog-fec-params.d.ts +17 -1
- package/dist/catalog-fec-params.d.ts.map +1 -1
- package/dist/catalog-fec-params.js +15 -0
- package/dist/catalog-fec-params.js.map +2 -2
- package/dist/cmcd-producer.d.ts +60 -47
- package/dist/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js +138 -109
- package/dist/cmcd-producer.js.map +2 -2
- package/dist/delivery-catalog.js +26 -16
- package/dist/delivery-catalog.js.map +2 -2
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +33 -1
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +222 -6
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-worker-setup.d.ts +1 -1
- package/dist/fec-worker-setup.js +1 -1
- package/dist/fec-worker-setup.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -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/media-router.js.map +1 -1
- package/dist/mmtp-container-parser.d.ts +37 -2
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +86 -9
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +89 -11
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +85 -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 +304 -39
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.js +526 -45
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.js +26 -16
- package/dist/multicast-transport.js.map +2 -2
- package/dist/no-modules.js +85 -9
- package/dist/no-modules.js.map +2 -2
- package/dist/repair-layer-subscriptions.d.ts +6 -2
- package/dist/repair-layer-subscriptions.d.ts.map +1 -1
- package/dist/repair-layer-subscriptions.js +6 -3
- package/dist/repair-layer-subscriptions.js.map +2 -2
- package/dist/track-session.d.ts +1 -1
- package/dist/track-session.js.map +1 -1
- package/dist/wasm-mmtp-container-parser.js +85 -9
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +8 -5
- package/schemas/catalog.schema.json +4 -0
- package/src/catalog-fec-params.ts +36 -1
- package/src/cmcd-producer.ts +189 -179
- package/src/fec-manager.ts +277 -6
- package/src/fec-worker-setup.ts +2 -2
- package/src/index.ts +7 -2
- package/src/iso-block-locator.ts +19 -0
- package/src/iso-bmff-timing.ts +40 -1
- package/src/mmtp-container-parser.ts +96 -8
- package/src/mmtp-router.ts +350 -13
- package/src/repair-layer-subscriptions.ts +13 -5
- package/src/track-session.ts +1 -1
|
@@ -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
|
|
@@ -2292,6 +2314,10 @@ var FECManager = class _FECManager {
|
|
|
2292
2314
|
// ── Keyframe-protected repair policy (block-policy.ts) ──
|
|
2293
2315
|
// One classifier per physical stream, built from that stream's geometry.
|
|
2294
2316
|
this.#classifiers = /* @__PURE__ */ new Map();
|
|
2317
|
+
// A stream without catalog timing has no classifier, so the last RAP it
|
|
2318
|
+
// released stands in for one: "an MPU boundary bounds its RAPs" holds until
|
|
2319
|
+
// a second RAP is released inside one MPU (BLO-52085).
|
|
2320
|
+
this.#noSpanRaps = /* @__PURE__ */ new Map();
|
|
2295
2321
|
// Geometry a stream-scoped configuration gave one stream (streamKey). ABR
|
|
2296
2322
|
// rungs can share a packetId with a different framerate or GOP, so the
|
|
2297
2323
|
// packet-level geometry is only the geometry of a packet whose streams
|
|
@@ -2449,6 +2475,7 @@ var FECManager = class _FECManager {
|
|
|
2449
2475
|
#pendingWorkerCleanupBounds;
|
|
2450
2476
|
#blockSourceCounts;
|
|
2451
2477
|
#blockSourceEsis;
|
|
2478
|
+
#blockRebuiltEsis;
|
|
2452
2479
|
#blockAuthoredLengths;
|
|
2453
2480
|
#ssbLengthConflictCount;
|
|
2454
2481
|
#blockRepairRsIds;
|
|
@@ -2491,6 +2518,7 @@ var FECManager = class _FECManager {
|
|
|
2491
2518
|
#orderedFrontiers;
|
|
2492
2519
|
#orderedPendingBlocks;
|
|
2493
2520
|
#classifiers;
|
|
2521
|
+
#noSpanRaps;
|
|
2494
2522
|
#streamGeometries;
|
|
2495
2523
|
#failedBlocks;
|
|
2496
2524
|
#resolvedBlockClasses;
|
|
@@ -3152,6 +3180,7 @@ var FECManager = class _FECManager {
|
|
|
3152
3180
|
};
|
|
3153
3181
|
const discontinuity = () => {
|
|
3154
3182
|
this.#classifiers.get(streamKey)?.noteSourceDiscontinuity();
|
|
3183
|
+
this.#noSpanRaps.delete(streamKey);
|
|
3155
3184
|
this.#highestSbn.set(streamKey, { sbn, atMs: now });
|
|
3156
3185
|
this.#sourceFrontiers.set(streamKey, { sbn, atMs: now });
|
|
3157
3186
|
if (psn === void 0) this.#sourceAnchors.delete(streamKey);
|
|
@@ -3564,6 +3593,9 @@ var FECManager = class _FECManager {
|
|
|
3564
3593
|
}
|
|
3565
3594
|
if (policyChanged) {
|
|
3566
3595
|
this.#bumpEpoch(packetId);
|
|
3596
|
+
for (const key of [...this.#noSpanRaps.keys()]) {
|
|
3597
|
+
if (this.#belongsToPacket(key, packetId)) this.#noSpanRaps.delete(key);
|
|
3598
|
+
}
|
|
3567
3599
|
for (const [key, classifier] of [...this.#classifiers]) {
|
|
3568
3600
|
if (!this.#belongsToPacket(key, packetId)) continue;
|
|
3569
3601
|
const stream = this.#parseStreamKey(key);
|
|
@@ -3766,6 +3798,7 @@ var FECManager = class _FECManager {
|
|
|
3766
3798
|
}
|
|
3767
3799
|
resetMap(this.#blockSourceCounts);
|
|
3768
3800
|
resetMap(this.#blockSourceEsis);
|
|
3801
|
+
resetMap(this.#blockRebuiltEsis);
|
|
3769
3802
|
resetMap(this.#blockAuthoredLengths);
|
|
3770
3803
|
resetMap(this.#blockRepairRsIds);
|
|
3771
3804
|
resetMap(this.#blockRsbLengths);
|
|
@@ -3799,6 +3832,7 @@ var FECManager = class _FECManager {
|
|
|
3799
3832
|
resetMap(this.#isoLocators);
|
|
3800
3833
|
resetMap(this.#sourceAnchors);
|
|
3801
3834
|
this.#classifiers.delete(this.#streamKey(packetId, catalogTrackName));
|
|
3835
|
+
this.#noSpanRaps.delete(this.#streamKey(packetId, catalogTrackName));
|
|
3802
3836
|
this.#bumpEpoch(packetId);
|
|
3803
3837
|
for (const [sbn, keys] of this.#blockKeysBySbn) {
|
|
3804
3838
|
for (const key of keys.keys()) {
|
|
@@ -3889,6 +3923,7 @@ var FECManager = class _FECManager {
|
|
|
3889
3923
|
dropMap(this.#pendingWorkerCleanupClients);
|
|
3890
3924
|
dropMap(this.#blockSourceCounts);
|
|
3891
3925
|
dropMap(this.#blockSourceEsis);
|
|
3926
|
+
dropMap(this.#blockRebuiltEsis);
|
|
3892
3927
|
dropMap(this.#blockAuthoredLengths);
|
|
3893
3928
|
dropMap(this.#blockRepairRsIds);
|
|
3894
3929
|
dropMap(this.#blockRsbLengths);
|
|
@@ -3905,6 +3940,7 @@ var FECManager = class _FECManager {
|
|
|
3905
3940
|
dropMap(this.#isoLocators);
|
|
3906
3941
|
dropMap(this.#sourceAnchors);
|
|
3907
3942
|
dropMap(this.#classifiers);
|
|
3943
|
+
dropMap(this.#noSpanRaps);
|
|
3908
3944
|
dropMap(this.#streamGeometries);
|
|
3909
3945
|
dropSet(this.#pendingWorkerCleanups);
|
|
3910
3946
|
dropSet(this.#completedBlocks);
|
|
@@ -4050,6 +4086,9 @@ var FECManager = class _FECManager {
|
|
|
4050
4086
|
this.#unindexBlockKey(key);
|
|
4051
4087
|
}
|
|
4052
4088
|
}
|
|
4089
|
+
if (isSourceSymbol && source === "multicast") {
|
|
4090
|
+
this.#noteMulticastSource(this.#isoLocatorFor(packetId, catalogTrackName), sbn + esi >>> 0, packetId, catalogTrackName);
|
|
4091
|
+
}
|
|
4053
4092
|
return true;
|
|
4054
4093
|
}
|
|
4055
4094
|
#observeBlockCatalogTrackName(blockKey, sbn, packetId, catalogTrackName) {
|
|
@@ -4367,6 +4406,7 @@ var FECManager = class _FECManager {
|
|
|
4367
4406
|
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
4368
4407
|
const evictedBefore = locator.heldEvicted;
|
|
4369
4408
|
const held = locator.hold(ssId, source);
|
|
4409
|
+
if (held && source.source === "multicast") this.#noteMulticastSource(locator, ssId, packetId, catalogTrackName);
|
|
4370
4410
|
const evicted = locator.heldEvicted - evictedBefore;
|
|
4371
4411
|
if (evicted > 0) {
|
|
4372
4412
|
const before = this.#isoHeldEvictedCount;
|
|
@@ -4535,11 +4575,14 @@ var FECManager = class _FECManager {
|
|
|
4535
4575
|
/**
|
|
4536
4576
|
* Whether an MPU boundary still bounds this stream's RAPs (see
|
|
4537
4577
|
* `BlockClassifier.mpuBoundsRaps`). Gap decisions drop MPU evidence once
|
|
4538
|
-
* it does not. A stream with no classifier yet has shown nothing against it
|
|
4578
|
+
* it does not. A stream with no classifier yet has shown nothing against it,
|
|
4579
|
+
* and neither has a stream without catalog timing until it releases two
|
|
4580
|
+
* RAPs inside one MPU.
|
|
4539
4581
|
*/
|
|
4540
4582
|
mpuBoundsRapsFor(packetId, catalogTrackName) {
|
|
4541
|
-
|
|
4542
|
-
return this.#
|
|
4583
|
+
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
4584
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return this.#noSpanRaps.get(streamKey)?.inside !== true;
|
|
4585
|
+
return this.#classifiers.get(streamKey)?.mpuBoundsRaps ?? true;
|
|
4543
4586
|
}
|
|
4544
4587
|
/** A block's class now, or frozen at its resolution. */
|
|
4545
4588
|
blockClassFor(sbn, packetId, catalogTrackName) {
|
|
@@ -4565,7 +4608,14 @@ var FECManager = class _FECManager {
|
|
|
4565
4608
|
* invariant (consecutive MPUs exactly one keyframe interval apart).
|
|
4566
4609
|
*/
|
|
4567
4610
|
noteReleasedRap(packetId, catalogTrackName, mpu, presentationTime, mediaTimescale) {
|
|
4568
|
-
if (!this.#trackConfigs.has(packetId)
|
|
4611
|
+
if (!this.#trackConfigs.has(packetId)) return;
|
|
4612
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) {
|
|
4613
|
+
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
4614
|
+
const last = this.#noSpanRaps.get(streamKey);
|
|
4615
|
+
const inside = last?.inside === true || last?.mpu === mpu && last.presentationTime !== presentationTime;
|
|
4616
|
+
this.#noSpanRaps.set(streamKey, { mpu, presentationTime, inside });
|
|
4617
|
+
return;
|
|
4618
|
+
}
|
|
4569
4619
|
this.#applyClassifierChanges(
|
|
4570
4620
|
packetId,
|
|
4571
4621
|
catalogTrackName,
|
|
@@ -4952,7 +5002,15 @@ var FECManager = class _FECManager {
|
|
|
4952
5002
|
this.#isoSourceOversizeCount++;
|
|
4953
5003
|
return null;
|
|
4954
5004
|
}
|
|
4955
|
-
|
|
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) {
|
|
4956
5014
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
4957
5015
|
const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey);
|
|
4958
5016
|
const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName);
|
|
@@ -4981,6 +5039,74 @@ var FECManager = class _FECManager {
|
|
|
4981
5039
|
}
|
|
4982
5040
|
return recovered;
|
|
4983
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
|
+
}
|
|
4984
5110
|
/**
|
|
4985
5111
|
* fec-01 4.5: a reed-solomon repair packet whose SSB_length + RSB_length
|
|
4986
5112
|
* exceeds 255, or whose RS_ID is not below RSB_length, is discarded with the
|
|
@@ -5183,8 +5309,75 @@ var FECManager = class _FECManager {
|
|
|
5183
5309
|
this.#completeCleanBlock(blockKey, sbn, packetId, catalogTrackName);
|
|
5184
5310
|
return null;
|
|
5185
5311
|
}
|
|
5312
|
+
if (recovered === null) this.#shortenUnrecoverableWait(blockKey, sbn, packetId, catalogTrackName);
|
|
5186
5313
|
return recovered;
|
|
5187
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
|
+
}
|
|
5188
5381
|
/**
|
|
5189
5382
|
* Record a block's authored SSB_length from a repair symbol. Returns false,
|
|
5190
5383
|
* leaving the block unchanged, when the symbol contradicts the block: a
|
|
@@ -5339,6 +5532,15 @@ var FECManager = class _FECManager {
|
|
|
5339
5532
|
this.#completeCleanBlock(key, sbn, packetId, catalogTrackName);
|
|
5340
5533
|
return;
|
|
5341
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
|
+
}
|
|
5342
5544
|
this.noteBlockActivity(sbn, packetId, catalogTrackName, true);
|
|
5343
5545
|
}
|
|
5344
5546
|
#completeCleanBlock(key, sbn, packetId, catalogTrackName) {
|
|
@@ -5347,6 +5549,7 @@ var FECManager = class _FECManager {
|
|
|
5347
5549
|
this.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
5348
5550
|
this.#blockSourceCounts.delete(key);
|
|
5349
5551
|
this.#blockSourceEsis.delete(key);
|
|
5552
|
+
this.#blockRebuiltEsis.delete(key);
|
|
5350
5553
|
this.#blockAuthoredLengths.delete(key);
|
|
5351
5554
|
this.#blockRepairRsIds.delete(key);
|
|
5352
5555
|
this.#blockRsbLengths.delete(key);
|
|
@@ -5413,7 +5616,16 @@ var FECManager = class _FECManager {
|
|
|
5413
5616
|
this.#repairDeadlineMs(trackConfig)
|
|
5414
5617
|
);
|
|
5415
5618
|
}
|
|
5416
|
-
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
|
+
);
|
|
5417
5629
|
}
|
|
5418
5630
|
#failBlock(sbn, packetId, catalogTrackName) {
|
|
5419
5631
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
@@ -5426,6 +5638,7 @@ var FECManager = class _FECManager {
|
|
|
5426
5638
|
}
|
|
5427
5639
|
this.#blockSourceCounts.delete(key);
|
|
5428
5640
|
this.#blockSourceEsis.delete(key);
|
|
5641
|
+
this.#blockRebuiltEsis.delete(key);
|
|
5429
5642
|
this.#blockAuthoredLengths.delete(key);
|
|
5430
5643
|
this.#blockRepairRsIds.delete(key);
|
|
5431
5644
|
this.#blockRsbLengths.delete(key);
|
|
@@ -5464,6 +5677,7 @@ var FECManager = class _FECManager {
|
|
|
5464
5677
|
this.#retireBlockClass(key, block.sbn, block.packetId, block.catalogTrackName);
|
|
5465
5678
|
this.#blockSourceCounts.delete(key);
|
|
5466
5679
|
this.#blockSourceEsis.delete(key);
|
|
5680
|
+
this.#blockRebuiltEsis.delete(key);
|
|
5467
5681
|
this.#blockAuthoredLengths.delete(key);
|
|
5468
5682
|
this.#blockRepairRsIds.delete(key);
|
|
5469
5683
|
this.#blockRsbLengths.delete(key);
|
|
@@ -5555,6 +5769,7 @@ var FECManager = class _FECManager {
|
|
|
5555
5769
|
this.#blockKeysBySbn.clear();
|
|
5556
5770
|
this.#blockSourceCounts.clear();
|
|
5557
5771
|
this.#blockSourceEsis.clear();
|
|
5772
|
+
this.#blockRebuiltEsis.clear();
|
|
5558
5773
|
this.#blockAuthoredLengths.clear();
|
|
5559
5774
|
this.#blockRepairRsIds.clear();
|
|
5560
5775
|
this.#blockRsbLengths.clear();
|
|
@@ -5580,6 +5795,7 @@ var FECManager = class _FECManager {
|
|
|
5580
5795
|
this.#blockAnchorsMs.clear();
|
|
5581
5796
|
this.#blockCatalogTrackNames.clear();
|
|
5582
5797
|
this.#classifiers.clear();
|
|
5798
|
+
this.#noSpanRaps.clear();
|
|
5583
5799
|
this.#streamGeometries.clear();
|
|
5584
5800
|
this.#failedBlocks.clear();
|
|
5585
5801
|
this.#resolvedBlockClasses.clear();
|
|
@@ -7054,14 +7270,15 @@ var MOQT_PUBLISHER_PRIORITY = Object.freeze({
|
|
|
7054
7270
|
});
|
|
7055
7271
|
|
|
7056
7272
|
// node_modules/@blockcast/mmt-transport/dist/amt-gateway.js
|
|
7273
|
+
var MAX_PENDING_PACKETS = 256;
|
|
7057
7274
|
var AMTGatewayManager = class {
|
|
7058
7275
|
subscriptions = /* @__PURE__ */ new Map();
|
|
7059
7276
|
messageHandler;
|
|
7277
|
+
messageListenerRegistered = false;
|
|
7060
7278
|
pendingPackets = /* @__PURE__ */ new Map();
|
|
7061
7279
|
constructor() {
|
|
7062
7280
|
this.messageHandler = (event) => {
|
|
7063
7281
|
const data = event.data;
|
|
7064
|
-
console.log(`[AMT] SW message received:`, data.type);
|
|
7065
7282
|
if (data.type === "UDP_PACKET") {
|
|
7066
7283
|
this.handlePacket(data);
|
|
7067
7284
|
} else if (data.type === "SOCKET_ERROR") {
|
|
@@ -7070,12 +7287,22 @@ var AMTGatewayManager = class {
|
|
|
7070
7287
|
console.log(`[AMT SW Debug] ${data.message}`);
|
|
7071
7288
|
}
|
|
7072
7289
|
};
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
console.warn("[AMT] Service worker not available");
|
|
7290
|
+
}
|
|
7291
|
+
ensureMessageListener() {
|
|
7292
|
+
if (this.messageListenerRegistered || typeof navigator === "undefined" || !navigator.serviceWorker || !this.messageHandler) {
|
|
7293
|
+
return;
|
|
7078
7294
|
}
|
|
7295
|
+
navigator.serviceWorker.addEventListener("message", this.messageHandler);
|
|
7296
|
+
this.messageListenerRegistered = true;
|
|
7297
|
+
console.log("[AMT] Registered service worker message handler");
|
|
7298
|
+
}
|
|
7299
|
+
removeMessageListener() {
|
|
7300
|
+
if (!this.messageListenerRegistered)
|
|
7301
|
+
return;
|
|
7302
|
+
if (this.messageHandler && typeof navigator !== "undefined" && navigator.serviceWorker) {
|
|
7303
|
+
navigator.serviceWorker.removeEventListener("message", this.messageHandler);
|
|
7304
|
+
}
|
|
7305
|
+
this.messageListenerRegistered = false;
|
|
7079
7306
|
}
|
|
7080
7307
|
/**
|
|
7081
7308
|
* Subscribe via AMT tunnel
|
|
@@ -7085,6 +7312,7 @@ var AMTGatewayManager = class {
|
|
|
7085
7312
|
if (config.protected) {
|
|
7086
7313
|
throw new Error("AMTGatewayManager refuses a protected join: it reports no per-datagram sender (multicast-02 7.2.7)");
|
|
7087
7314
|
}
|
|
7315
|
+
this.ensureMessageListener();
|
|
7088
7316
|
const keyStr = this.getKeyString(key);
|
|
7089
7317
|
if (this.subscriptions.has(keyStr)) {
|
|
7090
7318
|
console.log(`[AMT] Reusing existing subscription to ${keyStr}`);
|
|
@@ -7120,6 +7348,8 @@ var AMTGatewayManager = class {
|
|
|
7120
7348
|
this.subscriptions.delete(keyStr);
|
|
7121
7349
|
this.pendingPackets.delete(subscription.subscriptionId);
|
|
7122
7350
|
}
|
|
7351
|
+
if (this.subscriptions.size === 0)
|
|
7352
|
+
this.removeMessageListener();
|
|
7123
7353
|
throw new Error(`Failed to subscribe via AMT: ${error instanceof Error ? error.message : String(error)}`);
|
|
7124
7354
|
}
|
|
7125
7355
|
}
|
|
@@ -7141,6 +7371,8 @@ var AMTGatewayManager = class {
|
|
|
7141
7371
|
} finally {
|
|
7142
7372
|
this.subscriptions.delete(handle);
|
|
7143
7373
|
this.pendingPackets.delete(subscription.subscriptionId);
|
|
7374
|
+
if (this.subscriptions.size === 0)
|
|
7375
|
+
this.removeMessageListener();
|
|
7144
7376
|
}
|
|
7145
7377
|
}
|
|
7146
7378
|
/**
|
|
@@ -7157,14 +7389,12 @@ var AMTGatewayManager = class {
|
|
|
7157
7389
|
if (!subscriptionId)
|
|
7158
7390
|
return;
|
|
7159
7391
|
const subscription = this.subscriptions.get(subscriptionId);
|
|
7160
|
-
if (!subscription)
|
|
7161
|
-
console.warn(`[AMT] Received packet for unknown subscription: ${subscriptionId}`);
|
|
7392
|
+
if (!subscription)
|
|
7162
7393
|
return;
|
|
7163
|
-
}
|
|
7164
7394
|
if (!subscription.handshakeComplete) {
|
|
7165
7395
|
const queue = this.pendingPackets.get(subscriptionId);
|
|
7166
7396
|
const packetData2 = data;
|
|
7167
|
-
if (queue) {
|
|
7397
|
+
if (queue && queue.length < MAX_PENDING_PACKETS) {
|
|
7168
7398
|
queue.push({
|
|
7169
7399
|
data: packetData2.data,
|
|
7170
7400
|
remoteAddress: packetData2.remoteAddress,
|
|
@@ -7190,19 +7420,15 @@ var AMTGatewayManager = class {
|
|
|
7190
7420
|
if (!subscriptionId)
|
|
7191
7421
|
return;
|
|
7192
7422
|
const subscription = this.subscriptions.get(subscriptionId);
|
|
7193
|
-
if (!subscription)
|
|
7194
|
-
console.warn(`[AMT] Received error for unknown subscription: ${subscriptionId}`);
|
|
7423
|
+
if (!subscription)
|
|
7195
7424
|
return;
|
|
7196
|
-
}
|
|
7197
7425
|
subscription.config.onError(new Error(error || "Unknown error"));
|
|
7198
7426
|
}
|
|
7199
7427
|
/**
|
|
7200
7428
|
* Cleanup on destroy
|
|
7201
7429
|
*/
|
|
7202
7430
|
destroy() {
|
|
7203
|
-
|
|
7204
|
-
navigator.serviceWorker.removeEventListener("message", this.messageHandler);
|
|
7205
|
-
}
|
|
7431
|
+
this.removeMessageListener();
|
|
7206
7432
|
const handles = Array.from(this.subscriptions.keys());
|
|
7207
7433
|
for (const handle of handles) {
|
|
7208
7434
|
this.unsubscribe(handle).catch((err) => {
|
|
@@ -13111,9 +13337,21 @@ var IsoBmffTimingResolver = class {
|
|
|
13111
13337
|
}
|
|
13112
13338
|
#track = null;
|
|
13113
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();
|
|
13114
13348
|
#pendingSamples = 0;
|
|
13115
13349
|
#evictedSamples = 0;
|
|
13116
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
|
+
}
|
|
13117
13355
|
get pendingSampleCount() {
|
|
13118
13356
|
return this.#pendingSamples;
|
|
13119
13357
|
}
|
|
@@ -13137,7 +13375,14 @@ var IsoBmffTimingResolver = class {
|
|
|
13137
13375
|
this.#track = timing;
|
|
13138
13376
|
return true;
|
|
13139
13377
|
}
|
|
13140
|
-
|
|
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) {
|
|
13141
13386
|
this.#lastMovieFragmentRejection = null;
|
|
13142
13387
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
13143
13388
|
this.#lastMovieFragmentRejection = "invalid";
|
|
@@ -13163,6 +13408,8 @@ var IsoBmffTimingResolver = class {
|
|
|
13163
13408
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
13164
13409
|
);
|
|
13165
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;
|
|
13166
13413
|
let freed = 0;
|
|
13167
13414
|
while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
13168
13415
|
freed += this.#evictOldestFragment();
|
|
@@ -13176,6 +13423,7 @@ var IsoBmffTimingResolver = class {
|
|
|
13176
13423
|
return false;
|
|
13177
13424
|
}
|
|
13178
13425
|
this.#fragments.set(key, [...parsed.samples]);
|
|
13426
|
+
this.#firstSamples.set(key, parsed.samples[0]);
|
|
13179
13427
|
this.#pendingSamples += parsed.samples.length;
|
|
13180
13428
|
return true;
|
|
13181
13429
|
}
|
|
@@ -13233,6 +13481,7 @@ var IsoBmffTimingResolver = class {
|
|
|
13233
13481
|
let live = 0;
|
|
13234
13482
|
for (const sample of samples) if (sample !== null) live++;
|
|
13235
13483
|
this.#fragments.delete(oldest);
|
|
13484
|
+
this.#firstSamples.delete(oldest);
|
|
13236
13485
|
this.#pendingSamples -= live;
|
|
13237
13486
|
this.#evictedSamples += live;
|
|
13238
13487
|
return live;
|
|
@@ -13248,12 +13497,23 @@ var IsoBmffTimingResolver = class {
|
|
|
13248
13497
|
if (!samples || !samples[index]) return false;
|
|
13249
13498
|
samples[index] = null;
|
|
13250
13499
|
this.#pendingSamples--;
|
|
13251
|
-
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
|
+
}
|
|
13252
13510
|
return true;
|
|
13253
13511
|
}
|
|
13254
13512
|
reset() {
|
|
13255
13513
|
this.#track = null;
|
|
13256
13514
|
this.#fragments.clear();
|
|
13515
|
+
this.#firstSamples.clear();
|
|
13516
|
+
this.#delivered.clear();
|
|
13257
13517
|
this.#pendingSamples = 0;
|
|
13258
13518
|
}
|
|
13259
13519
|
};
|
|
@@ -13735,6 +13995,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
13735
13995
|
this.#reclaimedTimingSamples = 0;
|
|
13736
13996
|
this.#evictedTimingSamples = 0;
|
|
13737
13997
|
this.#rejectedMovieFragments = 0;
|
|
13998
|
+
this.#recoveredHeadsAwaitingTiming = {
|
|
13999
|
+
held: 0,
|
|
14000
|
+
expired: 0,
|
|
14001
|
+
discarded: 0,
|
|
14002
|
+
superseded: 0
|
|
14003
|
+
};
|
|
13738
14004
|
this.#sequencers = /* @__PURE__ */ new Map();
|
|
13739
14005
|
this.#fecGate = null;
|
|
13740
14006
|
this.#fecSequencer = null;
|
|
@@ -13843,6 +14109,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
13843
14109
|
#reclaimedTimingSamples;
|
|
13844
14110
|
#evictedTimingSamples;
|
|
13845
14111
|
#rejectedMovieFragments;
|
|
14112
|
+
#recoveredHeadsAwaitingTiming;
|
|
13846
14113
|
#sequencers;
|
|
13847
14114
|
#fecGate;
|
|
13848
14115
|
#fecSequencer;
|
|
@@ -13963,6 +14230,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
13963
14230
|
rejectedMovieFragments: this.#rejectedMovieFragments
|
|
13964
14231
|
};
|
|
13965
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
|
+
}
|
|
13966
14241
|
get reorderStats() {
|
|
13967
14242
|
let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
|
|
13968
14243
|
for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
|
|
@@ -14136,7 +14411,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14136
14411
|
const now = Date.now();
|
|
14137
14412
|
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
14138
14413
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
14139
|
-
awaitingTiming.shift();
|
|
14414
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
14140
14415
|
}
|
|
14141
14416
|
}
|
|
14142
14417
|
}
|
|
@@ -14254,11 +14529,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14254
14529
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
14255
14530
|
return this.#drop("fragment-unexpected-fragmentation");
|
|
14256
14531
|
const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
|
|
14257
|
-
if (!resolver2) return this.#drop("fragment-no-resolver");
|
|
14532
|
+
if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
|
|
14258
14533
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
14259
14534
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
14260
14535
|
const remaining = this.#maxPendingTimingSamples - this.#pendingTimingSamples;
|
|
14261
|
-
const admitted = resolver2.addMovieFragment(
|
|
14536
|
+
const admitted = resolver2.addMovieFragment(
|
|
14537
|
+
mpu.mpuSequenceNumber,
|
|
14538
|
+
data,
|
|
14539
|
+
remaining,
|
|
14540
|
+
options?.recovered === true
|
|
14541
|
+
);
|
|
14262
14542
|
this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
|
|
14263
14543
|
this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
|
|
14264
14544
|
if (!admitted) {
|
|
@@ -14278,7 +14558,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14278
14558
|
};
|
|
14279
14559
|
}
|
|
14280
14560
|
if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
|
|
14281
|
-
const resolver = this.#timingResolver(streamKey, info.packetId,
|
|
14561
|
+
const resolver = this.#timingResolver(streamKey, info.packetId, true);
|
|
14282
14562
|
if (!resolver) return this.#drop("mfu-no-resolver");
|
|
14283
14563
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
14284
14564
|
return this.#drop("mfu-empty-payload");
|
|
@@ -14299,6 +14579,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14299
14579
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
14300
14580
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
14301
14581
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
14582
|
+
sequenceNumber: info.sequenceNumber,
|
|
14302
14583
|
arrivedAt: Date.now()
|
|
14303
14584
|
});
|
|
14304
14585
|
return this.#drop("mfu-sample-timing-missing");
|
|
@@ -14622,6 +14903,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14622
14903
|
}
|
|
14623
14904
|
this.#emitFrame(data, meta);
|
|
14624
14905
|
}
|
|
14906
|
+
/** Whether FT=1 metadata already names this sample, without claiming it. */
|
|
14907
|
+
hasAuthoredTimingFor(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
|
|
14908
|
+
return this.#timingResolver(this.#streamKey(packetId, catalogTrackName), packetId, false)?.sampleTiming(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) != null;
|
|
14909
|
+
}
|
|
14625
14910
|
/**
|
|
14626
14911
|
* Claim and return the authored timing for a recovered sample. Recovery does
|
|
14627
14912
|
* not pass through the ordinary `feed()` completion path, so it must retire
|
|
@@ -14683,6 +14968,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14683
14968
|
for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
|
|
14684
14969
|
this.#sequencers.clear();
|
|
14685
14970
|
this.#fecSequencer?.discard();
|
|
14971
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
14972
|
+
for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
14973
|
+
}
|
|
14686
14974
|
this.#timingStreams.clear();
|
|
14687
14975
|
this.#pendingTimingSamples = 0;
|
|
14688
14976
|
this.#reliableMoqStartupGate.clear();
|
|
@@ -14701,10 +14989,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14701
14989
|
return resolver;
|
|
14702
14990
|
}
|
|
14703
14991
|
#awaitTiming(streamKey, head) {
|
|
14704
|
-
const
|
|
14705
|
-
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;
|
|
14706
15000
|
awaitingTiming.push(head);
|
|
14707
|
-
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]++;
|
|
14708
15009
|
}
|
|
14709
15010
|
/**
|
|
14710
15011
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -14732,6 +15033,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
14732
15033
|
const state = this.#timingStreams.get(streamKey);
|
|
14733
15034
|
if (!state) return;
|
|
14734
15035
|
this.#timingStreams.delete(streamKey);
|
|
15036
|
+
for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
14735
15037
|
this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
|
|
14736
15038
|
if (this.#pendingTimingSamples < 0) {
|
|
14737
15039
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
@@ -17937,6 +18239,16 @@ function keyframeOverlaySsId(header) {
|
|
|
17937
18239
|
if (ext?.type !== KEYFRAME_OVERLAY_EXTENSION_TYPE || ext.data.length !== 4) return void 0;
|
|
17938
18240
|
return new DataView(ext.data.buffer, ext.data.byteOffset, 4).getUint32(0);
|
|
17939
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;
|
|
17940
18252
|
var OVERLAY_OFFSET_MPUS = 16;
|
|
17941
18253
|
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
17942
18254
|
function assertStreamIdentity(role, stream) {
|
|
@@ -18048,6 +18360,7 @@ var MmtpRouter = class {
|
|
|
18048
18360
|
#provenance = /* @__PURE__ */ new Map();
|
|
18049
18361
|
#onFlowProtected = null;
|
|
18050
18362
|
#unadmittedListedDrops = 0;
|
|
18363
|
+
#admittedSourceDatagrams = 0;
|
|
18051
18364
|
#unlistedRepairSetAside = 0;
|
|
18052
18365
|
#assistBlocks = /* @__PURE__ */ new Map();
|
|
18053
18366
|
#assistFailures = /* @__PURE__ */ new Map();
|
|
@@ -18104,6 +18417,15 @@ var MmtpRouter = class {
|
|
|
18104
18417
|
* sequence is its SS_ID minus this; see #routeProcessedFecSymbols.
|
|
18105
18418
|
*/
|
|
18106
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();
|
|
18107
18429
|
/**
|
|
18108
18430
|
* Streams whose multicast leg delivered a RAP-flagged source packet that was
|
|
18109
18431
|
* not parsed, typically because its overlap copy was parsed first
|
|
@@ -18173,7 +18495,9 @@ var MmtpRouter = class {
|
|
|
18173
18495
|
recovered: 0,
|
|
18174
18496
|
restarts: 0,
|
|
18175
18497
|
unsequencedDropped: 0,
|
|
18176
|
-
protectedDropped: 0
|
|
18498
|
+
protectedDropped: 0,
|
|
18499
|
+
baseFed: 0,
|
|
18500
|
+
baseUndetermined: 0
|
|
18177
18501
|
};
|
|
18178
18502
|
/** Callback fired when a delta-track FEC block completes or fails. */
|
|
18179
18503
|
#onDeltaBlockCompleteCb = null;
|
|
@@ -18346,7 +18670,7 @@ var MmtpRouter = class {
|
|
|
18346
18670
|
const cb = this.#getFrameCallback(packetId);
|
|
18347
18671
|
if (isVideo && !assembler) {
|
|
18348
18672
|
if (frameMeta.recovered) {
|
|
18349
|
-
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb);
|
|
18673
|
+
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb, meta.fecSbn);
|
|
18350
18674
|
} else {
|
|
18351
18675
|
this.#routeDirectSourceVideo(packetId, data, frameMeta, cb);
|
|
18352
18676
|
}
|
|
@@ -18736,6 +19060,10 @@ var MmtpRouter = class {
|
|
|
18736
19060
|
get keyframeOverlayStats() {
|
|
18737
19061
|
return { ...this.#overlayStats };
|
|
18738
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
|
+
}
|
|
18739
19067
|
#isOverlayRepair(repairPacketId, repairTrackName) {
|
|
18740
19068
|
return this.#overlayLegs.has(repairPacketId) || repairTrackName !== void 0 && this.#overlayRepairNames.has(repairTrackName);
|
|
18741
19069
|
}
|
|
@@ -18786,6 +19114,11 @@ var MmtpRouter = class {
|
|
|
18786
19114
|
*/
|
|
18787
19115
|
resetKeyframeOverlays(sourcePacketId, catalogTrackName) {
|
|
18788
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
|
+
}
|
|
18789
19122
|
}
|
|
18790
19123
|
#resetOverlay(inst) {
|
|
18791
19124
|
inst.fec.resetTrack(inst.repairPacketId, inst.streamName);
|
|
@@ -18842,9 +19175,10 @@ var MmtpRouter = class {
|
|
|
18842
19175
|
* A source packet that carries a 0x8B01 overlay SS_ID is also a symbol of
|
|
18843
19176
|
* every overlay installed for its source, at that SS_ID, with the bytes the
|
|
18844
19177
|
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
18845
|
-
* 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.
|
|
18846
19180
|
*/
|
|
18847
|
-
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source) {
|
|
19181
|
+
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source, baseSsId) {
|
|
18848
19182
|
if (this.#overlays.size === 0) return;
|
|
18849
19183
|
const ssId = keyframeOverlaySsId(header);
|
|
18850
19184
|
if (ssId === void 0) return;
|
|
@@ -18853,7 +19187,11 @@ var MmtpRouter = class {
|
|
|
18853
19187
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
18854
19188
|
if (identity !== null) {
|
|
18855
19189
|
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
18856
|
-
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
|
+
});
|
|
18857
19195
|
if (inst.offsets.size > OVERLAY_OFFSET_MPUS) inst.offsets.delete(inst.offsets.keys().next().value);
|
|
18858
19196
|
}
|
|
18859
19197
|
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
@@ -18932,14 +19270,27 @@ var MmtpRouter = class {
|
|
|
18932
19270
|
const streamKey = this.#mediaStreamKey(sourcePacketId, sourceName);
|
|
18933
19271
|
const restamped = this.#restampedStreams.has(streamKey);
|
|
18934
19272
|
const restored = /* @__PURE__ */ new Set();
|
|
19273
|
+
const baseSsIds = /* @__PURE__ */ new Map();
|
|
18935
19274
|
const symbols = result.symbols.flatMap((sym) => {
|
|
18936
|
-
const
|
|
18937
|
-
if (
|
|
18938
|
-
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;
|
|
18939
19279
|
restored.add(packetSequenceNumber);
|
|
18940
|
-
|
|
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];
|
|
18941
19293
|
}
|
|
18942
|
-
if (!restamped) return [sym];
|
|
18943
19294
|
this.#overlayStats.unsequencedDropped++;
|
|
18944
19295
|
return [];
|
|
18945
19296
|
});
|
|
@@ -18948,6 +19299,85 @@ var MmtpRouter = class {
|
|
|
18948
19299
|
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
18949
19300
|
});
|
|
18950
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
|
+
}
|
|
18951
19381
|
}
|
|
18952
19382
|
/** fec-multicast-00 §6.6: report each base block whose RAP MFU fragments
|
|
18953
19383
|
* the overlay restored, when the keyframe's FT=0 and FT=1 are held too. A
|
|
@@ -19087,6 +19517,10 @@ var MmtpRouter = class {
|
|
|
19087
19517
|
get unadmittedListedDropCount() {
|
|
19088
19518
|
return this.#unadmittedListedDrops;
|
|
19089
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
|
+
}
|
|
19090
19524
|
/** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
|
|
19091
19525
|
get unlistedRepairSetAsideCount() {
|
|
19092
19526
|
return this.#unlistedRepairSetAside;
|
|
@@ -19573,6 +20007,8 @@ var MmtpRouter = class {
|
|
|
19573
20007
|
)) return;
|
|
19574
20008
|
fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);
|
|
19575
20009
|
fec.noteRepairGroupLength?.(blockStart, ownerPacketId, repair.rsbLength, catalogTrackName);
|
|
20010
|
+
this.#feedPendingRebuilt(ownerPacketId, catalogTrackName, placement.block);
|
|
20011
|
+
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
19576
20012
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
19577
20013
|
const recovered = fec.feedRepairSymbol(
|
|
19578
20014
|
ownerPacketId,
|
|
@@ -19710,6 +20146,7 @@ var MmtpRouter = class {
|
|
|
19710
20146
|
this.#droppedPreSyncVideo.delete(streamKey);
|
|
19711
20147
|
this.#latestVideoSeqByStream.delete(streamKey);
|
|
19712
20148
|
this.#ssIdSeqOffsetByStream.delete(streamKey);
|
|
20149
|
+
this.#unstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
19713
20150
|
this.#multicastRapTwinByStream.delete(streamKey);
|
|
19714
20151
|
this.#lastDirectSourceVideoTsByStream.delete(streamKey);
|
|
19715
20152
|
this.#lastDirectEmittedVideoTsByStream.delete(streamKey);
|
|
@@ -20344,6 +20781,7 @@ var MmtpRouter = class {
|
|
|
20344
20781
|
const parsed = this.#parser.parsePacket(packet);
|
|
20345
20782
|
if (!parsed) return;
|
|
20346
20783
|
const info = parsed.info;
|
|
20784
|
+
if (manifestAdmitted && !info.isRepair && ingress?.admittedBy !== USER_AGENT_VERIFIED) this.#admittedSourceDatagrams++;
|
|
20347
20785
|
if (source === "multicast" && !manifestAdmitted) {
|
|
20348
20786
|
this.#fecManager?.noteMulticastArrival?.(info.packetId, info.sequenceNumber);
|
|
20349
20787
|
}
|
|
@@ -20625,7 +21063,8 @@ var MmtpRouter = class {
|
|
|
20625
21063
|
parsed.header,
|
|
20626
21064
|
info.sequenceNumber,
|
|
20627
21065
|
info.rapFlag,
|
|
20628
|
-
source
|
|
21066
|
+
source,
|
|
21067
|
+
fecPayloadId?.ssId
|
|
20629
21068
|
);
|
|
20630
21069
|
}
|
|
20631
21070
|
}
|
|
@@ -20775,7 +21214,8 @@ var MmtpRouter = class {
|
|
|
20775
21214
|
if (stream === void 0) {
|
|
20776
21215
|
stream = {
|
|
20777
21216
|
key: JSON.stringify([packetId, catalogTrackName ?? null]),
|
|
20778
|
-
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId))
|
|
21217
|
+
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId)),
|
|
21218
|
+
overlayToBase: { routed: /* @__PURE__ */ new Set(), pending: /* @__PURE__ */ new Map() }
|
|
20779
21219
|
};
|
|
20780
21220
|
byName.set(catalogTrackName, stream);
|
|
20781
21221
|
}
|
|
@@ -20890,8 +21330,16 @@ var MmtpRouter = class {
|
|
|
20890
21330
|
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
20891
21331
|
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
20892
21332
|
}
|
|
20893
|
-
#routeDirectRecoveredVideo(packetId, data, meta, cb) {
|
|
21333
|
+
#routeDirectRecoveredVideo(packetId, data, meta, cb, fecSbn) {
|
|
20894
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
|
+
}
|
|
20895
21343
|
this.#enqueueDirectRecoveredVideo(packetId, data, meta);
|
|
20896
21344
|
const lastSourceTs = this.#lastDirectSourceVideoTsByStream.get(streamKey);
|
|
20897
21345
|
this.#drainDirectRecoveredVideo(
|
|
@@ -21045,6 +21493,13 @@ var MmtpRouter = class {
|
|
|
21045
21493
|
* frames by epoch only if pre-protection input ever matters.
|
|
21046
21494
|
*/
|
|
21047
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
|
+
}
|
|
21048
21503
|
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
21049
21504
|
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
21050
21505
|
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
@@ -21109,7 +21564,10 @@ var MmtpRouter = class {
|
|
|
21109
21564
|
...result,
|
|
21110
21565
|
symbols: result.symbols.map((sym) => {
|
|
21111
21566
|
const wire = this.#fecAnchorForRecovered(blockId, sym.index) - ssIdSeqOffset >>> 0;
|
|
21112
|
-
if (wire === sym.packetSequenceNumber)
|
|
21567
|
+
if (wire === sym.packetSequenceNumber) {
|
|
21568
|
+
this.#unstampedSsIdSeqOffsetByStream.set(streamKey, ssIdSeqOffset);
|
|
21569
|
+
return sym;
|
|
21570
|
+
}
|
|
21113
21571
|
this.#restampedRecoveredSeqs++;
|
|
21114
21572
|
this.#restampedStreams.add(streamKey);
|
|
21115
21573
|
return { ...sym, packetSequenceNumber: wire };
|
|
@@ -21145,6 +21603,29 @@ var MmtpRouter = class {
|
|
|
21145
21603
|
}
|
|
21146
21604
|
);
|
|
21147
21605
|
}
|
|
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)) };
|
|
21148
21629
|
const gated = this.#isFecTrack(ownerPacketId);
|
|
21149
21630
|
const assembler = this.#mseAssemblerFor(ownerPacketId, catalogTrackName);
|
|
21150
21631
|
if (assembler) {
|