@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
package/dist/mmtp-router.js
CHANGED
|
@@ -1610,14 +1610,15 @@ 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;
|
|
1617
|
+
messageListenerRegistered = false;
|
|
1616
1618
|
pendingPackets = /* @__PURE__ */ new Map();
|
|
1617
1619
|
constructor() {
|
|
1618
1620
|
this.messageHandler = (event) => {
|
|
1619
1621
|
const data = event.data;
|
|
1620
|
-
console.log(`[AMT] SW message received:`, data.type);
|
|
1621
1622
|
if (data.type === "UDP_PACKET") {
|
|
1622
1623
|
this.handlePacket(data);
|
|
1623
1624
|
} else if (data.type === "SOCKET_ERROR") {
|
|
@@ -1626,12 +1627,22 @@ var AMTGatewayManager = class {
|
|
|
1626
1627
|
console.log(`[AMT SW Debug] ${data.message}`);
|
|
1627
1628
|
}
|
|
1628
1629
|
};
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
console.warn("[AMT] Service worker not available");
|
|
1630
|
+
}
|
|
1631
|
+
ensureMessageListener() {
|
|
1632
|
+
if (this.messageListenerRegistered || typeof navigator === "undefined" || !navigator.serviceWorker || !this.messageHandler) {
|
|
1633
|
+
return;
|
|
1634
1634
|
}
|
|
1635
|
+
navigator.serviceWorker.addEventListener("message", this.messageHandler);
|
|
1636
|
+
this.messageListenerRegistered = true;
|
|
1637
|
+
console.log("[AMT] Registered service worker message handler");
|
|
1638
|
+
}
|
|
1639
|
+
removeMessageListener() {
|
|
1640
|
+
if (!this.messageListenerRegistered)
|
|
1641
|
+
return;
|
|
1642
|
+
if (this.messageHandler && typeof navigator !== "undefined" && navigator.serviceWorker) {
|
|
1643
|
+
navigator.serviceWorker.removeEventListener("message", this.messageHandler);
|
|
1644
|
+
}
|
|
1645
|
+
this.messageListenerRegistered = false;
|
|
1635
1646
|
}
|
|
1636
1647
|
/**
|
|
1637
1648
|
* Subscribe via AMT tunnel
|
|
@@ -1641,6 +1652,7 @@ var AMTGatewayManager = class {
|
|
|
1641
1652
|
if (config.protected) {
|
|
1642
1653
|
throw new Error("AMTGatewayManager refuses a protected join: it reports no per-datagram sender (multicast-02 7.2.7)");
|
|
1643
1654
|
}
|
|
1655
|
+
this.ensureMessageListener();
|
|
1644
1656
|
const keyStr = this.getKeyString(key);
|
|
1645
1657
|
if (this.subscriptions.has(keyStr)) {
|
|
1646
1658
|
console.log(`[AMT] Reusing existing subscription to ${keyStr}`);
|
|
@@ -1676,6 +1688,8 @@ var AMTGatewayManager = class {
|
|
|
1676
1688
|
this.subscriptions.delete(keyStr);
|
|
1677
1689
|
this.pendingPackets.delete(subscription.subscriptionId);
|
|
1678
1690
|
}
|
|
1691
|
+
if (this.subscriptions.size === 0)
|
|
1692
|
+
this.removeMessageListener();
|
|
1679
1693
|
throw new Error(`Failed to subscribe via AMT: ${error instanceof Error ? error.message : String(error)}`);
|
|
1680
1694
|
}
|
|
1681
1695
|
}
|
|
@@ -1697,6 +1711,8 @@ var AMTGatewayManager = class {
|
|
|
1697
1711
|
} finally {
|
|
1698
1712
|
this.subscriptions.delete(handle);
|
|
1699
1713
|
this.pendingPackets.delete(subscription.subscriptionId);
|
|
1714
|
+
if (this.subscriptions.size === 0)
|
|
1715
|
+
this.removeMessageListener();
|
|
1700
1716
|
}
|
|
1701
1717
|
}
|
|
1702
1718
|
/**
|
|
@@ -1713,14 +1729,12 @@ var AMTGatewayManager = class {
|
|
|
1713
1729
|
if (!subscriptionId)
|
|
1714
1730
|
return;
|
|
1715
1731
|
const subscription = this.subscriptions.get(subscriptionId);
|
|
1716
|
-
if (!subscription)
|
|
1717
|
-
console.warn(`[AMT] Received packet for unknown subscription: ${subscriptionId}`);
|
|
1732
|
+
if (!subscription)
|
|
1718
1733
|
return;
|
|
1719
|
-
}
|
|
1720
1734
|
if (!subscription.handshakeComplete) {
|
|
1721
1735
|
const queue = this.pendingPackets.get(subscriptionId);
|
|
1722
1736
|
const packetData2 = data;
|
|
1723
|
-
if (queue) {
|
|
1737
|
+
if (queue && queue.length < MAX_PENDING_PACKETS) {
|
|
1724
1738
|
queue.push({
|
|
1725
1739
|
data: packetData2.data,
|
|
1726
1740
|
remoteAddress: packetData2.remoteAddress,
|
|
@@ -1746,19 +1760,15 @@ var AMTGatewayManager = class {
|
|
|
1746
1760
|
if (!subscriptionId)
|
|
1747
1761
|
return;
|
|
1748
1762
|
const subscription = this.subscriptions.get(subscriptionId);
|
|
1749
|
-
if (!subscription)
|
|
1750
|
-
console.warn(`[AMT] Received error for unknown subscription: ${subscriptionId}`);
|
|
1763
|
+
if (!subscription)
|
|
1751
1764
|
return;
|
|
1752
|
-
}
|
|
1753
1765
|
subscription.config.onError(new Error(error || "Unknown error"));
|
|
1754
1766
|
}
|
|
1755
1767
|
/**
|
|
1756
1768
|
* Cleanup on destroy
|
|
1757
1769
|
*/
|
|
1758
1770
|
destroy() {
|
|
1759
|
-
|
|
1760
|
-
navigator.serviceWorker.removeEventListener("message", this.messageHandler);
|
|
1761
|
-
}
|
|
1771
|
+
this.removeMessageListener();
|
|
1762
1772
|
const handles = Array.from(this.subscriptions.keys());
|
|
1763
1773
|
for (const handle of handles) {
|
|
1764
1774
|
this.unsubscribe(handle).catch((err) => {
|
|
@@ -7667,9 +7677,21 @@ var IsoBmffTimingResolver = class {
|
|
|
7667
7677
|
}
|
|
7668
7678
|
#track = null;
|
|
7669
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();
|
|
7670
7688
|
#pendingSamples = 0;
|
|
7671
7689
|
#evictedSamples = 0;
|
|
7672
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
|
+
}
|
|
7673
7695
|
get pendingSampleCount() {
|
|
7674
7696
|
return this.#pendingSamples;
|
|
7675
7697
|
}
|
|
@@ -7693,7 +7715,14 @@ var IsoBmffTimingResolver = class {
|
|
|
7693
7715
|
this.#track = timing;
|
|
7694
7716
|
return true;
|
|
7695
7717
|
}
|
|
7696
|
-
|
|
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) {
|
|
7697
7726
|
this.#lastMovieFragmentRejection = null;
|
|
7698
7727
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
7699
7728
|
this.#lastMovieFragmentRejection = "invalid";
|
|
@@ -7719,6 +7748,8 @@ var IsoBmffTimingResolver = class {
|
|
|
7719
7748
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
7720
7749
|
);
|
|
7721
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;
|
|
7722
7753
|
let freed = 0;
|
|
7723
7754
|
while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
7724
7755
|
freed += this.#evictOldestFragment();
|
|
@@ -7732,6 +7763,7 @@ var IsoBmffTimingResolver = class {
|
|
|
7732
7763
|
return false;
|
|
7733
7764
|
}
|
|
7734
7765
|
this.#fragments.set(key, [...parsed.samples]);
|
|
7766
|
+
this.#firstSamples.set(key, parsed.samples[0]);
|
|
7735
7767
|
this.#pendingSamples += parsed.samples.length;
|
|
7736
7768
|
return true;
|
|
7737
7769
|
}
|
|
@@ -7789,6 +7821,7 @@ var IsoBmffTimingResolver = class {
|
|
|
7789
7821
|
let live = 0;
|
|
7790
7822
|
for (const sample of samples) if (sample !== null) live++;
|
|
7791
7823
|
this.#fragments.delete(oldest);
|
|
7824
|
+
this.#firstSamples.delete(oldest);
|
|
7792
7825
|
this.#pendingSamples -= live;
|
|
7793
7826
|
this.#evictedSamples += live;
|
|
7794
7827
|
return live;
|
|
@@ -7804,12 +7837,23 @@ var IsoBmffTimingResolver = class {
|
|
|
7804
7837
|
if (!samples || !samples[index]) return false;
|
|
7805
7838
|
samples[index] = null;
|
|
7806
7839
|
this.#pendingSamples--;
|
|
7807
|
-
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
|
+
}
|
|
7808
7850
|
return true;
|
|
7809
7851
|
}
|
|
7810
7852
|
reset() {
|
|
7811
7853
|
this.#track = null;
|
|
7812
7854
|
this.#fragments.clear();
|
|
7855
|
+
this.#firstSamples.clear();
|
|
7856
|
+
this.#delivered.clear();
|
|
7813
7857
|
this.#pendingSamples = 0;
|
|
7814
7858
|
}
|
|
7815
7859
|
};
|
|
@@ -8291,6 +8335,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8291
8335
|
this.#reclaimedTimingSamples = 0;
|
|
8292
8336
|
this.#evictedTimingSamples = 0;
|
|
8293
8337
|
this.#rejectedMovieFragments = 0;
|
|
8338
|
+
this.#recoveredHeadsAwaitingTiming = {
|
|
8339
|
+
held: 0,
|
|
8340
|
+
expired: 0,
|
|
8341
|
+
discarded: 0,
|
|
8342
|
+
superseded: 0
|
|
8343
|
+
};
|
|
8294
8344
|
this.#sequencers = /* @__PURE__ */ new Map();
|
|
8295
8345
|
this.#fecGate = null;
|
|
8296
8346
|
this.#fecSequencer = null;
|
|
@@ -8399,6 +8449,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8399
8449
|
#reclaimedTimingSamples;
|
|
8400
8450
|
#evictedTimingSamples;
|
|
8401
8451
|
#rejectedMovieFragments;
|
|
8452
|
+
#recoveredHeadsAwaitingTiming;
|
|
8402
8453
|
#sequencers;
|
|
8403
8454
|
#fecGate;
|
|
8404
8455
|
#fecSequencer;
|
|
@@ -8519,6 +8570,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8519
8570
|
rejectedMovieFragments: this.#rejectedMovieFragments
|
|
8520
8571
|
};
|
|
8521
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
|
+
}
|
|
8522
8581
|
get reorderStats() {
|
|
8523
8582
|
let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
|
|
8524
8583
|
for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
|
|
@@ -8692,7 +8751,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8692
8751
|
const now = Date.now();
|
|
8693
8752
|
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
8694
8753
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
8695
|
-
awaitingTiming.shift();
|
|
8754
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
8696
8755
|
}
|
|
8697
8756
|
}
|
|
8698
8757
|
}
|
|
@@ -8810,11 +8869,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8810
8869
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
8811
8870
|
return this.#drop("fragment-unexpected-fragmentation");
|
|
8812
8871
|
const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
|
|
8813
|
-
if (!resolver2) return this.#drop("fragment-no-resolver");
|
|
8872
|
+
if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
|
|
8814
8873
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
8815
8874
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
8816
8875
|
const remaining = this.#maxPendingTimingSamples - this.#pendingTimingSamples;
|
|
8817
|
-
const admitted = resolver2.addMovieFragment(
|
|
8876
|
+
const admitted = resolver2.addMovieFragment(
|
|
8877
|
+
mpu.mpuSequenceNumber,
|
|
8878
|
+
data,
|
|
8879
|
+
remaining,
|
|
8880
|
+
options?.recovered === true
|
|
8881
|
+
);
|
|
8818
8882
|
this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
|
|
8819
8883
|
this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
|
|
8820
8884
|
if (!admitted) {
|
|
@@ -8834,7 +8898,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8834
8898
|
};
|
|
8835
8899
|
}
|
|
8836
8900
|
if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
|
|
8837
|
-
const resolver = this.#timingResolver(streamKey, info.packetId,
|
|
8901
|
+
const resolver = this.#timingResolver(streamKey, info.packetId, true);
|
|
8838
8902
|
if (!resolver) return this.#drop("mfu-no-resolver");
|
|
8839
8903
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
8840
8904
|
return this.#drop("mfu-empty-payload");
|
|
@@ -8855,6 +8919,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8855
8919
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
8856
8920
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
8857
8921
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
8922
|
+
sequenceNumber: info.sequenceNumber,
|
|
8858
8923
|
arrivedAt: Date.now()
|
|
8859
8924
|
});
|
|
8860
8925
|
return this.#drop("mfu-sample-timing-missing");
|
|
@@ -9178,6 +9243,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9178
9243
|
}
|
|
9179
9244
|
this.#emitFrame(data, meta);
|
|
9180
9245
|
}
|
|
9246
|
+
/** Whether FT=1 metadata already names this sample, without claiming it. */
|
|
9247
|
+
hasAuthoredTimingFor(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
|
|
9248
|
+
return this.#timingResolver(this.#streamKey(packetId, catalogTrackName), packetId, false)?.sampleTiming(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) != null;
|
|
9249
|
+
}
|
|
9181
9250
|
/**
|
|
9182
9251
|
* Claim and return the authored timing for a recovered sample. Recovery does
|
|
9183
9252
|
* not pass through the ordinary `feed()` completion path, so it must retire
|
|
@@ -9239,6 +9308,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9239
9308
|
for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
|
|
9240
9309
|
this.#sequencers.clear();
|
|
9241
9310
|
this.#fecSequencer?.discard();
|
|
9311
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
9312
|
+
for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
9313
|
+
}
|
|
9242
9314
|
this.#timingStreams.clear();
|
|
9243
9315
|
this.#pendingTimingSamples = 0;
|
|
9244
9316
|
this.#reliableMoqStartupGate.clear();
|
|
@@ -9257,10 +9329,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9257
9329
|
return resolver;
|
|
9258
9330
|
}
|
|
9259
9331
|
#awaitTiming(streamKey, head) {
|
|
9260
|
-
const
|
|
9261
|
-
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;
|
|
9262
9340
|
awaitingTiming.push(head);
|
|
9263
|
-
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]++;
|
|
9264
9349
|
}
|
|
9265
9350
|
/**
|
|
9266
9351
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -9288,6 +9373,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9288
9373
|
const state = this.#timingStreams.get(streamKey);
|
|
9289
9374
|
if (!state) return;
|
|
9290
9375
|
this.#timingStreams.delete(streamKey);
|
|
9376
|
+
for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
9291
9377
|
this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
|
|
9292
9378
|
if (this.#pendingTimingSamples < 0) {
|
|
9293
9379
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
@@ -9828,6 +9914,16 @@ function keyframeOverlaySsId(header) {
|
|
|
9828
9914
|
if (ext?.type !== KEYFRAME_OVERLAY_EXTENSION_TYPE || ext.data.length !== 4) return void 0;
|
|
9829
9915
|
return new DataView(ext.data.buffer, ext.data.byteOffset, 4).getUint32(0);
|
|
9830
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;
|
|
9831
9927
|
var OVERLAY_OFFSET_MPUS = 16;
|
|
9832
9928
|
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
9833
9929
|
function assertStreamIdentity(role, stream) {
|
|
@@ -9939,6 +10035,7 @@ var MmtpRouter = class {
|
|
|
9939
10035
|
#provenance = /* @__PURE__ */ new Map();
|
|
9940
10036
|
#onFlowProtected = null;
|
|
9941
10037
|
#unadmittedListedDrops = 0;
|
|
10038
|
+
#admittedSourceDatagrams = 0;
|
|
9942
10039
|
#unlistedRepairSetAside = 0;
|
|
9943
10040
|
#assistBlocks = /* @__PURE__ */ new Map();
|
|
9944
10041
|
#assistFailures = /* @__PURE__ */ new Map();
|
|
@@ -9995,6 +10092,15 @@ var MmtpRouter = class {
|
|
|
9995
10092
|
* sequence is its SS_ID minus this; see #routeProcessedFecSymbols.
|
|
9996
10093
|
*/
|
|
9997
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();
|
|
9998
10104
|
/**
|
|
9999
10105
|
* Streams whose multicast leg delivered a RAP-flagged source packet that was
|
|
10000
10106
|
* not parsed, typically because its overlap copy was parsed first
|
|
@@ -10064,7 +10170,9 @@ var MmtpRouter = class {
|
|
|
10064
10170
|
recovered: 0,
|
|
10065
10171
|
restarts: 0,
|
|
10066
10172
|
unsequencedDropped: 0,
|
|
10067
|
-
protectedDropped: 0
|
|
10173
|
+
protectedDropped: 0,
|
|
10174
|
+
baseFed: 0,
|
|
10175
|
+
baseUndetermined: 0
|
|
10068
10176
|
};
|
|
10069
10177
|
/** Callback fired when a delta-track FEC block completes or fails. */
|
|
10070
10178
|
#onDeltaBlockCompleteCb = null;
|
|
@@ -10237,7 +10345,7 @@ var MmtpRouter = class {
|
|
|
10237
10345
|
const cb = this.#getFrameCallback(packetId);
|
|
10238
10346
|
if (isVideo && !assembler) {
|
|
10239
10347
|
if (frameMeta.recovered) {
|
|
10240
|
-
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb);
|
|
10348
|
+
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb, meta.fecSbn);
|
|
10241
10349
|
} else {
|
|
10242
10350
|
this.#routeDirectSourceVideo(packetId, data, frameMeta, cb);
|
|
10243
10351
|
}
|
|
@@ -10627,6 +10735,10 @@ var MmtpRouter = class {
|
|
|
10627
10735
|
get keyframeOverlayStats() {
|
|
10628
10736
|
return { ...this.#overlayStats };
|
|
10629
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
|
+
}
|
|
10630
10742
|
#isOverlayRepair(repairPacketId, repairTrackName) {
|
|
10631
10743
|
return this.#overlayLegs.has(repairPacketId) || repairTrackName !== void 0 && this.#overlayRepairNames.has(repairTrackName);
|
|
10632
10744
|
}
|
|
@@ -10677,6 +10789,11 @@ var MmtpRouter = class {
|
|
|
10677
10789
|
*/
|
|
10678
10790
|
resetKeyframeOverlays(sourcePacketId, catalogTrackName) {
|
|
10679
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
|
+
}
|
|
10680
10797
|
}
|
|
10681
10798
|
#resetOverlay(inst) {
|
|
10682
10799
|
inst.fec.resetTrack(inst.repairPacketId, inst.streamName);
|
|
@@ -10733,9 +10850,10 @@ var MmtpRouter = class {
|
|
|
10733
10850
|
* A source packet that carries a 0x8B01 overlay SS_ID is also a symbol of
|
|
10734
10851
|
* every overlay installed for its source, at that SS_ID, with the bytes the
|
|
10735
10852
|
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
10736
|
-
* 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.
|
|
10737
10855
|
*/
|
|
10738
|
-
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source) {
|
|
10856
|
+
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source, baseSsId) {
|
|
10739
10857
|
if (this.#overlays.size === 0) return;
|
|
10740
10858
|
const ssId = keyframeOverlaySsId(header);
|
|
10741
10859
|
if (ssId === void 0) return;
|
|
@@ -10744,7 +10862,11 @@ var MmtpRouter = class {
|
|
|
10744
10862
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
10745
10863
|
if (identity !== null) {
|
|
10746
10864
|
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
10747
|
-
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
|
+
});
|
|
10748
10870
|
if (inst.offsets.size > OVERLAY_OFFSET_MPUS) inst.offsets.delete(inst.offsets.keys().next().value);
|
|
10749
10871
|
}
|
|
10750
10872
|
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
@@ -10823,14 +10945,27 @@ var MmtpRouter = class {
|
|
|
10823
10945
|
const streamKey = this.#mediaStreamKey(sourcePacketId, sourceName);
|
|
10824
10946
|
const restamped = this.#restampedStreams.has(streamKey);
|
|
10825
10947
|
const restored = /* @__PURE__ */ new Set();
|
|
10948
|
+
const baseSsIds = /* @__PURE__ */ new Map();
|
|
10826
10949
|
const symbols = result.symbols.flatMap((sym) => {
|
|
10827
|
-
const
|
|
10828
|
-
if (
|
|
10829
|
-
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;
|
|
10830
10954
|
restored.add(packetSequenceNumber);
|
|
10831
|
-
|
|
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];
|
|
10832
10968
|
}
|
|
10833
|
-
if (!restamped) return [sym];
|
|
10834
10969
|
this.#overlayStats.unsequencedDropped++;
|
|
10835
10970
|
return [];
|
|
10836
10971
|
});
|
|
@@ -10839,6 +10974,85 @@ var MmtpRouter = class {
|
|
|
10839
10974
|
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
10840
10975
|
});
|
|
10841
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
|
+
}
|
|
10842
11056
|
}
|
|
10843
11057
|
/** fec-multicast-00 §6.6: report each base block whose RAP MFU fragments
|
|
10844
11058
|
* the overlay restored, when the keyframe's FT=0 and FT=1 are held too. A
|
|
@@ -10978,6 +11192,10 @@ var MmtpRouter = class {
|
|
|
10978
11192
|
get unadmittedListedDropCount() {
|
|
10979
11193
|
return this.#unadmittedListedDrops;
|
|
10980
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
|
+
}
|
|
10981
11199
|
/** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
|
|
10982
11200
|
get unlistedRepairSetAsideCount() {
|
|
10983
11201
|
return this.#unlistedRepairSetAside;
|
|
@@ -11464,6 +11682,8 @@ var MmtpRouter = class {
|
|
|
11464
11682
|
)) return;
|
|
11465
11683
|
fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);
|
|
11466
11684
|
fec.noteRepairGroupLength?.(blockStart, ownerPacketId, repair.rsbLength, catalogTrackName);
|
|
11685
|
+
this.#feedPendingRebuilt(ownerPacketId, catalogTrackName, placement.block);
|
|
11686
|
+
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
11467
11687
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
11468
11688
|
const recovered = fec.feedRepairSymbol(
|
|
11469
11689
|
ownerPacketId,
|
|
@@ -11601,6 +11821,7 @@ var MmtpRouter = class {
|
|
|
11601
11821
|
this.#droppedPreSyncVideo.delete(streamKey);
|
|
11602
11822
|
this.#latestVideoSeqByStream.delete(streamKey);
|
|
11603
11823
|
this.#ssIdSeqOffsetByStream.delete(streamKey);
|
|
11824
|
+
this.#unstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
11604
11825
|
this.#multicastRapTwinByStream.delete(streamKey);
|
|
11605
11826
|
this.#lastDirectSourceVideoTsByStream.delete(streamKey);
|
|
11606
11827
|
this.#lastDirectEmittedVideoTsByStream.delete(streamKey);
|
|
@@ -12235,6 +12456,7 @@ var MmtpRouter = class {
|
|
|
12235
12456
|
const parsed = this.#parser.parsePacket(packet);
|
|
12236
12457
|
if (!parsed) return;
|
|
12237
12458
|
const info = parsed.info;
|
|
12459
|
+
if (manifestAdmitted && !info.isRepair && ingress?.admittedBy !== USER_AGENT_VERIFIED) this.#admittedSourceDatagrams++;
|
|
12238
12460
|
if (source === "multicast" && !manifestAdmitted) {
|
|
12239
12461
|
this.#fecManager?.noteMulticastArrival?.(info.packetId, info.sequenceNumber);
|
|
12240
12462
|
}
|
|
@@ -12516,7 +12738,8 @@ var MmtpRouter = class {
|
|
|
12516
12738
|
parsed.header,
|
|
12517
12739
|
info.sequenceNumber,
|
|
12518
12740
|
info.rapFlag,
|
|
12519
|
-
source
|
|
12741
|
+
source,
|
|
12742
|
+
fecPayloadId?.ssId
|
|
12520
12743
|
);
|
|
12521
12744
|
}
|
|
12522
12745
|
}
|
|
@@ -12666,7 +12889,8 @@ var MmtpRouter = class {
|
|
|
12666
12889
|
if (stream === void 0) {
|
|
12667
12890
|
stream = {
|
|
12668
12891
|
key: JSON.stringify([packetId, catalogTrackName ?? null]),
|
|
12669
|
-
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId))
|
|
12892
|
+
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId)),
|
|
12893
|
+
overlayToBase: { routed: /* @__PURE__ */ new Set(), pending: /* @__PURE__ */ new Map() }
|
|
12670
12894
|
};
|
|
12671
12895
|
byName.set(catalogTrackName, stream);
|
|
12672
12896
|
}
|
|
@@ -12781,8 +13005,16 @@ var MmtpRouter = class {
|
|
|
12781
13005
|
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
12782
13006
|
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
12783
13007
|
}
|
|
12784
|
-
#routeDirectRecoveredVideo(packetId, data, meta, cb) {
|
|
13008
|
+
#routeDirectRecoveredVideo(packetId, data, meta, cb, fecSbn) {
|
|
12785
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
|
+
}
|
|
12786
13018
|
this.#enqueueDirectRecoveredVideo(packetId, data, meta);
|
|
12787
13019
|
const lastSourceTs = this.#lastDirectSourceVideoTsByStream.get(streamKey);
|
|
12788
13020
|
this.#drainDirectRecoveredVideo(
|
|
@@ -12936,6 +13168,13 @@ var MmtpRouter = class {
|
|
|
12936
13168
|
* frames by epoch only if pre-protection input ever matters.
|
|
12937
13169
|
*/
|
|
12938
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
|
+
}
|
|
12939
13178
|
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
12940
13179
|
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
12941
13180
|
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
@@ -13000,7 +13239,10 @@ var MmtpRouter = class {
|
|
|
13000
13239
|
...result,
|
|
13001
13240
|
symbols: result.symbols.map((sym) => {
|
|
13002
13241
|
const wire = this.#fecAnchorForRecovered(blockId, sym.index) - ssIdSeqOffset >>> 0;
|
|
13003
|
-
if (wire === sym.packetSequenceNumber)
|
|
13242
|
+
if (wire === sym.packetSequenceNumber) {
|
|
13243
|
+
this.#unstampedSsIdSeqOffsetByStream.set(streamKey, ssIdSeqOffset);
|
|
13244
|
+
return sym;
|
|
13245
|
+
}
|
|
13004
13246
|
this.#restampedRecoveredSeqs++;
|
|
13005
13247
|
this.#restampedStreams.add(streamKey);
|
|
13006
13248
|
return { ...sym, packetSequenceNumber: wire };
|
|
@@ -13036,6 +13278,29 @@ var MmtpRouter = class {
|
|
|
13036
13278
|
}
|
|
13037
13279
|
);
|
|
13038
13280
|
}
|
|
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)) };
|
|
13039
13304
|
const gated = this.#isFecTrack(ownerPacketId);
|
|
13040
13305
|
const assembler = this.#mseAssemblerFor(ownerPacketId, catalogTrackName);
|
|
13041
13306
|
if (assembler) {
|