@blockcast/mmt-container 2.2.0-main.a88c3a0aa4f3 → 2.2.0-main.efb4fa4521af
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-runtime.js +293 -35
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.js.map +1 -1
- package/dist/catalog-fec-params.js.map +1 -1
- package/dist/catalog-network-source.js.map +1 -1
- package/dist/delivery-catalog.js.map +1 -1
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-state.js.map +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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.js.map +1 -1
- package/dist/mfu-reassembler.js +12 -19
- package/dist/mfu-reassembler.js.map +2 -2
- package/dist/mmtp-container-parser.js +12 -19
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +133 -34
- package/dist/mmtp-container-parser.no-modules.js.map +3 -3
- package/dist/mmtp-decode-pipeline.js +12 -19
- 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.js +12 -19
- 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.d.ts +2 -0
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +13 -19
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.js.map +1 -1
- package/dist/multicast-unwrap.js +12 -19
- package/dist/multicast-unwrap.js.map +2 -2
- package/dist/no-modules.js +130 -33
- package/dist/no-modules.js.map +3 -3
- package/dist/repair-fetch.js.map +1 -1
- package/dist/repair-layer-subscriptions.d.ts +78 -0
- package/dist/repair-layer-subscriptions.d.ts.map +1 -0
- package/dist/repair-layer-subscriptions.js +189 -0
- package/dist/repair-layer-subscriptions.js.map +7 -0
- package/dist/signaling.js.map +2 -2
- package/dist/timestamp-codec.js +12 -19
- package/dist/timestamp-codec.js.map +2 -2
- package/dist/wasm-mfu-reassembler.js.map +1 -1
- package/dist/wasm-mmtp-container-parser.js +12 -19
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +5 -5
- package/schemas/catalog.schema.json +4 -0
- package/src/index.ts +11 -0
- package/src/mmtp-fec-session.ts +115 -15
- package/src/multicast-manager.ts +3 -0
- package/src/repair-layer-subscriptions.ts +253 -0
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;
|
|
@@ -28678,6 +28717,7 @@ var MulticastManager = class {
|
|
|
28678
28717
|
mode: endpoint.mode,
|
|
28679
28718
|
amtRelay: endpoint.amtRelay,
|
|
28680
28719
|
amtRelayPort: endpoint.amtRelayPort ?? (endpoint.amtRelay?.trim() ? DEFAULT_AMT_RELAY_PORT : void 0),
|
|
28720
|
+
amtDiscovery: endpoint.amtDiscovery,
|
|
28681
28721
|
packetIds: endpoint.packetIds,
|
|
28682
28722
|
tracks: endpoint.tracks.map(({ name, packetId }) => ({ name, packetId })),
|
|
28683
28723
|
// A protected repair group's records ride in band on its port (7.2.3).
|
|
@@ -31453,12 +31493,12 @@ function moqtPriority2(value) {
|
|
|
31453
31493
|
function catalogRepairTarget(catalog, source) {
|
|
31454
31494
|
const binding = bindingOf(catalog, source);
|
|
31455
31495
|
if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
|
|
31456
|
-
const
|
|
31496
|
+
const layerZero2 = binding.layers[0];
|
|
31457
31497
|
const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
|
|
31458
31498
|
const tracks = catalog.tracks;
|
|
31459
31499
|
const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
|
|
31460
31500
|
const sourcePriority = entry ? moqtPriority2(entry["priority"]) : null;
|
|
31461
|
-
return { repairTrack:
|
|
31501
|
+
return { repairTrack: layerZero2.repairTrackName, sourcePriority, repairPriority: layerZero2.priority };
|
|
31462
31502
|
}
|
|
31463
31503
|
function numericField(value) {
|
|
31464
31504
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
@@ -31540,7 +31580,14 @@ function resolveCatalogTrackFec(catalog, tracks) {
|
|
|
31540
31580
|
var PATH_BITS = { moq: 1, multicast: 2 };
|
|
31541
31581
|
var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
|
|
31542
31582
|
var MmtpFecSession = class {
|
|
31583
|
+
/** P_0 = B_0, the held total every repair packet is checked against. */
|
|
31584
|
+
get repairSymbols() {
|
|
31585
|
+
return this.#repairSymbols;
|
|
31586
|
+
}
|
|
31543
31587
|
#decoder;
|
|
31588
|
+
#repairSymbols;
|
|
31589
|
+
/** The message entry a catalog-less session was built from; undefined under a catalog. */
|
|
31590
|
+
#message;
|
|
31544
31591
|
/** Blocks a repair has defined and that are not settled yet, by SS_Start. */
|
|
31545
31592
|
#blocks = /* @__PURE__ */ new Map();
|
|
31546
31593
|
/** SS_Starts of settled blocks, so a later repair for one is refused. */
|
|
@@ -31578,6 +31625,24 @@ var MmtpFecSession = class {
|
|
|
31578
31625
|
* `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
|
|
31579
31626
|
*/
|
|
31580
31627
|
constructor(config) {
|
|
31628
|
+
if (config && "alFecFlow" in config) {
|
|
31629
|
+
const message = alFecRaptorQFlowConfig(config.alFecFlow);
|
|
31630
|
+
if (!message) {
|
|
31631
|
+
throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
|
|
31632
|
+
}
|
|
31633
|
+
this.#message = message;
|
|
31634
|
+
this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
|
|
31635
|
+
this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
|
|
31636
|
+
this.sourceSymbols = message.maximumK;
|
|
31637
|
+
this.#repairSymbols = message.maximumP;
|
|
31638
|
+
this.symbolSize = message.repairSymbolSize;
|
|
31639
|
+
this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
|
|
31640
|
+
this.#repairPacketId = message.repairFlowId;
|
|
31641
|
+
this.#heldLayers = layerZero(message.maximumP);
|
|
31642
|
+
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31643
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31644
|
+
return;
|
|
31645
|
+
}
|
|
31581
31646
|
const fec = config?.fec;
|
|
31582
31647
|
if (!fec || typeof fec !== "object") {
|
|
31583
31648
|
throw new Error("MmtpFecSession: the catalog fec instance is required");
|
|
@@ -31588,7 +31653,7 @@ var MmtpFecSession = class {
|
|
|
31588
31653
|
}
|
|
31589
31654
|
this.algorithm = fec.scheme;
|
|
31590
31655
|
this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
|
|
31591
|
-
this
|
|
31656
|
+
this.#repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
|
|
31592
31657
|
this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
|
|
31593
31658
|
const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
|
|
31594
31659
|
const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
|
|
@@ -31608,17 +31673,30 @@ var MmtpFecSession = class {
|
|
|
31608
31673
|
config.heldLayers
|
|
31609
31674
|
);
|
|
31610
31675
|
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31611
|
-
|
|
31612
|
-
|
|
31613
|
-
|
|
31614
|
-
|
|
31615
|
-
|
|
31616
|
-
|
|
31617
|
-
|
|
31618
|
-
|
|
31619
|
-
|
|
31620
|
-
|
|
31621
|
-
|
|
31676
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31677
|
+
}
|
|
31678
|
+
/**
|
|
31679
|
+
* A later version of the AL-FEC message, catalog-less sessions only. A
|
|
31680
|
+
* maximum_p_for_repair_flow above the held total raises it (`raised`): the
|
|
31681
|
+
* anchored check restarts at the new total, so a block generated under the
|
|
31682
|
+
* previous one stays valid before the first block carrying the new total
|
|
31683
|
+
* (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
|
|
31684
|
+
* (`unchanged`): no publisher lowers it, and the check catches a wire that
|
|
31685
|
+
* does. An entry with another asset, repair flow, code point, K or T, or
|
|
31686
|
+
* one outside 8.3, is `refused`: that is a new source encoding, which needs
|
|
31687
|
+
* a new session. A latched fail-closed stays latched either way.
|
|
31688
|
+
*/
|
|
31689
|
+
updateAlFecFlow(flow) {
|
|
31690
|
+
const held = this.#message;
|
|
31691
|
+
if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
|
|
31692
|
+
const next = alFecRaptorQFlowConfig(flow);
|
|
31693
|
+
if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
|
|
31694
|
+
return "refused";
|
|
31695
|
+
}
|
|
31696
|
+
if (next.maximumP <= this.#repairSymbols) return "unchanged";
|
|
31697
|
+
this.#repairSymbols = next.maximumP;
|
|
31698
|
+
this.#heldLayers = layerZero(next.maximumP);
|
|
31699
|
+
return "raised";
|
|
31622
31700
|
}
|
|
31623
31701
|
/** True once release() ran. */
|
|
31624
31702
|
get released() {
|
|
@@ -31713,7 +31791,7 @@ var MmtpFecSession = class {
|
|
|
31713
31791
|
}
|
|
31714
31792
|
if (admission.reject) {
|
|
31715
31793
|
const verdict2 = this.#heldLayers.failsClosed(
|
|
31716
|
-
repairTrackName,
|
|
31794
|
+
this.#trackFor(repairTrackName),
|
|
31717
31795
|
frame.ssStart,
|
|
31718
31796
|
frame.rsbLength,
|
|
31719
31797
|
this.#anchorHorizon,
|
|
@@ -31728,7 +31806,7 @@ var MmtpFecSession = class {
|
|
|
31728
31806
|
this.#repairPacketId ??= frame.packetId;
|
|
31729
31807
|
if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
|
|
31730
31808
|
const verdict = this.#heldLayers.admit(
|
|
31731
|
-
repairTrackName,
|
|
31809
|
+
this.#trackFor(repairTrackName),
|
|
31732
31810
|
frame.ssStart,
|
|
31733
31811
|
frame.rsbLength,
|
|
31734
31812
|
this.#anchorHorizon,
|
|
@@ -31787,6 +31865,10 @@ var MmtpFecSession = class {
|
|
|
31787
31865
|
decoder.free();
|
|
31788
31866
|
return true;
|
|
31789
31867
|
}
|
|
31868
|
+
/** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
|
|
31869
|
+
#trackFor(repairTrackName) {
|
|
31870
|
+
return this.#message ? void 0 : repairTrackName;
|
|
31871
|
+
}
|
|
31790
31872
|
#drop(reason) {
|
|
31791
31873
|
this.#stats.dropped[reason] = (this.#stats.dropped[reason] ?? 0) + 1;
|
|
31792
31874
|
return [];
|
|
@@ -31908,6 +31990,22 @@ var MmtpFecSession = class {
|
|
|
31908
31990
|
}
|
|
31909
31991
|
}
|
|
31910
31992
|
};
|
|
31993
|
+
function layerZero(total) {
|
|
31994
|
+
return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
|
|
31995
|
+
}
|
|
31996
|
+
function buildDecoder(Decoder, session) {
|
|
31997
|
+
if (typeof Decoder !== "function") {
|
|
31998
|
+
throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
|
|
31999
|
+
}
|
|
32000
|
+
const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
|
|
32001
|
+
try {
|
|
32002
|
+
decoder.configure_params(session.sourceSymbols, session.symbolSize);
|
|
32003
|
+
} catch (error) {
|
|
32004
|
+
decoder.free();
|
|
32005
|
+
throw error;
|
|
32006
|
+
}
|
|
32007
|
+
return decoder;
|
|
32008
|
+
}
|
|
31911
32009
|
function requirePositiveInteger(value, path) {
|
|
31912
32010
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
31913
32011
|
throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);
|
|
@@ -31921,6 +32019,165 @@ function requireNumber(value, path) {
|
|
|
31921
32019
|
return value;
|
|
31922
32020
|
}
|
|
31923
32021
|
|
|
32022
|
+
// src/repair-layer-subscriptions.ts
|
|
32023
|
+
var SERIAL32_HALF2 = 2147483648;
|
|
32024
|
+
var RepairLayerSubscriptions = class {
|
|
32025
|
+
#opts;
|
|
32026
|
+
#windowPackets;
|
|
32027
|
+
#margin;
|
|
32028
|
+
#dropAfterWindows;
|
|
32029
|
+
#source;
|
|
32030
|
+
#layerCount = 0;
|
|
32031
|
+
#subs = /* @__PURE__ */ new Map();
|
|
32032
|
+
#disposed = false;
|
|
32033
|
+
#lastSeq;
|
|
32034
|
+
#expected = 0;
|
|
32035
|
+
#lost = 0;
|
|
32036
|
+
#wantFewer = 0;
|
|
32037
|
+
#lossRate = null;
|
|
32038
|
+
#repairPackets = 0;
|
|
32039
|
+
#repairBytes = 0;
|
|
32040
|
+
#staleDropped = 0;
|
|
32041
|
+
constructor(opts) {
|
|
32042
|
+
this.#opts = opts;
|
|
32043
|
+
this.#source = opts.source;
|
|
32044
|
+
this.#windowPackets = opts.windowPackets ?? 500;
|
|
32045
|
+
this.#margin = opts.margin ?? 2;
|
|
32046
|
+
this.#dropAfterWindows = opts.dropAfterWindows ?? 3;
|
|
32047
|
+
}
|
|
32048
|
+
get layerCount() {
|
|
32049
|
+
return this.#layerCount;
|
|
32050
|
+
}
|
|
32051
|
+
/**
|
|
32052
|
+
* One source packet's MMTP `packet_sequence_number`, in arrival order.
|
|
32053
|
+
* The sequence is per `packet_id`: pass the packets of ONE packet_id only,
|
|
32054
|
+
* or interleaved sequences read as loss.
|
|
32055
|
+
*/
|
|
32056
|
+
noteSourceSequence(seq) {
|
|
32057
|
+
if (this.#disposed || this.#opts.delivery === "stream") return;
|
|
32058
|
+
seq >>>= 0;
|
|
32059
|
+
if (this.#lastSeq === void 0) {
|
|
32060
|
+
this.#lastSeq = seq;
|
|
32061
|
+
return;
|
|
32062
|
+
}
|
|
32063
|
+
const d = serial32Distance(this.#lastSeq, seq);
|
|
32064
|
+
if (d === 0) return;
|
|
32065
|
+
const back = d >= SERIAL32_HALF2 ? 2 ** 32 - d : 0;
|
|
32066
|
+
if (d > this.#windowPackets && (back === 0 || back > this.#windowPackets)) {
|
|
32067
|
+
this.#lastSeq = seq;
|
|
32068
|
+
this.#expected = 0;
|
|
32069
|
+
this.#lost = 0;
|
|
32070
|
+
return;
|
|
32071
|
+
}
|
|
32072
|
+
if (back === 0) {
|
|
32073
|
+
this.#expected += d;
|
|
32074
|
+
this.#lost += d - 1;
|
|
32075
|
+
this.#lastSeq = seq;
|
|
32076
|
+
} else if (this.#lost > 0) {
|
|
32077
|
+
this.#lost -= 1;
|
|
32078
|
+
}
|
|
32079
|
+
if (this.#expected >= this.#windowPackets) this.#closeWindow();
|
|
32080
|
+
}
|
|
32081
|
+
/** ABR switch R -> R': close R's tracks, carry the layer count to R'. */
|
|
32082
|
+
switchSource(next) {
|
|
32083
|
+
if (this.#disposed) return;
|
|
32084
|
+
this.#closeAll();
|
|
32085
|
+
this.#source = next;
|
|
32086
|
+
this.#lastSeq = void 0;
|
|
32087
|
+
this.#expected = 0;
|
|
32088
|
+
this.#lost = 0;
|
|
32089
|
+
this.#wantFewer = 0;
|
|
32090
|
+
this.#layerCount = Math.min(this.#layerCount, this.#layers().length);
|
|
32091
|
+
this.#reconcile();
|
|
32092
|
+
}
|
|
32093
|
+
snapshot() {
|
|
32094
|
+
return {
|
|
32095
|
+
delivery: this.#opts.delivery,
|
|
32096
|
+
layerCount: this.#layerCount,
|
|
32097
|
+
subscribedTracks: [...this.#subs.keys()],
|
|
32098
|
+
repairPackets: this.#repairPackets,
|
|
32099
|
+
repairBytes: this.#repairBytes,
|
|
32100
|
+
staleDropped: this.#staleDropped,
|
|
32101
|
+
lossRate: this.#lossRate
|
|
32102
|
+
};
|
|
32103
|
+
}
|
|
32104
|
+
dispose() {
|
|
32105
|
+
if (this.#disposed) return;
|
|
32106
|
+
this.#closeAll();
|
|
32107
|
+
this.#disposed = true;
|
|
32108
|
+
}
|
|
32109
|
+
#layers() {
|
|
32110
|
+
return this.#source.repairLayers ?? [];
|
|
32111
|
+
}
|
|
32112
|
+
#closeWindow() {
|
|
32113
|
+
const rate = this.#lost / this.#expected;
|
|
32114
|
+
this.#lossRate = rate;
|
|
32115
|
+
this.#expected = 0;
|
|
32116
|
+
this.#lost = 0;
|
|
32117
|
+
const target = this.#targetLayers(rate);
|
|
32118
|
+
if (target > this.#layerCount) {
|
|
32119
|
+
this.#wantFewer = 0;
|
|
32120
|
+
this.#layerCount = target;
|
|
32121
|
+
} else if (target < this.#layerCount) {
|
|
32122
|
+
this.#wantFewer += 1;
|
|
32123
|
+
if (this.#wantFewer >= this.#dropAfterWindows) {
|
|
32124
|
+
this.#wantFewer = 0;
|
|
32125
|
+
this.#layerCount -= 1;
|
|
32126
|
+
}
|
|
32127
|
+
} else {
|
|
32128
|
+
this.#wantFewer = 0;
|
|
32129
|
+
}
|
|
32130
|
+
this.#reconcile();
|
|
32131
|
+
}
|
|
32132
|
+
/** Fewest base layers whose summed P covers margin x the measured loss of K. */
|
|
32133
|
+
#targetLayers(rate) {
|
|
32134
|
+
if (rate <= 0) return 0;
|
|
32135
|
+
const needed = Math.ceil(rate * this.#source.fecK * this.#margin);
|
|
32136
|
+
const layers = this.#layers();
|
|
32137
|
+
let held = 0;
|
|
32138
|
+
for (let n = 0; n < layers.length; n++) {
|
|
32139
|
+
held += layers[n].repairSymbols;
|
|
32140
|
+
if (held >= needed) return n + 1;
|
|
32141
|
+
}
|
|
32142
|
+
return layers.length;
|
|
32143
|
+
}
|
|
32144
|
+
/** Stream delivery never leaves layer count 0, so it never subscribes. */
|
|
32145
|
+
#reconcile() {
|
|
32146
|
+
const want = /* @__PURE__ */ new Map();
|
|
32147
|
+
for (const layer of this.#layers().slice(0, this.#layerCount)) {
|
|
32148
|
+
want.set(layer.repairTrackName, layer.priority);
|
|
32149
|
+
}
|
|
32150
|
+
const overlay = this.#source.repairOverlays?.[0];
|
|
32151
|
+
if (this.#layerCount > 0 && overlay) want.set(overlay.repairTrackName, overlay.priority);
|
|
32152
|
+
for (const [name, entry] of this.#subs) {
|
|
32153
|
+
if (!want.has(name)) {
|
|
32154
|
+
entry.sub.close();
|
|
32155
|
+
this.#subs.delete(name);
|
|
32156
|
+
}
|
|
32157
|
+
}
|
|
32158
|
+
for (const [name, priority] of want) {
|
|
32159
|
+
if (this.#subs.has(name)) continue;
|
|
32160
|
+
const entry = {};
|
|
32161
|
+
entry.sub = this.#opts.subscribe(name, priority, (packet) => this.#onPacket(name, entry, packet));
|
|
32162
|
+
this.#subs.set(name, entry);
|
|
32163
|
+
}
|
|
32164
|
+
}
|
|
32165
|
+
/** Fenced on the entry itself, so a closed subscription is stale even if its track is re-added. */
|
|
32166
|
+
#onPacket(name, entry, packet) {
|
|
32167
|
+
if (this.#disposed || this.#subs.get(name) !== entry) {
|
|
32168
|
+
this.#staleDropped += 1;
|
|
32169
|
+
return;
|
|
32170
|
+
}
|
|
32171
|
+
this.#repairPackets += 1;
|
|
32172
|
+
this.#repairBytes += packet.byteLength;
|
|
32173
|
+
this.#opts.feed(packet, name);
|
|
32174
|
+
}
|
|
32175
|
+
#closeAll() {
|
|
32176
|
+
for (const entry of this.#subs.values()) entry.sub.close();
|
|
32177
|
+
this.#subs.clear();
|
|
32178
|
+
}
|
|
32179
|
+
};
|
|
32180
|
+
|
|
31924
32181
|
// src/repair-fetch.ts
|
|
31925
32182
|
var MOQT_PRIORITY_MAX = 255;
|
|
31926
32183
|
var SOURCE_MEDIA_PRIORITY_MAX = REPAIR_PRIORITY_MIN - 1;
|
|
@@ -32566,6 +32823,7 @@ export {
|
|
|
32566
32823
|
REPAIR_LIFECYCLE_LOG_PREFIX,
|
|
32567
32824
|
REPAIR_PRIORITY_MAX,
|
|
32568
32825
|
REPAIR_PRIORITY_MIN,
|
|
32826
|
+
RepairLayerSubscriptions,
|
|
32569
32827
|
SOURCE_MEDIA_PRIORITY_MAX,
|
|
32570
32828
|
SampleCombineError,
|
|
32571
32829
|
SampleCombiner,
|