@blockcast/mmt-container 2.1.0 → 2.2.0-main.20c61ec37932
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 +636 -77
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.js.map +1 -1
- package/dist/catalog-fec-params.d.ts +16 -0
- 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 +5 -0
- package/dist/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js.map +2 -2
- 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 +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -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.d.ts +9 -1
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +1468 -33
- package/dist/mmtp-router.js.map +4 -4
- package/dist/multicast-auth-catalog.js.map +1 -1
- package/dist/multicast-input.js.map +1 -1
- package/dist/multicast-manager.d.ts +15 -2
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +15246 -14969
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +11 -3
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +255 -19
- package/dist/multicast-transport.js.map +4 -4
- 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 +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 +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/catalog-fec-params.ts +35 -0
- package/src/cmcd-producer.ts +5 -0
- package/src/index.ts +12 -0
- package/src/mmtp-fec-session.ts +115 -15
- package/src/mmtp-router.ts +81 -16
- package/src/multicast-manager.ts +26 -5
- package/src/multicast-transport.ts +44 -20
- package/src/repair-layer-subscriptions.ts +261 -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;
|
|
@@ -7457,11 +7496,33 @@ var AdaptiveBitrateController = class {
|
|
|
7457
7496
|
};
|
|
7458
7497
|
|
|
7459
7498
|
// node_modules/@blockcast/mmt-base/dist/mmtp.js
|
|
7499
|
+
var MMTP_HEADER_SIZE2 = 12;
|
|
7460
7500
|
var MPU_HEADER_SIZE2 = 8;
|
|
7461
7501
|
var MPU_LENGTH_FIELD_SIZE2 = 2;
|
|
7462
7502
|
var MPU_HEADER_REMAINDER_SIZE2 = MPU_HEADER_SIZE2 - MPU_LENGTH_FIELD_SIZE2;
|
|
7463
7503
|
var MMTP_SIGNALING_PACKET_ID2 = 0;
|
|
7464
7504
|
var MMTP_PACKET_ID_MAX2 = 65535;
|
|
7505
|
+
function readMMTPPacketId2(buffer) {
|
|
7506
|
+
if (buffer.length < MMTP_HEADER_SIZE2)
|
|
7507
|
+
return null;
|
|
7508
|
+
const flags = buffer[0];
|
|
7509
|
+
if ((flags >> 6 & 3) !== 0)
|
|
7510
|
+
return null;
|
|
7511
|
+
let offset = MMTP_HEADER_SIZE2;
|
|
7512
|
+
if ((flags & 32) !== 0) {
|
|
7513
|
+
if (offset + 4 > buffer.length)
|
|
7514
|
+
return null;
|
|
7515
|
+
offset += 4;
|
|
7516
|
+
}
|
|
7517
|
+
if ((flags & 2) !== 0) {
|
|
7518
|
+
if (offset + 4 > buffer.length)
|
|
7519
|
+
return null;
|
|
7520
|
+
const length = buffer[offset + 2] << 8 | buffer[offset + 3];
|
|
7521
|
+
if (offset + 4 + length > buffer.length)
|
|
7522
|
+
return null;
|
|
7523
|
+
}
|
|
7524
|
+
return buffer[2] << 8 | buffer[3];
|
|
7525
|
+
}
|
|
7465
7526
|
|
|
7466
7527
|
// node_modules/@blockcast/mmt-base/dist/net-constants.js
|
|
7467
7528
|
var DEFAULT_AMT_RELAY_PORT2 = 2268;
|
|
@@ -8447,6 +8508,205 @@ var TransportManager = class {
|
|
|
8447
8508
|
};
|
|
8448
8509
|
var transportManager = new TransportManager();
|
|
8449
8510
|
|
|
8511
|
+
// node_modules/@blockcast/mmt-transport/dist/multicast-receiver-gateway.js
|
|
8512
|
+
var REGISTRY = /* @__PURE__ */ Symbol.for("@blockcast/mmt-transport/native-provider-registry/v1");
|
|
8513
|
+
var USER_AGENT_VERIFIED = /* @__PURE__ */ Symbol.for("@blockcast/mmt-transport/user-agent-verified/v1");
|
|
8514
|
+
var INTEGRITY_SCHEME = "manifest-ed25519-2";
|
|
8515
|
+
var MulticastError = class extends Error {
|
|
8516
|
+
code;
|
|
8517
|
+
constructor(code, message, cause) {
|
|
8518
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
8519
|
+
this.name = "MulticastError";
|
|
8520
|
+
this.code = code;
|
|
8521
|
+
}
|
|
8522
|
+
};
|
|
8523
|
+
function datagramsOf(chunk) {
|
|
8524
|
+
const list = Array.isArray(chunk) ? chunk : chunk?.datagrams ?? [];
|
|
8525
|
+
const out = [];
|
|
8526
|
+
for (const d of list) {
|
|
8527
|
+
const data = d instanceof Uint8Array || d instanceof ArrayBuffer ? d : d?.data;
|
|
8528
|
+
if (data instanceof Uint8Array)
|
|
8529
|
+
out.push(data);
|
|
8530
|
+
else if (data instanceof ArrayBuffer)
|
|
8531
|
+
out.push(new Uint8Array(data));
|
|
8532
|
+
}
|
|
8533
|
+
return out;
|
|
8534
|
+
}
|
|
8535
|
+
function reasonOf(value) {
|
|
8536
|
+
return typeof value === "object" && value !== null && "reason" in value ? value.reason : value;
|
|
8537
|
+
}
|
|
8538
|
+
function registry() {
|
|
8539
|
+
const g = globalThis;
|
|
8540
|
+
if (g[REGISTRY] === void 0) {
|
|
8541
|
+
const providers = /* @__PURE__ */ new WeakSet();
|
|
8542
|
+
const byScope = /* @__PURE__ */ new WeakMap();
|
|
8543
|
+
Object.defineProperty(g, REGISTRY, {
|
|
8544
|
+
value: Object.freeze({
|
|
8545
|
+
isProvider: (x) => typeof x === "object" && x !== null && providers.has(x),
|
|
8546
|
+
providerFor: (scope) => {
|
|
8547
|
+
let provider = byScope.get(scope);
|
|
8548
|
+
if (provider === void 0) {
|
|
8549
|
+
provider = createProvider(scope);
|
|
8550
|
+
providers.add(provider);
|
|
8551
|
+
byScope.set(scope, provider);
|
|
8552
|
+
}
|
|
8553
|
+
return provider;
|
|
8554
|
+
}
|
|
8555
|
+
}),
|
|
8556
|
+
writable: false,
|
|
8557
|
+
configurable: false,
|
|
8558
|
+
enumerable: false
|
|
8559
|
+
});
|
|
8560
|
+
}
|
|
8561
|
+
return g[REGISTRY];
|
|
8562
|
+
}
|
|
8563
|
+
function resolveMulticastGateway(scope = globalThis) {
|
|
8564
|
+
const s = scope;
|
|
8565
|
+
if (typeof s.MulticastReceiver === "function")
|
|
8566
|
+
return registry().providerFor(scope);
|
|
8567
|
+
if (typeof s.multicast?.subscribe === "function")
|
|
8568
|
+
return s.multicast;
|
|
8569
|
+
return void 0;
|
|
8570
|
+
}
|
|
8571
|
+
function isUserAgentVerifiedGateway(gateway) {
|
|
8572
|
+
return registry().isProvider(gateway);
|
|
8573
|
+
}
|
|
8574
|
+
function userAgentVerifiedIngress(ingress, gateway) {
|
|
8575
|
+
if (isUserAgentVerifiedGateway(gateway))
|
|
8576
|
+
return { ...ingress, admittedBy: USER_AGENT_VERIFIED };
|
|
8577
|
+
if (!("admittedBy" in ingress))
|
|
8578
|
+
return ingress;
|
|
8579
|
+
const { admittedBy: _, ...rest } = ingress;
|
|
8580
|
+
return rest;
|
|
8581
|
+
}
|
|
8582
|
+
function unsupported(message, cause) {
|
|
8583
|
+
return new MulticastError("capability-unsupported", message, cause);
|
|
8584
|
+
}
|
|
8585
|
+
function createProvider(scope) {
|
|
8586
|
+
const Receiver = scope.MulticastReceiver;
|
|
8587
|
+
let gate = null;
|
|
8588
|
+
return {
|
|
8589
|
+
capabilities: () => Promise.resolve({ consumeModes: ["bytes"] }),
|
|
8590
|
+
subscribe: async (config) => {
|
|
8591
|
+
const c = config;
|
|
8592
|
+
if (c.consume !== "bytes")
|
|
8593
|
+
throw unsupported(`consume mode ${String(c.consume)} is not supported`);
|
|
8594
|
+
const origin = c.origin ?? "ip-multicast";
|
|
8595
|
+
if (origin !== "ip-multicast" && !(origin === "amt" && c.discovery === "driad")) {
|
|
8596
|
+
throw unsupported(`origin ${String(origin)} is not supported by MulticastReceiver`);
|
|
8597
|
+
}
|
|
8598
|
+
if (c.relay !== void 0)
|
|
8599
|
+
throw unsupported("MulticastReceiver picks its own AMT relay");
|
|
8600
|
+
if (!c.source)
|
|
8601
|
+
throw unsupported("MulticastReceiver needs an SSM source");
|
|
8602
|
+
if (c.senderAddress !== void 0 && c.senderAddress !== c.source) {
|
|
8603
|
+
throw unsupported("MulticastReceiver filters by source only");
|
|
8604
|
+
}
|
|
8605
|
+
const options = { sourceAddress: c.source, groupAddress: c.group, localPort: c.port };
|
|
8606
|
+
if (gate !== null)
|
|
8607
|
+
await gate;
|
|
8608
|
+
let rx;
|
|
8609
|
+
try {
|
|
8610
|
+
rx = new Receiver(options);
|
|
8611
|
+
} catch (error) {
|
|
8612
|
+
throw unsupported("MulticastReceiver refused the request", error);
|
|
8613
|
+
}
|
|
8614
|
+
const opened = rx.opened;
|
|
8615
|
+
if (gate === null)
|
|
8616
|
+
gate = opened.then(() => void 0, () => void 0);
|
|
8617
|
+
return openSubscription(rx, opened, options, c.packetIds);
|
|
8618
|
+
}
|
|
8619
|
+
};
|
|
8620
|
+
}
|
|
8621
|
+
async function openSubscription(rx, openedPromise, options, packetIds) {
|
|
8622
|
+
let opened;
|
|
8623
|
+
try {
|
|
8624
|
+
opened = await openedPromise;
|
|
8625
|
+
} catch (error) {
|
|
8626
|
+
throw new MulticastError("subscription-failed", "MulticastReceiver did not open", error);
|
|
8627
|
+
}
|
|
8628
|
+
if (rx.integrityScheme !== INTEGRITY_SCHEME) {
|
|
8629
|
+
await rx.close();
|
|
8630
|
+
throw unsupported(`integrity scheme ${String(rx.integrityScheme)} is not ${INTEGRITY_SCHEME}`);
|
|
8631
|
+
}
|
|
8632
|
+
const events = new EventTarget();
|
|
8633
|
+
let state = "connected";
|
|
8634
|
+
let filter = packetIds?.length ? new Set(packetIds) : null;
|
|
8635
|
+
let closing = false;
|
|
8636
|
+
const setState = (next) => {
|
|
8637
|
+
if (state === next)
|
|
8638
|
+
return;
|
|
8639
|
+
state = next;
|
|
8640
|
+
events.dispatchEvent(new CustomEvent("state", { detail: next }));
|
|
8641
|
+
};
|
|
8642
|
+
const sourceAddress = options.sourceAddress;
|
|
8643
|
+
const subscription = {
|
|
8644
|
+
id: `${options.sourceAddress}|${options.groupAddress}|${options.localPort}`,
|
|
8645
|
+
get state() {
|
|
8646
|
+
return state;
|
|
8647
|
+
},
|
|
8648
|
+
delivery: "datagram",
|
|
8649
|
+
eventTypes: Object.freeze(["bytes", "packets", "state", "error"]),
|
|
8650
|
+
setPacketIds(ids) {
|
|
8651
|
+
filter = ids.length ? new Set(ids) : null;
|
|
8652
|
+
},
|
|
8653
|
+
addEventListener: events.addEventListener.bind(events),
|
|
8654
|
+
removeEventListener: events.removeEventListener.bind(events),
|
|
8655
|
+
async unsubscribe() {
|
|
8656
|
+
closing = true;
|
|
8657
|
+
await rx.close();
|
|
8658
|
+
setState("closed");
|
|
8659
|
+
}
|
|
8660
|
+
};
|
|
8661
|
+
void (async () => {
|
|
8662
|
+
const reader = opened.readable.getReader();
|
|
8663
|
+
try {
|
|
8664
|
+
for (; ; ) {
|
|
8665
|
+
const { value, done } = await reader.read();
|
|
8666
|
+
if (done)
|
|
8667
|
+
break;
|
|
8668
|
+
const packets = [];
|
|
8669
|
+
for (const data of datagramsOf(value)) {
|
|
8670
|
+
if (filter !== null) {
|
|
8671
|
+
const id = readMMTPPacketId2(data);
|
|
8672
|
+
if (id === null || !filter.has(id))
|
|
8673
|
+
continue;
|
|
8674
|
+
}
|
|
8675
|
+
const copy = data.slice().buffer;
|
|
8676
|
+
packets.push(copy);
|
|
8677
|
+
events.dispatchEvent(Object.assign(new CustomEvent("bytes", { detail: copy }), { sourceAddress }));
|
|
8678
|
+
}
|
|
8679
|
+
if (packets.length > 0)
|
|
8680
|
+
events.dispatchEvent(new CustomEvent("packets", { detail: packets }));
|
|
8681
|
+
}
|
|
8682
|
+
} catch {
|
|
8683
|
+
}
|
|
8684
|
+
})();
|
|
8685
|
+
void rx.closed.then((value) => {
|
|
8686
|
+
const reason = reasonOf(value);
|
|
8687
|
+
if (!closing) {
|
|
8688
|
+
if (reason === "consent-withdrawn") {
|
|
8689
|
+
events.dispatchEvent(new CustomEvent("error", {
|
|
8690
|
+
detail: new MulticastError("origin-not-trusted", "multicast consent withdrawn")
|
|
8691
|
+
}));
|
|
8692
|
+
} else if (reason === "oversubscribed" || reason === "network") {
|
|
8693
|
+
events.dispatchEvent(new CustomEvent("error", {
|
|
8694
|
+
detail: new MulticastError("subscription-failed", `MulticastReceiver closed: ${reason}`)
|
|
8695
|
+
}));
|
|
8696
|
+
}
|
|
8697
|
+
}
|
|
8698
|
+
setState("closed");
|
|
8699
|
+
}, (error) => {
|
|
8700
|
+
if (!closing) {
|
|
8701
|
+
events.dispatchEvent(new CustomEvent("error", {
|
|
8702
|
+
detail: new MulticastError("subscription-failed", "MulticastReceiver failed", error)
|
|
8703
|
+
}));
|
|
8704
|
+
}
|
|
8705
|
+
setState("closed");
|
|
8706
|
+
});
|
|
8707
|
+
return subscription;
|
|
8708
|
+
}
|
|
8709
|
+
|
|
8450
8710
|
// node_modules/@blockcast/mmt-transport/dist/moqt-priority.js
|
|
8451
8711
|
var MAX_MOQT_PRIORITY = 255;
|
|
8452
8712
|
function moqtPriority(value, field) {
|
|
@@ -8733,9 +8993,9 @@ function validateTemplatePair(mediaUriTemplate, mediaObjectTemplate, path) {
|
|
|
8733
8993
|
}
|
|
8734
8994
|
function validateMmtpDashTemplate(template, path) {
|
|
8735
8995
|
const variables = [...template.matchAll(TEMPLATE_VARIABLE)].map((match) => match[1]);
|
|
8736
|
-
const
|
|
8737
|
-
if (
|
|
8738
|
-
throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${
|
|
8996
|
+
const unsupported2 = variables.find((variable) => variable !== "Number" && variable !== "RepresentationID");
|
|
8997
|
+
if (unsupported2 !== void 0) {
|
|
8998
|
+
throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${unsupported2}$`);
|
|
8739
8999
|
}
|
|
8740
9000
|
if (variables.filter((variable) => variable === "Number").length !== 1) {
|
|
8741
9001
|
throw new Error(`${path} must contain exactly one $Number$ for MMTP-backed DASH`);
|
|
@@ -17098,17 +17358,17 @@ var FECManager = class _FECManager {
|
|
|
17098
17358
|
|
|
17099
17359
|
// src/mfu-reassembly-policy.ts
|
|
17100
17360
|
function validateReliableMoqOptionShape(value) {
|
|
17101
|
-
const
|
|
17361
|
+
const unsupported2 = Object.keys(value).filter(
|
|
17102
17362
|
(key) => key !== "transport" && key !== "startsAtRandomAccessGroup"
|
|
17103
17363
|
);
|
|
17104
|
-
if (
|
|
17105
|
-
if (
|
|
17364
|
+
if (unsupported2.length > 0) {
|
|
17365
|
+
if (unsupported2.includes("maxRacingGroups")) {
|
|
17106
17366
|
throw new Error(
|
|
17107
17367
|
"[MFU reassembly] maxRacingGroups was removed; reliable MoQ completion is transport-owned"
|
|
17108
17368
|
);
|
|
17109
17369
|
}
|
|
17110
17370
|
throw new Error(
|
|
17111
|
-
`[MFU reassembly] unsupported reliable MoQ option(s): ${
|
|
17371
|
+
`[MFU reassembly] unsupported reliable MoQ option(s): ${unsupported2.join(", ")}`
|
|
17112
17372
|
);
|
|
17113
17373
|
}
|
|
17114
17374
|
const startsAtRandomAccessGroup = value["startsAtRandomAccessGroup"];
|
|
@@ -17152,12 +17412,12 @@ function assertValidMfuReassemblyPolicy(policy) {
|
|
|
17152
17412
|
return;
|
|
17153
17413
|
}
|
|
17154
17414
|
if (value["transport"] === "datagram") {
|
|
17155
|
-
const
|
|
17415
|
+
const unsupported2 = Object.keys(value).filter(
|
|
17156
17416
|
(key) => key !== "transport" && key !== "deadlineMs"
|
|
17157
17417
|
);
|
|
17158
|
-
if (
|
|
17418
|
+
if (unsupported2.length > 0) {
|
|
17159
17419
|
throw new Error(
|
|
17160
|
-
`[MFU reassembly] unsupported datagram option(s): ${
|
|
17420
|
+
`[MFU reassembly] unsupported datagram option(s): ${unsupported2.join(", ")}`
|
|
17161
17421
|
);
|
|
17162
17422
|
}
|
|
17163
17423
|
assertValidDatagramDeadline(value["deadlineMs"]);
|
|
@@ -23579,10 +23839,11 @@ var MmtpRouter = class {
|
|
|
23579
23839
|
if (!entry) return;
|
|
23580
23840
|
entry.receiver.release();
|
|
23581
23841
|
entry.protected = false;
|
|
23842
|
+
entry.userAgent = false;
|
|
23582
23843
|
this.#assistBlocks.clear();
|
|
23583
23844
|
this.#assistFailures.clear();
|
|
23584
23845
|
}
|
|
23585
|
-
/**
|
|
23846
|
+
/** Packets of a track a protected flow lists, dropped unread: a MoQ copy (duplicate, overflow, or listed by more than one flow) or a multicast source datagram that names no flow, of a track a user-agent-admitted flow lists. */
|
|
23586
23847
|
get unadmittedListedDropCount() {
|
|
23587
23848
|
return this.#unadmittedListedDrops;
|
|
23588
23849
|
}
|
|
@@ -23623,6 +23884,13 @@ var MmtpRouter = class {
|
|
|
23623
23884
|
}
|
|
23624
23885
|
return false;
|
|
23625
23886
|
}
|
|
23887
|
+
/** Whether a protected flow the user agent admitted lists `packetId`. */
|
|
23888
|
+
#userAgentGoverns(packetId) {
|
|
23889
|
+
for (const entry of this.#provenance.values()) {
|
|
23890
|
+
if (entry.protected && entry.userAgent && ingressHasPacketId(entry.ingress, packetId)) return true;
|
|
23891
|
+
}
|
|
23892
|
+
return false;
|
|
23893
|
+
}
|
|
23626
23894
|
/**
|
|
23627
23895
|
* A MoQ repair symbol no protected flow lists, owned by a source track one
|
|
23628
23896
|
* does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
|
|
@@ -23713,7 +23981,7 @@ var MmtpRouter = class {
|
|
|
23713
23981
|
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
23714
23982
|
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
23715
23983
|
fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
23716
|
-
})) {
|
|
23984
|
+
}, true)) {
|
|
23717
23985
|
this.#unlistedAssistedRecoveries++;
|
|
23718
23986
|
this.#assistBlocks.delete(key);
|
|
23719
23987
|
return;
|
|
@@ -24869,6 +25137,12 @@ var MmtpRouter = class {
|
|
|
24869
25137
|
debugRouter(`[MmtpRouter] Dropping multicast packet pid=${info.packetId}: flow ${ingress.flow} has no receiver`);
|
|
24870
25138
|
return;
|
|
24871
25139
|
}
|
|
25140
|
+
if (ingress.admittedBy === USER_AGENT_VERIFIED) {
|
|
25141
|
+
this.#noteFlowAdmitted(ingress.flow);
|
|
25142
|
+
protectedFlow.userAgent = true;
|
|
25143
|
+
this.#processMMTP(packet, source, true, ingress, overlapCopy);
|
|
25144
|
+
return;
|
|
25145
|
+
}
|
|
24872
25146
|
const { verdict } = protectedFlow.receiver.onDatagram(packet, "multicast");
|
|
24873
25147
|
if (verdict === "admitted") {
|
|
24874
25148
|
this.#noteFlowAdmitted(ingress.flow);
|
|
@@ -24886,6 +25160,11 @@ var MmtpRouter = class {
|
|
|
24886
25160
|
return;
|
|
24887
25161
|
}
|
|
24888
25162
|
}
|
|
25163
|
+
const flowless = source === "multicast" && !manifestAdmitted && ingress?.flow === void 0;
|
|
25164
|
+
if (flowless && !info.isRepair && this.#userAgentGoverns(info.packetId)) {
|
|
25165
|
+
this.#unadmittedListedDrops++;
|
|
25166
|
+
return;
|
|
25167
|
+
}
|
|
24889
25168
|
if (info.isRepair) {
|
|
24890
25169
|
const overlay = this.#overlayRepairTarget(info.packetId, catalogTrackNameForPacket(ingress, info.packetId));
|
|
24891
25170
|
if (overlay !== void 0) {
|
|
@@ -24903,7 +25182,7 @@ var MmtpRouter = class {
|
|
|
24903
25182
|
);
|
|
24904
25183
|
if (!repair) return;
|
|
24905
25184
|
if (ownerPacketId != null && this.#fecManager) {
|
|
24906
|
-
if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
25185
|
+
if ((flowless && this.#userAgentGoverns(ownerPacketId) || source === "moq" && !manifestAdmitted) && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
24907
25186
|
this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
|
|
24908
25187
|
const count = this.#repairPidCounts[info.packetId];
|
|
24909
25188
|
if (count <= 2 || count % 500 === 0) {
|
|
@@ -25482,7 +25761,7 @@ var MmtpRouter = class {
|
|
|
25482
25761
|
}
|
|
25483
25762
|
// ── Private: FEC recovery routing ───────────────────────────────
|
|
25484
25763
|
/** Route WASM-recovered block through appropriate path based on container kind. */
|
|
25485
|
-
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete) {
|
|
25764
|
+
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
25486
25765
|
const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
|
|
25487
25766
|
if (ownerPacketId === void 0) {
|
|
25488
25767
|
throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
|
|
@@ -25499,7 +25778,7 @@ var MmtpRouter = class {
|
|
|
25499
25778
|
ownerPacketId,
|
|
25500
25779
|
blockProfile
|
|
25501
25780
|
});
|
|
25502
|
-
return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete);
|
|
25781
|
+
return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted);
|
|
25503
25782
|
}
|
|
25504
25783
|
/**
|
|
25505
25784
|
* A recovered source packet of a protected flow is admitted on its own
|
|
@@ -25511,9 +25790,24 @@ var MmtpRouter = class {
|
|
|
25511
25790
|
* recovered packets is refused is unrecovered (7.2.7): its caller leaves it
|
|
25512
25791
|
* open, so it reaches its FEC deadline and fails by class. Always true
|
|
25513
25792
|
* outside a protected flow.
|
|
25514
|
-
|
|
25515
|
-
|
|
25516
|
-
|
|
25793
|
+
*
|
|
25794
|
+
* On a flow the user agent admitted, the live decoder's recoveries are kept
|
|
25795
|
+
* without a record: while the flow is protected that decoder takes only
|
|
25796
|
+
* symbols the user agent or a record verified (a flow-less multicast
|
|
25797
|
+
* datagram of a listed track is dropped or set aside in #processMMTP), so
|
|
25798
|
+
* what it recovers is as verified as they are. An `assisted` (scratch-decoder) recovery mixes in
|
|
25799
|
+
* unlisted MoQ repair and still needs a record.
|
|
25800
|
+
* ponytail: per-flow flag; per-block provenance only if a protected flow
|
|
25801
|
+
* ever mixes verified and unverified live inputs.
|
|
25802
|
+
* ponytail: a worker frame computed before the protection reset can still
|
|
25803
|
+
* arrive after it (frames carry no epoch); its inputs are MoQ copies read
|
|
25804
|
+
* and played before protection, so it admits nothing new. Drop pre-reset
|
|
25805
|
+
* frames by epoch only if pre-protection input ever matters.
|
|
25806
|
+
*/
|
|
25807
|
+
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
25808
|
+
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
25809
|
+
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
25810
|
+
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
25517
25811
|
let recovered = true;
|
|
25518
25812
|
if (receiver) {
|
|
25519
25813
|
const parsed = this.#stream(ownerPacketId, catalogTrackName).duplicates.parsedSymbols;
|
|
@@ -25525,6 +25819,8 @@ var MmtpRouter = class {
|
|
|
25525
25819
|
let admitted = false;
|
|
25526
25820
|
if (receiver === "ambiguous") {
|
|
25527
25821
|
this.#ambiguousRecoveredDrops++;
|
|
25822
|
+
} else if (userAgent) {
|
|
25823
|
+
admitted = true;
|
|
25528
25824
|
} else {
|
|
25529
25825
|
const wire = new Uint8Array(sym.rawSymbol.byteLength + 4);
|
|
25530
25826
|
wire.set(sym.rawSymbol);
|
|
@@ -25552,12 +25848,12 @@ var MmtpRouter = class {
|
|
|
25552
25848
|
catalogTrackName === void 0 || !byName ? byName?.values() : byName.has(catalogTrackName) ? [byName.get(catalogTrackName)] : []
|
|
25553
25849
|
);
|
|
25554
25850
|
if (flows.size > 1) return "ambiguous";
|
|
25555
|
-
for (const bound of flows) return this.#provenance.get(bound)
|
|
25851
|
+
for (const bound of flows) return this.#provenance.get(bound) ?? "ambiguous";
|
|
25556
25852
|
let match;
|
|
25557
|
-
for (const
|
|
25558
|
-
if (ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
|
|
25853
|
+
for (const entry of this.#provenance.values()) {
|
|
25854
|
+
if (entry.ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
|
|
25559
25855
|
if (match) return "ambiguous";
|
|
25560
|
-
match =
|
|
25856
|
+
match = entry;
|
|
25561
25857
|
}
|
|
25562
25858
|
}
|
|
25563
25859
|
return match;
|
|
@@ -26339,11 +26635,8 @@ function providerErrorOf(error) {
|
|
|
26339
26635
|
return Object.freeze(typeof code === "string" ? { name, message, code } : { name, message });
|
|
26340
26636
|
}
|
|
26341
26637
|
var MAX_TRACKED_DROPPED_PIDS = 16;
|
|
26342
|
-
function getMulticastGlobals() {
|
|
26343
|
-
return globalThis;
|
|
26344
|
-
}
|
|
26345
26638
|
function hasMulticastGateway() {
|
|
26346
|
-
return typeof
|
|
26639
|
+
return typeof resolveMulticastGateway()?.subscribe === "function";
|
|
26347
26640
|
}
|
|
26348
26641
|
function senderOf(event) {
|
|
26349
26642
|
const sourceAddress = event.sourceAddress;
|
|
@@ -26435,6 +26728,9 @@ var MulticastTransport = class {
|
|
|
26435
26728
|
// listener closure, so an in-place admission change (setContext) applies
|
|
26436
26729
|
// from the next packet on without rejoining.
|
|
26437
26730
|
#context = void 0;
|
|
26731
|
+
#deliveredContext = void 0;
|
|
26732
|
+
/** The live join's gateway, when it is the user agent's MulticastReceiver provider. */
|
|
26733
|
+
#userAgentGateway = void 0;
|
|
26438
26734
|
// The live join's provider-side filter. An in-place widening must reach it,
|
|
26439
26735
|
// or the provider never delivers the new ID; null without a live join.
|
|
26440
26736
|
#providerFilter = null;
|
|
@@ -26471,7 +26767,7 @@ var MulticastTransport = class {
|
|
|
26471
26767
|
this.#dataPlaneLive = false;
|
|
26472
26768
|
this.#sender = void 0;
|
|
26473
26769
|
if (this.#disposed) return this.#declined(generation, "disposed");
|
|
26474
|
-
const gateway = this.#gateway ??
|
|
26770
|
+
const gateway = this.#gateway ?? resolveMulticastGateway();
|
|
26475
26771
|
if (typeof gateway?.subscribe !== "function") {
|
|
26476
26772
|
console.warn("[MulticastTransport] Multicast API not available");
|
|
26477
26773
|
return this.#declined(generation, "unavailable");
|
|
@@ -26487,15 +26783,16 @@ var MulticastTransport = class {
|
|
|
26487
26783
|
console.log(
|
|
26488
26784
|
`[MulticastTransport] window.multicast.subscribe(${config.sourceAddress ?? config.senderAddress ?? "*"}, ${config.groupAddress}, ${config.port})`
|
|
26489
26785
|
);
|
|
26786
|
+
const userAgent = isUserAgentVerifiedGateway(gateway);
|
|
26490
26787
|
const subscribeConfig = toWindowMulticastSubscribeConfig({
|
|
26491
26788
|
source: config.sourceAddress,
|
|
26492
26789
|
senderAddress: config.senderAddress,
|
|
26493
26790
|
group: config.groupAddress,
|
|
26494
26791
|
port: config.port,
|
|
26495
|
-
mode: config.mode,
|
|
26496
|
-
amtRelay: config.amtRelay,
|
|
26497
|
-
amtRelayPort: config.amtRelayPort,
|
|
26498
|
-
amtDiscovery: config.amtDiscovery,
|
|
26792
|
+
mode: userAgent ? "native" : config.mode,
|
|
26793
|
+
amtRelay: userAgent ? void 0 : config.amtRelay,
|
|
26794
|
+
amtRelayPort: userAgent ? void 0 : config.amtRelayPort,
|
|
26795
|
+
amtDiscovery: userAgent ? void 0 : config.amtDiscovery,
|
|
26499
26796
|
packetIds: config.packetIds,
|
|
26500
26797
|
tracks: config.tracks,
|
|
26501
26798
|
atsc3: config.atsc3
|
|
@@ -26541,7 +26838,8 @@ var MulticastTransport = class {
|
|
|
26541
26838
|
acknowledgedUpdate: 0,
|
|
26542
26839
|
inFlight: 0
|
|
26543
26840
|
};
|
|
26544
|
-
this.#
|
|
26841
|
+
this.#userAgentGateway = userAgent ? gateway : void 0;
|
|
26842
|
+
this.#setContext(context);
|
|
26545
26843
|
if (packetIds) {
|
|
26546
26844
|
for (const packetId of packetIds) {
|
|
26547
26845
|
if (!this.#acceptedBytesByPacketId.has(packetId)) {
|
|
@@ -26549,7 +26847,7 @@ var MulticastTransport = class {
|
|
|
26549
26847
|
}
|
|
26550
26848
|
}
|
|
26551
26849
|
}
|
|
26552
|
-
this.#resolvedTransport = subscribeConfig.origin === "amt" ? "amt" : "native";
|
|
26850
|
+
this.#resolvedTransport = userAgent ? "user-agent" : subscribeConfig.origin === "amt" ? "amt" : "native";
|
|
26553
26851
|
const delivery = subscription.delivery ?? "datagram";
|
|
26554
26852
|
if (delivery !== "datagram" && delivery !== "ordered-relay") {
|
|
26555
26853
|
throw new Error(
|
|
@@ -26630,7 +26928,7 @@ var MulticastTransport = class {
|
|
|
26630
26928
|
}
|
|
26631
26929
|
if (!this.#acceptPacket(packet, sourceAddress)) return false;
|
|
26632
26930
|
const liveness = !this.#isRecordCandidate(packet);
|
|
26633
|
-
this.#onPacket(packet, this.#
|
|
26931
|
+
this.#onPacket(packet, this.#deliveredContext);
|
|
26634
26932
|
return liveness;
|
|
26635
26933
|
}
|
|
26636
26934
|
#isRecordCandidate(packet) {
|
|
@@ -26841,8 +27139,10 @@ var MulticastTransport = class {
|
|
|
26841
27139
|
return this.#subscription !== null;
|
|
26842
27140
|
}
|
|
26843
27141
|
/**
|
|
26844
|
-
*
|
|
26845
|
-
*
|
|
27142
|
+
* The live join's context as the caller gave it (the join's `context`, or
|
|
27143
|
+
* the last setContext), or undefined without a live join. On a native
|
|
27144
|
+
* provider each accepted packet is delivered with a marked copy of it
|
|
27145
|
+
* (`userAgentVerifiedIngress`); this getter never carries the mark.
|
|
26846
27146
|
*/
|
|
26847
27147
|
get context() {
|
|
26848
27148
|
return this.#context;
|
|
@@ -26866,7 +27166,16 @@ var MulticastTransport = class {
|
|
|
26866
27166
|
"[MulticastTransport] setContext needs a live join; join() carries the first context"
|
|
26867
27167
|
);
|
|
26868
27168
|
}
|
|
27169
|
+
this.#setContext(context);
|
|
27170
|
+
}
|
|
27171
|
+
/**
|
|
27172
|
+
* The join's context, and what the subscription delivers with each packet:
|
|
27173
|
+
* marked admitted by the user agent when the gateway is its native
|
|
27174
|
+
* MulticastReceiver provider. feedPacket never marks.
|
|
27175
|
+
*/
|
|
27176
|
+
#setContext(context) {
|
|
26869
27177
|
this.#context = context;
|
|
27178
|
+
this.#deliveredContext = this.#userAgentGateway !== void 0 && typeof context === "object" && context !== null ? userAgentVerifiedIngress(context, this.#userAgentGateway) : context;
|
|
26870
27179
|
}
|
|
26871
27180
|
/**
|
|
26872
27181
|
* Replace the live join's packet-ID allow-list in place, without rejoining.
|
|
@@ -26998,6 +27307,8 @@ var MulticastTransport = class {
|
|
|
26998
27307
|
this.#matchedSource = void 0;
|
|
26999
27308
|
this.#providerFilter = null;
|
|
27000
27309
|
this.#context = void 0;
|
|
27310
|
+
this.#deliveredContext = void 0;
|
|
27311
|
+
this.#userAgentGateway = void 0;
|
|
27001
27312
|
this.#withdrawnPacketIds.clear();
|
|
27002
27313
|
}
|
|
27003
27314
|
/** The ingress filter; an accepted packet is counted here, once. */
|
|
@@ -28350,8 +28661,17 @@ var MulticastManager = class {
|
|
|
28350
28661
|
* repair roles and owners are registered here; in-band owners stay the
|
|
28351
28662
|
* caller's. Joining is best effort: a group that fails to join is logged
|
|
28352
28663
|
* and leaves the media join as it was.
|
|
28353
|
-
|
|
28354
|
-
|
|
28664
|
+
*
|
|
28665
|
+
* `options.gateway` names the gateway a subscribe callback reads through.
|
|
28666
|
+
* When it is the browser's own MulticastReceiver provider, the callback's
|
|
28667
|
+
* packets are fed as admitted by the user agent
|
|
28668
|
+
* (`userAgentVerifiedIngress`); nothing the callback passes can mark them.
|
|
28669
|
+
* The mark rests on this statement, not on the data path: every packet the
|
|
28670
|
+
* callback delivers must come from a subscription opened on
|
|
28671
|
+
* `options.gateway`. A callback that also merges another source (a MoQ or
|
|
28672
|
+
* WebSocket repair feed) must not name a native provider here.
|
|
28673
|
+
*/
|
|
28674
|
+
joinRepairEndpoints(endpoints, provider, options) {
|
|
28355
28675
|
const releases = [];
|
|
28356
28676
|
let released = false;
|
|
28357
28677
|
const safely = (each) => {
|
|
@@ -28373,17 +28693,19 @@ var MulticastManager = class {
|
|
|
28373
28693
|
...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
|
|
28374
28694
|
};
|
|
28375
28695
|
let sender;
|
|
28376
|
-
const
|
|
28696
|
+
const feed = (packet, sourceAddress, ingressToFeed) => {
|
|
28377
28697
|
if (released) return;
|
|
28378
28698
|
if (sourceAddress !== void 0 && !sameDatagramSource(sourceAddress, this.#transport.sender ?? (sender ??= sourceAddress))) return;
|
|
28379
|
-
this.#router.feedPacket(packet, "multicast",
|
|
28699
|
+
this.#router.feedPacket(packet, "multicast", ingressToFeed);
|
|
28380
28700
|
};
|
|
28381
28701
|
const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
|
|
28382
28702
|
if (typeof provider === "function") {
|
|
28703
|
+
const marked = userAgentVerifiedIngress(ingress, options?.gateway);
|
|
28704
|
+
const onPacket = (packet, sourceAddress) => feed(packet, sourceAddress, marked);
|
|
28383
28705
|
Promise.resolve().then(() => provider(endpoint, onPacket)).then((release2) => released ? safely(release2) : releases.push(release2), fail);
|
|
28384
28706
|
continue;
|
|
28385
28707
|
}
|
|
28386
|
-
const transport = new MulticastTransport((packet) =>
|
|
28708
|
+
const transport = new MulticastTransport((packet, delivered) => feed(packet, void 0, delivered ?? ingress), {
|
|
28387
28709
|
gateway: provider,
|
|
28388
28710
|
lockedTo: () => this.#transport.sender
|
|
28389
28711
|
});
|
|
@@ -28395,11 +28717,12 @@ var MulticastManager = class {
|
|
|
28395
28717
|
mode: endpoint.mode,
|
|
28396
28718
|
amtRelay: endpoint.amtRelay,
|
|
28397
28719
|
amtRelayPort: endpoint.amtRelayPort ?? (endpoint.amtRelay?.trim() ? DEFAULT_AMT_RELAY_PORT : void 0),
|
|
28720
|
+
amtDiscovery: endpoint.amtDiscovery,
|
|
28398
28721
|
packetIds: endpoint.packetIds,
|
|
28399
28722
|
tracks: endpoint.tracks.map(({ name, packetId }) => ({ name, packetId })),
|
|
28400
28723
|
// A protected repair group's records ride in band on its port (7.2.3).
|
|
28401
28724
|
...endpoint.flow === void 0 ? {} : { protected: true }
|
|
28402
|
-
}).then((joined) => {
|
|
28725
|
+
}, ingress).then((joined) => {
|
|
28403
28726
|
if (!joined && !released) fail(transport.lastJoinOutcome);
|
|
28404
28727
|
});
|
|
28405
28728
|
}
|
|
@@ -31164,18 +31487,31 @@ function resolveRepairLayers(catalog, source) {
|
|
|
31164
31487
|
totalBaseRepairSymbols: binding.totalBaseRepairSymbols
|
|
31165
31488
|
};
|
|
31166
31489
|
}
|
|
31490
|
+
function valueAt(catalog, pointer) {
|
|
31491
|
+
let node = catalog;
|
|
31492
|
+
for (const segment of pointer.split("/").slice(1)) {
|
|
31493
|
+
if (typeof node !== "object" || node === null) return void 0;
|
|
31494
|
+
node = node[segment.replace(/~1/g, "/").replace(/~0/g, "~")];
|
|
31495
|
+
}
|
|
31496
|
+
return node;
|
|
31497
|
+
}
|
|
31498
|
+
function catalogTrackDeliveryMode(catalog, source) {
|
|
31499
|
+
const binding = bindingOf(catalog, source);
|
|
31500
|
+
const entry = binding ? valueAt(catalog, binding.pointer) : void 0;
|
|
31501
|
+
return entry?.deliveryMode === "stream" ? "stream" : "datagram";
|
|
31502
|
+
}
|
|
31167
31503
|
function moqtPriority2(value) {
|
|
31168
31504
|
return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
|
|
31169
31505
|
}
|
|
31170
31506
|
function catalogRepairTarget(catalog, source) {
|
|
31171
31507
|
const binding = bindingOf(catalog, source);
|
|
31172
31508
|
if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
|
|
31173
|
-
const
|
|
31509
|
+
const layerZero2 = binding.layers[0];
|
|
31174
31510
|
const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
|
|
31175
31511
|
const tracks = catalog.tracks;
|
|
31176
31512
|
const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
|
|
31177
31513
|
const sourcePriority = entry ? moqtPriority2(entry["priority"]) : null;
|
|
31178
|
-
return { repairTrack:
|
|
31514
|
+
return { repairTrack: layerZero2.repairTrackName, sourcePriority, repairPriority: layerZero2.priority };
|
|
31179
31515
|
}
|
|
31180
31516
|
function numericField(value) {
|
|
31181
31517
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
@@ -31207,6 +31543,7 @@ function fecParams(catalog, track, fps, sampleRate) {
|
|
|
31207
31543
|
repairTrackName: base.repairTrack,
|
|
31208
31544
|
repairLayers: repair.baseLayers,
|
|
31209
31545
|
repairOverlays: repair.overlays,
|
|
31546
|
+
deliveryMode: catalogTrackDeliveryMode(catalog, track),
|
|
31210
31547
|
...mediaRate
|
|
31211
31548
|
};
|
|
31212
31549
|
}
|
|
@@ -31257,7 +31594,14 @@ function resolveCatalogTrackFec(catalog, tracks) {
|
|
|
31257
31594
|
var PATH_BITS = { moq: 1, multicast: 2 };
|
|
31258
31595
|
var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
|
|
31259
31596
|
var MmtpFecSession = class {
|
|
31597
|
+
/** P_0 = B_0, the held total every repair packet is checked against. */
|
|
31598
|
+
get repairSymbols() {
|
|
31599
|
+
return this.#repairSymbols;
|
|
31600
|
+
}
|
|
31260
31601
|
#decoder;
|
|
31602
|
+
#repairSymbols;
|
|
31603
|
+
/** The message entry a catalog-less session was built from; undefined under a catalog. */
|
|
31604
|
+
#message;
|
|
31261
31605
|
/** Blocks a repair has defined and that are not settled yet, by SS_Start. */
|
|
31262
31606
|
#blocks = /* @__PURE__ */ new Map();
|
|
31263
31607
|
/** SS_Starts of settled blocks, so a later repair for one is refused. */
|
|
@@ -31295,6 +31639,24 @@ var MmtpFecSession = class {
|
|
|
31295
31639
|
* `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
|
|
31296
31640
|
*/
|
|
31297
31641
|
constructor(config) {
|
|
31642
|
+
if (config && "alFecFlow" in config) {
|
|
31643
|
+
const message = alFecRaptorQFlowConfig(config.alFecFlow);
|
|
31644
|
+
if (!message) {
|
|
31645
|
+
throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
|
|
31646
|
+
}
|
|
31647
|
+
this.#message = message;
|
|
31648
|
+
this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
|
|
31649
|
+
this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
|
|
31650
|
+
this.sourceSymbols = message.maximumK;
|
|
31651
|
+
this.#repairSymbols = message.maximumP;
|
|
31652
|
+
this.symbolSize = message.repairSymbolSize;
|
|
31653
|
+
this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
|
|
31654
|
+
this.#repairPacketId = message.repairFlowId;
|
|
31655
|
+
this.#heldLayers = layerZero(message.maximumP);
|
|
31656
|
+
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31657
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31658
|
+
return;
|
|
31659
|
+
}
|
|
31298
31660
|
const fec = config?.fec;
|
|
31299
31661
|
if (!fec || typeof fec !== "object") {
|
|
31300
31662
|
throw new Error("MmtpFecSession: the catalog fec instance is required");
|
|
@@ -31305,7 +31667,7 @@ var MmtpFecSession = class {
|
|
|
31305
31667
|
}
|
|
31306
31668
|
this.algorithm = fec.scheme;
|
|
31307
31669
|
this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
|
|
31308
|
-
this
|
|
31670
|
+
this.#repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
|
|
31309
31671
|
this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
|
|
31310
31672
|
const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
|
|
31311
31673
|
const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
|
|
@@ -31325,17 +31687,30 @@ var MmtpFecSession = class {
|
|
|
31325
31687
|
config.heldLayers
|
|
31326
31688
|
);
|
|
31327
31689
|
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31331
|
-
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
|
|
31337
|
-
|
|
31338
|
-
|
|
31690
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31691
|
+
}
|
|
31692
|
+
/**
|
|
31693
|
+
* A later version of the AL-FEC message, catalog-less sessions only. A
|
|
31694
|
+
* maximum_p_for_repair_flow above the held total raises it (`raised`): the
|
|
31695
|
+
* anchored check restarts at the new total, so a block generated under the
|
|
31696
|
+
* previous one stays valid before the first block carrying the new total
|
|
31697
|
+
* (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
|
|
31698
|
+
* (`unchanged`): no publisher lowers it, and the check catches a wire that
|
|
31699
|
+
* does. An entry with another asset, repair flow, code point, K or T, or
|
|
31700
|
+
* one outside 8.3, is `refused`: that is a new source encoding, which needs
|
|
31701
|
+
* a new session. A latched fail-closed stays latched either way.
|
|
31702
|
+
*/
|
|
31703
|
+
updateAlFecFlow(flow) {
|
|
31704
|
+
const held = this.#message;
|
|
31705
|
+
if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
|
|
31706
|
+
const next = alFecRaptorQFlowConfig(flow);
|
|
31707
|
+
if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
|
|
31708
|
+
return "refused";
|
|
31709
|
+
}
|
|
31710
|
+
if (next.maximumP <= this.#repairSymbols) return "unchanged";
|
|
31711
|
+
this.#repairSymbols = next.maximumP;
|
|
31712
|
+
this.#heldLayers = layerZero(next.maximumP);
|
|
31713
|
+
return "raised";
|
|
31339
31714
|
}
|
|
31340
31715
|
/** True once release() ran. */
|
|
31341
31716
|
get released() {
|
|
@@ -31430,7 +31805,7 @@ var MmtpFecSession = class {
|
|
|
31430
31805
|
}
|
|
31431
31806
|
if (admission.reject) {
|
|
31432
31807
|
const verdict2 = this.#heldLayers.failsClosed(
|
|
31433
|
-
repairTrackName,
|
|
31808
|
+
this.#trackFor(repairTrackName),
|
|
31434
31809
|
frame.ssStart,
|
|
31435
31810
|
frame.rsbLength,
|
|
31436
31811
|
this.#anchorHorizon,
|
|
@@ -31445,7 +31820,7 @@ var MmtpFecSession = class {
|
|
|
31445
31820
|
this.#repairPacketId ??= frame.packetId;
|
|
31446
31821
|
if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
|
|
31447
31822
|
const verdict = this.#heldLayers.admit(
|
|
31448
|
-
repairTrackName,
|
|
31823
|
+
this.#trackFor(repairTrackName),
|
|
31449
31824
|
frame.ssStart,
|
|
31450
31825
|
frame.rsbLength,
|
|
31451
31826
|
this.#anchorHorizon,
|
|
@@ -31504,6 +31879,10 @@ var MmtpFecSession = class {
|
|
|
31504
31879
|
decoder.free();
|
|
31505
31880
|
return true;
|
|
31506
31881
|
}
|
|
31882
|
+
/** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
|
|
31883
|
+
#trackFor(repairTrackName) {
|
|
31884
|
+
return this.#message ? void 0 : repairTrackName;
|
|
31885
|
+
}
|
|
31507
31886
|
#drop(reason) {
|
|
31508
31887
|
this.#stats.dropped[reason] = (this.#stats.dropped[reason] ?? 0) + 1;
|
|
31509
31888
|
return [];
|
|
@@ -31625,6 +32004,22 @@ var MmtpFecSession = class {
|
|
|
31625
32004
|
}
|
|
31626
32005
|
}
|
|
31627
32006
|
};
|
|
32007
|
+
function layerZero(total) {
|
|
32008
|
+
return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
|
|
32009
|
+
}
|
|
32010
|
+
function buildDecoder(Decoder, session) {
|
|
32011
|
+
if (typeof Decoder !== "function") {
|
|
32012
|
+
throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
|
|
32013
|
+
}
|
|
32014
|
+
const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
|
|
32015
|
+
try {
|
|
32016
|
+
decoder.configure_params(session.sourceSymbols, session.symbolSize);
|
|
32017
|
+
} catch (error) {
|
|
32018
|
+
decoder.free();
|
|
32019
|
+
throw error;
|
|
32020
|
+
}
|
|
32021
|
+
return decoder;
|
|
32022
|
+
}
|
|
31628
32023
|
function requirePositiveInteger(value, path) {
|
|
31629
32024
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
31630
32025
|
throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);
|
|
@@ -31638,6 +32033,168 @@ function requireNumber(value, path) {
|
|
|
31638
32033
|
return value;
|
|
31639
32034
|
}
|
|
31640
32035
|
|
|
32036
|
+
// src/repair-layer-subscriptions.ts
|
|
32037
|
+
var SERIAL32_HALF2 = 2147483648;
|
|
32038
|
+
var RepairLayerSubscriptions = class {
|
|
32039
|
+
#opts;
|
|
32040
|
+
#windowPackets;
|
|
32041
|
+
#margin;
|
|
32042
|
+
#dropAfterWindows;
|
|
32043
|
+
#source;
|
|
32044
|
+
#layerCount = 0;
|
|
32045
|
+
#subs = /* @__PURE__ */ new Map();
|
|
32046
|
+
#disposed = false;
|
|
32047
|
+
#lastSeq;
|
|
32048
|
+
#expected = 0;
|
|
32049
|
+
#lost = 0;
|
|
32050
|
+
#wantFewer = 0;
|
|
32051
|
+
#lossRate = null;
|
|
32052
|
+
#repairPackets = 0;
|
|
32053
|
+
#repairBytes = 0;
|
|
32054
|
+
#staleDropped = 0;
|
|
32055
|
+
constructor(opts) {
|
|
32056
|
+
this.#opts = opts;
|
|
32057
|
+
this.#source = opts.source;
|
|
32058
|
+
this.#windowPackets = opts.windowPackets ?? 500;
|
|
32059
|
+
this.#margin = opts.margin ?? 2;
|
|
32060
|
+
this.#dropAfterWindows = opts.dropAfterWindows ?? 3;
|
|
32061
|
+
}
|
|
32062
|
+
get layerCount() {
|
|
32063
|
+
return this.#layerCount;
|
|
32064
|
+
}
|
|
32065
|
+
/**
|
|
32066
|
+
* One source packet's MMTP `packet_sequence_number`, in arrival order.
|
|
32067
|
+
* The sequence is per `packet_id`: pass the packets of ONE packet_id only,
|
|
32068
|
+
* or interleaved sequences read as loss.
|
|
32069
|
+
*/
|
|
32070
|
+
noteSourceSequence(seq) {
|
|
32071
|
+
if (this.#disposed || this.#delivery() === "stream") return;
|
|
32072
|
+
seq >>>= 0;
|
|
32073
|
+
if (this.#lastSeq === void 0) {
|
|
32074
|
+
this.#lastSeq = seq;
|
|
32075
|
+
return;
|
|
32076
|
+
}
|
|
32077
|
+
const d = serial32Distance(this.#lastSeq, seq);
|
|
32078
|
+
if (d === 0) return;
|
|
32079
|
+
const back = d >= SERIAL32_HALF2 ? 2 ** 32 - d : 0;
|
|
32080
|
+
if (d > this.#windowPackets && (back === 0 || back > this.#windowPackets)) {
|
|
32081
|
+
this.#lastSeq = seq;
|
|
32082
|
+
this.#expected = 0;
|
|
32083
|
+
this.#lost = 0;
|
|
32084
|
+
return;
|
|
32085
|
+
}
|
|
32086
|
+
if (back === 0) {
|
|
32087
|
+
this.#expected += d;
|
|
32088
|
+
this.#lost += d - 1;
|
|
32089
|
+
this.#lastSeq = seq;
|
|
32090
|
+
} else if (this.#lost > 0) {
|
|
32091
|
+
this.#lost -= 1;
|
|
32092
|
+
}
|
|
32093
|
+
if (this.#expected >= this.#windowPackets) this.#closeWindow();
|
|
32094
|
+
}
|
|
32095
|
+
/** ABR switch R -> R': close R's tracks, carry the layer count to R'. */
|
|
32096
|
+
switchSource(next) {
|
|
32097
|
+
if (this.#disposed) return;
|
|
32098
|
+
this.#closeAll();
|
|
32099
|
+
this.#source = next;
|
|
32100
|
+
this.#lastSeq = void 0;
|
|
32101
|
+
this.#expected = 0;
|
|
32102
|
+
this.#lost = 0;
|
|
32103
|
+
this.#wantFewer = 0;
|
|
32104
|
+
this.#layerCount = this.#delivery() === "stream" ? 0 : Math.min(this.#layerCount, this.#layers().length);
|
|
32105
|
+
this.#reconcile();
|
|
32106
|
+
}
|
|
32107
|
+
snapshot() {
|
|
32108
|
+
return {
|
|
32109
|
+
delivery: this.#delivery(),
|
|
32110
|
+
layerCount: this.#layerCount,
|
|
32111
|
+
subscribedTracks: [...this.#subs.keys()],
|
|
32112
|
+
repairPackets: this.#repairPackets,
|
|
32113
|
+
repairBytes: this.#repairBytes,
|
|
32114
|
+
staleDropped: this.#staleDropped,
|
|
32115
|
+
lossRate: this.#lossRate
|
|
32116
|
+
};
|
|
32117
|
+
}
|
|
32118
|
+
dispose() {
|
|
32119
|
+
if (this.#disposed) return;
|
|
32120
|
+
this.#closeAll();
|
|
32121
|
+
this.#disposed = true;
|
|
32122
|
+
}
|
|
32123
|
+
#delivery() {
|
|
32124
|
+
return this.#source.deliveryMode === "stream" ? "stream" : "datagram";
|
|
32125
|
+
}
|
|
32126
|
+
#layers() {
|
|
32127
|
+
return this.#source.repairLayers ?? [];
|
|
32128
|
+
}
|
|
32129
|
+
#closeWindow() {
|
|
32130
|
+
const rate = this.#lost / this.#expected;
|
|
32131
|
+
this.#lossRate = rate;
|
|
32132
|
+
this.#expected = 0;
|
|
32133
|
+
this.#lost = 0;
|
|
32134
|
+
const target = this.#targetLayers(rate);
|
|
32135
|
+
if (target > this.#layerCount) {
|
|
32136
|
+
this.#wantFewer = 0;
|
|
32137
|
+
this.#layerCount = target;
|
|
32138
|
+
} else if (target < this.#layerCount) {
|
|
32139
|
+
this.#wantFewer += 1;
|
|
32140
|
+
if (this.#wantFewer >= this.#dropAfterWindows) {
|
|
32141
|
+
this.#wantFewer = 0;
|
|
32142
|
+
this.#layerCount -= 1;
|
|
32143
|
+
}
|
|
32144
|
+
} else {
|
|
32145
|
+
this.#wantFewer = 0;
|
|
32146
|
+
}
|
|
32147
|
+
this.#reconcile();
|
|
32148
|
+
}
|
|
32149
|
+
/** Fewest base layers whose summed P covers margin x the measured loss of K. */
|
|
32150
|
+
#targetLayers(rate) {
|
|
32151
|
+
if (rate <= 0) return 0;
|
|
32152
|
+
const needed = Math.ceil(rate * this.#source.fecK * this.#margin);
|
|
32153
|
+
const layers = this.#layers();
|
|
32154
|
+
let held = 0;
|
|
32155
|
+
for (let n = 0; n < layers.length; n++) {
|
|
32156
|
+
held += layers[n].repairSymbols;
|
|
32157
|
+
if (held >= needed) return n + 1;
|
|
32158
|
+
}
|
|
32159
|
+
return layers.length;
|
|
32160
|
+
}
|
|
32161
|
+
/** Stream delivery never leaves layer count 0, so it never subscribes. */
|
|
32162
|
+
#reconcile() {
|
|
32163
|
+
const want = /* @__PURE__ */ new Map();
|
|
32164
|
+
for (const layer of this.#layers().slice(0, this.#layerCount)) {
|
|
32165
|
+
want.set(layer.repairTrackName, layer.priority);
|
|
32166
|
+
}
|
|
32167
|
+
const overlay = this.#source.repairOverlays?.[0];
|
|
32168
|
+
if (this.#layerCount > 0 && overlay) want.set(overlay.repairTrackName, overlay.priority);
|
|
32169
|
+
for (const [name, entry] of this.#subs) {
|
|
32170
|
+
if (!want.has(name)) {
|
|
32171
|
+
entry.sub.close();
|
|
32172
|
+
this.#subs.delete(name);
|
|
32173
|
+
}
|
|
32174
|
+
}
|
|
32175
|
+
for (const [name, priority] of want) {
|
|
32176
|
+
if (this.#subs.has(name)) continue;
|
|
32177
|
+
const entry = {};
|
|
32178
|
+
entry.sub = this.#opts.subscribe(name, priority, (packet) => this.#onPacket(name, entry, packet));
|
|
32179
|
+
this.#subs.set(name, entry);
|
|
32180
|
+
}
|
|
32181
|
+
}
|
|
32182
|
+
/** Fenced on the entry itself, so a closed subscription is stale even if its track is re-added. */
|
|
32183
|
+
#onPacket(name, entry, packet) {
|
|
32184
|
+
if (this.#disposed || this.#subs.get(name) !== entry) {
|
|
32185
|
+
this.#staleDropped += 1;
|
|
32186
|
+
return;
|
|
32187
|
+
}
|
|
32188
|
+
this.#repairPackets += 1;
|
|
32189
|
+
this.#repairBytes += packet.byteLength;
|
|
32190
|
+
this.#opts.feed(packet, name);
|
|
32191
|
+
}
|
|
32192
|
+
#closeAll() {
|
|
32193
|
+
for (const entry of this.#subs.values()) entry.sub.close();
|
|
32194
|
+
this.#subs.clear();
|
|
32195
|
+
}
|
|
32196
|
+
};
|
|
32197
|
+
|
|
31641
32198
|
// src/repair-fetch.ts
|
|
31642
32199
|
var MOQT_PRIORITY_MAX = 255;
|
|
31643
32200
|
var SOURCE_MEDIA_PRIORITY_MAX = REPAIR_PRIORITY_MIN - 1;
|
|
@@ -32283,6 +32840,7 @@ export {
|
|
|
32283
32840
|
REPAIR_LIFECYCLE_LOG_PREFIX,
|
|
32284
32841
|
REPAIR_PRIORITY_MAX,
|
|
32285
32842
|
REPAIR_PRIORITY_MIN,
|
|
32843
|
+
RepairLayerSubscriptions,
|
|
32286
32844
|
SOURCE_MEDIA_PRIORITY_MAX,
|
|
32287
32845
|
SampleCombineError,
|
|
32288
32846
|
SampleCombiner,
|
|
@@ -32307,6 +32865,7 @@ export {
|
|
|
32307
32865
|
catalogGroupDurationMs,
|
|
32308
32866
|
catalogKeyframeCadenceMs,
|
|
32309
32867
|
catalogRepairTarget,
|
|
32868
|
+
catalogTrackDeliveryMode,
|
|
32310
32869
|
catalogTrackFecUnavailable,
|
|
32311
32870
|
classifyH264Keyframe,
|
|
32312
32871
|
classifyHevcKeyframe,
|