@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/mmtp-router.js
CHANGED
|
@@ -1610,6 +1610,7 @@ var MOQT_PUBLISHER_PRIORITY = Object.freeze({
|
|
|
1610
1610
|
});
|
|
1611
1611
|
|
|
1612
1612
|
// node_modules/@blockcast/mmt-transport/dist/amt-gateway.js
|
|
1613
|
+
var MAX_PENDING_PACKETS = 256;
|
|
1613
1614
|
var AMTGatewayManager = class {
|
|
1614
1615
|
subscriptions = /* @__PURE__ */ new Map();
|
|
1615
1616
|
messageHandler;
|
|
@@ -1733,7 +1734,7 @@ var AMTGatewayManager = class {
|
|
|
1733
1734
|
if (!subscription.handshakeComplete) {
|
|
1734
1735
|
const queue = this.pendingPackets.get(subscriptionId);
|
|
1735
1736
|
const packetData2 = data;
|
|
1736
|
-
if (queue) {
|
|
1737
|
+
if (queue && queue.length < MAX_PENDING_PACKETS) {
|
|
1737
1738
|
queue.push({
|
|
1738
1739
|
data: packetData2.data,
|
|
1739
1740
|
remoteAddress: packetData2.remoteAddress,
|
|
@@ -7676,9 +7677,21 @@ var IsoBmffTimingResolver = class {
|
|
|
7676
7677
|
}
|
|
7677
7678
|
#track = null;
|
|
7678
7679
|
#fragments = /* @__PURE__ */ new Map();
|
|
7680
|
+
/** First sample of each pending fragment, kept in `#delivered` with its sample count. */
|
|
7681
|
+
#firstSamples = /* @__PURE__ */ new Map();
|
|
7682
|
+
/**
|
|
7683
|
+
* First sample and sample count of the newest fragments whose every slot was
|
|
7684
|
+
* cleared. Abandonment counts: `releaseSample` clears a slot too, which
|
|
7685
|
+
* matches the combiner, since it retires an abandoned sample as well.
|
|
7686
|
+
*/
|
|
7687
|
+
#delivered = /* @__PURE__ */ new Map();
|
|
7679
7688
|
#pendingSamples = 0;
|
|
7680
7689
|
#evictedSamples = 0;
|
|
7681
7690
|
#lastMovieFragmentRejection = null;
|
|
7691
|
+
/** True once an FT=0 has bound the track that FT=1 timing is read against. */
|
|
7692
|
+
get initialized() {
|
|
7693
|
+
return this.#track !== null;
|
|
7694
|
+
}
|
|
7682
7695
|
get pendingSampleCount() {
|
|
7683
7696
|
return this.#pendingSamples;
|
|
7684
7697
|
}
|
|
@@ -7702,7 +7715,14 @@ var IsoBmffTimingResolver = class {
|
|
|
7702
7715
|
this.#track = timing;
|
|
7703
7716
|
return true;
|
|
7704
7717
|
}
|
|
7705
|
-
|
|
7718
|
+
/**
|
|
7719
|
+
* `recovered` marks an FEC-recovered copy. A recovered block hands back
|
|
7720
|
+
* every FT=1 it holds, the received ones included, so a recovered copy of a
|
|
7721
|
+
* fragment whose samples are all done is a duplicate: admitting it again
|
|
7722
|
+
* would charge stale timing and could displace the live MPU's. A source
|
|
7723
|
+
* copy is still admitted again (a rendition switch re-arms timing that way).
|
|
7724
|
+
*/
|
|
7725
|
+
addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
|
|
7706
7726
|
this.#lastMovieFragmentRejection = null;
|
|
7707
7727
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
7708
7728
|
this.#lastMovieFragmentRejection = "invalid";
|
|
@@ -7728,6 +7748,8 @@ var IsoBmffTimingResolver = class {
|
|
|
7728
7748
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
7729
7749
|
);
|
|
7730
7750
|
}
|
|
7751
|
+
const delivered = recovered ? this.#delivered.get(key) : void 0;
|
|
7752
|
+
if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
|
|
7731
7753
|
let freed = 0;
|
|
7732
7754
|
while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
7733
7755
|
freed += this.#evictOldestFragment();
|
|
@@ -7741,6 +7763,7 @@ var IsoBmffTimingResolver = class {
|
|
|
7741
7763
|
return false;
|
|
7742
7764
|
}
|
|
7743
7765
|
this.#fragments.set(key, [...parsed.samples]);
|
|
7766
|
+
this.#firstSamples.set(key, parsed.samples[0]);
|
|
7744
7767
|
this.#pendingSamples += parsed.samples.length;
|
|
7745
7768
|
return true;
|
|
7746
7769
|
}
|
|
@@ -7798,6 +7821,7 @@ var IsoBmffTimingResolver = class {
|
|
|
7798
7821
|
let live = 0;
|
|
7799
7822
|
for (const sample of samples) if (sample !== null) live++;
|
|
7800
7823
|
this.#fragments.delete(oldest);
|
|
7824
|
+
this.#firstSamples.delete(oldest);
|
|
7801
7825
|
this.#pendingSamples -= live;
|
|
7802
7826
|
this.#evictedSamples += live;
|
|
7803
7827
|
return live;
|
|
@@ -7813,12 +7837,23 @@ var IsoBmffTimingResolver = class {
|
|
|
7813
7837
|
if (!samples || !samples[index]) return false;
|
|
7814
7838
|
samples[index] = null;
|
|
7815
7839
|
this.#pendingSamples--;
|
|
7816
|
-
if (samples.every((value) => value === null))
|
|
7840
|
+
if (samples.every((value) => value === null)) {
|
|
7841
|
+
this.#fragments.delete(key);
|
|
7842
|
+
const first = this.#firstSamples.get(key);
|
|
7843
|
+
this.#firstSamples.delete(key);
|
|
7844
|
+
this.#delivered.delete(key);
|
|
7845
|
+
this.#delivered.set(key, { first, sampleCount: samples.length });
|
|
7846
|
+
if (this.#delivered.size > this.maxPendingSamples) {
|
|
7847
|
+
this.#delivered.delete(this.#delivered.keys().next().value);
|
|
7848
|
+
}
|
|
7849
|
+
}
|
|
7817
7850
|
return true;
|
|
7818
7851
|
}
|
|
7819
7852
|
reset() {
|
|
7820
7853
|
this.#track = null;
|
|
7821
7854
|
this.#fragments.clear();
|
|
7855
|
+
this.#firstSamples.clear();
|
|
7856
|
+
this.#delivered.clear();
|
|
7822
7857
|
this.#pendingSamples = 0;
|
|
7823
7858
|
}
|
|
7824
7859
|
};
|
|
@@ -8300,6 +8335,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8300
8335
|
this.#reclaimedTimingSamples = 0;
|
|
8301
8336
|
this.#evictedTimingSamples = 0;
|
|
8302
8337
|
this.#rejectedMovieFragments = 0;
|
|
8338
|
+
this.#recoveredHeadsAwaitingTiming = {
|
|
8339
|
+
held: 0,
|
|
8340
|
+
expired: 0,
|
|
8341
|
+
discarded: 0,
|
|
8342
|
+
superseded: 0
|
|
8343
|
+
};
|
|
8303
8344
|
this.#sequencers = /* @__PURE__ */ new Map();
|
|
8304
8345
|
this.#fecGate = null;
|
|
8305
8346
|
this.#fecSequencer = null;
|
|
@@ -8408,6 +8449,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8408
8449
|
#reclaimedTimingSamples;
|
|
8409
8450
|
#evictedTimingSamples;
|
|
8410
8451
|
#rejectedMovieFragments;
|
|
8452
|
+
#recoveredHeadsAwaitingTiming;
|
|
8411
8453
|
#sequencers;
|
|
8412
8454
|
#fecGate;
|
|
8413
8455
|
#fecSequencer;
|
|
@@ -8528,6 +8570,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8528
8570
|
rejectedMovieFragments: this.#rejectedMovieFragments
|
|
8529
8571
|
};
|
|
8530
8572
|
}
|
|
8573
|
+
/** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
|
|
8574
|
+
get recoveredHeadsAwaitingTiming() {
|
|
8575
|
+
let pending = 0;
|
|
8576
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
8577
|
+
for (const head of awaitingTiming) if (head.options?.recovered) pending++;
|
|
8578
|
+
}
|
|
8579
|
+
return { ...this.#recoveredHeadsAwaitingTiming, pending };
|
|
8580
|
+
}
|
|
8531
8581
|
get reorderStats() {
|
|
8532
8582
|
let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
|
|
8533
8583
|
for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
|
|
@@ -8701,7 +8751,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8701
8751
|
const now = Date.now();
|
|
8702
8752
|
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
8703
8753
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
8704
|
-
awaitingTiming.shift();
|
|
8754
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
8705
8755
|
}
|
|
8706
8756
|
}
|
|
8707
8757
|
}
|
|
@@ -8819,11 +8869,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8819
8869
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
8820
8870
|
return this.#drop("fragment-unexpected-fragmentation");
|
|
8821
8871
|
const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
|
|
8822
|
-
if (!resolver2) return this.#drop("fragment-no-resolver");
|
|
8872
|
+
if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
|
|
8823
8873
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
8824
8874
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
8825
8875
|
const remaining = this.#maxPendingTimingSamples - this.#pendingTimingSamples;
|
|
8826
|
-
const admitted = resolver2.addMovieFragment(
|
|
8876
|
+
const admitted = resolver2.addMovieFragment(
|
|
8877
|
+
mpu.mpuSequenceNumber,
|
|
8878
|
+
data,
|
|
8879
|
+
remaining,
|
|
8880
|
+
options?.recovered === true
|
|
8881
|
+
);
|
|
8827
8882
|
this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
|
|
8828
8883
|
this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
|
|
8829
8884
|
if (!admitted) {
|
|
@@ -8843,7 +8898,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8843
8898
|
};
|
|
8844
8899
|
}
|
|
8845
8900
|
if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
|
|
8846
|
-
const resolver = this.#timingResolver(streamKey, info.packetId,
|
|
8901
|
+
const resolver = this.#timingResolver(streamKey, info.packetId, true);
|
|
8847
8902
|
if (!resolver) return this.#drop("mfu-no-resolver");
|
|
8848
8903
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
8849
8904
|
return this.#drop("mfu-empty-payload");
|
|
@@ -8864,6 +8919,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8864
8919
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
8865
8920
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
8866
8921
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
8922
|
+
sequenceNumber: info.sequenceNumber,
|
|
8867
8923
|
arrivedAt: Date.now()
|
|
8868
8924
|
});
|
|
8869
8925
|
return this.#drop("mfu-sample-timing-missing");
|
|
@@ -9252,6 +9308,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9252
9308
|
for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
|
|
9253
9309
|
this.#sequencers.clear();
|
|
9254
9310
|
this.#fecSequencer?.discard();
|
|
9311
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
9312
|
+
for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
9313
|
+
}
|
|
9255
9314
|
this.#timingStreams.clear();
|
|
9256
9315
|
this.#pendingTimingSamples = 0;
|
|
9257
9316
|
this.#reliableMoqStartupGate.clear();
|
|
@@ -9270,10 +9329,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9270
9329
|
return resolver;
|
|
9271
9330
|
}
|
|
9272
9331
|
#awaitTiming(streamKey, head) {
|
|
9273
|
-
const
|
|
9274
|
-
if (!
|
|
9332
|
+
const state = this.#timingStreams.get(streamKey);
|
|
9333
|
+
if (!state) return;
|
|
9334
|
+
if (!head.options?.recovered) {
|
|
9335
|
+
const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
|
|
9336
|
+
this.#recoveredHeadsAwaitingTiming.superseded += state.awaitingTiming.length - kept.length;
|
|
9337
|
+
state.awaitingTiming = kept;
|
|
9338
|
+
}
|
|
9339
|
+
const awaitingTiming = state.awaitingTiming;
|
|
9275
9340
|
awaitingTiming.push(head);
|
|
9276
|
-
if (
|
|
9341
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming.held++;
|
|
9342
|
+
if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
|
|
9343
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
9344
|
+
}
|
|
9345
|
+
}
|
|
9346
|
+
/** Count a recovered head leaving the queue other than by replay or its source copy. */
|
|
9347
|
+
#leaveAwaitingTiming(head, outcome) {
|
|
9348
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming[outcome]++;
|
|
9277
9349
|
}
|
|
9278
9350
|
/**
|
|
9279
9351
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -9301,6 +9373,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9301
9373
|
const state = this.#timingStreams.get(streamKey);
|
|
9302
9374
|
if (!state) return;
|
|
9303
9375
|
this.#timingStreams.delete(streamKey);
|
|
9376
|
+
for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
9304
9377
|
this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
|
|
9305
9378
|
if (this.#pendingTimingSamples < 0) {
|
|
9306
9379
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
@@ -9841,6 +9914,16 @@ function keyframeOverlaySsId(header) {
|
|
|
9841
9914
|
if (ext?.type !== KEYFRAME_OVERLAY_EXTENSION_TYPE || ext.data.length !== 4) return void 0;
|
|
9842
9915
|
return new DataView(ext.data.buffer, ext.data.byteOffset, 4).getUint32(0);
|
|
9843
9916
|
}
|
|
9917
|
+
function overlayBaseSsId(anchor, overlaySsId, restoredPsn, headerPsn) {
|
|
9918
|
+
if (anchor.baseSsId === void 0 || headerPsn !== restoredPsn) return void 0;
|
|
9919
|
+
return serial32Add(anchor.baseSsId, overlaySsId - anchor.overlaySsId | 0);
|
|
9920
|
+
}
|
|
9921
|
+
function unstampedBaseSsId(offset, unstampedOffset, headerPsn) {
|
|
9922
|
+
if (offset === void 0 || offset !== unstampedOffset) return void 0;
|
|
9923
|
+
return headerPsn + offset >>> 0;
|
|
9924
|
+
}
|
|
9925
|
+
var OVERLAY_ROUTED_LIMIT = 1024;
|
|
9926
|
+
var OVERLAY_PENDING_LIMIT = 256;
|
|
9844
9927
|
var OVERLAY_OFFSET_MPUS = 16;
|
|
9845
9928
|
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
9846
9929
|
function assertStreamIdentity(role, stream) {
|
|
@@ -9952,6 +10035,7 @@ var MmtpRouter = class {
|
|
|
9952
10035
|
#provenance = /* @__PURE__ */ new Map();
|
|
9953
10036
|
#onFlowProtected = null;
|
|
9954
10037
|
#unadmittedListedDrops = 0;
|
|
10038
|
+
#admittedSourceDatagrams = 0;
|
|
9955
10039
|
#unlistedRepairSetAside = 0;
|
|
9956
10040
|
#assistBlocks = /* @__PURE__ */ new Map();
|
|
9957
10041
|
#assistFailures = /* @__PURE__ */ new Map();
|
|
@@ -10008,6 +10092,15 @@ var MmtpRouter = class {
|
|
|
10008
10092
|
* sequence is its SS_ID minus this; see #routeProcessedFecSymbols.
|
|
10009
10093
|
*/
|
|
10010
10094
|
#ssIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
10095
|
+
/**
|
|
10096
|
+
* Per stream, the SS_ID offset at which a base recovery last showed the
|
|
10097
|
+
* sender's own packet_sequence_number: a recovered packet's header psn
|
|
10098
|
+
* equal to its SS_ID minus that offset. While it equals the stream's
|
|
10099
|
+
* current offset, no hop re-stamps the stream, and a packet's protected psn
|
|
10100
|
+
* names its base SS_ID within unstampedBaseSsId's limits (joint decoding of
|
|
10101
|
+
* an MPU no member of which arrived).
|
|
10102
|
+
*/
|
|
10103
|
+
#unstampedSsIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
10011
10104
|
/**
|
|
10012
10105
|
* Streams whose multicast leg delivered a RAP-flagged source packet that was
|
|
10013
10106
|
* not parsed, typically because its overlap copy was parsed first
|
|
@@ -10077,7 +10170,9 @@ var MmtpRouter = class {
|
|
|
10077
10170
|
recovered: 0,
|
|
10078
10171
|
restarts: 0,
|
|
10079
10172
|
unsequencedDropped: 0,
|
|
10080
|
-
protectedDropped: 0
|
|
10173
|
+
protectedDropped: 0,
|
|
10174
|
+
baseFed: 0,
|
|
10175
|
+
baseUndetermined: 0
|
|
10081
10176
|
};
|
|
10082
10177
|
/** Callback fired when a delta-track FEC block completes or fails. */
|
|
10083
10178
|
#onDeltaBlockCompleteCb = null;
|
|
@@ -10250,7 +10345,7 @@ var MmtpRouter = class {
|
|
|
10250
10345
|
const cb = this.#getFrameCallback(packetId);
|
|
10251
10346
|
if (isVideo && !assembler) {
|
|
10252
10347
|
if (frameMeta.recovered) {
|
|
10253
|
-
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb);
|
|
10348
|
+
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb, meta.fecSbn);
|
|
10254
10349
|
} else {
|
|
10255
10350
|
this.#routeDirectSourceVideo(packetId, data, frameMeta, cb);
|
|
10256
10351
|
}
|
|
@@ -10640,6 +10735,10 @@ var MmtpRouter = class {
|
|
|
10640
10735
|
get keyframeOverlayStats() {
|
|
10641
10736
|
return { ...this.#overlayStats };
|
|
10642
10737
|
}
|
|
10738
|
+
/** Recovered FT=2 heads held for their FT=1, and how each left the wait (BLO-53369). */
|
|
10739
|
+
get recoveredHeadsAwaitingTiming() {
|
|
10740
|
+
return this.#parser.recoveredHeadsAwaitingTiming;
|
|
10741
|
+
}
|
|
10643
10742
|
#isOverlayRepair(repairPacketId, repairTrackName) {
|
|
10644
10743
|
return this.#overlayLegs.has(repairPacketId) || repairTrackName !== void 0 && this.#overlayRepairNames.has(repairTrackName);
|
|
10645
10744
|
}
|
|
@@ -10690,6 +10789,11 @@ var MmtpRouter = class {
|
|
|
10690
10789
|
*/
|
|
10691
10790
|
resetKeyframeOverlays(sourcePacketId, catalogTrackName) {
|
|
10692
10791
|
for (const inst of this.#overlaysForSource(sourcePacketId, catalogTrackName)) this.#resetOverlay(inst);
|
|
10792
|
+
for (const [name, stream] of this.#streams.get(sourcePacketId) ?? []) {
|
|
10793
|
+
if (catalogTrackName !== void 0 && name !== catalogTrackName) continue;
|
|
10794
|
+
stream.overlayToBase.routed.clear();
|
|
10795
|
+
stream.overlayToBase.pending.clear();
|
|
10796
|
+
}
|
|
10693
10797
|
}
|
|
10694
10798
|
#resetOverlay(inst) {
|
|
10695
10799
|
inst.fec.resetTrack(inst.repairPacketId, inst.streamName);
|
|
@@ -10746,9 +10850,10 @@ var MmtpRouter = class {
|
|
|
10746
10850
|
* A source packet that carries a 0x8B01 overlay SS_ID is also a symbol of
|
|
10747
10851
|
* every overlay installed for its source, at that SS_ID, with the bytes the
|
|
10748
10852
|
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
10749
|
-
* included (7.1, 7.4.3).
|
|
10853
|
+
* included (7.1, 7.4.3). `baseSsId` is that trailer's SS_ID, when the base
|
|
10854
|
+
* FEC reads it.
|
|
10750
10855
|
*/
|
|
10751
|
-
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source) {
|
|
10856
|
+
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source, baseSsId) {
|
|
10752
10857
|
if (this.#overlays.size === 0) return;
|
|
10753
10858
|
const ssId = keyframeOverlaySsId(header);
|
|
10754
10859
|
if (ssId === void 0) return;
|
|
@@ -10757,7 +10862,11 @@ var MmtpRouter = class {
|
|
|
10757
10862
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
10758
10863
|
if (identity !== null) {
|
|
10759
10864
|
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
10760
|
-
inst.offsets.set(identity.mpuSequenceNumber,
|
|
10865
|
+
inst.offsets.set(identity.mpuSequenceNumber, {
|
|
10866
|
+
psnOffset: ssId - sequenceNumber >>> 0,
|
|
10867
|
+
overlaySsId: ssId,
|
|
10868
|
+
...baseSsId === void 0 ? {} : { baseSsId }
|
|
10869
|
+
});
|
|
10761
10870
|
if (inst.offsets.size > OVERLAY_OFFSET_MPUS) inst.offsets.delete(inst.offsets.keys().next().value);
|
|
10762
10871
|
}
|
|
10763
10872
|
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
@@ -10836,14 +10945,27 @@ var MmtpRouter = class {
|
|
|
10836
10945
|
const streamKey = this.#mediaStreamKey(sourcePacketId, sourceName);
|
|
10837
10946
|
const restamped = this.#restampedStreams.has(streamKey);
|
|
10838
10947
|
const restored = /* @__PURE__ */ new Set();
|
|
10948
|
+
const baseSsIds = /* @__PURE__ */ new Map();
|
|
10839
10949
|
const symbols = result.symbols.flatMap((sym) => {
|
|
10840
|
-
const
|
|
10841
|
-
if (
|
|
10842
|
-
const
|
|
10950
|
+
const anchor = inst.offsets.get(sym.fragment.mpuSequenceNumber);
|
|
10951
|
+
if (anchor !== void 0) {
|
|
10952
|
+
const overlaySsId = serial32Add(blockStart, sym.index);
|
|
10953
|
+
const packetSequenceNumber = overlaySsId - anchor.psnOffset >>> 0;
|
|
10843
10954
|
restored.add(packetSequenceNumber);
|
|
10844
|
-
|
|
10955
|
+
const out = { ...sym, packetSequenceNumber };
|
|
10956
|
+
const baseSsId = overlayBaseSsId(anchor, overlaySsId, packetSequenceNumber, sym.packetSequenceNumber);
|
|
10957
|
+
if (baseSsId !== void 0) baseSsIds.set(out, baseSsId);
|
|
10958
|
+
return [out];
|
|
10959
|
+
}
|
|
10960
|
+
if (!restamped) {
|
|
10961
|
+
const baseSsId = unstampedBaseSsId(
|
|
10962
|
+
this.#ssIdSeqOffsetByStream.get(streamKey),
|
|
10963
|
+
this.#unstampedSsIdSeqOffsetByStream.get(streamKey),
|
|
10964
|
+
sym.packetSequenceNumber
|
|
10965
|
+
);
|
|
10966
|
+
if (baseSsId !== void 0) baseSsIds.set(sym, baseSsId);
|
|
10967
|
+
return [sym];
|
|
10845
10968
|
}
|
|
10846
|
-
if (!restamped) return [sym];
|
|
10847
10969
|
this.#overlayStats.unsequencedDropped++;
|
|
10848
10970
|
return [];
|
|
10849
10971
|
});
|
|
@@ -10852,6 +10974,85 @@ var MmtpRouter = class {
|
|
|
10852
10974
|
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
10853
10975
|
});
|
|
10854
10976
|
this.#noteOverlayRecoveredRaps(sourcePacketId, sourceName, streamKey, symbols.filter((sym) => restored.has(sym.packetSequenceNumber)));
|
|
10977
|
+
this.#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds);
|
|
10978
|
+
}
|
|
10979
|
+
/**
|
|
10980
|
+
* Joint decoding (BLO-52085). A packet the overlay rebuilt is a source of
|
|
10981
|
+
* its base block too, once its base SS_ID is determined (overlayBaseSsId,
|
|
10982
|
+
* from a received member of its MPU, else unstampedBaseSsId),
|
|
10983
|
+
* so it is fed to the base decoder as a rebuilt source
|
|
10984
|
+
* (FECManager.feedRebuiltSourceSymbol): the base block has one erasure
|
|
10985
|
+
* fewer to solve, and its repair can recover the block's other packets.
|
|
10986
|
+
* A base block no repair has stated yet takes it when one does
|
|
10987
|
+
* (#feedPendingRebuilt).
|
|
10988
|
+
*
|
|
10989
|
+
* The overlay route already handled each packet fed (delivered it, or
|
|
10990
|
+
* held an untimed head for its FT=1), so a base recovery routes none of
|
|
10991
|
+
* them again (#routeVerifiedFecSymbols): a second recovered copy of a
|
|
10992
|
+
* held head would wait in the parser for timing its first copy consumed.
|
|
10993
|
+
* Only an overlay block decoded from multicast symbols alone is fed: with
|
|
10994
|
+
* a MoQ symbol in it, the base block's recovery would depend on bytes
|
|
10995
|
+
* that crossed no multicast socket, which the provenance counted from the
|
|
10996
|
+
* block's own arrivals cannot show.
|
|
10997
|
+
*/
|
|
10998
|
+
#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds) {
|
|
10999
|
+
const fec = this.#fecManager;
|
|
11000
|
+
const owner = inst.leg.sourcePacketId;
|
|
11001
|
+
const name = inst.leg.sourceCatalogTrackName;
|
|
11002
|
+
if (!fec?.feedRebuiltSourceSymbol || inst.moqBlocks.has(blockStart)) return;
|
|
11003
|
+
if (this.#classifyPacketId(owner) === null || fec.isTrackConfigured?.(owner) === false) return;
|
|
11004
|
+
const { duplicates, overlayToBase: { routed } } = this.#stream(owner, name);
|
|
11005
|
+
const feeds = [];
|
|
11006
|
+
for (const sym of symbols) {
|
|
11007
|
+
if (duplicates.sourceSeqs.has(sym.packetSequenceNumber)) continue;
|
|
11008
|
+
const ssId = baseSsIds.get(sym);
|
|
11009
|
+
if (ssId === void 0) {
|
|
11010
|
+
this.#overlayStats.baseUndetermined++;
|
|
11011
|
+
continue;
|
|
11012
|
+
}
|
|
11013
|
+
routed.delete(ssId);
|
|
11014
|
+
routed.add(ssId);
|
|
11015
|
+
if (routed.size > OVERLAY_ROUTED_LIMIT) routed.delete(routed.values().next().value);
|
|
11016
|
+
feeds.push([ssId, sym.rawSymbol]);
|
|
11017
|
+
}
|
|
11018
|
+
for (const [ssId, symbolData] of feeds) {
|
|
11019
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
11020
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
11021
|
+
}
|
|
11022
|
+
}
|
|
11023
|
+
/** Feed one rebuilt source to its base block, or keep it until a repair states that block. */
|
|
11024
|
+
#feedRebuiltToBase(owner, name, ssId, symbolData) {
|
|
11025
|
+
const fec = this.#fecManager;
|
|
11026
|
+
if (!fec?.feedRebuiltSourceSymbol) return;
|
|
11027
|
+
const coords = fec.locateIsoSource?.(owner, name, ssId);
|
|
11028
|
+
if (coords == null) {
|
|
11029
|
+
const pending = this.#stream(owner, name).overlayToBase.pending;
|
|
11030
|
+
pending.delete(ssId);
|
|
11031
|
+
pending.set(ssId, symbolData.slice());
|
|
11032
|
+
if (pending.size > OVERLAY_PENDING_LIMIT) pending.delete(pending.keys().next().value);
|
|
11033
|
+
return;
|
|
11034
|
+
}
|
|
11035
|
+
const fed = fec.feedRebuiltSourceSymbol(owner, ssId, symbolData, name);
|
|
11036
|
+
if (!fed.taken) return;
|
|
11037
|
+
this.#overlayStats.baseFed++;
|
|
11038
|
+
if (fed.recovered === null) return;
|
|
11039
|
+
const sbn = coords.sbn;
|
|
11040
|
+
this.#routeRecoveredBlock(sbn, fed.recovered, owner, name, () => {
|
|
11041
|
+
fec.cancelBlockTimeout(sbn, owner, name);
|
|
11042
|
+
fec.countRecovery(owner, sbn, name);
|
|
11043
|
+
fec.markBlockCompleted(sbn, owner, name);
|
|
11044
|
+
});
|
|
11045
|
+
}
|
|
11046
|
+
/** A repair stated `block`: the rebuilt sources that waited for it join it now. */
|
|
11047
|
+
#feedPendingRebuilt(owner, name, block) {
|
|
11048
|
+
const pending = this.#streams.get(owner)?.get(name)?.overlayToBase.pending;
|
|
11049
|
+
if (pending === void 0 || pending.size === 0) return;
|
|
11050
|
+
for (const [ssId, symbolData] of [...pending]) {
|
|
11051
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
11052
|
+
if (serial32Distance(block.start, ssId) >= block.length) continue;
|
|
11053
|
+
pending.delete(ssId);
|
|
11054
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
11055
|
+
}
|
|
10855
11056
|
}
|
|
10856
11057
|
/** fec-multicast-00 §6.6: report each base block whose RAP MFU fragments
|
|
10857
11058
|
* the overlay restored, when the keyframe's FT=0 and FT=1 are held too. A
|
|
@@ -10991,6 +11192,10 @@ var MmtpRouter = class {
|
|
|
10991
11192
|
get unadmittedListedDropCount() {
|
|
10992
11193
|
return this.#unadmittedListedDrops;
|
|
10993
11194
|
}
|
|
11195
|
+
/** 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. */
|
|
11196
|
+
get admittedSourceDatagramCount() {
|
|
11197
|
+
return this.#admittedSourceDatagrams;
|
|
11198
|
+
}
|
|
10994
11199
|
/** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
|
|
10995
11200
|
get unlistedRepairSetAsideCount() {
|
|
10996
11201
|
return this.#unlistedRepairSetAside;
|
|
@@ -11477,6 +11682,8 @@ var MmtpRouter = class {
|
|
|
11477
11682
|
)) return;
|
|
11478
11683
|
fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);
|
|
11479
11684
|
fec.noteRepairGroupLength?.(blockStart, ownerPacketId, repair.rsbLength, catalogTrackName);
|
|
11685
|
+
this.#feedPendingRebuilt(ownerPacketId, catalogTrackName, placement.block);
|
|
11686
|
+
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
11480
11687
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
11481
11688
|
const recovered = fec.feedRepairSymbol(
|
|
11482
11689
|
ownerPacketId,
|
|
@@ -11614,6 +11821,7 @@ var MmtpRouter = class {
|
|
|
11614
11821
|
this.#droppedPreSyncVideo.delete(streamKey);
|
|
11615
11822
|
this.#latestVideoSeqByStream.delete(streamKey);
|
|
11616
11823
|
this.#ssIdSeqOffsetByStream.delete(streamKey);
|
|
11824
|
+
this.#unstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
11617
11825
|
this.#multicastRapTwinByStream.delete(streamKey);
|
|
11618
11826
|
this.#lastDirectSourceVideoTsByStream.delete(streamKey);
|
|
11619
11827
|
this.#lastDirectEmittedVideoTsByStream.delete(streamKey);
|
|
@@ -12248,6 +12456,7 @@ var MmtpRouter = class {
|
|
|
12248
12456
|
const parsed = this.#parser.parsePacket(packet);
|
|
12249
12457
|
if (!parsed) return;
|
|
12250
12458
|
const info = parsed.info;
|
|
12459
|
+
if (manifestAdmitted && !info.isRepair && ingress?.admittedBy !== USER_AGENT_VERIFIED) this.#admittedSourceDatagrams++;
|
|
12251
12460
|
if (source === "multicast" && !manifestAdmitted) {
|
|
12252
12461
|
this.#fecManager?.noteMulticastArrival?.(info.packetId, info.sequenceNumber);
|
|
12253
12462
|
}
|
|
@@ -12529,7 +12738,8 @@ var MmtpRouter = class {
|
|
|
12529
12738
|
parsed.header,
|
|
12530
12739
|
info.sequenceNumber,
|
|
12531
12740
|
info.rapFlag,
|
|
12532
|
-
source
|
|
12741
|
+
source,
|
|
12742
|
+
fecPayloadId?.ssId
|
|
12533
12743
|
);
|
|
12534
12744
|
}
|
|
12535
12745
|
}
|
|
@@ -12679,7 +12889,8 @@ var MmtpRouter = class {
|
|
|
12679
12889
|
if (stream === void 0) {
|
|
12680
12890
|
stream = {
|
|
12681
12891
|
key: JSON.stringify([packetId, catalogTrackName ?? null]),
|
|
12682
|
-
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId))
|
|
12892
|
+
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId)),
|
|
12893
|
+
overlayToBase: { routed: /* @__PURE__ */ new Set(), pending: /* @__PURE__ */ new Map() }
|
|
12683
12894
|
};
|
|
12684
12895
|
byName.set(catalogTrackName, stream);
|
|
12685
12896
|
}
|
|
@@ -12794,8 +13005,16 @@ var MmtpRouter = class {
|
|
|
12794
13005
|
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
12795
13006
|
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
12796
13007
|
}
|
|
12797
|
-
#routeDirectRecoveredVideo(packetId, data, meta, cb) {
|
|
13008
|
+
#routeDirectRecoveredVideo(packetId, data, meta, cb, fecSbn) {
|
|
12798
13009
|
const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);
|
|
13010
|
+
if (this.#isFecTrack(packetId)) {
|
|
13011
|
+
const high = this.#deliveredVideoHighWaterByStream.get(streamKey);
|
|
13012
|
+
if (fecSbn !== void 0 || high !== void 0 && authoredAdjacent(high, meta)) {
|
|
13013
|
+
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
13014
|
+
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
13015
|
+
return;
|
|
13016
|
+
}
|
|
13017
|
+
}
|
|
12799
13018
|
this.#enqueueDirectRecoveredVideo(packetId, data, meta);
|
|
12800
13019
|
const lastSourceTs = this.#lastDirectSourceVideoTsByStream.get(streamKey);
|
|
12801
13020
|
this.#drainDirectRecoveredVideo(
|
|
@@ -12949,6 +13168,13 @@ var MmtpRouter = class {
|
|
|
12949
13168
|
* frames by epoch only if pre-protection input ever matters.
|
|
12950
13169
|
*/
|
|
12951
13170
|
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
13171
|
+
const overlayRouted = ownerPacketId == null ? void 0 : this.#streams.get(ownerPacketId)?.get(catalogTrackName)?.overlayToBase.routed;
|
|
13172
|
+
if (overlayRouted !== void 0 && overlayRouted.size > 0) {
|
|
13173
|
+
result = {
|
|
13174
|
+
...result,
|
|
13175
|
+
symbols: result.symbols.filter((sym) => !overlayRouted.has(this.#fecAnchorForRecovered(blockId, sym.index)))
|
|
13176
|
+
};
|
|
13177
|
+
}
|
|
12952
13178
|
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
12953
13179
|
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
12954
13180
|
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
@@ -13013,7 +13239,10 @@ var MmtpRouter = class {
|
|
|
13013
13239
|
...result,
|
|
13014
13240
|
symbols: result.symbols.map((sym) => {
|
|
13015
13241
|
const wire = this.#fecAnchorForRecovered(blockId, sym.index) - ssIdSeqOffset >>> 0;
|
|
13016
|
-
if (wire === sym.packetSequenceNumber)
|
|
13242
|
+
if (wire === sym.packetSequenceNumber) {
|
|
13243
|
+
this.#unstampedSsIdSeqOffsetByStream.set(streamKey, ssIdSeqOffset);
|
|
13244
|
+
return sym;
|
|
13245
|
+
}
|
|
13017
13246
|
this.#restampedRecoveredSeqs++;
|
|
13018
13247
|
this.#restampedStreams.add(streamKey);
|
|
13019
13248
|
return { ...sym, packetSequenceNumber: wire };
|
|
@@ -13049,16 +13278,29 @@ var MmtpRouter = class {
|
|
|
13049
13278
|
}
|
|
13050
13279
|
);
|
|
13051
13280
|
}
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
))
|
|
13061
|
-
|
|
13281
|
+
const untimedHead = (sym) => sym.fragmentType === 2 && sym.fragment.fragmentationIndicator === 1 && sym.fragment.movieFragmentSequenceNumber !== void 0 && sym.fragment.sampleNumber !== void 0 && !this.#parser.hasAuthoredTimingFor(
|
|
13282
|
+
ownerPacketId,
|
|
13283
|
+
sym.fragment.mpuSequenceNumber,
|
|
13284
|
+
sym.fragment.movieFragmentSequenceNumber,
|
|
13285
|
+
sym.fragment.sampleNumber,
|
|
13286
|
+
catalogTrackName
|
|
13287
|
+
);
|
|
13288
|
+
for (const sym of result.symbols) {
|
|
13289
|
+
if (!untimedHead(sym) || this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) continue;
|
|
13290
|
+
this.#feedViaParser(
|
|
13291
|
+
sym.rawSymbol,
|
|
13292
|
+
{ packetId: ownerPacketId, sequenceNumber: sym.packetSequenceNumber },
|
|
13293
|
+
void 0,
|
|
13294
|
+
{
|
|
13295
|
+
skipFecPayloadId: true,
|
|
13296
|
+
recovered: true,
|
|
13297
|
+
deliveryProvenance,
|
|
13298
|
+
...fecSbnOf(sym.index),
|
|
13299
|
+
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
13300
|
+
}
|
|
13301
|
+
);
|
|
13302
|
+
}
|
|
13303
|
+
result = { ...result, symbols: result.symbols.filter((sym) => !untimedHead(sym)) };
|
|
13062
13304
|
const gated = this.#isFecTrack(ownerPacketId);
|
|
13063
13305
|
const assembler = this.#mseAssemblerFor(ownerPacketId, catalogTrackName);
|
|
13064
13306
|
if (assembler) {
|