@blockcast/mmt-container 2.2.0-main.73af0eedcbc8 → 2.2.0-main.75b39a2cdddf
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 +1001 -215
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.js.map +1 -1
- 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/catalog-network-source.js.map +1 -1
- 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-state.js.map +1 -1
- 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/held-layer-admission.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -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/mfu-reassembler.js +12 -19
- package/dist/mfu-reassembler.js.map +2 -2
- package/dist/mmtp-container-parser.d.ts +58 -10
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +105 -34
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +229 -51
- package/dist/mmtp-container-parser.no-modules.js.map +3 -3
- package/dist/mmtp-decode-pipeline.js +104 -34
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-fec-session.d.ts +41 -3
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +118 -13
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +18 -1
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +323 -64
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-auth-catalog.js.map +1 -1
- package/dist/multicast-input.js.map +1 -1
- package/dist/multicast-manager.js +547 -72
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.js +28 -18
- package/dist/multicast-transport.js.map +2 -2
- package/dist/multicast-unwrap.js +12 -19
- package/dist/multicast-unwrap.js.map +2 -2
- package/dist/no-modules.js +222 -48
- package/dist/no-modules.js.map +3 -3
- package/dist/repair-fetch.js.map +1 -1
- package/dist/repair-layer-subscriptions.d.ts +82 -0
- package/dist/repair-layer-subscriptions.d.ts.map +1 -0
- package/dist/repair-layer-subscriptions.js +192 -0
- package/dist/repair-layer-subscriptions.js.map +7 -0
- package/dist/signaling.js.map +1 -1
- package/dist/timestamp-codec.js +12 -19
- package/dist/timestamp-codec.js.map +2 -2
- package/dist/track-session.d.ts +1 -1
- package/dist/track-session.js.map +1 -1
- package/dist/wasm-mfu-reassembler.js.map +1 -1
- package/dist/wasm-mmtp-container-parser.js +104 -34
- 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 +18 -2
- package/src/iso-block-locator.ts +19 -0
- package/src/iso-bmff-timing.ts +40 -1
- package/src/mmtp-container-parser.ts +125 -19
- package/src/mmtp-fec-session.ts +115 -15
- package/src/mmtp-router.ts +350 -13
- package/src/repair-layer-subscriptions.ts +261 -0
- package/src/track-session.ts +1 -1
package/dist/mmtp-router.js
CHANGED
|
@@ -149,30 +149,26 @@ function isEmptyOrExactLegacyTrailer(payload, start, end) {
|
|
|
149
149
|
const authLength = view2.getUint16(2, false);
|
|
150
150
|
return start + LEGACY_TRAILER_HEADER_SIZE + authLength === end;
|
|
151
151
|
}
|
|
152
|
-
function resolveDeclaredTrailers(payload, declaredEnd, fecType, options
|
|
152
|
+
function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
|
|
153
153
|
if (declaredEnd > payload.length)
|
|
154
154
|
return null;
|
|
155
155
|
if (options?.skipFecId) {
|
|
156
|
-
return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false
|
|
156
|
+
return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
|
|
157
157
|
}
|
|
158
|
-
const overlay = options?.overlayDeclared === true && rapFlag;
|
|
159
158
|
if (fecType === 1) {
|
|
160
|
-
const fecStart = payload.length -
|
|
159
|
+
const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
161
160
|
if (fecStart < declaredEnd || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {
|
|
162
161
|
return null;
|
|
163
162
|
}
|
|
164
|
-
return { hasFecId: true
|
|
163
|
+
return { hasFecId: true };
|
|
165
164
|
}
|
|
166
|
-
if (fecType !== 0
|
|
165
|
+
if (fecType !== 0)
|
|
167
166
|
return null;
|
|
168
167
|
if (isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length)) {
|
|
169
|
-
return { hasFecId: false
|
|
168
|
+
return { hasFecId: false };
|
|
170
169
|
}
|
|
171
170
|
return null;
|
|
172
171
|
}
|
|
173
|
-
function sourceFecTrailerSize(overlay) {
|
|
174
|
-
return overlay ? 2 * SOURCE_FEC_PAYLOAD_ID_SIZE : SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
175
|
-
}
|
|
176
172
|
function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
177
173
|
if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {
|
|
178
174
|
return {
|
|
@@ -225,11 +221,11 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
|
225
221
|
if ("rejection" in layoutResult)
|
|
226
222
|
return layoutResult;
|
|
227
223
|
const { layout } = layoutResult;
|
|
228
|
-
const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options
|
|
224
|
+
const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options);
|
|
229
225
|
if (!trailerLayout) {
|
|
230
226
|
if (!options?.skipFecId && header.fecType === 1) {
|
|
231
227
|
const observed = payload.length - layout.dataEnd;
|
|
232
|
-
const expected =
|
|
228
|
+
const expected = SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
233
229
|
if (observed !== expected) {
|
|
234
230
|
return {
|
|
235
231
|
rejection: {
|
|
@@ -261,13 +257,10 @@ function extractPlannedMfu(mmtpPacket, result) {
|
|
|
261
257
|
const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE) : null;
|
|
262
258
|
const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false) : null;
|
|
263
259
|
const data = payload.subarray(mfuOffset, dataEnd);
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
const fecPayloadId = trailerLayout.hasFecId ? ssIdAt(trailerStart) : null;
|
|
269
|
-
const overlayFecPayloadId = trailerLayout.hasOverlayFecId ? ssIdAt(trailerStart + SOURCE_FEC_PAYLOAD_ID_SIZE) : null;
|
|
270
|
-
return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId, overlayFecPayloadId };
|
|
260
|
+
const fecPayloadId = trailerLayout.hasFecId ? {
|
|
261
|
+
ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, 4).getUint32(0, false)
|
|
262
|
+
} : null;
|
|
263
|
+
return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
|
|
271
264
|
}
|
|
272
265
|
function sourceFecPayloadIdOf(mmtpPacket, header) {
|
|
273
266
|
if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
|
|
@@ -1617,14 +1610,15 @@ var MOQT_PUBLISHER_PRIORITY = Object.freeze({
|
|
|
1617
1610
|
});
|
|
1618
1611
|
|
|
1619
1612
|
// node_modules/@blockcast/mmt-transport/dist/amt-gateway.js
|
|
1613
|
+
var MAX_PENDING_PACKETS = 256;
|
|
1620
1614
|
var AMTGatewayManager = class {
|
|
1621
1615
|
subscriptions = /* @__PURE__ */ new Map();
|
|
1622
1616
|
messageHandler;
|
|
1617
|
+
messageListenerRegistered = false;
|
|
1623
1618
|
pendingPackets = /* @__PURE__ */ new Map();
|
|
1624
1619
|
constructor() {
|
|
1625
1620
|
this.messageHandler = (event) => {
|
|
1626
1621
|
const data = event.data;
|
|
1627
|
-
console.log(`[AMT] SW message received:`, data.type);
|
|
1628
1622
|
if (data.type === "UDP_PACKET") {
|
|
1629
1623
|
this.handlePacket(data);
|
|
1630
1624
|
} else if (data.type === "SOCKET_ERROR") {
|
|
@@ -1633,12 +1627,22 @@ var AMTGatewayManager = class {
|
|
|
1633
1627
|
console.log(`[AMT SW Debug] ${data.message}`);
|
|
1634
1628
|
}
|
|
1635
1629
|
};
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1630
|
+
}
|
|
1631
|
+
ensureMessageListener() {
|
|
1632
|
+
if (this.messageListenerRegistered || typeof navigator === "undefined" || !navigator.serviceWorker || !this.messageHandler) {
|
|
1633
|
+
return;
|
|
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);
|
|
1641
1644
|
}
|
|
1645
|
+
this.messageListenerRegistered = false;
|
|
1642
1646
|
}
|
|
1643
1647
|
/**
|
|
1644
1648
|
* Subscribe via AMT tunnel
|
|
@@ -1648,6 +1652,7 @@ var AMTGatewayManager = class {
|
|
|
1648
1652
|
if (config.protected) {
|
|
1649
1653
|
throw new Error("AMTGatewayManager refuses a protected join: it reports no per-datagram sender (multicast-02 7.2.7)");
|
|
1650
1654
|
}
|
|
1655
|
+
this.ensureMessageListener();
|
|
1651
1656
|
const keyStr = this.getKeyString(key);
|
|
1652
1657
|
if (this.subscriptions.has(keyStr)) {
|
|
1653
1658
|
console.log(`[AMT] Reusing existing subscription to ${keyStr}`);
|
|
@@ -1683,6 +1688,8 @@ var AMTGatewayManager = class {
|
|
|
1683
1688
|
this.subscriptions.delete(keyStr);
|
|
1684
1689
|
this.pendingPackets.delete(subscription.subscriptionId);
|
|
1685
1690
|
}
|
|
1691
|
+
if (this.subscriptions.size === 0)
|
|
1692
|
+
this.removeMessageListener();
|
|
1686
1693
|
throw new Error(`Failed to subscribe via AMT: ${error instanceof Error ? error.message : String(error)}`);
|
|
1687
1694
|
}
|
|
1688
1695
|
}
|
|
@@ -1704,6 +1711,8 @@ var AMTGatewayManager = class {
|
|
|
1704
1711
|
} finally {
|
|
1705
1712
|
this.subscriptions.delete(handle);
|
|
1706
1713
|
this.pendingPackets.delete(subscription.subscriptionId);
|
|
1714
|
+
if (this.subscriptions.size === 0)
|
|
1715
|
+
this.removeMessageListener();
|
|
1707
1716
|
}
|
|
1708
1717
|
}
|
|
1709
1718
|
/**
|
|
@@ -1720,14 +1729,12 @@ var AMTGatewayManager = class {
|
|
|
1720
1729
|
if (!subscriptionId)
|
|
1721
1730
|
return;
|
|
1722
1731
|
const subscription = this.subscriptions.get(subscriptionId);
|
|
1723
|
-
if (!subscription)
|
|
1724
|
-
console.warn(`[AMT] Received packet for unknown subscription: ${subscriptionId}`);
|
|
1732
|
+
if (!subscription)
|
|
1725
1733
|
return;
|
|
1726
|
-
}
|
|
1727
1734
|
if (!subscription.handshakeComplete) {
|
|
1728
1735
|
const queue = this.pendingPackets.get(subscriptionId);
|
|
1729
1736
|
const packetData2 = data;
|
|
1730
|
-
if (queue) {
|
|
1737
|
+
if (queue && queue.length < MAX_PENDING_PACKETS) {
|
|
1731
1738
|
queue.push({
|
|
1732
1739
|
data: packetData2.data,
|
|
1733
1740
|
remoteAddress: packetData2.remoteAddress,
|
|
@@ -1753,19 +1760,15 @@ var AMTGatewayManager = class {
|
|
|
1753
1760
|
if (!subscriptionId)
|
|
1754
1761
|
return;
|
|
1755
1762
|
const subscription = this.subscriptions.get(subscriptionId);
|
|
1756
|
-
if (!subscription)
|
|
1757
|
-
console.warn(`[AMT] Received error for unknown subscription: ${subscriptionId}`);
|
|
1763
|
+
if (!subscription)
|
|
1758
1764
|
return;
|
|
1759
|
-
}
|
|
1760
1765
|
subscription.config.onError(new Error(error || "Unknown error"));
|
|
1761
1766
|
}
|
|
1762
1767
|
/**
|
|
1763
1768
|
* Cleanup on destroy
|
|
1764
1769
|
*/
|
|
1765
1770
|
destroy() {
|
|
1766
|
-
|
|
1767
|
-
navigator.serviceWorker.removeEventListener("message", this.messageHandler);
|
|
1768
|
-
}
|
|
1771
|
+
this.removeMessageListener();
|
|
1769
1772
|
const handles = Array.from(this.subscriptions.keys());
|
|
1770
1773
|
for (const handle of handles) {
|
|
1771
1774
|
this.unsubscribe(handle).catch((err) => {
|
|
@@ -7674,9 +7677,21 @@ var IsoBmffTimingResolver = class {
|
|
|
7674
7677
|
}
|
|
7675
7678
|
#track = null;
|
|
7676
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();
|
|
7677
7688
|
#pendingSamples = 0;
|
|
7678
7689
|
#evictedSamples = 0;
|
|
7679
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
|
+
}
|
|
7680
7695
|
get pendingSampleCount() {
|
|
7681
7696
|
return this.#pendingSamples;
|
|
7682
7697
|
}
|
|
@@ -7700,7 +7715,14 @@ var IsoBmffTimingResolver = class {
|
|
|
7700
7715
|
this.#track = timing;
|
|
7701
7716
|
return true;
|
|
7702
7717
|
}
|
|
7703
|
-
|
|
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) {
|
|
7704
7726
|
this.#lastMovieFragmentRejection = null;
|
|
7705
7727
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
7706
7728
|
this.#lastMovieFragmentRejection = "invalid";
|
|
@@ -7726,6 +7748,8 @@ var IsoBmffTimingResolver = class {
|
|
|
7726
7748
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
7727
7749
|
);
|
|
7728
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;
|
|
7729
7753
|
let freed = 0;
|
|
7730
7754
|
while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
7731
7755
|
freed += this.#evictOldestFragment();
|
|
@@ -7739,6 +7763,7 @@ var IsoBmffTimingResolver = class {
|
|
|
7739
7763
|
return false;
|
|
7740
7764
|
}
|
|
7741
7765
|
this.#fragments.set(key, [...parsed.samples]);
|
|
7766
|
+
this.#firstSamples.set(key, parsed.samples[0]);
|
|
7742
7767
|
this.#pendingSamples += parsed.samples.length;
|
|
7743
7768
|
return true;
|
|
7744
7769
|
}
|
|
@@ -7796,6 +7821,7 @@ var IsoBmffTimingResolver = class {
|
|
|
7796
7821
|
let live = 0;
|
|
7797
7822
|
for (const sample of samples) if (sample !== null) live++;
|
|
7798
7823
|
this.#fragments.delete(oldest);
|
|
7824
|
+
this.#firstSamples.delete(oldest);
|
|
7799
7825
|
this.#pendingSamples -= live;
|
|
7800
7826
|
this.#evictedSamples += live;
|
|
7801
7827
|
return live;
|
|
@@ -7811,12 +7837,23 @@ var IsoBmffTimingResolver = class {
|
|
|
7811
7837
|
if (!samples || !samples[index]) return false;
|
|
7812
7838
|
samples[index] = null;
|
|
7813
7839
|
this.#pendingSamples--;
|
|
7814
|
-
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
|
+
}
|
|
7815
7850
|
return true;
|
|
7816
7851
|
}
|
|
7817
7852
|
reset() {
|
|
7818
7853
|
this.#track = null;
|
|
7819
7854
|
this.#fragments.clear();
|
|
7855
|
+
this.#firstSamples.clear();
|
|
7856
|
+
this.#delivered.clear();
|
|
7820
7857
|
this.#pendingSamples = 0;
|
|
7821
7858
|
}
|
|
7822
7859
|
};
|
|
@@ -8298,6 +8335,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8298
8335
|
this.#reclaimedTimingSamples = 0;
|
|
8299
8336
|
this.#evictedTimingSamples = 0;
|
|
8300
8337
|
this.#rejectedMovieFragments = 0;
|
|
8338
|
+
this.#recoveredHeadsAwaitingTiming = {
|
|
8339
|
+
held: 0,
|
|
8340
|
+
expired: 0,
|
|
8341
|
+
discarded: 0,
|
|
8342
|
+
superseded: 0
|
|
8343
|
+
};
|
|
8301
8344
|
this.#sequencers = /* @__PURE__ */ new Map();
|
|
8302
8345
|
this.#fecGate = null;
|
|
8303
8346
|
this.#fecSequencer = null;
|
|
@@ -8406,6 +8449,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8406
8449
|
#reclaimedTimingSamples;
|
|
8407
8450
|
#evictedTimingSamples;
|
|
8408
8451
|
#rejectedMovieFragments;
|
|
8452
|
+
#recoveredHeadsAwaitingTiming;
|
|
8409
8453
|
#sequencers;
|
|
8410
8454
|
#fecGate;
|
|
8411
8455
|
#fecSequencer;
|
|
@@ -8511,11 +8555,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8511
8555
|
/**
|
|
8512
8556
|
* Typed view of the ISO-BMFF timing-sample budget.
|
|
8513
8557
|
*
|
|
8514
|
-
*
|
|
8515
|
-
*
|
|
8516
|
-
*
|
|
8517
|
-
*
|
|
8518
|
-
*
|
|
8558
|
+
* Each stream admits timing against its own window of `capacity` slots
|
|
8559
|
+
* (BLO-52085). A stream at capacity displaces its own oldest timing to
|
|
8560
|
+
* admit the moof in hand, and `evicted` counts the slots so displaced:
|
|
8561
|
+
* timing a stream declared and never delivered shows up there. `pending`
|
|
8562
|
+
* sums every stream, so it can exceed `capacity`. Per repo policy this is
|
|
8563
|
+
* a producer-owned counter -- never infer budget state by parsing logs.
|
|
8519
8564
|
*/
|
|
8520
8565
|
get timingBudgetStats() {
|
|
8521
8566
|
return {
|
|
@@ -8526,6 +8571,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8526
8571
|
rejectedMovieFragments: this.#rejectedMovieFragments
|
|
8527
8572
|
};
|
|
8528
8573
|
}
|
|
8574
|
+
/** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
|
|
8575
|
+
get recoveredHeadsAwaitingTiming() {
|
|
8576
|
+
let pending = 0;
|
|
8577
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
8578
|
+
for (const head of awaitingTiming) if (head.options?.recovered) pending++;
|
|
8579
|
+
}
|
|
8580
|
+
return { ...this.#recoveredHeadsAwaitingTiming, pending };
|
|
8581
|
+
}
|
|
8529
8582
|
get reorderStats() {
|
|
8530
8583
|
let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
|
|
8531
8584
|
for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
|
|
@@ -8699,7 +8752,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8699
8752
|
const now = Date.now();
|
|
8700
8753
|
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
8701
8754
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
8702
|
-
awaitingTiming.shift();
|
|
8755
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
8703
8756
|
}
|
|
8704
8757
|
}
|
|
8705
8758
|
}
|
|
@@ -8817,11 +8870,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8817
8870
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
8818
8871
|
return this.#drop("fragment-unexpected-fragmentation");
|
|
8819
8872
|
const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
|
|
8820
|
-
if (!resolver2) return this.#drop("fragment-no-resolver");
|
|
8873
|
+
if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
|
|
8821
8874
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
8822
8875
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
8823
|
-
const remaining = this.#maxPendingTimingSamples -
|
|
8824
|
-
const admitted = resolver2.addMovieFragment(
|
|
8876
|
+
const remaining = this.#maxPendingTimingSamples - resolver2.pendingSampleCount;
|
|
8877
|
+
const admitted = resolver2.addMovieFragment(
|
|
8878
|
+
mpu.mpuSequenceNumber,
|
|
8879
|
+
data,
|
|
8880
|
+
remaining,
|
|
8881
|
+
options?.recovered === true
|
|
8882
|
+
);
|
|
8825
8883
|
this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
|
|
8826
8884
|
this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
|
|
8827
8885
|
if (!admitted) {
|
|
@@ -8841,7 +8899,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8841
8899
|
};
|
|
8842
8900
|
}
|
|
8843
8901
|
if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
|
|
8844
|
-
const resolver = this.#timingResolver(streamKey, info.packetId,
|
|
8902
|
+
const resolver = this.#timingResolver(streamKey, info.packetId, true);
|
|
8845
8903
|
if (!resolver) return this.#drop("mfu-no-resolver");
|
|
8846
8904
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
8847
8905
|
return this.#drop("mfu-empty-payload");
|
|
@@ -8862,6 +8920,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8862
8920
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
8863
8921
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
8864
8922
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
8923
|
+
sequenceNumber: info.sequenceNumber,
|
|
8865
8924
|
arrivedAt: Date.now()
|
|
8866
8925
|
});
|
|
8867
8926
|
return this.#drop("mfu-sample-timing-missing");
|
|
@@ -9185,6 +9244,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9185
9244
|
}
|
|
9186
9245
|
this.#emitFrame(data, meta);
|
|
9187
9246
|
}
|
|
9247
|
+
/** Whether FT=1 metadata already names this sample, without claiming it. */
|
|
9248
|
+
hasAuthoredTimingFor(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
|
|
9249
|
+
return this.#timingResolver(this.#streamKey(packetId, catalogTrackName), packetId, false)?.sampleTiming(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) != null;
|
|
9250
|
+
}
|
|
9188
9251
|
/**
|
|
9189
9252
|
* Claim and return the authored timing for a recovered sample. Recovery does
|
|
9190
9253
|
* not pass through the ordinary `feed()` completion path, so it must retire
|
|
@@ -9246,6 +9309,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9246
9309
|
for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
|
|
9247
9310
|
this.#sequencers.clear();
|
|
9248
9311
|
this.#fecSequencer?.discard();
|
|
9312
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
9313
|
+
for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
9314
|
+
}
|
|
9249
9315
|
this.#timingStreams.clear();
|
|
9250
9316
|
this.#pendingTimingSamples = 0;
|
|
9251
9317
|
this.#reliableMoqStartupGate.clear();
|
|
@@ -9264,10 +9330,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9264
9330
|
return resolver;
|
|
9265
9331
|
}
|
|
9266
9332
|
#awaitTiming(streamKey, head) {
|
|
9267
|
-
const
|
|
9268
|
-
if (!
|
|
9333
|
+
const state = this.#timingStreams.get(streamKey);
|
|
9334
|
+
if (!state) return;
|
|
9335
|
+
if (!head.options?.recovered) {
|
|
9336
|
+
const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
|
|
9337
|
+
this.#recoveredHeadsAwaitingTiming.superseded += state.awaitingTiming.length - kept.length;
|
|
9338
|
+
state.awaitingTiming = kept;
|
|
9339
|
+
}
|
|
9340
|
+
const awaitingTiming = state.awaitingTiming;
|
|
9269
9341
|
awaitingTiming.push(head);
|
|
9270
|
-
if (
|
|
9342
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming.held++;
|
|
9343
|
+
if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
|
|
9344
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
9347
|
+
/** Count a recovered head leaving the queue other than by replay or its source copy. */
|
|
9348
|
+
#leaveAwaitingTiming(head, outcome) {
|
|
9349
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming[outcome]++;
|
|
9271
9350
|
}
|
|
9272
9351
|
/**
|
|
9273
9352
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -9295,6 +9374,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9295
9374
|
const state = this.#timingStreams.get(streamKey);
|
|
9296
9375
|
if (!state) return;
|
|
9297
9376
|
this.#timingStreams.delete(streamKey);
|
|
9377
|
+
for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
9298
9378
|
this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
|
|
9299
9379
|
if (this.#pendingTimingSamples < 0) {
|
|
9300
9380
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
@@ -9835,6 +9915,16 @@ function keyframeOverlaySsId(header) {
|
|
|
9835
9915
|
if (ext?.type !== KEYFRAME_OVERLAY_EXTENSION_TYPE || ext.data.length !== 4) return void 0;
|
|
9836
9916
|
return new DataView(ext.data.buffer, ext.data.byteOffset, 4).getUint32(0);
|
|
9837
9917
|
}
|
|
9918
|
+
function overlayBaseSsId(anchor, overlaySsId, restoredPsn, headerPsn) {
|
|
9919
|
+
if (anchor.baseSsId === void 0 || headerPsn !== restoredPsn) return void 0;
|
|
9920
|
+
return serial32Add(anchor.baseSsId, overlaySsId - anchor.overlaySsId | 0);
|
|
9921
|
+
}
|
|
9922
|
+
function unstampedBaseSsId(offset, unstampedOffset, headerPsn) {
|
|
9923
|
+
if (offset === void 0 || offset !== unstampedOffset) return void 0;
|
|
9924
|
+
return headerPsn + offset >>> 0;
|
|
9925
|
+
}
|
|
9926
|
+
var OVERLAY_ROUTED_LIMIT = 1024;
|
|
9927
|
+
var OVERLAY_PENDING_LIMIT = 256;
|
|
9838
9928
|
var OVERLAY_OFFSET_MPUS = 16;
|
|
9839
9929
|
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
9840
9930
|
function assertStreamIdentity(role, stream) {
|
|
@@ -9946,6 +10036,7 @@ var MmtpRouter = class {
|
|
|
9946
10036
|
#provenance = /* @__PURE__ */ new Map();
|
|
9947
10037
|
#onFlowProtected = null;
|
|
9948
10038
|
#unadmittedListedDrops = 0;
|
|
10039
|
+
#admittedSourceDatagrams = 0;
|
|
9949
10040
|
#unlistedRepairSetAside = 0;
|
|
9950
10041
|
#assistBlocks = /* @__PURE__ */ new Map();
|
|
9951
10042
|
#assistFailures = /* @__PURE__ */ new Map();
|
|
@@ -10002,6 +10093,15 @@ var MmtpRouter = class {
|
|
|
10002
10093
|
* sequence is its SS_ID minus this; see #routeProcessedFecSymbols.
|
|
10003
10094
|
*/
|
|
10004
10095
|
#ssIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
10096
|
+
/**
|
|
10097
|
+
* Per stream, the SS_ID offset at which a base recovery last showed the
|
|
10098
|
+
* sender's own packet_sequence_number: a recovered packet's header psn
|
|
10099
|
+
* equal to its SS_ID minus that offset. While it equals the stream's
|
|
10100
|
+
* current offset, no hop re-stamps the stream, and a packet's protected psn
|
|
10101
|
+
* names its base SS_ID within unstampedBaseSsId's limits (joint decoding of
|
|
10102
|
+
* an MPU no member of which arrived).
|
|
10103
|
+
*/
|
|
10104
|
+
#unstampedSsIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
10005
10105
|
/**
|
|
10006
10106
|
* Streams whose multicast leg delivered a RAP-flagged source packet that was
|
|
10007
10107
|
* not parsed, typically because its overlap copy was parsed first
|
|
@@ -10071,7 +10171,9 @@ var MmtpRouter = class {
|
|
|
10071
10171
|
recovered: 0,
|
|
10072
10172
|
restarts: 0,
|
|
10073
10173
|
unsequencedDropped: 0,
|
|
10074
|
-
protectedDropped: 0
|
|
10174
|
+
protectedDropped: 0,
|
|
10175
|
+
baseFed: 0,
|
|
10176
|
+
baseUndetermined: 0
|
|
10075
10177
|
};
|
|
10076
10178
|
/** Callback fired when a delta-track FEC block completes or fails. */
|
|
10077
10179
|
#onDeltaBlockCompleteCb = null;
|
|
@@ -10244,7 +10346,7 @@ var MmtpRouter = class {
|
|
|
10244
10346
|
const cb = this.#getFrameCallback(packetId);
|
|
10245
10347
|
if (isVideo && !assembler) {
|
|
10246
10348
|
if (frameMeta.recovered) {
|
|
10247
|
-
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb);
|
|
10349
|
+
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb, meta.fecSbn);
|
|
10248
10350
|
} else {
|
|
10249
10351
|
this.#routeDirectSourceVideo(packetId, data, frameMeta, cb);
|
|
10250
10352
|
}
|
|
@@ -10634,6 +10736,10 @@ var MmtpRouter = class {
|
|
|
10634
10736
|
get keyframeOverlayStats() {
|
|
10635
10737
|
return { ...this.#overlayStats };
|
|
10636
10738
|
}
|
|
10739
|
+
/** Recovered FT=2 heads held for their FT=1, and how each left the wait (BLO-53369). */
|
|
10740
|
+
get recoveredHeadsAwaitingTiming() {
|
|
10741
|
+
return this.#parser.recoveredHeadsAwaitingTiming;
|
|
10742
|
+
}
|
|
10637
10743
|
#isOverlayRepair(repairPacketId, repairTrackName) {
|
|
10638
10744
|
return this.#overlayLegs.has(repairPacketId) || repairTrackName !== void 0 && this.#overlayRepairNames.has(repairTrackName);
|
|
10639
10745
|
}
|
|
@@ -10684,6 +10790,11 @@ var MmtpRouter = class {
|
|
|
10684
10790
|
*/
|
|
10685
10791
|
resetKeyframeOverlays(sourcePacketId, catalogTrackName) {
|
|
10686
10792
|
for (const inst of this.#overlaysForSource(sourcePacketId, catalogTrackName)) this.#resetOverlay(inst);
|
|
10793
|
+
for (const [name, stream] of this.#streams.get(sourcePacketId) ?? []) {
|
|
10794
|
+
if (catalogTrackName !== void 0 && name !== catalogTrackName) continue;
|
|
10795
|
+
stream.overlayToBase.routed.clear();
|
|
10796
|
+
stream.overlayToBase.pending.clear();
|
|
10797
|
+
}
|
|
10687
10798
|
}
|
|
10688
10799
|
#resetOverlay(inst) {
|
|
10689
10800
|
inst.fec.resetTrack(inst.repairPacketId, inst.streamName);
|
|
@@ -10740,9 +10851,10 @@ var MmtpRouter = class {
|
|
|
10740
10851
|
* A source packet that carries a 0x8B01 overlay SS_ID is also a symbol of
|
|
10741
10852
|
* every overlay installed for its source, at that SS_ID, with the bytes the
|
|
10742
10853
|
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
10743
|
-
* included (7.1, 7.4.3).
|
|
10854
|
+
* included (7.1, 7.4.3). `baseSsId` is that trailer's SS_ID, when the base
|
|
10855
|
+
* FEC reads it.
|
|
10744
10856
|
*/
|
|
10745
|
-
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source) {
|
|
10857
|
+
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source, baseSsId) {
|
|
10746
10858
|
if (this.#overlays.size === 0) return;
|
|
10747
10859
|
const ssId = keyframeOverlaySsId(header);
|
|
10748
10860
|
if (ssId === void 0) return;
|
|
@@ -10751,7 +10863,11 @@ var MmtpRouter = class {
|
|
|
10751
10863
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
10752
10864
|
if (identity !== null) {
|
|
10753
10865
|
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
10754
|
-
inst.offsets.set(identity.mpuSequenceNumber,
|
|
10866
|
+
inst.offsets.set(identity.mpuSequenceNumber, {
|
|
10867
|
+
psnOffset: ssId - sequenceNumber >>> 0,
|
|
10868
|
+
overlaySsId: ssId,
|
|
10869
|
+
...baseSsId === void 0 ? {} : { baseSsId }
|
|
10870
|
+
});
|
|
10755
10871
|
if (inst.offsets.size > OVERLAY_OFFSET_MPUS) inst.offsets.delete(inst.offsets.keys().next().value);
|
|
10756
10872
|
}
|
|
10757
10873
|
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
@@ -10830,14 +10946,27 @@ var MmtpRouter = class {
|
|
|
10830
10946
|
const streamKey = this.#mediaStreamKey(sourcePacketId, sourceName);
|
|
10831
10947
|
const restamped = this.#restampedStreams.has(streamKey);
|
|
10832
10948
|
const restored = /* @__PURE__ */ new Set();
|
|
10949
|
+
const baseSsIds = /* @__PURE__ */ new Map();
|
|
10833
10950
|
const symbols = result.symbols.flatMap((sym) => {
|
|
10834
|
-
const
|
|
10835
|
-
if (
|
|
10836
|
-
const
|
|
10951
|
+
const anchor = inst.offsets.get(sym.fragment.mpuSequenceNumber);
|
|
10952
|
+
if (anchor !== void 0) {
|
|
10953
|
+
const overlaySsId = serial32Add(blockStart, sym.index);
|
|
10954
|
+
const packetSequenceNumber = overlaySsId - anchor.psnOffset >>> 0;
|
|
10837
10955
|
restored.add(packetSequenceNumber);
|
|
10838
|
-
|
|
10956
|
+
const out = { ...sym, packetSequenceNumber };
|
|
10957
|
+
const baseSsId = overlayBaseSsId(anchor, overlaySsId, packetSequenceNumber, sym.packetSequenceNumber);
|
|
10958
|
+
if (baseSsId !== void 0) baseSsIds.set(out, baseSsId);
|
|
10959
|
+
return [out];
|
|
10960
|
+
}
|
|
10961
|
+
if (!restamped) {
|
|
10962
|
+
const baseSsId = unstampedBaseSsId(
|
|
10963
|
+
this.#ssIdSeqOffsetByStream.get(streamKey),
|
|
10964
|
+
this.#unstampedSsIdSeqOffsetByStream.get(streamKey),
|
|
10965
|
+
sym.packetSequenceNumber
|
|
10966
|
+
);
|
|
10967
|
+
if (baseSsId !== void 0) baseSsIds.set(sym, baseSsId);
|
|
10968
|
+
return [sym];
|
|
10839
10969
|
}
|
|
10840
|
-
if (!restamped) return [sym];
|
|
10841
10970
|
this.#overlayStats.unsequencedDropped++;
|
|
10842
10971
|
return [];
|
|
10843
10972
|
});
|
|
@@ -10846,6 +10975,85 @@ var MmtpRouter = class {
|
|
|
10846
10975
|
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
10847
10976
|
});
|
|
10848
10977
|
this.#noteOverlayRecoveredRaps(sourcePacketId, sourceName, streamKey, symbols.filter((sym) => restored.has(sym.packetSequenceNumber)));
|
|
10978
|
+
this.#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds);
|
|
10979
|
+
}
|
|
10980
|
+
/**
|
|
10981
|
+
* Joint decoding (BLO-52085). A packet the overlay rebuilt is a source of
|
|
10982
|
+
* its base block too, once its base SS_ID is determined (overlayBaseSsId,
|
|
10983
|
+
* from a received member of its MPU, else unstampedBaseSsId),
|
|
10984
|
+
* so it is fed to the base decoder as a rebuilt source
|
|
10985
|
+
* (FECManager.feedRebuiltSourceSymbol): the base block has one erasure
|
|
10986
|
+
* fewer to solve, and its repair can recover the block's other packets.
|
|
10987
|
+
* A base block no repair has stated yet takes it when one does
|
|
10988
|
+
* (#feedPendingRebuilt).
|
|
10989
|
+
*
|
|
10990
|
+
* The overlay route already handled each packet fed (delivered it, or
|
|
10991
|
+
* held an untimed head for its FT=1), so a base recovery routes none of
|
|
10992
|
+
* them again (#routeVerifiedFecSymbols): a second recovered copy of a
|
|
10993
|
+
* held head would wait in the parser for timing its first copy consumed.
|
|
10994
|
+
* Only an overlay block decoded from multicast symbols alone is fed: with
|
|
10995
|
+
* a MoQ symbol in it, the base block's recovery would depend on bytes
|
|
10996
|
+
* that crossed no multicast socket, which the provenance counted from the
|
|
10997
|
+
* block's own arrivals cannot show.
|
|
10998
|
+
*/
|
|
10999
|
+
#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds) {
|
|
11000
|
+
const fec = this.#fecManager;
|
|
11001
|
+
const owner = inst.leg.sourcePacketId;
|
|
11002
|
+
const name = inst.leg.sourceCatalogTrackName;
|
|
11003
|
+
if (!fec?.feedRebuiltSourceSymbol || inst.moqBlocks.has(blockStart)) return;
|
|
11004
|
+
if (this.#classifyPacketId(owner) === null || fec.isTrackConfigured?.(owner) === false) return;
|
|
11005
|
+
const { duplicates, overlayToBase: { routed } } = this.#stream(owner, name);
|
|
11006
|
+
const feeds = [];
|
|
11007
|
+
for (const sym of symbols) {
|
|
11008
|
+
if (duplicates.sourceSeqs.has(sym.packetSequenceNumber)) continue;
|
|
11009
|
+
const ssId = baseSsIds.get(sym);
|
|
11010
|
+
if (ssId === void 0) {
|
|
11011
|
+
this.#overlayStats.baseUndetermined++;
|
|
11012
|
+
continue;
|
|
11013
|
+
}
|
|
11014
|
+
routed.delete(ssId);
|
|
11015
|
+
routed.add(ssId);
|
|
11016
|
+
if (routed.size > OVERLAY_ROUTED_LIMIT) routed.delete(routed.values().next().value);
|
|
11017
|
+
feeds.push([ssId, sym.rawSymbol]);
|
|
11018
|
+
}
|
|
11019
|
+
for (const [ssId, symbolData] of feeds) {
|
|
11020
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
11021
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
11022
|
+
}
|
|
11023
|
+
}
|
|
11024
|
+
/** Feed one rebuilt source to its base block, or keep it until a repair states that block. */
|
|
11025
|
+
#feedRebuiltToBase(owner, name, ssId, symbolData) {
|
|
11026
|
+
const fec = this.#fecManager;
|
|
11027
|
+
if (!fec?.feedRebuiltSourceSymbol) return;
|
|
11028
|
+
const coords = fec.locateIsoSource?.(owner, name, ssId);
|
|
11029
|
+
if (coords == null) {
|
|
11030
|
+
const pending = this.#stream(owner, name).overlayToBase.pending;
|
|
11031
|
+
pending.delete(ssId);
|
|
11032
|
+
pending.set(ssId, symbolData.slice());
|
|
11033
|
+
if (pending.size > OVERLAY_PENDING_LIMIT) pending.delete(pending.keys().next().value);
|
|
11034
|
+
return;
|
|
11035
|
+
}
|
|
11036
|
+
const fed = fec.feedRebuiltSourceSymbol(owner, ssId, symbolData, name);
|
|
11037
|
+
if (!fed.taken) return;
|
|
11038
|
+
this.#overlayStats.baseFed++;
|
|
11039
|
+
if (fed.recovered === null) return;
|
|
11040
|
+
const sbn = coords.sbn;
|
|
11041
|
+
this.#routeRecoveredBlock(sbn, fed.recovered, owner, name, () => {
|
|
11042
|
+
fec.cancelBlockTimeout(sbn, owner, name);
|
|
11043
|
+
fec.countRecovery(owner, sbn, name);
|
|
11044
|
+
fec.markBlockCompleted(sbn, owner, name);
|
|
11045
|
+
});
|
|
11046
|
+
}
|
|
11047
|
+
/** A repair stated `block`: the rebuilt sources that waited for it join it now. */
|
|
11048
|
+
#feedPendingRebuilt(owner, name, block) {
|
|
11049
|
+
const pending = this.#streams.get(owner)?.get(name)?.overlayToBase.pending;
|
|
11050
|
+
if (pending === void 0 || pending.size === 0) return;
|
|
11051
|
+
for (const [ssId, symbolData] of [...pending]) {
|
|
11052
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
11053
|
+
if (serial32Distance(block.start, ssId) >= block.length) continue;
|
|
11054
|
+
pending.delete(ssId);
|
|
11055
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
11056
|
+
}
|
|
10849
11057
|
}
|
|
10850
11058
|
/** fec-multicast-00 §6.6: report each base block whose RAP MFU fragments
|
|
10851
11059
|
* the overlay restored, when the keyframe's FT=0 and FT=1 are held too. A
|
|
@@ -10985,6 +11193,10 @@ var MmtpRouter = class {
|
|
|
10985
11193
|
get unadmittedListedDropCount() {
|
|
10986
11194
|
return this.#unadmittedListedDrops;
|
|
10987
11195
|
}
|
|
11196
|
+
/** 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. */
|
|
11197
|
+
get admittedSourceDatagramCount() {
|
|
11198
|
+
return this.#admittedSourceDatagrams;
|
|
11199
|
+
}
|
|
10988
11200
|
/** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
|
|
10989
11201
|
get unlistedRepairSetAsideCount() {
|
|
10990
11202
|
return this.#unlistedRepairSetAside;
|
|
@@ -11471,6 +11683,8 @@ var MmtpRouter = class {
|
|
|
11471
11683
|
)) return;
|
|
11472
11684
|
fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);
|
|
11473
11685
|
fec.noteRepairGroupLength?.(blockStart, ownerPacketId, repair.rsbLength, catalogTrackName);
|
|
11686
|
+
this.#feedPendingRebuilt(ownerPacketId, catalogTrackName, placement.block);
|
|
11687
|
+
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
11474
11688
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
11475
11689
|
const recovered = fec.feedRepairSymbol(
|
|
11476
11690
|
ownerPacketId,
|
|
@@ -11608,6 +11822,7 @@ var MmtpRouter = class {
|
|
|
11608
11822
|
this.#droppedPreSyncVideo.delete(streamKey);
|
|
11609
11823
|
this.#latestVideoSeqByStream.delete(streamKey);
|
|
11610
11824
|
this.#ssIdSeqOffsetByStream.delete(streamKey);
|
|
11825
|
+
this.#unstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
11611
11826
|
this.#multicastRapTwinByStream.delete(streamKey);
|
|
11612
11827
|
this.#lastDirectSourceVideoTsByStream.delete(streamKey);
|
|
11613
11828
|
this.#lastDirectEmittedVideoTsByStream.delete(streamKey);
|
|
@@ -12242,6 +12457,7 @@ var MmtpRouter = class {
|
|
|
12242
12457
|
const parsed = this.#parser.parsePacket(packet);
|
|
12243
12458
|
if (!parsed) return;
|
|
12244
12459
|
const info = parsed.info;
|
|
12460
|
+
if (manifestAdmitted && !info.isRepair && ingress?.admittedBy !== USER_AGENT_VERIFIED) this.#admittedSourceDatagrams++;
|
|
12245
12461
|
if (source === "multicast" && !manifestAdmitted) {
|
|
12246
12462
|
this.#fecManager?.noteMulticastArrival?.(info.packetId, info.sequenceNumber);
|
|
12247
12463
|
}
|
|
@@ -12523,7 +12739,8 @@ var MmtpRouter = class {
|
|
|
12523
12739
|
parsed.header,
|
|
12524
12740
|
info.sequenceNumber,
|
|
12525
12741
|
info.rapFlag,
|
|
12526
|
-
source
|
|
12742
|
+
source,
|
|
12743
|
+
fecPayloadId?.ssId
|
|
12527
12744
|
);
|
|
12528
12745
|
}
|
|
12529
12746
|
}
|
|
@@ -12673,7 +12890,8 @@ var MmtpRouter = class {
|
|
|
12673
12890
|
if (stream === void 0) {
|
|
12674
12891
|
stream = {
|
|
12675
12892
|
key: JSON.stringify([packetId, catalogTrackName ?? null]),
|
|
12676
|
-
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId))
|
|
12893
|
+
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId)),
|
|
12894
|
+
overlayToBase: { routed: /* @__PURE__ */ new Set(), pending: /* @__PURE__ */ new Map() }
|
|
12677
12895
|
};
|
|
12678
12896
|
byName.set(catalogTrackName, stream);
|
|
12679
12897
|
}
|
|
@@ -12788,8 +13006,16 @@ var MmtpRouter = class {
|
|
|
12788
13006
|
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
12789
13007
|
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
12790
13008
|
}
|
|
12791
|
-
#routeDirectRecoveredVideo(packetId, data, meta, cb) {
|
|
13009
|
+
#routeDirectRecoveredVideo(packetId, data, meta, cb, fecSbn) {
|
|
12792
13010
|
const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);
|
|
13011
|
+
if (this.#isFecTrack(packetId)) {
|
|
13012
|
+
const high = this.#deliveredVideoHighWaterByStream.get(streamKey);
|
|
13013
|
+
if (fecSbn !== void 0 || high !== void 0 && authoredAdjacent(high, meta)) {
|
|
13014
|
+
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
13015
|
+
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
13016
|
+
return;
|
|
13017
|
+
}
|
|
13018
|
+
}
|
|
12793
13019
|
this.#enqueueDirectRecoveredVideo(packetId, data, meta);
|
|
12794
13020
|
const lastSourceTs = this.#lastDirectSourceVideoTsByStream.get(streamKey);
|
|
12795
13021
|
this.#drainDirectRecoveredVideo(
|
|
@@ -12943,6 +13169,13 @@ var MmtpRouter = class {
|
|
|
12943
13169
|
* frames by epoch only if pre-protection input ever matters.
|
|
12944
13170
|
*/
|
|
12945
13171
|
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
13172
|
+
const overlayRouted = ownerPacketId == null ? void 0 : this.#streams.get(ownerPacketId)?.get(catalogTrackName)?.overlayToBase.routed;
|
|
13173
|
+
if (overlayRouted !== void 0 && overlayRouted.size > 0) {
|
|
13174
|
+
result = {
|
|
13175
|
+
...result,
|
|
13176
|
+
symbols: result.symbols.filter((sym) => !overlayRouted.has(this.#fecAnchorForRecovered(blockId, sym.index)))
|
|
13177
|
+
};
|
|
13178
|
+
}
|
|
12946
13179
|
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
12947
13180
|
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
12948
13181
|
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
@@ -13007,7 +13240,10 @@ var MmtpRouter = class {
|
|
|
13007
13240
|
...result,
|
|
13008
13241
|
symbols: result.symbols.map((sym) => {
|
|
13009
13242
|
const wire = this.#fecAnchorForRecovered(blockId, sym.index) - ssIdSeqOffset >>> 0;
|
|
13010
|
-
if (wire === sym.packetSequenceNumber)
|
|
13243
|
+
if (wire === sym.packetSequenceNumber) {
|
|
13244
|
+
this.#unstampedSsIdSeqOffsetByStream.set(streamKey, ssIdSeqOffset);
|
|
13245
|
+
return sym;
|
|
13246
|
+
}
|
|
13011
13247
|
this.#restampedRecoveredSeqs++;
|
|
13012
13248
|
this.#restampedStreams.add(streamKey);
|
|
13013
13249
|
return { ...sym, packetSequenceNumber: wire };
|
|
@@ -13043,6 +13279,29 @@ var MmtpRouter = class {
|
|
|
13043
13279
|
}
|
|
13044
13280
|
);
|
|
13045
13281
|
}
|
|
13282
|
+
const untimedHead = (sym) => sym.fragmentType === 2 && sym.fragment.fragmentationIndicator === 1 && sym.fragment.movieFragmentSequenceNumber !== void 0 && sym.fragment.sampleNumber !== void 0 && !this.#parser.hasAuthoredTimingFor(
|
|
13283
|
+
ownerPacketId,
|
|
13284
|
+
sym.fragment.mpuSequenceNumber,
|
|
13285
|
+
sym.fragment.movieFragmentSequenceNumber,
|
|
13286
|
+
sym.fragment.sampleNumber,
|
|
13287
|
+
catalogTrackName
|
|
13288
|
+
);
|
|
13289
|
+
for (const sym of result.symbols) {
|
|
13290
|
+
if (!untimedHead(sym) || this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) continue;
|
|
13291
|
+
this.#feedViaParser(
|
|
13292
|
+
sym.rawSymbol,
|
|
13293
|
+
{ packetId: ownerPacketId, sequenceNumber: sym.packetSequenceNumber },
|
|
13294
|
+
void 0,
|
|
13295
|
+
{
|
|
13296
|
+
skipFecPayloadId: true,
|
|
13297
|
+
recovered: true,
|
|
13298
|
+
deliveryProvenance,
|
|
13299
|
+
...fecSbnOf(sym.index),
|
|
13300
|
+
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
13301
|
+
}
|
|
13302
|
+
);
|
|
13303
|
+
}
|
|
13304
|
+
result = { ...result, symbols: result.symbols.filter((sym) => !untimedHead(sym)) };
|
|
13046
13305
|
const gated = this.#isFecTrack(ownerPacketId);
|
|
13047
13306
|
const assembler = this.#mseAssemblerFor(ownerPacketId, catalogTrackName);
|
|
13048
13307
|
if (assembler) {
|