@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/browser-runtime.js
CHANGED
|
@@ -489,6 +489,8 @@ var PACKET_TYPE_SIGNALING = 2;
|
|
|
489
489
|
var PACKET_TYPE_REPAIR = 3;
|
|
490
490
|
var MMTP_SIGNALING_PACKET_ID = 0;
|
|
491
491
|
var MMTP_PACKET_ID_MAX = 65535;
|
|
492
|
+
var FEC_CODE_REED_SOLOMON = 1;
|
|
493
|
+
var FEC_CODE_RAPTORQ = 3;
|
|
492
494
|
function parseMMTPHeader(buffer) {
|
|
493
495
|
if (buffer.length < MMTP_HEADER_SIZE) {
|
|
494
496
|
throw new Error(`MMTP packet too small: ${buffer.length} bytes`);
|
|
@@ -655,30 +657,26 @@ function isEmptyOrExactLegacyTrailer(payload, start, end) {
|
|
|
655
657
|
const authLength = view2.getUint16(2, false);
|
|
656
658
|
return start + LEGACY_TRAILER_HEADER_SIZE + authLength === end;
|
|
657
659
|
}
|
|
658
|
-
function resolveDeclaredTrailers(payload, declaredEnd, fecType, options
|
|
660
|
+
function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
|
|
659
661
|
if (declaredEnd > payload.length)
|
|
660
662
|
return null;
|
|
661
663
|
if (options?.skipFecId) {
|
|
662
|
-
return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false
|
|
664
|
+
return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
|
|
663
665
|
}
|
|
664
|
-
const overlay = options?.overlayDeclared === true && rapFlag;
|
|
665
666
|
if (fecType === 1) {
|
|
666
|
-
const fecStart = payload.length -
|
|
667
|
+
const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
667
668
|
if (fecStart < declaredEnd || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {
|
|
668
669
|
return null;
|
|
669
670
|
}
|
|
670
|
-
return { hasFecId: true
|
|
671
|
+
return { hasFecId: true };
|
|
671
672
|
}
|
|
672
|
-
if (fecType !== 0
|
|
673
|
+
if (fecType !== 0)
|
|
673
674
|
return null;
|
|
674
675
|
if (isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length)) {
|
|
675
|
-
return { hasFecId: false
|
|
676
|
+
return { hasFecId: false };
|
|
676
677
|
}
|
|
677
678
|
return null;
|
|
678
679
|
}
|
|
679
|
-
function sourceFecTrailerSize(overlay) {
|
|
680
|
-
return overlay ? 2 * SOURCE_FEC_PAYLOAD_ID_SIZE : SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
681
|
-
}
|
|
682
680
|
function extractNALUnit(mmtpPacket) {
|
|
683
681
|
const extraction = extractMFUPayload(mmtpPacket);
|
|
684
682
|
if (!extraction)
|
|
@@ -737,11 +735,11 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
|
737
735
|
if ("rejection" in layoutResult)
|
|
738
736
|
return layoutResult;
|
|
739
737
|
const { layout } = layoutResult;
|
|
740
|
-
const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options
|
|
738
|
+
const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options);
|
|
741
739
|
if (!trailerLayout) {
|
|
742
740
|
if (!options?.skipFecId && header.fecType === 1) {
|
|
743
741
|
const observed = payload.length - layout.dataEnd;
|
|
744
|
-
const expected =
|
|
742
|
+
const expected = SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
745
743
|
if (observed !== expected) {
|
|
746
744
|
return {
|
|
747
745
|
rejection: {
|
|
@@ -776,13 +774,10 @@ function extractPlannedMfu(mmtpPacket, result) {
|
|
|
776
774
|
const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE) : null;
|
|
777
775
|
const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false) : null;
|
|
778
776
|
const data = payload.subarray(mfuOffset, dataEnd);
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
const fecPayloadId = trailerLayout.hasFecId ? ssIdAt(trailerStart) : null;
|
|
784
|
-
const overlayFecPayloadId = trailerLayout.hasOverlayFecId ? ssIdAt(trailerStart + SOURCE_FEC_PAYLOAD_ID_SIZE) : null;
|
|
785
|
-
return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId, overlayFecPayloadId };
|
|
777
|
+
const fecPayloadId = trailerLayout.hasFecId ? {
|
|
778
|
+
ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, 4).getUint32(0, false)
|
|
779
|
+
} : null;
|
|
780
|
+
return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
|
|
786
781
|
}
|
|
787
782
|
function extractTimestamp(mmtpPacket) {
|
|
788
783
|
const { header } = unwrapMMTP(mmtpPacket);
|
|
@@ -903,6 +898,50 @@ var NTP_SHORT_BITS = 32;
|
|
|
903
898
|
var NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS;
|
|
904
899
|
var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
|
|
905
900
|
|
|
901
|
+
// ../mmt-base/dist/signalling.js
|
|
902
|
+
var FEC_CODING_STRUCTURE_ONE_STAGE = 1;
|
|
903
|
+
var SSBG_MODE_1 = 1;
|
|
904
|
+
var RAPTORQ_MAX_SOURCE_SYMBOLS = 56403;
|
|
905
|
+
var REPAIR_SYMBOL_ID_MAX = 16777215;
|
|
906
|
+
var MIN_REPAIR_SYMBOL_SIZE = 14;
|
|
907
|
+
var REED_SOLOMON_MAX_BLOCK = 255;
|
|
908
|
+
function alFecRaptorQFlowConfig(flow) {
|
|
909
|
+
if (flow.fecCodingStructure !== FEC_CODING_STRUCTURE_ONE_STAGE || flow.repairFlows.length !== 1)
|
|
910
|
+
return null;
|
|
911
|
+
if (flow.ssbgMode !== SSBG_MODE_1 || flow.ffsrptsFlag !== 0 || flow.fecPayloadIdMode !== 0)
|
|
912
|
+
return null;
|
|
913
|
+
if (flow.packetIds.length !== 1)
|
|
914
|
+
return null;
|
|
915
|
+
const repair = flow.repairFlows[0];
|
|
916
|
+
const { fecCodeId, maximumK: k, maximumP: p } = repair;
|
|
917
|
+
const t = flow.repairSymbolSize;
|
|
918
|
+
if (t < MIN_REPAIR_SYMBOL_SIZE || k < 1 || p < 1 || k + p - 1 > REPAIR_SYMBOL_ID_MAX)
|
|
919
|
+
return null;
|
|
920
|
+
if (fecCodeId === FEC_CODE_RAPTORQ) {
|
|
921
|
+
if (t % 8 !== 0 || k > RAPTORQ_MAX_SOURCE_SYMBOLS)
|
|
922
|
+
return null;
|
|
923
|
+
} else if (fecCodeId === FEC_CODE_REED_SOLOMON) {
|
|
924
|
+
if (k + p > REED_SOLOMON_MAX_BLOCK)
|
|
925
|
+
return null;
|
|
926
|
+
} else {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
if (repair.repairFlowId === MMTP_SIGNALING_PACKET_ID || repair.repairFlowId === flow.packetIds[0])
|
|
930
|
+
return null;
|
|
931
|
+
return {
|
|
932
|
+
sourcePacketIds: [...flow.packetIds],
|
|
933
|
+
repairFlowId: repair.repairFlowId,
|
|
934
|
+
fecCodeId,
|
|
935
|
+
ssbgMode: flow.ssbgMode,
|
|
936
|
+
fecPayloadIdMode: flow.fecPayloadIdMode,
|
|
937
|
+
repairSymbolSize: t,
|
|
938
|
+
maximumK: k,
|
|
939
|
+
maximumP: p,
|
|
940
|
+
protectionWindowTime: repair.protectionWindowTime,
|
|
941
|
+
protectionWindowSize: repair.protectionWindowSize
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
906
945
|
// ../mmt-base/dist/serial-number.js
|
|
907
946
|
var SERIAL32_SPAN = 4294967296;
|
|
908
947
|
var SERIAL32_HALF = 2147483648;
|
|
@@ -7949,8 +7988,8 @@ var MulticastSubscriptionPool = class {
|
|
|
7949
7988
|
record.claims--;
|
|
7950
7989
|
return this.acquire(gateway, config, consumer);
|
|
7951
7990
|
}
|
|
7952
|
-
const
|
|
7953
|
-
entry.consumers.set(
|
|
7991
|
+
const token2 = Symbol(key);
|
|
7992
|
+
entry.consumers.set(token2, consumer);
|
|
7954
7993
|
let released = false;
|
|
7955
7994
|
return {
|
|
7956
7995
|
key,
|
|
@@ -7958,7 +7997,7 @@ var MulticastSubscriptionPool = class {
|
|
|
7958
7997
|
if (released)
|
|
7959
7998
|
return;
|
|
7960
7999
|
released = true;
|
|
7961
|
-
entry.consumers.delete(
|
|
8000
|
+
entry.consumers.delete(token2);
|
|
7962
8001
|
record.claims--;
|
|
7963
8002
|
if (record.claims > 0)
|
|
7964
8003
|
return;
|
|
@@ -8586,9 +8625,9 @@ async function openSubscription(rx, openedPromise, options, packetIds) {
|
|
|
8586
8625
|
} catch (error) {
|
|
8587
8626
|
throw new MulticastError("subscription-failed", "MulticastReceiver did not open", error);
|
|
8588
8627
|
}
|
|
8589
|
-
if (
|
|
8628
|
+
if (opened.integrityScheme !== INTEGRITY_SCHEME) {
|
|
8590
8629
|
await rx.close();
|
|
8591
|
-
throw unsupported(`integrity scheme ${String(
|
|
8630
|
+
throw unsupported(`integrity scheme ${String(opened.integrityScheme)} is not ${INTEGRITY_SCHEME}`);
|
|
8592
8631
|
}
|
|
8593
8632
|
const events = new EventTarget();
|
|
8594
8633
|
let state = "connected";
|
|
@@ -8707,14 +8746,15 @@ var MOQT_PUBLISHER_PRIORITY = Object.freeze({
|
|
|
8707
8746
|
});
|
|
8708
8747
|
|
|
8709
8748
|
// node_modules/@blockcast/mmt-transport/dist/amt-gateway.js
|
|
8749
|
+
var MAX_PENDING_PACKETS = 256;
|
|
8710
8750
|
var AMTGatewayManager = class {
|
|
8711
8751
|
subscriptions = /* @__PURE__ */ new Map();
|
|
8712
8752
|
messageHandler;
|
|
8753
|
+
messageListenerRegistered = false;
|
|
8713
8754
|
pendingPackets = /* @__PURE__ */ new Map();
|
|
8714
8755
|
constructor() {
|
|
8715
8756
|
this.messageHandler = (event) => {
|
|
8716
8757
|
const data = event.data;
|
|
8717
|
-
console.log(`[AMT] SW message received:`, data.type);
|
|
8718
8758
|
if (data.type === "UDP_PACKET") {
|
|
8719
8759
|
this.handlePacket(data);
|
|
8720
8760
|
} else if (data.type === "SOCKET_ERROR") {
|
|
@@ -8723,12 +8763,22 @@ var AMTGatewayManager = class {
|
|
|
8723
8763
|
console.log(`[AMT SW Debug] ${data.message}`);
|
|
8724
8764
|
}
|
|
8725
8765
|
};
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8766
|
+
}
|
|
8767
|
+
ensureMessageListener() {
|
|
8768
|
+
if (this.messageListenerRegistered || typeof navigator === "undefined" || !navigator.serviceWorker || !this.messageHandler) {
|
|
8769
|
+
return;
|
|
8770
|
+
}
|
|
8771
|
+
navigator.serviceWorker.addEventListener("message", this.messageHandler);
|
|
8772
|
+
this.messageListenerRegistered = true;
|
|
8773
|
+
console.log("[AMT] Registered service worker message handler");
|
|
8774
|
+
}
|
|
8775
|
+
removeMessageListener() {
|
|
8776
|
+
if (!this.messageListenerRegistered)
|
|
8777
|
+
return;
|
|
8778
|
+
if (this.messageHandler && typeof navigator !== "undefined" && navigator.serviceWorker) {
|
|
8779
|
+
navigator.serviceWorker.removeEventListener("message", this.messageHandler);
|
|
8731
8780
|
}
|
|
8781
|
+
this.messageListenerRegistered = false;
|
|
8732
8782
|
}
|
|
8733
8783
|
/**
|
|
8734
8784
|
* Subscribe via AMT tunnel
|
|
@@ -8738,6 +8788,7 @@ var AMTGatewayManager = class {
|
|
|
8738
8788
|
if (config.protected) {
|
|
8739
8789
|
throw new Error("AMTGatewayManager refuses a protected join: it reports no per-datagram sender (multicast-02 7.2.7)");
|
|
8740
8790
|
}
|
|
8791
|
+
this.ensureMessageListener();
|
|
8741
8792
|
const keyStr = this.getKeyString(key);
|
|
8742
8793
|
if (this.subscriptions.has(keyStr)) {
|
|
8743
8794
|
console.log(`[AMT] Reusing existing subscription to ${keyStr}`);
|
|
@@ -8773,6 +8824,8 @@ var AMTGatewayManager = class {
|
|
|
8773
8824
|
this.subscriptions.delete(keyStr);
|
|
8774
8825
|
this.pendingPackets.delete(subscription.subscriptionId);
|
|
8775
8826
|
}
|
|
8827
|
+
if (this.subscriptions.size === 0)
|
|
8828
|
+
this.removeMessageListener();
|
|
8776
8829
|
throw new Error(`Failed to subscribe via AMT: ${error instanceof Error ? error.message : String(error)}`);
|
|
8777
8830
|
}
|
|
8778
8831
|
}
|
|
@@ -8794,6 +8847,8 @@ var AMTGatewayManager = class {
|
|
|
8794
8847
|
} finally {
|
|
8795
8848
|
this.subscriptions.delete(handle);
|
|
8796
8849
|
this.pendingPackets.delete(subscription.subscriptionId);
|
|
8850
|
+
if (this.subscriptions.size === 0)
|
|
8851
|
+
this.removeMessageListener();
|
|
8797
8852
|
}
|
|
8798
8853
|
}
|
|
8799
8854
|
/**
|
|
@@ -8810,14 +8865,12 @@ var AMTGatewayManager = class {
|
|
|
8810
8865
|
if (!subscriptionId)
|
|
8811
8866
|
return;
|
|
8812
8867
|
const subscription = this.subscriptions.get(subscriptionId);
|
|
8813
|
-
if (!subscription)
|
|
8814
|
-
console.warn(`[AMT] Received packet for unknown subscription: ${subscriptionId}`);
|
|
8868
|
+
if (!subscription)
|
|
8815
8869
|
return;
|
|
8816
|
-
}
|
|
8817
8870
|
if (!subscription.handshakeComplete) {
|
|
8818
8871
|
const queue = this.pendingPackets.get(subscriptionId);
|
|
8819
8872
|
const packetData2 = data;
|
|
8820
|
-
if (queue) {
|
|
8873
|
+
if (queue && queue.length < MAX_PENDING_PACKETS) {
|
|
8821
8874
|
queue.push({
|
|
8822
8875
|
data: packetData2.data,
|
|
8823
8876
|
remoteAddress: packetData2.remoteAddress,
|
|
@@ -8843,19 +8896,15 @@ var AMTGatewayManager = class {
|
|
|
8843
8896
|
if (!subscriptionId)
|
|
8844
8897
|
return;
|
|
8845
8898
|
const subscription = this.subscriptions.get(subscriptionId);
|
|
8846
|
-
if (!subscription)
|
|
8847
|
-
console.warn(`[AMT] Received error for unknown subscription: ${subscriptionId}`);
|
|
8899
|
+
if (!subscription)
|
|
8848
8900
|
return;
|
|
8849
|
-
}
|
|
8850
8901
|
subscription.config.onError(new Error(error || "Unknown error"));
|
|
8851
8902
|
}
|
|
8852
8903
|
/**
|
|
8853
8904
|
* Cleanup on destroy
|
|
8854
8905
|
*/
|
|
8855
8906
|
destroy() {
|
|
8856
|
-
|
|
8857
|
-
navigator.serviceWorker.removeEventListener("message", this.messageHandler);
|
|
8858
|
-
}
|
|
8907
|
+
this.removeMessageListener();
|
|
8859
8908
|
const handles = Array.from(this.subscriptions.keys());
|
|
8860
8909
|
for (const handle of handles) {
|
|
8861
8910
|
this.unsubscribe(handle).catch((err) => {
|
|
@@ -12900,6 +12949,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
12900
12949
|
#retentionBlocks;
|
|
12901
12950
|
#heldEvicted;
|
|
12902
12951
|
#newest;
|
|
12952
|
+
#newestSource;
|
|
12903
12953
|
#holes;
|
|
12904
12954
|
static #checkedRetention(retentionBlocks) {
|
|
12905
12955
|
if (!Number.isSafeInteger(retentionBlocks) || retentionBlocks <= 0) {
|
|
@@ -12937,6 +12987,21 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
12937
12987
|
observe(position) {
|
|
12938
12988
|
if (this.#newest === void 0 || serial32Precedes(this.#newest, position)) this.#newest = position;
|
|
12939
12989
|
}
|
|
12990
|
+
/**
|
|
12991
|
+
* The newest source SS_ID noted with `noteSource` (RFC 1982 order). Unlike
|
|
12992
|
+
* `newest`, a repair never moves it: a repair can run ahead of its sources,
|
|
12993
|
+
* a source cannot run ahead of the sources sent before it except by
|
|
12994
|
+
* reordering.
|
|
12995
|
+
*/
|
|
12996
|
+
get newestSource() {
|
|
12997
|
+
return this.#newestSource;
|
|
12998
|
+
}
|
|
12999
|
+
/** Note a received source's SS_ID; true when it is the newest one yet. */
|
|
13000
|
+
noteSource(ssId) {
|
|
13001
|
+
if (this.#newestSource !== void 0 && !serial32Precedes(this.#newestSource, ssId)) return false;
|
|
13002
|
+
this.#newestSource = ssId;
|
|
13003
|
+
return true;
|
|
13004
|
+
}
|
|
12940
13005
|
/** Held sources evicted by `heldCapacity` (never by the horizon itself). */
|
|
12941
13006
|
get heldEvicted() {
|
|
12942
13007
|
return this.#heldEvicted;
|
|
@@ -13169,6 +13234,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
13169
13234
|
this.#held.clear();
|
|
13170
13235
|
this.#coverageEnd = void 0;
|
|
13171
13236
|
this.#newest = void 0;
|
|
13237
|
+
this.#newestSource = void 0;
|
|
13172
13238
|
}
|
|
13173
13239
|
#claim(block) {
|
|
13174
13240
|
const placed = [];
|
|
@@ -13788,6 +13854,7 @@ var WORKER_ERROR_LOG_CAP = 5;
|
|
|
13788
13854
|
var WORKER_ERROR_KIND_CAP = 32;
|
|
13789
13855
|
var WORKER_ERROR_OVERFLOW_KIND = "other";
|
|
13790
13856
|
var STARTUP_REPAIR_WAIT_SOURCE_BOUNDARIES = 2;
|
|
13857
|
+
var NOT_TAKEN = Object.freeze({ taken: false, recovered: null });
|
|
13791
13858
|
function isMulticastSymbolDelivery(value) {
|
|
13792
13859
|
return value === "datagram" || value === "ordered-relay";
|
|
13793
13860
|
}
|
|
@@ -13885,6 +13952,10 @@ var FECManager = class _FECManager {
|
|
|
13885
13952
|
// Source symbol count per track-scoped block — marks complete when K symbols arrive.
|
|
13886
13953
|
this.#blockSourceCounts = /* @__PURE__ */ new Map();
|
|
13887
13954
|
this.#blockSourceEsis = /* @__PURE__ */ new Map();
|
|
13955
|
+
// Source ESIs per block that another FEC instance rebuilt and fed in
|
|
13956
|
+
// (feedRebuiltSourceSymbol). Kept apart from #blockSourceEsis: they are
|
|
13957
|
+
// not network arrivals and never make a block clean.
|
|
13958
|
+
this.#blockRebuiltEsis = /* @__PURE__ */ new Map();
|
|
13888
13959
|
// Authored SSB_length per block (ISO/IEC 23008-1 §C.5.3), learned from the
|
|
13889
13960
|
// first repair symbol. The FFmpeg moq_mmt publisher closes a block every D
|
|
13890
13961
|
// media units, usually before it holds K symbols; under iso-ssbg1-v1
|
|
@@ -14013,6 +14084,10 @@ var FECManager = class _FECManager {
|
|
|
14013
14084
|
// ── Keyframe-protected repair policy (block-policy.ts) ──
|
|
14014
14085
|
// One classifier per physical stream, built from that stream's geometry.
|
|
14015
14086
|
this.#classifiers = /* @__PURE__ */ new Map();
|
|
14087
|
+
// A stream without catalog timing has no classifier, so the last RAP it
|
|
14088
|
+
// released stands in for one: "an MPU boundary bounds its RAPs" holds until
|
|
14089
|
+
// a second RAP is released inside one MPU (BLO-52085).
|
|
14090
|
+
this.#noSpanRaps = /* @__PURE__ */ new Map();
|
|
14016
14091
|
// Geometry a stream-scoped configuration gave one stream (streamKey). ABR
|
|
14017
14092
|
// rungs can share a packetId with a different framerate or GOP, so the
|
|
14018
14093
|
// packet-level geometry is only the geometry of a packet whose streams
|
|
@@ -14170,6 +14245,7 @@ var FECManager = class _FECManager {
|
|
|
14170
14245
|
#pendingWorkerCleanupBounds;
|
|
14171
14246
|
#blockSourceCounts;
|
|
14172
14247
|
#blockSourceEsis;
|
|
14248
|
+
#blockRebuiltEsis;
|
|
14173
14249
|
#blockAuthoredLengths;
|
|
14174
14250
|
#ssbLengthConflictCount;
|
|
14175
14251
|
#blockRepairRsIds;
|
|
@@ -14212,6 +14288,7 @@ var FECManager = class _FECManager {
|
|
|
14212
14288
|
#orderedFrontiers;
|
|
14213
14289
|
#orderedPendingBlocks;
|
|
14214
14290
|
#classifiers;
|
|
14291
|
+
#noSpanRaps;
|
|
14215
14292
|
#streamGeometries;
|
|
14216
14293
|
#failedBlocks;
|
|
14217
14294
|
#resolvedBlockClasses;
|
|
@@ -14873,6 +14950,7 @@ var FECManager = class _FECManager {
|
|
|
14873
14950
|
};
|
|
14874
14951
|
const discontinuity = () => {
|
|
14875
14952
|
this.#classifiers.get(streamKey)?.noteSourceDiscontinuity();
|
|
14953
|
+
this.#noSpanRaps.delete(streamKey);
|
|
14876
14954
|
this.#highestSbn.set(streamKey, { sbn, atMs: now });
|
|
14877
14955
|
this.#sourceFrontiers.set(streamKey, { sbn, atMs: now });
|
|
14878
14956
|
if (psn === void 0) this.#sourceAnchors.delete(streamKey);
|
|
@@ -15285,6 +15363,9 @@ var FECManager = class _FECManager {
|
|
|
15285
15363
|
}
|
|
15286
15364
|
if (policyChanged) {
|
|
15287
15365
|
this.#bumpEpoch(packetId);
|
|
15366
|
+
for (const key of [...this.#noSpanRaps.keys()]) {
|
|
15367
|
+
if (this.#belongsToPacket(key, packetId)) this.#noSpanRaps.delete(key);
|
|
15368
|
+
}
|
|
15288
15369
|
for (const [key, classifier] of [...this.#classifiers]) {
|
|
15289
15370
|
if (!this.#belongsToPacket(key, packetId)) continue;
|
|
15290
15371
|
const stream = this.#parseStreamKey(key);
|
|
@@ -15487,6 +15568,7 @@ var FECManager = class _FECManager {
|
|
|
15487
15568
|
}
|
|
15488
15569
|
resetMap(this.#blockSourceCounts);
|
|
15489
15570
|
resetMap(this.#blockSourceEsis);
|
|
15571
|
+
resetMap(this.#blockRebuiltEsis);
|
|
15490
15572
|
resetMap(this.#blockAuthoredLengths);
|
|
15491
15573
|
resetMap(this.#blockRepairRsIds);
|
|
15492
15574
|
resetMap(this.#blockRsbLengths);
|
|
@@ -15520,6 +15602,7 @@ var FECManager = class _FECManager {
|
|
|
15520
15602
|
resetMap(this.#isoLocators);
|
|
15521
15603
|
resetMap(this.#sourceAnchors);
|
|
15522
15604
|
this.#classifiers.delete(this.#streamKey(packetId, catalogTrackName));
|
|
15605
|
+
this.#noSpanRaps.delete(this.#streamKey(packetId, catalogTrackName));
|
|
15523
15606
|
this.#bumpEpoch(packetId);
|
|
15524
15607
|
for (const [sbn, keys] of this.#blockKeysBySbn) {
|
|
15525
15608
|
for (const key of keys.keys()) {
|
|
@@ -15610,6 +15693,7 @@ var FECManager = class _FECManager {
|
|
|
15610
15693
|
dropMap(this.#pendingWorkerCleanupClients);
|
|
15611
15694
|
dropMap(this.#blockSourceCounts);
|
|
15612
15695
|
dropMap(this.#blockSourceEsis);
|
|
15696
|
+
dropMap(this.#blockRebuiltEsis);
|
|
15613
15697
|
dropMap(this.#blockAuthoredLengths);
|
|
15614
15698
|
dropMap(this.#blockRepairRsIds);
|
|
15615
15699
|
dropMap(this.#blockRsbLengths);
|
|
@@ -15626,6 +15710,7 @@ var FECManager = class _FECManager {
|
|
|
15626
15710
|
dropMap(this.#isoLocators);
|
|
15627
15711
|
dropMap(this.#sourceAnchors);
|
|
15628
15712
|
dropMap(this.#classifiers);
|
|
15713
|
+
dropMap(this.#noSpanRaps);
|
|
15629
15714
|
dropMap(this.#streamGeometries);
|
|
15630
15715
|
dropSet(this.#pendingWorkerCleanups);
|
|
15631
15716
|
dropSet(this.#completedBlocks);
|
|
@@ -15771,6 +15856,9 @@ var FECManager = class _FECManager {
|
|
|
15771
15856
|
this.#unindexBlockKey(key);
|
|
15772
15857
|
}
|
|
15773
15858
|
}
|
|
15859
|
+
if (isSourceSymbol && source === "multicast") {
|
|
15860
|
+
this.#noteMulticastSource(this.#isoLocatorFor(packetId, catalogTrackName), sbn + esi >>> 0, packetId, catalogTrackName);
|
|
15861
|
+
}
|
|
15774
15862
|
return true;
|
|
15775
15863
|
}
|
|
15776
15864
|
#observeBlockCatalogTrackName(blockKey, sbn, packetId, catalogTrackName) {
|
|
@@ -16088,6 +16176,7 @@ var FECManager = class _FECManager {
|
|
|
16088
16176
|
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
16089
16177
|
const evictedBefore = locator.heldEvicted;
|
|
16090
16178
|
const held = locator.hold(ssId, source);
|
|
16179
|
+
if (held && source.source === "multicast") this.#noteMulticastSource(locator, ssId, packetId, catalogTrackName);
|
|
16091
16180
|
const evicted = locator.heldEvicted - evictedBefore;
|
|
16092
16181
|
if (evicted > 0) {
|
|
16093
16182
|
const before = this.#isoHeldEvictedCount;
|
|
@@ -16256,11 +16345,14 @@ var FECManager = class _FECManager {
|
|
|
16256
16345
|
/**
|
|
16257
16346
|
* Whether an MPU boundary still bounds this stream's RAPs (see
|
|
16258
16347
|
* `BlockClassifier.mpuBoundsRaps`). Gap decisions drop MPU evidence once
|
|
16259
|
-
* it does not. A stream with no classifier yet has shown nothing against it
|
|
16348
|
+
* it does not. A stream with no classifier yet has shown nothing against it,
|
|
16349
|
+
* and neither has a stream without catalog timing until it releases two
|
|
16350
|
+
* RAPs inside one MPU.
|
|
16260
16351
|
*/
|
|
16261
16352
|
mpuBoundsRapsFor(packetId, catalogTrackName) {
|
|
16262
|
-
|
|
16263
|
-
return this.#
|
|
16353
|
+
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
16354
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return this.#noSpanRaps.get(streamKey)?.inside !== true;
|
|
16355
|
+
return this.#classifiers.get(streamKey)?.mpuBoundsRaps ?? true;
|
|
16264
16356
|
}
|
|
16265
16357
|
/** A block's class now, or frozen at its resolution. */
|
|
16266
16358
|
blockClassFor(sbn, packetId, catalogTrackName) {
|
|
@@ -16286,7 +16378,14 @@ var FECManager = class _FECManager {
|
|
|
16286
16378
|
* invariant (consecutive MPUs exactly one keyframe interval apart).
|
|
16287
16379
|
*/
|
|
16288
16380
|
noteReleasedRap(packetId, catalogTrackName, mpu, presentationTime, mediaTimescale) {
|
|
16289
|
-
if (!this.#trackConfigs.has(packetId)
|
|
16381
|
+
if (!this.#trackConfigs.has(packetId)) return;
|
|
16382
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) {
|
|
16383
|
+
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
16384
|
+
const last = this.#noSpanRaps.get(streamKey);
|
|
16385
|
+
const inside = last?.inside === true || last?.mpu === mpu && last.presentationTime !== presentationTime;
|
|
16386
|
+
this.#noSpanRaps.set(streamKey, { mpu, presentationTime, inside });
|
|
16387
|
+
return;
|
|
16388
|
+
}
|
|
16290
16389
|
this.#applyClassifierChanges(
|
|
16291
16390
|
packetId,
|
|
16292
16391
|
catalogTrackName,
|
|
@@ -16673,7 +16772,15 @@ var FECManager = class _FECManager {
|
|
|
16673
16772
|
this.#isoSourceOversizeCount++;
|
|
16674
16773
|
return null;
|
|
16675
16774
|
}
|
|
16676
|
-
|
|
16775
|
+
return this.#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, located.block.start);
|
|
16776
|
+
}
|
|
16777
|
+
/**
|
|
16778
|
+
* Hand one placed source symbol to the stream's decoder: its Worker, which
|
|
16779
|
+
* answers through onFrame (null here), or its synchronous decoder, whose
|
|
16780
|
+
* recovered block is returned. `sbn` is the SS_Start of the block a repair
|
|
16781
|
+
* stated for `ssId`.
|
|
16782
|
+
*/
|
|
16783
|
+
#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, sbn) {
|
|
16677
16784
|
const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
16678
16785
|
const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey);
|
|
16679
16786
|
const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName);
|
|
@@ -16702,6 +16809,74 @@ var FECManager = class _FECManager {
|
|
|
16702
16809
|
}
|
|
16703
16810
|
return recovered;
|
|
16704
16811
|
}
|
|
16812
|
+
/**
|
|
16813
|
+
* Feed a source symbol of this stream that another FEC instance over the
|
|
16814
|
+
* same packets rebuilt: a keyframe overlay recovery whose base SS_ID the
|
|
16815
|
+
* caller determined (draft-ramadan-moq-mmt-02 7.1, 7.4.4; joint decoding,
|
|
16816
|
+
* BLO-52085). It joins its block's decoder as a received source would, so
|
|
16817
|
+
* it reduces the erasures the block still has to solve, but it is not a
|
|
16818
|
+
* network arrival: no transport accounting, no classification evidence,
|
|
16819
|
+
* no deadline refresh, and it never makes a block clean.
|
|
16820
|
+
*
|
|
16821
|
+
* Only a block a repair stated takes one (a repair-less span has no
|
|
16822
|
+
* decoder block). A block that is terminal, or already holds that source,
|
|
16823
|
+
* does not. The symbol is the exact protected packet, unpadded, as
|
|
16824
|
+
* feedSourceSymbol takes it.
|
|
16825
|
+
*
|
|
16826
|
+
* `recovered` is the block the synchronous decoder completed with it; the
|
|
16827
|
+
* caller routes it and completes the block, as for any recovery. When
|
|
16828
|
+
* every source of the block is then present, received or rebuilt, and no
|
|
16829
|
+
* decoder returned it (a Worker answers asynchronously, and reports such a
|
|
16830
|
+
* block as clean on its own side), the block is completed here as
|
|
16831
|
+
* recovered, with nothing left to route.
|
|
16832
|
+
*/
|
|
16833
|
+
feedRebuiltSourceSymbol(packetId, ssId, symbolData, catalogTrackName) {
|
|
16834
|
+
if (!this.#hasBlockProfile(packetId)) return NOT_TAKEN;
|
|
16835
|
+
const located = this.#isoLocatorFor(packetId, catalogTrackName).locate(ssId);
|
|
16836
|
+
if (located === null || located.block.span) return NOT_TAKEN;
|
|
16837
|
+
if (!ssbg1FitsSymbol(symbolData.byteLength, this.#trackConfig(packetId).symbolSize)) return NOT_TAKEN;
|
|
16838
|
+
const sbn = located.block.start;
|
|
16839
|
+
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
16840
|
+
if (this.#completedBlocks.has(key) || this.#blockSourceEsis.get(key)?.has(located.esi)) return NOT_TAKEN;
|
|
16841
|
+
let rebuilt = this.#blockRebuiltEsis.get(key);
|
|
16842
|
+
if (rebuilt?.has(located.esi)) return NOT_TAKEN;
|
|
16843
|
+
if (rebuilt === void 0) {
|
|
16844
|
+
rebuilt = /* @__PURE__ */ new Set();
|
|
16845
|
+
this.#blockRebuiltEsis.set(key, rebuilt);
|
|
16846
|
+
}
|
|
16847
|
+
const config = this.#trackConfigs.get(packetId);
|
|
16848
|
+
const wasShort = config !== void 0 && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) !== void 0;
|
|
16849
|
+
rebuilt.add(located.esi);
|
|
16850
|
+
const recovered = this.#feedDecoderSource(packetId, ssId, symbolData, catalogTrackName, sbn);
|
|
16851
|
+
if (recovered === null && !this.#completeWhenSourcesPresent(key, sbn, located.block.length, packetId, catalogTrackName)) {
|
|
16852
|
+
if (wasShort && this.#activeBlocks.has(key) && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) === void 0) {
|
|
16853
|
+
this.#armBlockTimeout(sbn, packetId, catalogTrackName, this.#multicastWaitMs(config));
|
|
16854
|
+
}
|
|
16855
|
+
}
|
|
16856
|
+
return { taken: true, recovered };
|
|
16857
|
+
}
|
|
16858
|
+
/**
|
|
16859
|
+
* A block that holds every one of its `length` sources, received or
|
|
16860
|
+
* rebuilt by another instance, with at least one rebuilt, is recovered:
|
|
16861
|
+
* every packet of it was delivered, by its own arrival or by the instance
|
|
16862
|
+
* that rebuilt it, so nothing is routed. Returns whether it completed it.
|
|
16863
|
+
*/
|
|
16864
|
+
#completeWhenSourcesPresent(key, sbn, length, packetId, catalogTrackName) {
|
|
16865
|
+
const rebuilt = this.#blockRebuiltEsis.get(key);
|
|
16866
|
+
if (rebuilt === void 0 || this.#completedBlocks.has(key)) return false;
|
|
16867
|
+
let present = rebuilt.size;
|
|
16868
|
+
for (const esi of this.#blockSourceEsis.get(key) ?? []) if (!rebuilt.has(esi)) present++;
|
|
16869
|
+
if (present < length) return false;
|
|
16870
|
+
this.#traceLifecycle("recovered", sbn, packetId, catalogTrackName);
|
|
16871
|
+
this.countRecovery(packetId, sbn, catalogTrackName);
|
|
16872
|
+
this.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
16873
|
+
if (catalogTrackName === void 0) {
|
|
16874
|
+
this.onBlockComplete?.(sbn, packetId);
|
|
16875
|
+
} else {
|
|
16876
|
+
this.onBlockComplete?.(sbn, packetId, catalogTrackName);
|
|
16877
|
+
}
|
|
16878
|
+
return true;
|
|
16879
|
+
}
|
|
16705
16880
|
/**
|
|
16706
16881
|
* fec-01 4.5: a reed-solomon repair packet whose SSB_length + RSB_length
|
|
16707
16882
|
* exceeds 255, or whose RS_ID is not below RSB_length, is discarded with the
|
|
@@ -16904,8 +17079,75 @@ var FECManager = class _FECManager {
|
|
|
16904
17079
|
this.#completeCleanBlock(blockKey, sbn, packetId, catalogTrackName);
|
|
16905
17080
|
return null;
|
|
16906
17081
|
}
|
|
17082
|
+
if (recovered === null) this.#shortenUnrecoverableWait(blockKey, sbn, packetId, catalogTrackName);
|
|
16907
17083
|
return recovered;
|
|
16908
17084
|
}
|
|
17085
|
+
/**
|
|
17086
|
+
* A first-arrival multicast source, held or placed. A datagram flow keeps
|
|
17087
|
+
* its sources in send order, so one that moves the stream's newest source
|
|
17088
|
+
* position past a block leaves the sources that block still misses
|
|
17089
|
+
* reordered behind it at best: it shortens the wait of every block it
|
|
17090
|
+
* passed that only a late source can complete. MoQ is left out: its streams
|
|
17091
|
+
* can overtake one another, so a MoQ source shows nothing about the sources
|
|
17092
|
+
* sent before it.
|
|
17093
|
+
*/
|
|
17094
|
+
#noteMulticastSource(locator, ssId, packetId, catalogTrackName) {
|
|
17095
|
+
const previous = locator.newestSource;
|
|
17096
|
+
if (!locator.noteSource(ssId) || previous === void 0) return;
|
|
17097
|
+
for (const block of locator.blocksBetween(previous - 1 >>> 0, ssId)) {
|
|
17098
|
+
if (block.span || !serial32Precedes(serial32Add(block.start, block.length - 1), ssId)) continue;
|
|
17099
|
+
this.#shortenUnrecoverableWait(
|
|
17100
|
+
this.#blockKey(block.start, packetId, catalogTrackName),
|
|
17101
|
+
block.start,
|
|
17102
|
+
packetId,
|
|
17103
|
+
catalogTrackName
|
|
17104
|
+
);
|
|
17105
|
+
}
|
|
17106
|
+
}
|
|
17107
|
+
/**
|
|
17108
|
+
* The wait of a block that only a late source can complete (BLO-52085): a
|
|
17109
|
+
* repair stated it, its received sources plus every symbol its repair group
|
|
17110
|
+
* carries (RSB_length) are short of K' (the repair scheduler's
|
|
17111
|
+
* group-cannot-complete), and a later source of the stream has arrived, so
|
|
17112
|
+
* a source it misses is lost or reordered behind that one. The span, which
|
|
17113
|
+
* covers repair symbols paced after its sources, buys it nothing: it waits
|
|
17114
|
+
* the late-symbol allowance alone, the multicast wait less the span (the
|
|
17115
|
+
* catalog tolerance or the measured delivery gap). Undefined for any other
|
|
17116
|
+
* block.
|
|
17117
|
+
*/
|
|
17118
|
+
#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config) {
|
|
17119
|
+
const authoredLength = this.#blockAuthoredLengths.get(key);
|
|
17120
|
+
const rsbLength = this.#blockRsbLengths.get(key);
|
|
17121
|
+
if (authoredLength === void 0 || rsbLength === void 0) return void 0;
|
|
17122
|
+
let present = this.#blockSourceCounts.get(key) ?? 0;
|
|
17123
|
+
const received = this.#blockSourceEsis.get(key);
|
|
17124
|
+
for (const esi of this.#blockRebuiltEsis.get(key) ?? []) if (!received?.has(esi)) present++;
|
|
17125
|
+
if (present + rsbLength >= authoredLength) return void 0;
|
|
17126
|
+
const newestSource = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newestSource;
|
|
17127
|
+
if (newestSource === void 0 || !serial32Precedes(serial32Add(sbn, authoredLength - 1), newestSource)) {
|
|
17128
|
+
return void 0;
|
|
17129
|
+
}
|
|
17130
|
+
return this.#multicastWaitMs(config) - config.interleaveDepthMs;
|
|
17131
|
+
}
|
|
17132
|
+
/**
|
|
17133
|
+
* Bring an open block's wait in to its late-source wait, when it has one.
|
|
17134
|
+
* Never later than it is armed: only a source can complete such a block,
|
|
17135
|
+
* so a repair of its group (paced across the span) must not keep it open.
|
|
17136
|
+
*/
|
|
17137
|
+
#shortenUnrecoverableWait(key, sbn, packetId, catalogTrackName) {
|
|
17138
|
+
if (!this.#activeBlocks.has(key)) return;
|
|
17139
|
+
const config = this.#trackConfigs.get(packetId);
|
|
17140
|
+
if (config === void 0) return;
|
|
17141
|
+
const waitMs = this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, config);
|
|
17142
|
+
if (waitMs === void 0) return;
|
|
17143
|
+
const armedAtMs = this.#blockWaitDeadlinesMs.get(key);
|
|
17144
|
+
this.#armBlockTimeout(
|
|
17145
|
+
sbn,
|
|
17146
|
+
packetId,
|
|
17147
|
+
catalogTrackName,
|
|
17148
|
+
armedAtMs === void 0 ? waitMs : Math.min(waitMs, armedAtMs - this.#receiptNow())
|
|
17149
|
+
);
|
|
17150
|
+
}
|
|
16909
17151
|
/**
|
|
16910
17152
|
* Record a block's authored SSB_length from a repair symbol. Returns false,
|
|
16911
17153
|
* leaving the block unchanged, when the symbol contradicts the block: a
|
|
@@ -17060,6 +17302,15 @@ var FECManager = class _FECManager {
|
|
|
17060
17302
|
this.#completeCleanBlock(key, sbn, packetId, catalogTrackName);
|
|
17061
17303
|
return;
|
|
17062
17304
|
}
|
|
17305
|
+
if (this.#blockRebuiltEsis.has(key) && this.#completeWhenSourcesPresent(
|
|
17306
|
+
key,
|
|
17307
|
+
sbn,
|
|
17308
|
+
this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? authoredLength,
|
|
17309
|
+
packetId,
|
|
17310
|
+
catalogTrackName
|
|
17311
|
+
)) {
|
|
17312
|
+
return;
|
|
17313
|
+
}
|
|
17063
17314
|
this.noteBlockActivity(sbn, packetId, catalogTrackName, true);
|
|
17064
17315
|
}
|
|
17065
17316
|
#completeCleanBlock(key, sbn, packetId, catalogTrackName) {
|
|
@@ -17068,6 +17319,7 @@ var FECManager = class _FECManager {
|
|
|
17068
17319
|
this.cancelBlockTimeout(sbn, packetId, catalogTrackName);
|
|
17069
17320
|
this.#blockSourceCounts.delete(key);
|
|
17070
17321
|
this.#blockSourceEsis.delete(key);
|
|
17322
|
+
this.#blockRebuiltEsis.delete(key);
|
|
17071
17323
|
this.#blockAuthoredLengths.delete(key);
|
|
17072
17324
|
this.#blockRepairRsIds.delete(key);
|
|
17073
17325
|
this.#blockRsbLengths.delete(key);
|
|
@@ -17134,7 +17386,16 @@ var FECManager = class _FECManager {
|
|
|
17134
17386
|
this.#repairDeadlineMs(trackConfig)
|
|
17135
17387
|
);
|
|
17136
17388
|
}
|
|
17137
|
-
this.#
|
|
17389
|
+
if (!sourceProgress && this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, trackConfig) !== void 0) {
|
|
17390
|
+
this.#shortenUnrecoverableWait(key, sbn, packetId, catalogTrackName);
|
|
17391
|
+
return;
|
|
17392
|
+
}
|
|
17393
|
+
this.#armBlockTimeout(
|
|
17394
|
+
sbn,
|
|
17395
|
+
packetId,
|
|
17396
|
+
catalogTrackName,
|
|
17397
|
+
this.#lateSourceWaitMs(key, sbn, packetId, catalogTrackName, trackConfig) ?? timeoutMs
|
|
17398
|
+
);
|
|
17138
17399
|
}
|
|
17139
17400
|
#failBlock(sbn, packetId, catalogTrackName) {
|
|
17140
17401
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
@@ -17147,6 +17408,7 @@ var FECManager = class _FECManager {
|
|
|
17147
17408
|
}
|
|
17148
17409
|
this.#blockSourceCounts.delete(key);
|
|
17149
17410
|
this.#blockSourceEsis.delete(key);
|
|
17411
|
+
this.#blockRebuiltEsis.delete(key);
|
|
17150
17412
|
this.#blockAuthoredLengths.delete(key);
|
|
17151
17413
|
this.#blockRepairRsIds.delete(key);
|
|
17152
17414
|
this.#blockRsbLengths.delete(key);
|
|
@@ -17185,6 +17447,7 @@ var FECManager = class _FECManager {
|
|
|
17185
17447
|
this.#retireBlockClass(key, block.sbn, block.packetId, block.catalogTrackName);
|
|
17186
17448
|
this.#blockSourceCounts.delete(key);
|
|
17187
17449
|
this.#blockSourceEsis.delete(key);
|
|
17450
|
+
this.#blockRebuiltEsis.delete(key);
|
|
17188
17451
|
this.#blockAuthoredLengths.delete(key);
|
|
17189
17452
|
this.#blockRepairRsIds.delete(key);
|
|
17190
17453
|
this.#blockRsbLengths.delete(key);
|
|
@@ -17276,6 +17539,7 @@ var FECManager = class _FECManager {
|
|
|
17276
17539
|
this.#blockKeysBySbn.clear();
|
|
17277
17540
|
this.#blockSourceCounts.clear();
|
|
17278
17541
|
this.#blockSourceEsis.clear();
|
|
17542
|
+
this.#blockRebuiltEsis.clear();
|
|
17279
17543
|
this.#blockAuthoredLengths.clear();
|
|
17280
17544
|
this.#blockRepairRsIds.clear();
|
|
17281
17545
|
this.#blockRsbLengths.clear();
|
|
@@ -17301,6 +17565,7 @@ var FECManager = class _FECManager {
|
|
|
17301
17565
|
this.#blockAnchorsMs.clear();
|
|
17302
17566
|
this.#blockCatalogTrackNames.clear();
|
|
17303
17567
|
this.#classifiers.clear();
|
|
17568
|
+
this.#noSpanRaps.clear();
|
|
17304
17569
|
this.#streamGeometries.clear();
|
|
17305
17570
|
this.#failedBlocks.clear();
|
|
17306
17571
|
this.#resolvedBlockClasses.clear();
|
|
@@ -17831,9 +18096,21 @@ var IsoBmffTimingResolver = class {
|
|
|
17831
18096
|
}
|
|
17832
18097
|
#track = null;
|
|
17833
18098
|
#fragments = /* @__PURE__ */ new Map();
|
|
18099
|
+
/** First sample of each pending fragment, kept in `#delivered` with its sample count. */
|
|
18100
|
+
#firstSamples = /* @__PURE__ */ new Map();
|
|
18101
|
+
/**
|
|
18102
|
+
* First sample and sample count of the newest fragments whose every slot was
|
|
18103
|
+
* cleared. Abandonment counts: `releaseSample` clears a slot too, which
|
|
18104
|
+
* matches the combiner, since it retires an abandoned sample as well.
|
|
18105
|
+
*/
|
|
18106
|
+
#delivered = /* @__PURE__ */ new Map();
|
|
17834
18107
|
#pendingSamples = 0;
|
|
17835
18108
|
#evictedSamples = 0;
|
|
17836
18109
|
#lastMovieFragmentRejection = null;
|
|
18110
|
+
/** True once an FT=0 has bound the track that FT=1 timing is read against. */
|
|
18111
|
+
get initialized() {
|
|
18112
|
+
return this.#track !== null;
|
|
18113
|
+
}
|
|
17837
18114
|
get pendingSampleCount() {
|
|
17838
18115
|
return this.#pendingSamples;
|
|
17839
18116
|
}
|
|
@@ -17857,7 +18134,14 @@ var IsoBmffTimingResolver = class {
|
|
|
17857
18134
|
this.#track = timing;
|
|
17858
18135
|
return true;
|
|
17859
18136
|
}
|
|
17860
|
-
|
|
18137
|
+
/**
|
|
18138
|
+
* `recovered` marks an FEC-recovered copy. A recovered block hands back
|
|
18139
|
+
* every FT=1 it holds, the received ones included, so a recovered copy of a
|
|
18140
|
+
* fragment whose samples are all done is a duplicate: admitting it again
|
|
18141
|
+
* would charge stale timing and could displace the live MPU's. A source
|
|
18142
|
+
* copy is still admitted again (a rendition switch re-arms timing that way).
|
|
18143
|
+
*/
|
|
18144
|
+
addMovieFragment(mpuSequenceNumber2, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
|
|
17861
18145
|
this.#lastMovieFragmentRejection = null;
|
|
17862
18146
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
17863
18147
|
this.#lastMovieFragmentRejection = "invalid";
|
|
@@ -17883,6 +18167,8 @@ var IsoBmffTimingResolver = class {
|
|
|
17883
18167
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
17884
18168
|
);
|
|
17885
18169
|
}
|
|
18170
|
+
const delivered = recovered ? this.#delivered.get(key) : void 0;
|
|
18171
|
+
if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
|
|
17886
18172
|
let freed = 0;
|
|
17887
18173
|
while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
17888
18174
|
freed += this.#evictOldestFragment();
|
|
@@ -17896,6 +18182,7 @@ var IsoBmffTimingResolver = class {
|
|
|
17896
18182
|
return false;
|
|
17897
18183
|
}
|
|
17898
18184
|
this.#fragments.set(key, [...parsed.samples]);
|
|
18185
|
+
this.#firstSamples.set(key, parsed.samples[0]);
|
|
17899
18186
|
this.#pendingSamples += parsed.samples.length;
|
|
17900
18187
|
return true;
|
|
17901
18188
|
}
|
|
@@ -17953,6 +18240,7 @@ var IsoBmffTimingResolver = class {
|
|
|
17953
18240
|
let live = 0;
|
|
17954
18241
|
for (const sample of samples) if (sample !== null) live++;
|
|
17955
18242
|
this.#fragments.delete(oldest);
|
|
18243
|
+
this.#firstSamples.delete(oldest);
|
|
17956
18244
|
this.#pendingSamples -= live;
|
|
17957
18245
|
this.#evictedSamples += live;
|
|
17958
18246
|
return live;
|
|
@@ -17968,12 +18256,23 @@ var IsoBmffTimingResolver = class {
|
|
|
17968
18256
|
if (!samples || !samples[index]) return false;
|
|
17969
18257
|
samples[index] = null;
|
|
17970
18258
|
this.#pendingSamples--;
|
|
17971
|
-
if (samples.every((value) => value === null))
|
|
18259
|
+
if (samples.every((value) => value === null)) {
|
|
18260
|
+
this.#fragments.delete(key);
|
|
18261
|
+
const first = this.#firstSamples.get(key);
|
|
18262
|
+
this.#firstSamples.delete(key);
|
|
18263
|
+
this.#delivered.delete(key);
|
|
18264
|
+
this.#delivered.set(key, { first, sampleCount: samples.length });
|
|
18265
|
+
if (this.#delivered.size > this.maxPendingSamples) {
|
|
18266
|
+
this.#delivered.delete(this.#delivered.keys().next().value);
|
|
18267
|
+
}
|
|
18268
|
+
}
|
|
17972
18269
|
return true;
|
|
17973
18270
|
}
|
|
17974
18271
|
reset() {
|
|
17975
18272
|
this.#track = null;
|
|
17976
18273
|
this.#fragments.clear();
|
|
18274
|
+
this.#firstSamples.clear();
|
|
18275
|
+
this.#delivered.clear();
|
|
17977
18276
|
this.#pendingSamples = 0;
|
|
17978
18277
|
}
|
|
17979
18278
|
};
|
|
@@ -18455,6 +18754,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18455
18754
|
this.#reclaimedTimingSamples = 0;
|
|
18456
18755
|
this.#evictedTimingSamples = 0;
|
|
18457
18756
|
this.#rejectedMovieFragments = 0;
|
|
18757
|
+
this.#recoveredHeadsAwaitingTiming = {
|
|
18758
|
+
held: 0,
|
|
18759
|
+
expired: 0,
|
|
18760
|
+
discarded: 0,
|
|
18761
|
+
superseded: 0
|
|
18762
|
+
};
|
|
18458
18763
|
this.#sequencers = /* @__PURE__ */ new Map();
|
|
18459
18764
|
this.#fecGate = null;
|
|
18460
18765
|
this.#fecSequencer = null;
|
|
@@ -18563,6 +18868,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18563
18868
|
#reclaimedTimingSamples;
|
|
18564
18869
|
#evictedTimingSamples;
|
|
18565
18870
|
#rejectedMovieFragments;
|
|
18871
|
+
#recoveredHeadsAwaitingTiming;
|
|
18566
18872
|
#sequencers;
|
|
18567
18873
|
#fecGate;
|
|
18568
18874
|
#fecSequencer;
|
|
@@ -18668,11 +18974,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18668
18974
|
/**
|
|
18669
18975
|
* Typed view of the ISO-BMFF timing-sample budget.
|
|
18670
18976
|
*
|
|
18671
|
-
*
|
|
18672
|
-
*
|
|
18673
|
-
*
|
|
18674
|
-
*
|
|
18675
|
-
*
|
|
18977
|
+
* Each stream admits timing against its own window of `capacity` slots
|
|
18978
|
+
* (BLO-52085). A stream at capacity displaces its own oldest timing to
|
|
18979
|
+
* admit the moof in hand, and `evicted` counts the slots so displaced:
|
|
18980
|
+
* timing a stream declared and never delivered shows up there. `pending`
|
|
18981
|
+
* sums every stream, so it can exceed `capacity`. Per repo policy this is
|
|
18982
|
+
* a producer-owned counter -- never infer budget state by parsing logs.
|
|
18676
18983
|
*/
|
|
18677
18984
|
get timingBudgetStats() {
|
|
18678
18985
|
return {
|
|
@@ -18683,6 +18990,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18683
18990
|
rejectedMovieFragments: this.#rejectedMovieFragments
|
|
18684
18991
|
};
|
|
18685
18992
|
}
|
|
18993
|
+
/** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
|
|
18994
|
+
get recoveredHeadsAwaitingTiming() {
|
|
18995
|
+
let pending = 0;
|
|
18996
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
18997
|
+
for (const head of awaitingTiming) if (head.options?.recovered) pending++;
|
|
18998
|
+
}
|
|
18999
|
+
return { ...this.#recoveredHeadsAwaitingTiming, pending };
|
|
19000
|
+
}
|
|
18686
19001
|
get reorderStats() {
|
|
18687
19002
|
let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
|
|
18688
19003
|
for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
|
|
@@ -18811,10 +19126,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18811
19126
|
get reassemblerDataUnitsReturned() {
|
|
18812
19127
|
return this.#reassemblerDataUnitsReturned;
|
|
18813
19128
|
}
|
|
18814
|
-
#countOutcome(
|
|
18815
|
-
const count = (this.#feedOutcomes.get(
|
|
18816
|
-
this.#feedOutcomes.set(
|
|
18817
|
-
this.#lastOutcome =
|
|
19129
|
+
#countOutcome(token2) {
|
|
19130
|
+
const count = (this.#feedOutcomes.get(token2) ?? 0) + 1;
|
|
19131
|
+
this.#feedOutcomes.set(token2, count);
|
|
19132
|
+
this.#lastOutcome = token2;
|
|
18818
19133
|
this.#lastOutcomeCount = count;
|
|
18819
19134
|
}
|
|
18820
19135
|
#countDeferredOutcome(reason) {
|
|
@@ -18856,7 +19171,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18856
19171
|
const now = Date.now();
|
|
18857
19172
|
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
18858
19173
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
18859
|
-
awaitingTiming.shift();
|
|
19174
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
18860
19175
|
}
|
|
18861
19176
|
}
|
|
18862
19177
|
}
|
|
@@ -18974,11 +19289,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18974
19289
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
18975
19290
|
return this.#drop("fragment-unexpected-fragmentation");
|
|
18976
19291
|
const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
|
|
18977
|
-
if (!resolver2) return this.#drop("fragment-no-resolver");
|
|
19292
|
+
if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
|
|
18978
19293
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
18979
19294
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
18980
|
-
const remaining = this.#maxPendingTimingSamples -
|
|
18981
|
-
const admitted = resolver2.addMovieFragment(
|
|
19295
|
+
const remaining = this.#maxPendingTimingSamples - resolver2.pendingSampleCount;
|
|
19296
|
+
const admitted = resolver2.addMovieFragment(
|
|
19297
|
+
mpu.mpuSequenceNumber,
|
|
19298
|
+
data,
|
|
19299
|
+
remaining,
|
|
19300
|
+
options?.recovered === true
|
|
19301
|
+
);
|
|
18982
19302
|
this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
|
|
18983
19303
|
this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
|
|
18984
19304
|
if (!admitted) {
|
|
@@ -18998,7 +19318,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
18998
19318
|
};
|
|
18999
19319
|
}
|
|
19000
19320
|
if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
|
|
19001
|
-
const resolver = this.#timingResolver(streamKey, info.packetId,
|
|
19321
|
+
const resolver = this.#timingResolver(streamKey, info.packetId, true);
|
|
19002
19322
|
if (!resolver) return this.#drop("mfu-no-resolver");
|
|
19003
19323
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
19004
19324
|
return this.#drop("mfu-empty-payload");
|
|
@@ -19019,6 +19339,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19019
19339
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
19020
19340
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
19021
19341
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
19342
|
+
sequenceNumber: info.sequenceNumber,
|
|
19022
19343
|
arrivedAt: Date.now()
|
|
19023
19344
|
});
|
|
19024
19345
|
return this.#drop("mfu-sample-timing-missing");
|
|
@@ -19342,6 +19663,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19342
19663
|
}
|
|
19343
19664
|
this.#emitFrame(data, meta);
|
|
19344
19665
|
}
|
|
19666
|
+
/** Whether FT=1 metadata already names this sample, without claiming it. */
|
|
19667
|
+
hasAuthoredTimingFor(packetId, mpuSequenceNumber2, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
|
|
19668
|
+
return this.#timingResolver(this.#streamKey(packetId, catalogTrackName), packetId, false)?.sampleTiming(mpuSequenceNumber2, movieFragmentSequenceNumber, sampleNumber) != null;
|
|
19669
|
+
}
|
|
19345
19670
|
/**
|
|
19346
19671
|
* Claim and return the authored timing for a recovered sample. Recovery does
|
|
19347
19672
|
* not pass through the ordinary `feed()` completion path, so it must retire
|
|
@@ -19403,6 +19728,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19403
19728
|
for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
|
|
19404
19729
|
this.#sequencers.clear();
|
|
19405
19730
|
this.#fecSequencer?.discard();
|
|
19731
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
19732
|
+
for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
19733
|
+
}
|
|
19406
19734
|
this.#timingStreams.clear();
|
|
19407
19735
|
this.#pendingTimingSamples = 0;
|
|
19408
19736
|
this.#reliableMoqStartupGate.clear();
|
|
@@ -19421,10 +19749,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19421
19749
|
return resolver;
|
|
19422
19750
|
}
|
|
19423
19751
|
#awaitTiming(streamKey, head) {
|
|
19424
|
-
const
|
|
19425
|
-
if (!
|
|
19752
|
+
const state = this.#timingStreams.get(streamKey);
|
|
19753
|
+
if (!state) return;
|
|
19754
|
+
if (!head.options?.recovered) {
|
|
19755
|
+
const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
|
|
19756
|
+
this.#recoveredHeadsAwaitingTiming.superseded += state.awaitingTiming.length - kept.length;
|
|
19757
|
+
state.awaitingTiming = kept;
|
|
19758
|
+
}
|
|
19759
|
+
const awaitingTiming = state.awaitingTiming;
|
|
19426
19760
|
awaitingTiming.push(head);
|
|
19427
|
-
if (
|
|
19761
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming.held++;
|
|
19762
|
+
if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
|
|
19763
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
19764
|
+
}
|
|
19765
|
+
}
|
|
19766
|
+
/** Count a recovered head leaving the queue other than by replay or its source copy. */
|
|
19767
|
+
#leaveAwaitingTiming(head, outcome) {
|
|
19768
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming[outcome]++;
|
|
19428
19769
|
}
|
|
19429
19770
|
/**
|
|
19430
19771
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -19452,6 +19793,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
19452
19793
|
const state = this.#timingStreams.get(streamKey);
|
|
19453
19794
|
if (!state) return;
|
|
19454
19795
|
this.#timingStreams.delete(streamKey);
|
|
19796
|
+
for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
19455
19797
|
this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
|
|
19456
19798
|
if (this.#pendingTimingSamples < 0) {
|
|
19457
19799
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
@@ -22658,6 +23000,16 @@ function keyframeOverlaySsId(header) {
|
|
|
22658
23000
|
if (ext?.type !== KEYFRAME_OVERLAY_EXTENSION_TYPE || ext.data.length !== 4) return void 0;
|
|
22659
23001
|
return new DataView(ext.data.buffer, ext.data.byteOffset, 4).getUint32(0);
|
|
22660
23002
|
}
|
|
23003
|
+
function overlayBaseSsId(anchor, overlaySsId, restoredPsn, headerPsn) {
|
|
23004
|
+
if (anchor.baseSsId === void 0 || headerPsn !== restoredPsn) return void 0;
|
|
23005
|
+
return serial32Add(anchor.baseSsId, overlaySsId - anchor.overlaySsId | 0);
|
|
23006
|
+
}
|
|
23007
|
+
function unstampedBaseSsId(offset, unstampedOffset, headerPsn) {
|
|
23008
|
+
if (offset === void 0 || offset !== unstampedOffset) return void 0;
|
|
23009
|
+
return headerPsn + offset >>> 0;
|
|
23010
|
+
}
|
|
23011
|
+
var OVERLAY_ROUTED_LIMIT = 1024;
|
|
23012
|
+
var OVERLAY_PENDING_LIMIT = 256;
|
|
22661
23013
|
var OVERLAY_OFFSET_MPUS = 16;
|
|
22662
23014
|
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
22663
23015
|
function assertStreamIdentity(role, stream) {
|
|
@@ -22769,6 +23121,7 @@ var MmtpRouter = class {
|
|
|
22769
23121
|
#provenance = /* @__PURE__ */ new Map();
|
|
22770
23122
|
#onFlowProtected = null;
|
|
22771
23123
|
#unadmittedListedDrops = 0;
|
|
23124
|
+
#admittedSourceDatagrams = 0;
|
|
22772
23125
|
#unlistedRepairSetAside = 0;
|
|
22773
23126
|
#assistBlocks = /* @__PURE__ */ new Map();
|
|
22774
23127
|
#assistFailures = /* @__PURE__ */ new Map();
|
|
@@ -22825,6 +23178,15 @@ var MmtpRouter = class {
|
|
|
22825
23178
|
* sequence is its SS_ID minus this; see #routeProcessedFecSymbols.
|
|
22826
23179
|
*/
|
|
22827
23180
|
#ssIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
23181
|
+
/**
|
|
23182
|
+
* Per stream, the SS_ID offset at which a base recovery last showed the
|
|
23183
|
+
* sender's own packet_sequence_number: a recovered packet's header psn
|
|
23184
|
+
* equal to its SS_ID minus that offset. While it equals the stream's
|
|
23185
|
+
* current offset, no hop re-stamps the stream, and a packet's protected psn
|
|
23186
|
+
* names its base SS_ID within unstampedBaseSsId's limits (joint decoding of
|
|
23187
|
+
* an MPU no member of which arrived).
|
|
23188
|
+
*/
|
|
23189
|
+
#unstampedSsIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
22828
23190
|
/**
|
|
22829
23191
|
* Streams whose multicast leg delivered a RAP-flagged source packet that was
|
|
22830
23192
|
* not parsed, typically because its overlap copy was parsed first
|
|
@@ -22894,7 +23256,9 @@ var MmtpRouter = class {
|
|
|
22894
23256
|
recovered: 0,
|
|
22895
23257
|
restarts: 0,
|
|
22896
23258
|
unsequencedDropped: 0,
|
|
22897
|
-
protectedDropped: 0
|
|
23259
|
+
protectedDropped: 0,
|
|
23260
|
+
baseFed: 0,
|
|
23261
|
+
baseUndetermined: 0
|
|
22898
23262
|
};
|
|
22899
23263
|
/** Callback fired when a delta-track FEC block completes or fails. */
|
|
22900
23264
|
#onDeltaBlockCompleteCb = null;
|
|
@@ -23067,7 +23431,7 @@ var MmtpRouter = class {
|
|
|
23067
23431
|
const cb = this.#getFrameCallback(packetId);
|
|
23068
23432
|
if (isVideo && !assembler) {
|
|
23069
23433
|
if (frameMeta.recovered) {
|
|
23070
|
-
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb);
|
|
23434
|
+
this.#routeDirectRecoveredVideo(packetId, data, frameMeta, cb, meta.fecSbn);
|
|
23071
23435
|
} else {
|
|
23072
23436
|
this.#routeDirectSourceVideo(packetId, data, frameMeta, cb);
|
|
23073
23437
|
}
|
|
@@ -23457,6 +23821,10 @@ var MmtpRouter = class {
|
|
|
23457
23821
|
get keyframeOverlayStats() {
|
|
23458
23822
|
return { ...this.#overlayStats };
|
|
23459
23823
|
}
|
|
23824
|
+
/** Recovered FT=2 heads held for their FT=1, and how each left the wait (BLO-53369). */
|
|
23825
|
+
get recoveredHeadsAwaitingTiming() {
|
|
23826
|
+
return this.#parser.recoveredHeadsAwaitingTiming;
|
|
23827
|
+
}
|
|
23460
23828
|
#isOverlayRepair(repairPacketId, repairTrackName) {
|
|
23461
23829
|
return this.#overlayLegs.has(repairPacketId) || repairTrackName !== void 0 && this.#overlayRepairNames.has(repairTrackName);
|
|
23462
23830
|
}
|
|
@@ -23507,6 +23875,11 @@ var MmtpRouter = class {
|
|
|
23507
23875
|
*/
|
|
23508
23876
|
resetKeyframeOverlays(sourcePacketId, catalogTrackName) {
|
|
23509
23877
|
for (const inst of this.#overlaysForSource(sourcePacketId, catalogTrackName)) this.#resetOverlay(inst);
|
|
23878
|
+
for (const [name, stream] of this.#streams.get(sourcePacketId) ?? []) {
|
|
23879
|
+
if (catalogTrackName !== void 0 && name !== catalogTrackName) continue;
|
|
23880
|
+
stream.overlayToBase.routed.clear();
|
|
23881
|
+
stream.overlayToBase.pending.clear();
|
|
23882
|
+
}
|
|
23510
23883
|
}
|
|
23511
23884
|
#resetOverlay(inst) {
|
|
23512
23885
|
inst.fec.resetTrack(inst.repairPacketId, inst.streamName);
|
|
@@ -23563,9 +23936,10 @@ var MmtpRouter = class {
|
|
|
23563
23936
|
* A source packet that carries a 0x8B01 overlay SS_ID is also a symbol of
|
|
23564
23937
|
* every overlay installed for its source, at that SS_ID, with the bytes the
|
|
23565
23938
|
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
23566
|
-
* included (7.1, 7.4.3).
|
|
23939
|
+
* included (7.1, 7.4.3). `baseSsId` is that trailer's SS_ID, when the base
|
|
23940
|
+
* FEC reads it.
|
|
23567
23941
|
*/
|
|
23568
|
-
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source) {
|
|
23942
|
+
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source, baseSsId) {
|
|
23569
23943
|
if (this.#overlays.size === 0) return;
|
|
23570
23944
|
const ssId = keyframeOverlaySsId(header);
|
|
23571
23945
|
if (ssId === void 0) return;
|
|
@@ -23574,7 +23948,11 @@ var MmtpRouter = class {
|
|
|
23574
23948
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
23575
23949
|
if (identity !== null) {
|
|
23576
23950
|
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
23577
|
-
inst.offsets.set(identity.mpuSequenceNumber,
|
|
23951
|
+
inst.offsets.set(identity.mpuSequenceNumber, {
|
|
23952
|
+
psnOffset: ssId - sequenceNumber >>> 0,
|
|
23953
|
+
overlaySsId: ssId,
|
|
23954
|
+
...baseSsId === void 0 ? {} : { baseSsId }
|
|
23955
|
+
});
|
|
23578
23956
|
if (inst.offsets.size > OVERLAY_OFFSET_MPUS) inst.offsets.delete(inst.offsets.keys().next().value);
|
|
23579
23957
|
}
|
|
23580
23958
|
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
@@ -23653,14 +24031,27 @@ var MmtpRouter = class {
|
|
|
23653
24031
|
const streamKey = this.#mediaStreamKey(sourcePacketId, sourceName);
|
|
23654
24032
|
const restamped = this.#restampedStreams.has(streamKey);
|
|
23655
24033
|
const restored = /* @__PURE__ */ new Set();
|
|
24034
|
+
const baseSsIds = /* @__PURE__ */ new Map();
|
|
23656
24035
|
const symbols = result.symbols.flatMap((sym) => {
|
|
23657
|
-
const
|
|
23658
|
-
if (
|
|
23659
|
-
const
|
|
24036
|
+
const anchor = inst.offsets.get(sym.fragment.mpuSequenceNumber);
|
|
24037
|
+
if (anchor !== void 0) {
|
|
24038
|
+
const overlaySsId = serial32Add(blockStart, sym.index);
|
|
24039
|
+
const packetSequenceNumber = overlaySsId - anchor.psnOffset >>> 0;
|
|
23660
24040
|
restored.add(packetSequenceNumber);
|
|
23661
|
-
|
|
24041
|
+
const out = { ...sym, packetSequenceNumber };
|
|
24042
|
+
const baseSsId = overlayBaseSsId(anchor, overlaySsId, packetSequenceNumber, sym.packetSequenceNumber);
|
|
24043
|
+
if (baseSsId !== void 0) baseSsIds.set(out, baseSsId);
|
|
24044
|
+
return [out];
|
|
24045
|
+
}
|
|
24046
|
+
if (!restamped) {
|
|
24047
|
+
const baseSsId = unstampedBaseSsId(
|
|
24048
|
+
this.#ssIdSeqOffsetByStream.get(streamKey),
|
|
24049
|
+
this.#unstampedSsIdSeqOffsetByStream.get(streamKey),
|
|
24050
|
+
sym.packetSequenceNumber
|
|
24051
|
+
);
|
|
24052
|
+
if (baseSsId !== void 0) baseSsIds.set(sym, baseSsId);
|
|
24053
|
+
return [sym];
|
|
23662
24054
|
}
|
|
23663
|
-
if (!restamped) return [sym];
|
|
23664
24055
|
this.#overlayStats.unsequencedDropped++;
|
|
23665
24056
|
return [];
|
|
23666
24057
|
});
|
|
@@ -23669,6 +24060,85 @@ var MmtpRouter = class {
|
|
|
23669
24060
|
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
23670
24061
|
});
|
|
23671
24062
|
this.#noteOverlayRecoveredRaps(sourcePacketId, sourceName, streamKey, symbols.filter((sym) => restored.has(sym.packetSequenceNumber)));
|
|
24063
|
+
this.#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds);
|
|
24064
|
+
}
|
|
24065
|
+
/**
|
|
24066
|
+
* Joint decoding (BLO-52085). A packet the overlay rebuilt is a source of
|
|
24067
|
+
* its base block too, once its base SS_ID is determined (overlayBaseSsId,
|
|
24068
|
+
* from a received member of its MPU, else unstampedBaseSsId),
|
|
24069
|
+
* so it is fed to the base decoder as a rebuilt source
|
|
24070
|
+
* (FECManager.feedRebuiltSourceSymbol): the base block has one erasure
|
|
24071
|
+
* fewer to solve, and its repair can recover the block's other packets.
|
|
24072
|
+
* A base block no repair has stated yet takes it when one does
|
|
24073
|
+
* (#feedPendingRebuilt).
|
|
24074
|
+
*
|
|
24075
|
+
* The overlay route already handled each packet fed (delivered it, or
|
|
24076
|
+
* held an untimed head for its FT=1), so a base recovery routes none of
|
|
24077
|
+
* them again (#routeVerifiedFecSymbols): a second recovered copy of a
|
|
24078
|
+
* held head would wait in the parser for timing its first copy consumed.
|
|
24079
|
+
* Only an overlay block decoded from multicast symbols alone is fed: with
|
|
24080
|
+
* a MoQ symbol in it, the base block's recovery would depend on bytes
|
|
24081
|
+
* that crossed no multicast socket, which the provenance counted from the
|
|
24082
|
+
* block's own arrivals cannot show.
|
|
24083
|
+
*/
|
|
24084
|
+
#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds) {
|
|
24085
|
+
const fec = this.#fecManager;
|
|
24086
|
+
const owner = inst.leg.sourcePacketId;
|
|
24087
|
+
const name = inst.leg.sourceCatalogTrackName;
|
|
24088
|
+
if (!fec?.feedRebuiltSourceSymbol || inst.moqBlocks.has(blockStart)) return;
|
|
24089
|
+
if (this.#classifyPacketId(owner) === null || fec.isTrackConfigured?.(owner) === false) return;
|
|
24090
|
+
const { duplicates, overlayToBase: { routed } } = this.#stream(owner, name);
|
|
24091
|
+
const feeds = [];
|
|
24092
|
+
for (const sym of symbols) {
|
|
24093
|
+
if (duplicates.sourceSeqs.has(sym.packetSequenceNumber)) continue;
|
|
24094
|
+
const ssId = baseSsIds.get(sym);
|
|
24095
|
+
if (ssId === void 0) {
|
|
24096
|
+
this.#overlayStats.baseUndetermined++;
|
|
24097
|
+
continue;
|
|
24098
|
+
}
|
|
24099
|
+
routed.delete(ssId);
|
|
24100
|
+
routed.add(ssId);
|
|
24101
|
+
if (routed.size > OVERLAY_ROUTED_LIMIT) routed.delete(routed.values().next().value);
|
|
24102
|
+
feeds.push([ssId, sym.rawSymbol]);
|
|
24103
|
+
}
|
|
24104
|
+
for (const [ssId, symbolData] of feeds) {
|
|
24105
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
24106
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
24107
|
+
}
|
|
24108
|
+
}
|
|
24109
|
+
/** Feed one rebuilt source to its base block, or keep it until a repair states that block. */
|
|
24110
|
+
#feedRebuiltToBase(owner, name, ssId, symbolData) {
|
|
24111
|
+
const fec = this.#fecManager;
|
|
24112
|
+
if (!fec?.feedRebuiltSourceSymbol) return;
|
|
24113
|
+
const coords = fec.locateIsoSource?.(owner, name, ssId);
|
|
24114
|
+
if (coords == null) {
|
|
24115
|
+
const pending = this.#stream(owner, name).overlayToBase.pending;
|
|
24116
|
+
pending.delete(ssId);
|
|
24117
|
+
pending.set(ssId, symbolData.slice());
|
|
24118
|
+
if (pending.size > OVERLAY_PENDING_LIMIT) pending.delete(pending.keys().next().value);
|
|
24119
|
+
return;
|
|
24120
|
+
}
|
|
24121
|
+
const fed = fec.feedRebuiltSourceSymbol(owner, ssId, symbolData, name);
|
|
24122
|
+
if (!fed.taken) return;
|
|
24123
|
+
this.#overlayStats.baseFed++;
|
|
24124
|
+
if (fed.recovered === null) return;
|
|
24125
|
+
const sbn = coords.sbn;
|
|
24126
|
+
this.#routeRecoveredBlock(sbn, fed.recovered, owner, name, () => {
|
|
24127
|
+
fec.cancelBlockTimeout(sbn, owner, name);
|
|
24128
|
+
fec.countRecovery(owner, sbn, name);
|
|
24129
|
+
fec.markBlockCompleted(sbn, owner, name);
|
|
24130
|
+
});
|
|
24131
|
+
}
|
|
24132
|
+
/** A repair stated `block`: the rebuilt sources that waited for it join it now. */
|
|
24133
|
+
#feedPendingRebuilt(owner, name, block) {
|
|
24134
|
+
const pending = this.#streams.get(owner)?.get(name)?.overlayToBase.pending;
|
|
24135
|
+
if (pending === void 0 || pending.size === 0) return;
|
|
24136
|
+
for (const [ssId, symbolData] of [...pending]) {
|
|
24137
|
+
if (this.#classifyPacketId(owner) === null) return;
|
|
24138
|
+
if (serial32Distance(block.start, ssId) >= block.length) continue;
|
|
24139
|
+
pending.delete(ssId);
|
|
24140
|
+
this.#feedRebuiltToBase(owner, name, ssId, symbolData);
|
|
24141
|
+
}
|
|
23672
24142
|
}
|
|
23673
24143
|
/** fec-multicast-00 §6.6: report each base block whose RAP MFU fragments
|
|
23674
24144
|
* the overlay restored, when the keyframe's FT=0 and FT=1 are held too. A
|
|
@@ -23808,6 +24278,10 @@ var MmtpRouter = class {
|
|
|
23808
24278
|
get unadmittedListedDropCount() {
|
|
23809
24279
|
return this.#unadmittedListedDrops;
|
|
23810
24280
|
}
|
|
24281
|
+
/** 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. */
|
|
24282
|
+
get admittedSourceDatagramCount() {
|
|
24283
|
+
return this.#admittedSourceDatagrams;
|
|
24284
|
+
}
|
|
23811
24285
|
/** MoQ repair symbols of an unlisted track kept out of a governed FEC instance (7.2.7 step 2). */
|
|
23812
24286
|
get unlistedRepairSetAsideCount() {
|
|
23813
24287
|
return this.#unlistedRepairSetAside;
|
|
@@ -24294,6 +24768,8 @@ var MmtpRouter = class {
|
|
|
24294
24768
|
)) return;
|
|
24295
24769
|
fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);
|
|
24296
24770
|
fec.noteRepairGroupLength?.(blockStart, ownerPacketId, repair.rsbLength, catalogTrackName);
|
|
24771
|
+
this.#feedPendingRebuilt(ownerPacketId, catalogTrackName, placement.block);
|
|
24772
|
+
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
24297
24773
|
const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
24298
24774
|
const recovered = fec.feedRepairSymbol(
|
|
24299
24775
|
ownerPacketId,
|
|
@@ -24431,6 +24907,7 @@ var MmtpRouter = class {
|
|
|
24431
24907
|
this.#droppedPreSyncVideo.delete(streamKey);
|
|
24432
24908
|
this.#latestVideoSeqByStream.delete(streamKey);
|
|
24433
24909
|
this.#ssIdSeqOffsetByStream.delete(streamKey);
|
|
24910
|
+
this.#unstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
24434
24911
|
this.#multicastRapTwinByStream.delete(streamKey);
|
|
24435
24912
|
this.#lastDirectSourceVideoTsByStream.delete(streamKey);
|
|
24436
24913
|
this.#lastDirectEmittedVideoTsByStream.delete(streamKey);
|
|
@@ -25065,6 +25542,7 @@ var MmtpRouter = class {
|
|
|
25065
25542
|
const parsed = this.#parser.parsePacket(packet);
|
|
25066
25543
|
if (!parsed) return;
|
|
25067
25544
|
const info = parsed.info;
|
|
25545
|
+
if (manifestAdmitted && !info.isRepair && ingress?.admittedBy !== USER_AGENT_VERIFIED) this.#admittedSourceDatagrams++;
|
|
25068
25546
|
if (source === "multicast" && !manifestAdmitted) {
|
|
25069
25547
|
this.#fecManager?.noteMulticastArrival?.(info.packetId, info.sequenceNumber);
|
|
25070
25548
|
}
|
|
@@ -25346,7 +25824,8 @@ var MmtpRouter = class {
|
|
|
25346
25824
|
parsed.header,
|
|
25347
25825
|
info.sequenceNumber,
|
|
25348
25826
|
info.rapFlag,
|
|
25349
|
-
source
|
|
25827
|
+
source,
|
|
25828
|
+
fecPayloadId?.ssId
|
|
25350
25829
|
);
|
|
25351
25830
|
}
|
|
25352
25831
|
}
|
|
@@ -25496,7 +25975,8 @@ var MmtpRouter = class {
|
|
|
25496
25975
|
if (stream === void 0) {
|
|
25497
25976
|
stream = {
|
|
25498
25977
|
key: JSON.stringify([packetId, catalogTrackName ?? null]),
|
|
25499
|
-
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId))
|
|
25978
|
+
duplicates: new StreamDuplicateLedgers(() => this.duplicateHorizonFor(packetId)),
|
|
25979
|
+
overlayToBase: { routed: /* @__PURE__ */ new Set(), pending: /* @__PURE__ */ new Map() }
|
|
25500
25980
|
};
|
|
25501
25981
|
byName.set(catalogTrackName, stream);
|
|
25502
25982
|
}
|
|
@@ -25611,8 +26091,16 @@ var MmtpRouter = class {
|
|
|
25611
26091
|
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
25612
26092
|
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
25613
26093
|
}
|
|
25614
|
-
#routeDirectRecoveredVideo(packetId, data, meta, cb) {
|
|
26094
|
+
#routeDirectRecoveredVideo(packetId, data, meta, cb, fecSbn) {
|
|
25615
26095
|
const streamKey = this.#mediaStreamKey(packetId, meta.catalogTrackName);
|
|
26096
|
+
if (this.#isFecTrack(packetId)) {
|
|
26097
|
+
const high = this.#deliveredVideoHighWaterByStream.get(streamKey);
|
|
26098
|
+
if (fecSbn !== void 0 || high !== void 0 && authoredAdjacent(high, meta)) {
|
|
26099
|
+
this.#drainDirectRecoveredVideo(streamKey, packetId, (frame) => frame.timestamp <= meta.timestamp, cb);
|
|
26100
|
+
this.#emitDirectVideo(packetId, data, meta, cb);
|
|
26101
|
+
return;
|
|
26102
|
+
}
|
|
26103
|
+
}
|
|
25616
26104
|
this.#enqueueDirectRecoveredVideo(packetId, data, meta);
|
|
25617
26105
|
const lastSourceTs = this.#lastDirectSourceVideoTsByStream.get(streamKey);
|
|
25618
26106
|
this.#drainDirectRecoveredVideo(
|
|
@@ -25766,6 +26254,13 @@ var MmtpRouter = class {
|
|
|
25766
26254
|
* frames by epoch only if pre-protection input ever matters.
|
|
25767
26255
|
*/
|
|
25768
26256
|
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
26257
|
+
const overlayRouted = ownerPacketId == null ? void 0 : this.#streams.get(ownerPacketId)?.get(catalogTrackName)?.overlayToBase.routed;
|
|
26258
|
+
if (overlayRouted !== void 0 && overlayRouted.size > 0) {
|
|
26259
|
+
result = {
|
|
26260
|
+
...result,
|
|
26261
|
+
symbols: result.symbols.filter((sym) => !overlayRouted.has(this.#fecAnchorForRecovered(blockId, sym.index)))
|
|
26262
|
+
};
|
|
26263
|
+
}
|
|
25769
26264
|
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
25770
26265
|
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
25771
26266
|
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
@@ -25830,7 +26325,10 @@ var MmtpRouter = class {
|
|
|
25830
26325
|
...result,
|
|
25831
26326
|
symbols: result.symbols.map((sym) => {
|
|
25832
26327
|
const wire = this.#fecAnchorForRecovered(blockId, sym.index) - ssIdSeqOffset >>> 0;
|
|
25833
|
-
if (wire === sym.packetSequenceNumber)
|
|
26328
|
+
if (wire === sym.packetSequenceNumber) {
|
|
26329
|
+
this.#unstampedSsIdSeqOffsetByStream.set(streamKey, ssIdSeqOffset);
|
|
26330
|
+
return sym;
|
|
26331
|
+
}
|
|
25834
26332
|
this.#restampedRecoveredSeqs++;
|
|
25835
26333
|
this.#restampedStreams.add(streamKey);
|
|
25836
26334
|
return { ...sym, packetSequenceNumber: wire };
|
|
@@ -25866,6 +26364,29 @@ var MmtpRouter = class {
|
|
|
25866
26364
|
}
|
|
25867
26365
|
);
|
|
25868
26366
|
}
|
|
26367
|
+
const untimedHead = (sym) => sym.fragmentType === 2 && sym.fragment.fragmentationIndicator === 1 && sym.fragment.movieFragmentSequenceNumber !== void 0 && sym.fragment.sampleNumber !== void 0 && !this.#parser.hasAuthoredTimingFor(
|
|
26368
|
+
ownerPacketId,
|
|
26369
|
+
sym.fragment.mpuSequenceNumber,
|
|
26370
|
+
sym.fragment.movieFragmentSequenceNumber,
|
|
26371
|
+
sym.fragment.sampleNumber,
|
|
26372
|
+
catalogTrackName
|
|
26373
|
+
);
|
|
26374
|
+
for (const sym of result.symbols) {
|
|
26375
|
+
if (!untimedHead(sym) || this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) continue;
|
|
26376
|
+
this.#feedViaParser(
|
|
26377
|
+
sym.rawSymbol,
|
|
26378
|
+
{ packetId: ownerPacketId, sequenceNumber: sym.packetSequenceNumber },
|
|
26379
|
+
void 0,
|
|
26380
|
+
{
|
|
26381
|
+
skipFecPayloadId: true,
|
|
26382
|
+
recovered: true,
|
|
26383
|
+
deliveryProvenance,
|
|
26384
|
+
...fecSbnOf(sym.index),
|
|
26385
|
+
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
26386
|
+
}
|
|
26387
|
+
);
|
|
26388
|
+
}
|
|
26389
|
+
result = { ...result, symbols: result.symbols.filter((sym) => !untimedHead(sym)) };
|
|
25869
26390
|
const gated = this.#isFecTrack(ownerPacketId);
|
|
25870
26391
|
const assembler = this.#mseAssemblerFor(ownerPacketId, catalogTrackName);
|
|
25871
26392
|
if (assembler) {
|
|
@@ -29627,7 +30148,7 @@ async function setupFecWorkerForTrack(opts) {
|
|
|
29627
30148
|
blockProfile,
|
|
29628
30149
|
k: opts.fecK,
|
|
29629
30150
|
// P as this receiver joined it, not the catalog total: the worker snapshot
|
|
29630
|
-
// and CMCD `
|
|
30151
|
+
// and CMCD `com.blockcast-fp` are read from this. Identity on a single layer.
|
|
29631
30152
|
repairCount: opts.joinedRepairCount ?? opts.fecRepairCount,
|
|
29632
30153
|
symbolSize: opts.fecSymbolSize,
|
|
29633
30154
|
interleaveDepth: interleaveDepthBlocks,
|
|
@@ -30169,24 +30690,24 @@ var MediaRouter = class {
|
|
|
30169
30690
|
if (!this.#multicastVideoKeyed) return;
|
|
30170
30691
|
if (this.#requireAudio && !this.#multicastAudioReceived) return;
|
|
30171
30692
|
if (this.#requireAudio && !this.#moqAudioWarmed) return;
|
|
30172
|
-
const
|
|
30693
|
+
const token2 = ++this.#sourceSwitchToken;
|
|
30173
30694
|
const maybeReady = this.#onSourceSwitch?.("multicast");
|
|
30174
30695
|
if (!maybeReady || typeof maybeReady.then !== "function") {
|
|
30175
|
-
this.#commitMulticastActivation(
|
|
30696
|
+
this.#commitMulticastActivation(token2);
|
|
30176
30697
|
return;
|
|
30177
30698
|
}
|
|
30178
30699
|
const ready = Promise.resolve(maybeReady);
|
|
30179
|
-
this.#pendingMulticastToken =
|
|
30700
|
+
this.#pendingMulticastToken = token2;
|
|
30180
30701
|
this.#sourceSwitchReady = ready;
|
|
30181
30702
|
void ready.then(
|
|
30182
|
-
() => this.#commitMulticastActivation(
|
|
30183
|
-
(err) => this.#rejectMulticastActivation(
|
|
30703
|
+
() => this.#commitMulticastActivation(token2),
|
|
30704
|
+
(err) => this.#rejectMulticastActivation(token2, err)
|
|
30184
30705
|
).finally(() => {
|
|
30185
30706
|
if (this.#sourceSwitchReady === ready) this.#sourceSwitchReady = null;
|
|
30186
30707
|
});
|
|
30187
30708
|
}
|
|
30188
|
-
#commitMulticastActivation(
|
|
30189
|
-
if (
|
|
30709
|
+
#commitMulticastActivation(token2) {
|
|
30710
|
+
if (token2 !== this.#sourceSwitchToken) return;
|
|
30190
30711
|
this.#active = "multicast";
|
|
30191
30712
|
this.#suppressed = true;
|
|
30192
30713
|
this.#pendingMulticastToken = null;
|
|
@@ -30197,9 +30718,9 @@ var MediaRouter = class {
|
|
|
30197
30718
|
if (video) this.#onVideoFrame?.(video.data, video.meta);
|
|
30198
30719
|
if (audio) this.#onAudioFrame?.(audio.data, audio.meta);
|
|
30199
30720
|
}
|
|
30200
|
-
#rejectMulticastActivation(
|
|
30201
|
-
if (
|
|
30202
|
-
if (this.#pendingMulticastToken ===
|
|
30721
|
+
#rejectMulticastActivation(token2, err) {
|
|
30722
|
+
if (token2 !== this.#sourceSwitchToken) return;
|
|
30723
|
+
if (this.#pendingMulticastToken === token2) this.#pendingMulticastToken = null;
|
|
30203
30724
|
this.#pendingMulticastVideo = null;
|
|
30204
30725
|
this.#pendingMulticastAudio = null;
|
|
30205
30726
|
console.warn("[MediaRouter] onSourceSwitch(multicast) failed", err);
|
|
@@ -31168,63 +31689,65 @@ var FecStarvationDetector = class {
|
|
|
31168
31689
|
|
|
31169
31690
|
// src/cmcd-producer.ts
|
|
31170
31691
|
var PLAY_MODES = ["full", "base-only", "single-track"];
|
|
31171
|
-
|
|
31172
|
-
|
|
31173
|
-
|
|
31174
|
-
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
|
|
31182
|
-
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
}
|
|
31190
|
-
if (snap.multicastSource != null) {
|
|
31191
|
-
params["x-bc-multicast-source"] = snap.multicastSource;
|
|
31192
|
-
}
|
|
31193
|
-
if (snap.amtRelay != null) {
|
|
31194
|
-
params["x-bc-amt-relay"] = snap.amtRelay;
|
|
31195
|
-
}
|
|
31196
|
-
return params;
|
|
31197
|
-
}
|
|
31198
|
-
function formatQueryString(params) {
|
|
31199
|
-
return Object.keys(params).sort().map((key) => {
|
|
31200
|
-
const v = params[key];
|
|
31201
|
-
if (typeof v === "string") return `${key}=${sanitize(v)}`;
|
|
31202
|
-
return `${key}=${v}`;
|
|
31692
|
+
var DEFAULT_INTERVAL_MS = 3e4;
|
|
31693
|
+
var CUSTOM_PREFIX = "com.blockcast-";
|
|
31694
|
+
var MAX_CUSTOM_VALUE = 64;
|
|
31695
|
+
var TRANSPORT_TOKEN = {
|
|
31696
|
+
native: "ssm",
|
|
31697
|
+
amt: "amt",
|
|
31698
|
+
moq: "moq"
|
|
31699
|
+
};
|
|
31700
|
+
function sfString(value) {
|
|
31701
|
+
return `"${value.replace(/[^\x20-\x7e]/g, "").replace(/[\\"]/g, "\\$&")}"`;
|
|
31702
|
+
}
|
|
31703
|
+
function encodeCmcdRecord(record) {
|
|
31704
|
+
return Object.keys(record).sort().map((key) => {
|
|
31705
|
+
const value = record[key];
|
|
31706
|
+
if (typeof value === "number") return `${key}=${value}`;
|
|
31707
|
+
if (typeof value === "string") return `${key}=${sfString(value)}`;
|
|
31708
|
+
if (Array.isArray(value)) return `${key}=(${value.map(sfString).join(" ")})`;
|
|
31709
|
+
return `${key}=${value.token}`;
|
|
31203
31710
|
}).join(",");
|
|
31204
31711
|
}
|
|
31712
|
+
var token = (value) => ({ token: value });
|
|
31713
|
+
function putCustom(record, name, value) {
|
|
31714
|
+
if (value === void 0) return;
|
|
31715
|
+
if (typeof value === "number") value = String(value);
|
|
31716
|
+
if (typeof value === "string" && value.length > MAX_CUSTOM_VALUE) return;
|
|
31717
|
+
record[CUSTOM_PREFIX + name] = value;
|
|
31718
|
+
}
|
|
31205
31719
|
var CmcdProducer = class {
|
|
31206
|
-
#onCmcdParams;
|
|
31207
31720
|
#onCmcdPost;
|
|
31721
|
+
#intervalMs;
|
|
31722
|
+
#sessionId;
|
|
31723
|
+
#now;
|
|
31724
|
+
#sn = 0;
|
|
31725
|
+
#lastHeartbeat = null;
|
|
31208
31726
|
#transport;
|
|
31209
31727
|
#lastStats;
|
|
31210
|
-
// Phase 14 state — Plan 03 Task 2 (D-12, D-15).
|
|
31211
31728
|
#playMode = null;
|
|
31212
31729
|
#deltaFailRate = null;
|
|
31213
31730
|
#switchMiscount = 0;
|
|
31214
31731
|
#manifestStats = null;
|
|
31215
31732
|
#fecFailClosed = null;
|
|
31733
|
+
/** Video-block resolutions from the repair lifecycle; null until the player feeds one. */
|
|
31734
|
+
#lifecycle = null;
|
|
31216
31735
|
constructor(options) {
|
|
31217
|
-
this.#onCmcdParams = options.onCmcdParams;
|
|
31218
31736
|
this.#onCmcdPost = options.onCmcdPost;
|
|
31737
|
+
this.#intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
31738
|
+
if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {
|
|
31739
|
+
throw new Error(`[CmcdProducer] invalid intervalMs "${String(options.intervalMs)}"; expected a positive number`);
|
|
31740
|
+
}
|
|
31741
|
+
this.#sessionId = options.sessionId ?? globalThis.crypto.randomUUID();
|
|
31742
|
+
this.#now = options.now ?? Date.now;
|
|
31219
31743
|
}
|
|
31220
31744
|
/** Update the stored transport state. Called by player wiring layer. */
|
|
31221
31745
|
updateTransport(snapshot) {
|
|
31222
31746
|
this.#transport = snapshot;
|
|
31223
31747
|
}
|
|
31224
31748
|
/**
|
|
31225
|
-
* Set the current play-mode
|
|
31226
|
-
*
|
|
31227
|
-
* (CLAUDE.md: error on bad init).
|
|
31749
|
+
* Set the current play-mode, reported as `com.blockcast-pm`. Throws on any
|
|
31750
|
+
* value outside {'full','base-only','single-track'} (CLAUDE.md: error on bad init).
|
|
31228
31751
|
*/
|
|
31229
31752
|
updatePlayMode(mode) {
|
|
31230
31753
|
if (typeof mode !== "string" || !PLAY_MODES.includes(mode)) {
|
|
@@ -31235,9 +31758,8 @@ var CmcdProducer = class {
|
|
|
31235
31758
|
this.#playMode = mode;
|
|
31236
31759
|
}
|
|
31237
31760
|
/**
|
|
31238
|
-
* Set the delta-track FEC block-failure rate (0..1 inclusive)
|
|
31239
|
-
* `
|
|
31240
|
-
* Omitted on single-track streams.
|
|
31761
|
+
* Set the delta-track FEC block-failure rate (0..1 inclusive), reported as
|
|
31762
|
+
* `com.blockcast-dfr` when play-mode is 'full' or 'base-only'.
|
|
31241
31763
|
*/
|
|
31242
31764
|
updateDeltaFailRate(rate) {
|
|
31243
31765
|
if (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0 || rate > 1) {
|
|
@@ -31247,112 +31769,139 @@ var CmcdProducer = class {
|
|
|
31247
31769
|
}
|
|
31248
31770
|
this.#deltaFailRate = rate;
|
|
31249
31771
|
}
|
|
31250
|
-
/**
|
|
31251
|
-
* Bump the cumulative switching-set misalignment counter. Emitted as
|
|
31252
|
-
* `x-bc-switch-miscount` on snapshots when the counter is > 0.
|
|
31253
|
-
*/
|
|
31772
|
+
/** Bump the cumulative switching-set misalignment counter (`com.blockcast-sm`). */
|
|
31254
31773
|
incrementSwitchMiscount() {
|
|
31255
31774
|
this.#switchMiscount += 1;
|
|
31256
31775
|
}
|
|
31257
31776
|
/**
|
|
31258
|
-
* Record that FEC failed closed at runtime (fec-01 6.3.9).
|
|
31259
|
-
*
|
|
31260
|
-
* `
|
|
31261
|
-
* stream playing without FEC is told apart from one
|
|
31777
|
+
* Record that FEC failed closed at runtime (fec-01 6.3.9). Reported at once
|
|
31778
|
+
* as `e=e` with the reason code in `ec`, and on every later counter report
|
|
31779
|
+
* as `com.blockcast-ffc` / `com.blockcast-ffl` (the held layer whose total
|
|
31780
|
+
* B_i was undercut), so a stream playing without FEC is told apart from one
|
|
31781
|
+
* with no loss.
|
|
31262
31782
|
*/
|
|
31263
31783
|
updateFecFailClosed(reason) {
|
|
31784
|
+
const changed = this.#fecFailClosed?.code !== reason.code || this.#fecFailClosed?.layer !== reason.layer;
|
|
31264
31785
|
this.#fecFailClosed = reason;
|
|
31786
|
+
if (changed) this.#post([{ ...this.#base("e"), ec: [reason.code] }]);
|
|
31265
31787
|
}
|
|
31266
31788
|
updateManifestVerification(stats) {
|
|
31267
31789
|
this.#manifestStats = stats;
|
|
31268
31790
|
}
|
|
31269
31791
|
/**
|
|
31270
|
-
*
|
|
31271
|
-
*
|
|
31272
|
-
*
|
|
31273
|
-
*
|
|
31792
|
+
* Count a video block's resolution from the repair lifecycle: recovered in
|
|
31793
|
+
* band (`com.blockcast-fbr`), recovered by repair FETCH (`-fbq`), or failed
|
|
31794
|
+
* as a keyframe-class block (`-fbfk`). Audio blocks are not counted, matching
|
|
31795
|
+
* the video-track `-fbf`. Until the player feeds this, the three are absent.
|
|
31274
31796
|
*/
|
|
31797
|
+
onRepairLifecycle(event) {
|
|
31798
|
+
this.#lifecycle ??= { inband: 0, fetch: 0, failedKeyframe: 0 };
|
|
31799
|
+
if (event.type === "recovered" && event.blockClass !== "audio") {
|
|
31800
|
+
if (event.via === "inband") this.#lifecycle.inband++;
|
|
31801
|
+
else this.#lifecycle.fetch++;
|
|
31802
|
+
} else if (event.type === "failed" && event.blockClass === "keyframe") {
|
|
31803
|
+
this.#lifecycle.failedKeyframe++;
|
|
31804
|
+
}
|
|
31805
|
+
}
|
|
31806
|
+
/** One-shot custom event `com.blockcast-switch-drift` for a single misalignment. */
|
|
31275
31807
|
emitSwitchDrift(driftUs) {
|
|
31276
31808
|
if (typeof driftUs !== "number" || !Number.isFinite(driftUs) || driftUs < 0) {
|
|
31277
31809
|
throw new Error(
|
|
31278
31810
|
`[CmcdProducer] invalid drift-us "${String(driftUs)}"; expected a non-negative finite number`
|
|
31279
31811
|
);
|
|
31280
31812
|
}
|
|
31281
|
-
const
|
|
31282
|
-
|
|
31283
|
-
|
|
31284
|
-
if (this.#switchMiscount > 0) params["x-bc-switch-miscount"] = this.#switchMiscount;
|
|
31285
|
-
params["x-bc-switch-drift-us"] = driftUs;
|
|
31286
|
-
this.#emit(params);
|
|
31813
|
+
const record = { ...this.#base("ce"), cen: CUSTOM_PREFIX + "switch-drift" };
|
|
31814
|
+
putCustom(record, "sdu", Math.round(driftUs));
|
|
31815
|
+
this.#post([record]);
|
|
31287
31816
|
}
|
|
31288
|
-
/**
|
|
31289
|
-
* Process a FecTrackStats snapshot. Finds the video track, builds
|
|
31290
|
-
* x-bc-* vendor keys, and fires both delivery callbacks.
|
|
31291
|
-
*/
|
|
31817
|
+
/** Take a FecTrackStats snapshot (first video track) and send the heartbeat when due. */
|
|
31292
31818
|
onSnapshot(data) {
|
|
31293
31819
|
const videoTrack = data.tracks.find((t) => t.config.trackType === "video");
|
|
31294
31820
|
if (!videoTrack) return;
|
|
31295
31821
|
this.#lastStats = videoTrack;
|
|
31296
|
-
|
|
31297
|
-
...buildFecParams(videoTrack),
|
|
31298
|
-
...buildTransportParams(this.#transport)
|
|
31299
|
-
};
|
|
31300
|
-
if (this.#playMode !== null) {
|
|
31301
|
-
params["x-bc-play-mode"] = this.#playMode;
|
|
31302
|
-
}
|
|
31303
|
-
if (this.#fecFailClosed) {
|
|
31304
|
-
params["x-bc-fec-fail-closed"] = this.#fecFailClosed.code;
|
|
31305
|
-
params["x-bc-fec-fail-layer"] = this.#fecFailClosed.layer;
|
|
31306
|
-
}
|
|
31307
|
-
if (this.#playMode !== null && this.#playMode !== "single-track" && this.#deltaFailRate !== null) {
|
|
31308
|
-
params["x-bc-delta-fail-rate"] = parseFloat(this.#deltaFailRate.toFixed(4));
|
|
31309
|
-
}
|
|
31310
|
-
if (this.#switchMiscount > 0) {
|
|
31311
|
-
params["x-bc-switch-miscount"] = this.#switchMiscount;
|
|
31312
|
-
}
|
|
31313
|
-
if (this.#manifestStats) {
|
|
31314
|
-
params["x-bc-manifest-verified"] = this.#manifestStats.verified > 0;
|
|
31315
|
-
params["x-bc-manifest-failures"] = Object.values(this.#manifestStats.failures).reduce((total, count) => total + count, 0);
|
|
31316
|
-
}
|
|
31317
|
-
this.#emit(params);
|
|
31822
|
+
this.#heartbeat();
|
|
31318
31823
|
}
|
|
31319
31824
|
/**
|
|
31320
|
-
*
|
|
31321
|
-
*
|
|
31825
|
+
* Take transport state for receivers that do not own FEC stats, and send the
|
|
31826
|
+
* heartbeat when due.
|
|
31322
31827
|
*/
|
|
31323
31828
|
onTransportSnapshot(data) {
|
|
31324
31829
|
if (!Number.isFinite(data.timestampMs) || data.timestampMs < 0) {
|
|
31325
31830
|
throw new Error("[CmcdProducer] transport snapshot timestamp must be a non-negative finite number");
|
|
31326
31831
|
}
|
|
31327
31832
|
if (data.playMode !== void 0) this.updatePlayMode(data.playMode);
|
|
31328
|
-
if (data.switchMiscount !== void 0
|
|
31329
|
-
|
|
31833
|
+
if (data.switchMiscount !== void 0) {
|
|
31834
|
+
if (!Number.isSafeInteger(data.switchMiscount) || data.switchMiscount < 0) {
|
|
31835
|
+
throw new Error("[CmcdProducer] transport snapshot switch-miscount must be a non-negative safe integer");
|
|
31836
|
+
}
|
|
31837
|
+
this.#switchMiscount = data.switchMiscount;
|
|
31330
31838
|
}
|
|
31331
31839
|
this.#transport = data.transport;
|
|
31332
|
-
|
|
31333
|
-
if (this.#playMode !== null) params["x-bc-play-mode"] = this.#playMode;
|
|
31334
|
-
const switchMiscount = data.switchMiscount ?? this.#switchMiscount;
|
|
31335
|
-
if (switchMiscount > 0) params["x-bc-switch-miscount"] = switchMiscount;
|
|
31336
|
-
this.#emit(params);
|
|
31840
|
+
this.#heartbeat();
|
|
31337
31841
|
}
|
|
31338
|
-
/**
|
|
31339
|
-
* Signal a transport transition event. Fires an immediate out-of-band
|
|
31340
|
-
* emit with x-bc-ct set. The subsequent onSnapshot() will NOT include
|
|
31341
|
-
* x-bc-ct (one-shot).
|
|
31342
|
-
*/
|
|
31842
|
+
/** Report a transport transition at once, as custom event `com.blockcast-ct`. */
|
|
31343
31843
|
onTransition(event) {
|
|
31344
|
-
const
|
|
31345
|
-
|
|
31346
|
-
|
|
31844
|
+
const record = { ...this.#base("ce"), cen: CUSTOM_PREFIX + "ct" };
|
|
31845
|
+
putCustom(record, "ct", token(event));
|
|
31846
|
+
this.#putTransport(record);
|
|
31847
|
+
this.#post([record]);
|
|
31848
|
+
}
|
|
31849
|
+
#base(event) {
|
|
31850
|
+
return { e: token(event), sid: this.#sessionId, sn: this.#sn++, ts: Math.floor(this.#now()), v: 2 };
|
|
31851
|
+
}
|
|
31852
|
+
#heartbeat() {
|
|
31853
|
+
const now = this.#now();
|
|
31854
|
+
if (this.#lastHeartbeat !== null && now - this.#lastHeartbeat < this.#intervalMs) return;
|
|
31855
|
+
this.#lastHeartbeat = now;
|
|
31856
|
+
this.#post([this.#base("t"), this.#counters()]);
|
|
31857
|
+
}
|
|
31858
|
+
#putTransport(record) {
|
|
31859
|
+
const snap = this.#transport;
|
|
31860
|
+
if (!snap) return;
|
|
31861
|
+
putCustom(record, "tr", token(TRANSPORT_TOKEN[snap.transport]));
|
|
31862
|
+
if (snap.multicastGroup != null && snap.multicastSource != null) {
|
|
31863
|
+
putCustom(record, "sg", `${snap.multicastGroup}@${snap.multicastSource}`);
|
|
31864
|
+
}
|
|
31865
|
+
putCustom(record, "ar", snap.amtRelay);
|
|
31866
|
+
}
|
|
31867
|
+
/** The `e=ce` counter report. */
|
|
31868
|
+
#counters() {
|
|
31869
|
+
const record = { ...this.#base("ce"), cen: CUSTOM_PREFIX + "fec" };
|
|
31870
|
+
const stats = this.#lastStats;
|
|
31871
|
+
if (stats) {
|
|
31872
|
+
putCustom(record, "fa", stats.config.algorithm);
|
|
31873
|
+
putCustom(record, "fk", stats.config.k);
|
|
31874
|
+
putCustom(record, "fp", stats.config.repairCount);
|
|
31875
|
+
putCustom(record, "pr", stats.sourceSymbols);
|
|
31876
|
+
putCustom(record, "pl", stats.sourcePacketsMissing);
|
|
31877
|
+
putCustom(record, "pu", stats.sourcePacketsUnrecovered);
|
|
31878
|
+
putCustom(record, "fbf", stats.blocksFailed);
|
|
31879
|
+
}
|
|
31880
|
+
if (this.#lifecycle) {
|
|
31881
|
+
putCustom(record, "fbr", this.#lifecycle.inband);
|
|
31882
|
+
putCustom(record, "fbq", this.#lifecycle.fetch);
|
|
31883
|
+
putCustom(record, "fbfk", this.#lifecycle.failedKeyframe);
|
|
31884
|
+
}
|
|
31885
|
+
this.#putTransport(record);
|
|
31886
|
+
if (this.#playMode !== null) {
|
|
31887
|
+
putCustom(record, "pm", token(this.#playMode));
|
|
31888
|
+
if (this.#playMode !== "single-track" && this.#deltaFailRate !== null) {
|
|
31889
|
+
putCustom(record, "dfr", this.#deltaFailRate.toFixed(3));
|
|
31890
|
+
}
|
|
31891
|
+
}
|
|
31892
|
+
if (this.#fecFailClosed) {
|
|
31893
|
+
putCustom(record, "ffc", token(this.#fecFailClosed.code));
|
|
31894
|
+
putCustom(record, "ffl", this.#fecFailClosed.layer);
|
|
31347
31895
|
}
|
|
31348
|
-
|
|
31349
|
-
|
|
31350
|
-
|
|
31896
|
+
if (this.#switchMiscount > 0) putCustom(record, "sm", this.#switchMiscount);
|
|
31897
|
+
if (this.#manifestStats) {
|
|
31898
|
+
putCustom(record, "mv", this.#manifestStats.verified);
|
|
31899
|
+
putCustom(record, "mf", Object.values(this.#manifestStats.failures).reduce((a, b) => a + b, 0));
|
|
31900
|
+
}
|
|
31901
|
+
return record;
|
|
31351
31902
|
}
|
|
31352
|
-
|
|
31353
|
-
|
|
31354
|
-
this.#onCmcdParams?.(params);
|
|
31355
|
-
this.#onCmcdPost?.(formatQueryString(params));
|
|
31903
|
+
#post(records) {
|
|
31904
|
+
this.#onCmcdPost?.(records.map(encodeCmcdRecord).join("\n"));
|
|
31356
31905
|
}
|
|
31357
31906
|
};
|
|
31358
31907
|
|
|
@@ -31448,18 +31997,31 @@ function resolveRepairLayers(catalog, source) {
|
|
|
31448
31997
|
totalBaseRepairSymbols: binding.totalBaseRepairSymbols
|
|
31449
31998
|
};
|
|
31450
31999
|
}
|
|
32000
|
+
function valueAt(catalog, pointer) {
|
|
32001
|
+
let node = catalog;
|
|
32002
|
+
for (const segment of pointer.split("/").slice(1)) {
|
|
32003
|
+
if (typeof node !== "object" || node === null) return void 0;
|
|
32004
|
+
node = node[segment.replace(/~1/g, "/").replace(/~0/g, "~")];
|
|
32005
|
+
}
|
|
32006
|
+
return node;
|
|
32007
|
+
}
|
|
32008
|
+
function catalogTrackDeliveryMode(catalog, source) {
|
|
32009
|
+
const binding = bindingOf(catalog, source);
|
|
32010
|
+
const entry = binding ? valueAt(catalog, binding.pointer) : void 0;
|
|
32011
|
+
return entry?.deliveryMode === "stream" ? "stream" : "datagram";
|
|
32012
|
+
}
|
|
31451
32013
|
function moqtPriority2(value) {
|
|
31452
32014
|
return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
|
|
31453
32015
|
}
|
|
31454
32016
|
function catalogRepairTarget(catalog, source) {
|
|
31455
32017
|
const binding = bindingOf(catalog, source);
|
|
31456
32018
|
if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
|
|
31457
|
-
const
|
|
32019
|
+
const layerZero2 = binding.layers[0];
|
|
31458
32020
|
const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
|
|
31459
32021
|
const tracks = catalog.tracks;
|
|
31460
32022
|
const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
|
|
31461
32023
|
const sourcePriority = entry ? moqtPriority2(entry["priority"]) : null;
|
|
31462
|
-
return { repairTrack:
|
|
32024
|
+
return { repairTrack: layerZero2.repairTrackName, sourcePriority, repairPriority: layerZero2.priority };
|
|
31463
32025
|
}
|
|
31464
32026
|
function numericField(value) {
|
|
31465
32027
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
@@ -31491,6 +32053,7 @@ function fecParams(catalog, track, fps, sampleRate) {
|
|
|
31491
32053
|
repairTrackName: base.repairTrack,
|
|
31492
32054
|
repairLayers: repair.baseLayers,
|
|
31493
32055
|
repairOverlays: repair.overlays,
|
|
32056
|
+
deliveryMode: catalogTrackDeliveryMode(catalog, track),
|
|
31494
32057
|
...mediaRate
|
|
31495
32058
|
};
|
|
31496
32059
|
}
|
|
@@ -31541,7 +32104,14 @@ function resolveCatalogTrackFec(catalog, tracks) {
|
|
|
31541
32104
|
var PATH_BITS = { moq: 1, multicast: 2 };
|
|
31542
32105
|
var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
|
|
31543
32106
|
var MmtpFecSession = class {
|
|
32107
|
+
/** P_0 = B_0, the held total every repair packet is checked against. */
|
|
32108
|
+
get repairSymbols() {
|
|
32109
|
+
return this.#repairSymbols;
|
|
32110
|
+
}
|
|
31544
32111
|
#decoder;
|
|
32112
|
+
#repairSymbols;
|
|
32113
|
+
/** The message entry a catalog-less session was built from; undefined under a catalog. */
|
|
32114
|
+
#message;
|
|
31545
32115
|
/** Blocks a repair has defined and that are not settled yet, by SS_Start. */
|
|
31546
32116
|
#blocks = /* @__PURE__ */ new Map();
|
|
31547
32117
|
/** SS_Starts of settled blocks, so a later repair for one is refused. */
|
|
@@ -31579,6 +32149,24 @@ var MmtpFecSession = class {
|
|
|
31579
32149
|
* `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
|
|
31580
32150
|
*/
|
|
31581
32151
|
constructor(config) {
|
|
32152
|
+
if (config && "alFecFlow" in config) {
|
|
32153
|
+
const message = alFecRaptorQFlowConfig(config.alFecFlow);
|
|
32154
|
+
if (!message) {
|
|
32155
|
+
throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
|
|
32156
|
+
}
|
|
32157
|
+
this.#message = message;
|
|
32158
|
+
this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
|
|
32159
|
+
this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
|
|
32160
|
+
this.sourceSymbols = message.maximumK;
|
|
32161
|
+
this.#repairSymbols = message.maximumP;
|
|
32162
|
+
this.symbolSize = message.repairSymbolSize;
|
|
32163
|
+
this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
|
|
32164
|
+
this.#repairPacketId = message.repairFlowId;
|
|
32165
|
+
this.#heldLayers = layerZero(message.maximumP);
|
|
32166
|
+
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
32167
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
32168
|
+
return;
|
|
32169
|
+
}
|
|
31582
32170
|
const fec = config?.fec;
|
|
31583
32171
|
if (!fec || typeof fec !== "object") {
|
|
31584
32172
|
throw new Error("MmtpFecSession: the catalog fec instance is required");
|
|
@@ -31589,7 +32177,7 @@ var MmtpFecSession = class {
|
|
|
31589
32177
|
}
|
|
31590
32178
|
this.algorithm = fec.scheme;
|
|
31591
32179
|
this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
|
|
31592
|
-
this
|
|
32180
|
+
this.#repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
|
|
31593
32181
|
this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
|
|
31594
32182
|
const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
|
|
31595
32183
|
const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
|
|
@@ -31609,17 +32197,30 @@ var MmtpFecSession = class {
|
|
|
31609
32197
|
config.heldLayers
|
|
31610
32198
|
);
|
|
31611
32199
|
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31612
|
-
|
|
31613
|
-
|
|
31614
|
-
|
|
31615
|
-
|
|
31616
|
-
|
|
31617
|
-
|
|
31618
|
-
|
|
31619
|
-
|
|
31620
|
-
|
|
31621
|
-
|
|
31622
|
-
|
|
32200
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
32201
|
+
}
|
|
32202
|
+
/**
|
|
32203
|
+
* A later version of the AL-FEC message, catalog-less sessions only. A
|
|
32204
|
+
* maximum_p_for_repair_flow above the held total raises it (`raised`): the
|
|
32205
|
+
* anchored check restarts at the new total, so a block generated under the
|
|
32206
|
+
* previous one stays valid before the first block carrying the new total
|
|
32207
|
+
* (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
|
|
32208
|
+
* (`unchanged`): no publisher lowers it, and the check catches a wire that
|
|
32209
|
+
* does. An entry with another asset, repair flow, code point, K or T, or
|
|
32210
|
+
* one outside 8.3, is `refused`: that is a new source encoding, which needs
|
|
32211
|
+
* a new session. A latched fail-closed stays latched either way.
|
|
32212
|
+
*/
|
|
32213
|
+
updateAlFecFlow(flow) {
|
|
32214
|
+
const held = this.#message;
|
|
32215
|
+
if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
|
|
32216
|
+
const next = alFecRaptorQFlowConfig(flow);
|
|
32217
|
+
if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
|
|
32218
|
+
return "refused";
|
|
32219
|
+
}
|
|
32220
|
+
if (next.maximumP <= this.#repairSymbols) return "unchanged";
|
|
32221
|
+
this.#repairSymbols = next.maximumP;
|
|
32222
|
+
this.#heldLayers = layerZero(next.maximumP);
|
|
32223
|
+
return "raised";
|
|
31623
32224
|
}
|
|
31624
32225
|
/** True once release() ran. */
|
|
31625
32226
|
get released() {
|
|
@@ -31714,7 +32315,7 @@ var MmtpFecSession = class {
|
|
|
31714
32315
|
}
|
|
31715
32316
|
if (admission.reject) {
|
|
31716
32317
|
const verdict2 = this.#heldLayers.failsClosed(
|
|
31717
|
-
repairTrackName,
|
|
32318
|
+
this.#trackFor(repairTrackName),
|
|
31718
32319
|
frame.ssStart,
|
|
31719
32320
|
frame.rsbLength,
|
|
31720
32321
|
this.#anchorHorizon,
|
|
@@ -31729,7 +32330,7 @@ var MmtpFecSession = class {
|
|
|
31729
32330
|
this.#repairPacketId ??= frame.packetId;
|
|
31730
32331
|
if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
|
|
31731
32332
|
const verdict = this.#heldLayers.admit(
|
|
31732
|
-
repairTrackName,
|
|
32333
|
+
this.#trackFor(repairTrackName),
|
|
31733
32334
|
frame.ssStart,
|
|
31734
32335
|
frame.rsbLength,
|
|
31735
32336
|
this.#anchorHorizon,
|
|
@@ -31788,6 +32389,10 @@ var MmtpFecSession = class {
|
|
|
31788
32389
|
decoder.free();
|
|
31789
32390
|
return true;
|
|
31790
32391
|
}
|
|
32392
|
+
/** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
|
|
32393
|
+
#trackFor(repairTrackName) {
|
|
32394
|
+
return this.#message ? void 0 : repairTrackName;
|
|
32395
|
+
}
|
|
31791
32396
|
#drop(reason) {
|
|
31792
32397
|
this.#stats.dropped[reason] = (this.#stats.dropped[reason] ?? 0) + 1;
|
|
31793
32398
|
return [];
|
|
@@ -31909,6 +32514,22 @@ var MmtpFecSession = class {
|
|
|
31909
32514
|
}
|
|
31910
32515
|
}
|
|
31911
32516
|
};
|
|
32517
|
+
function layerZero(total) {
|
|
32518
|
+
return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
|
|
32519
|
+
}
|
|
32520
|
+
function buildDecoder(Decoder, session) {
|
|
32521
|
+
if (typeof Decoder !== "function") {
|
|
32522
|
+
throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
|
|
32523
|
+
}
|
|
32524
|
+
const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
|
|
32525
|
+
try {
|
|
32526
|
+
decoder.configure_params(session.sourceSymbols, session.symbolSize);
|
|
32527
|
+
} catch (error) {
|
|
32528
|
+
decoder.free();
|
|
32529
|
+
throw error;
|
|
32530
|
+
}
|
|
32531
|
+
return decoder;
|
|
32532
|
+
}
|
|
31912
32533
|
function requirePositiveInteger(value, path) {
|
|
31913
32534
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
31914
32535
|
throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);
|
|
@@ -31922,6 +32543,168 @@ function requireNumber(value, path) {
|
|
|
31922
32543
|
return value;
|
|
31923
32544
|
}
|
|
31924
32545
|
|
|
32546
|
+
// src/repair-layer-subscriptions.ts
|
|
32547
|
+
var SERIAL32_HALF2 = 2147483648;
|
|
32548
|
+
var RepairLayerSubscriptions = class {
|
|
32549
|
+
#opts;
|
|
32550
|
+
#windowPackets;
|
|
32551
|
+
#margin;
|
|
32552
|
+
#dropAfterWindows;
|
|
32553
|
+
#source;
|
|
32554
|
+
#layerCount = 0;
|
|
32555
|
+
#subs = /* @__PURE__ */ new Map();
|
|
32556
|
+
#disposed = false;
|
|
32557
|
+
#lastSeq;
|
|
32558
|
+
#expected = 0;
|
|
32559
|
+
#lost = 0;
|
|
32560
|
+
#wantFewer = 0;
|
|
32561
|
+
#lossRate = null;
|
|
32562
|
+
#repairPackets = 0;
|
|
32563
|
+
#repairBytes = 0;
|
|
32564
|
+
#staleDropped = 0;
|
|
32565
|
+
constructor(opts) {
|
|
32566
|
+
this.#opts = opts;
|
|
32567
|
+
this.#source = opts.source;
|
|
32568
|
+
this.#windowPackets = opts.windowPackets ?? 500;
|
|
32569
|
+
this.#margin = opts.margin ?? 2;
|
|
32570
|
+
this.#dropAfterWindows = opts.dropAfterWindows ?? 3;
|
|
32571
|
+
}
|
|
32572
|
+
get layerCount() {
|
|
32573
|
+
return this.#layerCount;
|
|
32574
|
+
}
|
|
32575
|
+
/**
|
|
32576
|
+
* One source packet's MMTP `packet_sequence_number`, in arrival order.
|
|
32577
|
+
* The sequence is per `packet_id`: pass the packets of ONE packet_id only,
|
|
32578
|
+
* or interleaved sequences read as loss.
|
|
32579
|
+
*/
|
|
32580
|
+
noteSourceSequence(seq) {
|
|
32581
|
+
if (this.#disposed || this.#delivery() === "stream") return;
|
|
32582
|
+
seq >>>= 0;
|
|
32583
|
+
if (this.#lastSeq === void 0) {
|
|
32584
|
+
this.#lastSeq = seq;
|
|
32585
|
+
return;
|
|
32586
|
+
}
|
|
32587
|
+
const d = serial32Distance(this.#lastSeq, seq);
|
|
32588
|
+
if (d === 0) return;
|
|
32589
|
+
const back = d >= SERIAL32_HALF2 ? 2 ** 32 - d : 0;
|
|
32590
|
+
if (d > this.#windowPackets && (back === 0 || back > this.#windowPackets)) {
|
|
32591
|
+
this.#lastSeq = seq;
|
|
32592
|
+
this.#expected = 0;
|
|
32593
|
+
this.#lost = 0;
|
|
32594
|
+
return;
|
|
32595
|
+
}
|
|
32596
|
+
if (back === 0) {
|
|
32597
|
+
this.#expected += d;
|
|
32598
|
+
this.#lost += d - 1;
|
|
32599
|
+
this.#lastSeq = seq;
|
|
32600
|
+
} else if (this.#lost > 0) {
|
|
32601
|
+
this.#lost -= 1;
|
|
32602
|
+
}
|
|
32603
|
+
if (this.#expected >= this.#windowPackets) this.#closeWindow();
|
|
32604
|
+
}
|
|
32605
|
+
/** ABR switch R -> R': close R's tracks, carry the layer count to R'. */
|
|
32606
|
+
switchSource(next) {
|
|
32607
|
+
if (this.#disposed) return;
|
|
32608
|
+
this.#closeAll();
|
|
32609
|
+
this.#source = next;
|
|
32610
|
+
this.#lastSeq = void 0;
|
|
32611
|
+
this.#expected = 0;
|
|
32612
|
+
this.#lost = 0;
|
|
32613
|
+
this.#wantFewer = 0;
|
|
32614
|
+
this.#layerCount = this.#delivery() === "stream" ? 0 : Math.min(this.#layerCount, this.#layers().length);
|
|
32615
|
+
this.#reconcile();
|
|
32616
|
+
}
|
|
32617
|
+
snapshot() {
|
|
32618
|
+
return {
|
|
32619
|
+
delivery: this.#delivery(),
|
|
32620
|
+
layerCount: this.#layerCount,
|
|
32621
|
+
subscribedTracks: [...this.#subs.keys()],
|
|
32622
|
+
repairPackets: this.#repairPackets,
|
|
32623
|
+
repairBytes: this.#repairBytes,
|
|
32624
|
+
staleDropped: this.#staleDropped,
|
|
32625
|
+
lossRate: this.#lossRate
|
|
32626
|
+
};
|
|
32627
|
+
}
|
|
32628
|
+
dispose() {
|
|
32629
|
+
if (this.#disposed) return;
|
|
32630
|
+
this.#closeAll();
|
|
32631
|
+
this.#disposed = true;
|
|
32632
|
+
}
|
|
32633
|
+
#delivery() {
|
|
32634
|
+
return this.#source.deliveryMode === "stream" ? "stream" : "datagram";
|
|
32635
|
+
}
|
|
32636
|
+
#layers() {
|
|
32637
|
+
return this.#source.repairLayers ?? [];
|
|
32638
|
+
}
|
|
32639
|
+
#closeWindow() {
|
|
32640
|
+
const rate = this.#lost / this.#expected;
|
|
32641
|
+
this.#lossRate = rate;
|
|
32642
|
+
this.#expected = 0;
|
|
32643
|
+
this.#lost = 0;
|
|
32644
|
+
const target = this.#targetLayers(rate);
|
|
32645
|
+
if (target > this.#layerCount) {
|
|
32646
|
+
this.#wantFewer = 0;
|
|
32647
|
+
this.#layerCount = target;
|
|
32648
|
+
} else if (target < this.#layerCount) {
|
|
32649
|
+
this.#wantFewer += 1;
|
|
32650
|
+
if (this.#wantFewer >= this.#dropAfterWindows) {
|
|
32651
|
+
this.#wantFewer = 0;
|
|
32652
|
+
this.#layerCount -= 1;
|
|
32653
|
+
}
|
|
32654
|
+
} else {
|
|
32655
|
+
this.#wantFewer = 0;
|
|
32656
|
+
}
|
|
32657
|
+
this.#reconcile();
|
|
32658
|
+
}
|
|
32659
|
+
/** Fewest base layers whose summed P covers margin x the measured loss of K. */
|
|
32660
|
+
#targetLayers(rate) {
|
|
32661
|
+
if (rate <= 0) return 0;
|
|
32662
|
+
const needed = Math.ceil(rate * this.#source.fecK * this.#margin);
|
|
32663
|
+
const layers = this.#layers();
|
|
32664
|
+
let held = 0;
|
|
32665
|
+
for (let n = 0; n < layers.length; n++) {
|
|
32666
|
+
held += layers[n].repairSymbols;
|
|
32667
|
+
if (held >= needed) return n + 1;
|
|
32668
|
+
}
|
|
32669
|
+
return layers.length;
|
|
32670
|
+
}
|
|
32671
|
+
/** Stream delivery never leaves layer count 0, so it never subscribes. */
|
|
32672
|
+
#reconcile() {
|
|
32673
|
+
const want = /* @__PURE__ */ new Map();
|
|
32674
|
+
for (const layer of this.#layers().slice(0, this.#layerCount)) {
|
|
32675
|
+
want.set(layer.repairTrackName, layer.priority);
|
|
32676
|
+
}
|
|
32677
|
+
const overlay = this.#source.repairOverlays?.[0];
|
|
32678
|
+
if (this.#layerCount > 0 && overlay) want.set(overlay.repairTrackName, overlay.priority);
|
|
32679
|
+
for (const [name, entry] of this.#subs) {
|
|
32680
|
+
if (!want.has(name)) {
|
|
32681
|
+
entry.sub.close();
|
|
32682
|
+
this.#subs.delete(name);
|
|
32683
|
+
}
|
|
32684
|
+
}
|
|
32685
|
+
for (const [name, priority] of want) {
|
|
32686
|
+
if (this.#subs.has(name)) continue;
|
|
32687
|
+
const entry = {};
|
|
32688
|
+
entry.sub = this.#opts.subscribe(name, priority, (packet) => this.#onPacket(name, entry, packet));
|
|
32689
|
+
this.#subs.set(name, entry);
|
|
32690
|
+
}
|
|
32691
|
+
}
|
|
32692
|
+
/** Fenced on the entry itself, so a closed subscription is stale even if its track is re-added. */
|
|
32693
|
+
#onPacket(name, entry, packet) {
|
|
32694
|
+
if (this.#disposed || this.#subs.get(name) !== entry) {
|
|
32695
|
+
this.#staleDropped += 1;
|
|
32696
|
+
return;
|
|
32697
|
+
}
|
|
32698
|
+
this.#repairPackets += 1;
|
|
32699
|
+
this.#repairBytes += packet.byteLength;
|
|
32700
|
+
this.#opts.feed(packet, name);
|
|
32701
|
+
}
|
|
32702
|
+
#closeAll() {
|
|
32703
|
+
for (const entry of this.#subs.values()) entry.sub.close();
|
|
32704
|
+
this.#subs.clear();
|
|
32705
|
+
}
|
|
32706
|
+
};
|
|
32707
|
+
|
|
31925
32708
|
// src/repair-fetch.ts
|
|
31926
32709
|
var MOQT_PRIORITY_MAX = 255;
|
|
31927
32710
|
var SOURCE_MEDIA_PRIORITY_MAX = REPAIR_PRIORITY_MIN - 1;
|
|
@@ -32567,6 +33350,7 @@ export {
|
|
|
32567
33350
|
REPAIR_LIFECYCLE_LOG_PREFIX,
|
|
32568
33351
|
REPAIR_PRIORITY_MAX,
|
|
32569
33352
|
REPAIR_PRIORITY_MIN,
|
|
33353
|
+
RepairLayerSubscriptions,
|
|
32570
33354
|
SOURCE_MEDIA_PRIORITY_MAX,
|
|
32571
33355
|
SampleCombineError,
|
|
32572
33356
|
SampleCombiner,
|
|
@@ -32591,6 +33375,7 @@ export {
|
|
|
32591
33375
|
catalogGroupDurationMs,
|
|
32592
33376
|
catalogKeyframeCadenceMs,
|
|
32593
33377
|
catalogRepairTarget,
|
|
33378
|
+
catalogTrackDeliveryMode,
|
|
32594
33379
|
catalogTrackFecUnavailable,
|
|
32595
33380
|
classifyH264Keyframe,
|
|
32596
33381
|
classifyHevcKeyframe,
|
|
@@ -32611,6 +33396,7 @@ export {
|
|
|
32611
33396
|
detectH264Keyframe,
|
|
32612
33397
|
detectHevcKeyframe,
|
|
32613
33398
|
driftCheck,
|
|
33399
|
+
encodeCmcdRecord,
|
|
32614
33400
|
encodeDataSegment,
|
|
32615
33401
|
encodeFecSourceInfo,
|
|
32616
33402
|
encodeLocTimestamp,
|