@blockcast/mmt-container 2.1.0 → 2.2.0-main.10bec7edb00b
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 +647 -83
- 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 +11 -6
- package/dist/delivery-catalog.js.map +2 -2
- 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 +1473 -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 +15216 -14934
- 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 +266 -25
- 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) {
|
|
@@ -8489,6 +8749,7 @@ var MOQT_PUBLISHER_PRIORITY = Object.freeze({
|
|
|
8489
8749
|
var AMTGatewayManager = class {
|
|
8490
8750
|
subscriptions = /* @__PURE__ */ new Map();
|
|
8491
8751
|
messageHandler;
|
|
8752
|
+
messageListenerRegistered = false;
|
|
8492
8753
|
pendingPackets = /* @__PURE__ */ new Map();
|
|
8493
8754
|
constructor() {
|
|
8494
8755
|
this.messageHandler = (event) => {
|
|
@@ -8502,12 +8763,14 @@ var AMTGatewayManager = class {
|
|
|
8502
8763
|
console.log(`[AMT SW Debug] ${data.message}`);
|
|
8503
8764
|
}
|
|
8504
8765
|
};
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
console.warn("[AMT] Service worker not available");
|
|
8766
|
+
}
|
|
8767
|
+
ensureMessageListener() {
|
|
8768
|
+
if (this.messageListenerRegistered || typeof navigator === "undefined" || !navigator.serviceWorker || !this.messageHandler) {
|
|
8769
|
+
return;
|
|
8510
8770
|
}
|
|
8771
|
+
navigator.serviceWorker.addEventListener("message", this.messageHandler);
|
|
8772
|
+
this.messageListenerRegistered = true;
|
|
8773
|
+
console.log("[AMT] Registered service worker message handler");
|
|
8511
8774
|
}
|
|
8512
8775
|
/**
|
|
8513
8776
|
* Subscribe via AMT tunnel
|
|
@@ -8517,6 +8780,7 @@ var AMTGatewayManager = class {
|
|
|
8517
8780
|
if (config.protected) {
|
|
8518
8781
|
throw new Error("AMTGatewayManager refuses a protected join: it reports no per-datagram sender (multicast-02 7.2.7)");
|
|
8519
8782
|
}
|
|
8783
|
+
this.ensureMessageListener();
|
|
8520
8784
|
const keyStr = this.getKeyString(key);
|
|
8521
8785
|
if (this.subscriptions.has(keyStr)) {
|
|
8522
8786
|
console.log(`[AMT] Reusing existing subscription to ${keyStr}`);
|
|
@@ -8632,8 +8896,9 @@ var AMTGatewayManager = class {
|
|
|
8632
8896
|
* Cleanup on destroy
|
|
8633
8897
|
*/
|
|
8634
8898
|
destroy() {
|
|
8635
|
-
if (this.messageHandler && typeof navigator !== "undefined" && navigator.serviceWorker) {
|
|
8899
|
+
if (this.messageListenerRegistered && this.messageHandler && typeof navigator !== "undefined" && navigator.serviceWorker) {
|
|
8636
8900
|
navigator.serviceWorker.removeEventListener("message", this.messageHandler);
|
|
8901
|
+
this.messageListenerRegistered = false;
|
|
8637
8902
|
}
|
|
8638
8903
|
const handles = Array.from(this.subscriptions.keys());
|
|
8639
8904
|
for (const handle of handles) {
|
|
@@ -8733,9 +8998,9 @@ function validateTemplatePair(mediaUriTemplate, mediaObjectTemplate, path) {
|
|
|
8733
8998
|
}
|
|
8734
8999
|
function validateMmtpDashTemplate(template, path) {
|
|
8735
9000
|
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 $${
|
|
9001
|
+
const unsupported2 = variables.find((variable) => variable !== "Number" && variable !== "RepresentationID");
|
|
9002
|
+
if (unsupported2 !== void 0) {
|
|
9003
|
+
throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${unsupported2}$`);
|
|
8739
9004
|
}
|
|
8740
9005
|
if (variables.filter((variable) => variable === "Number").length !== 1) {
|
|
8741
9006
|
throw new Error(`${path} must contain exactly one $Number$ for MMTP-backed DASH`);
|
|
@@ -17098,17 +17363,17 @@ var FECManager = class _FECManager {
|
|
|
17098
17363
|
|
|
17099
17364
|
// src/mfu-reassembly-policy.ts
|
|
17100
17365
|
function validateReliableMoqOptionShape(value) {
|
|
17101
|
-
const
|
|
17366
|
+
const unsupported2 = Object.keys(value).filter(
|
|
17102
17367
|
(key) => key !== "transport" && key !== "startsAtRandomAccessGroup"
|
|
17103
17368
|
);
|
|
17104
|
-
if (
|
|
17105
|
-
if (
|
|
17369
|
+
if (unsupported2.length > 0) {
|
|
17370
|
+
if (unsupported2.includes("maxRacingGroups")) {
|
|
17106
17371
|
throw new Error(
|
|
17107
17372
|
"[MFU reassembly] maxRacingGroups was removed; reliable MoQ completion is transport-owned"
|
|
17108
17373
|
);
|
|
17109
17374
|
}
|
|
17110
17375
|
throw new Error(
|
|
17111
|
-
`[MFU reassembly] unsupported reliable MoQ option(s): ${
|
|
17376
|
+
`[MFU reassembly] unsupported reliable MoQ option(s): ${unsupported2.join(", ")}`
|
|
17112
17377
|
);
|
|
17113
17378
|
}
|
|
17114
17379
|
const startsAtRandomAccessGroup = value["startsAtRandomAccessGroup"];
|
|
@@ -17152,12 +17417,12 @@ function assertValidMfuReassemblyPolicy(policy) {
|
|
|
17152
17417
|
return;
|
|
17153
17418
|
}
|
|
17154
17419
|
if (value["transport"] === "datagram") {
|
|
17155
|
-
const
|
|
17420
|
+
const unsupported2 = Object.keys(value).filter(
|
|
17156
17421
|
(key) => key !== "transport" && key !== "deadlineMs"
|
|
17157
17422
|
);
|
|
17158
|
-
if (
|
|
17423
|
+
if (unsupported2.length > 0) {
|
|
17159
17424
|
throw new Error(
|
|
17160
|
-
`[MFU reassembly] unsupported datagram option(s): ${
|
|
17425
|
+
`[MFU reassembly] unsupported datagram option(s): ${unsupported2.join(", ")}`
|
|
17161
17426
|
);
|
|
17162
17427
|
}
|
|
17163
17428
|
assertValidDatagramDeadline(value["deadlineMs"]);
|
|
@@ -23579,10 +23844,11 @@ var MmtpRouter = class {
|
|
|
23579
23844
|
if (!entry) return;
|
|
23580
23845
|
entry.receiver.release();
|
|
23581
23846
|
entry.protected = false;
|
|
23847
|
+
entry.userAgent = false;
|
|
23582
23848
|
this.#assistBlocks.clear();
|
|
23583
23849
|
this.#assistFailures.clear();
|
|
23584
23850
|
}
|
|
23585
|
-
/**
|
|
23851
|
+
/** 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
23852
|
get unadmittedListedDropCount() {
|
|
23587
23853
|
return this.#unadmittedListedDrops;
|
|
23588
23854
|
}
|
|
@@ -23623,6 +23889,13 @@ var MmtpRouter = class {
|
|
|
23623
23889
|
}
|
|
23624
23890
|
return false;
|
|
23625
23891
|
}
|
|
23892
|
+
/** Whether a protected flow the user agent admitted lists `packetId`. */
|
|
23893
|
+
#userAgentGoverns(packetId) {
|
|
23894
|
+
for (const entry of this.#provenance.values()) {
|
|
23895
|
+
if (entry.protected && entry.userAgent && ingressHasPacketId(entry.ingress, packetId)) return true;
|
|
23896
|
+
}
|
|
23897
|
+
return false;
|
|
23898
|
+
}
|
|
23626
23899
|
/**
|
|
23627
23900
|
* A MoQ repair symbol no protected flow lists, owned by a source track one
|
|
23628
23901
|
* does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
|
|
@@ -23713,7 +23986,7 @@ var MmtpRouter = class {
|
|
|
23713
23986
|
fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
|
|
23714
23987
|
fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
|
|
23715
23988
|
fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
|
|
23716
|
-
})) {
|
|
23989
|
+
}, true)) {
|
|
23717
23990
|
this.#unlistedAssistedRecoveries++;
|
|
23718
23991
|
this.#assistBlocks.delete(key);
|
|
23719
23992
|
return;
|
|
@@ -24869,6 +25142,12 @@ var MmtpRouter = class {
|
|
|
24869
25142
|
debugRouter(`[MmtpRouter] Dropping multicast packet pid=${info.packetId}: flow ${ingress.flow} has no receiver`);
|
|
24870
25143
|
return;
|
|
24871
25144
|
}
|
|
25145
|
+
if (ingress.admittedBy === USER_AGENT_VERIFIED) {
|
|
25146
|
+
this.#noteFlowAdmitted(ingress.flow);
|
|
25147
|
+
protectedFlow.userAgent = true;
|
|
25148
|
+
this.#processMMTP(packet, source, true, ingress, overlapCopy);
|
|
25149
|
+
return;
|
|
25150
|
+
}
|
|
24872
25151
|
const { verdict } = protectedFlow.receiver.onDatagram(packet, "multicast");
|
|
24873
25152
|
if (verdict === "admitted") {
|
|
24874
25153
|
this.#noteFlowAdmitted(ingress.flow);
|
|
@@ -24886,6 +25165,11 @@ var MmtpRouter = class {
|
|
|
24886
25165
|
return;
|
|
24887
25166
|
}
|
|
24888
25167
|
}
|
|
25168
|
+
const flowless = source === "multicast" && !manifestAdmitted && ingress?.flow === void 0;
|
|
25169
|
+
if (flowless && !info.isRepair && this.#userAgentGoverns(info.packetId)) {
|
|
25170
|
+
this.#unadmittedListedDrops++;
|
|
25171
|
+
return;
|
|
25172
|
+
}
|
|
24889
25173
|
if (info.isRepair) {
|
|
24890
25174
|
const overlay = this.#overlayRepairTarget(info.packetId, catalogTrackNameForPacket(ingress, info.packetId));
|
|
24891
25175
|
if (overlay !== void 0) {
|
|
@@ -24903,7 +25187,7 @@ var MmtpRouter = class {
|
|
|
24903
25187
|
);
|
|
24904
25188
|
if (!repair) return;
|
|
24905
25189
|
if (ownerPacketId != null && this.#fecManager) {
|
|
24906
|
-
if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
25190
|
+
if ((flowless && this.#userAgentGoverns(ownerPacketId) || source === "moq" && !manifestAdmitted) && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
|
|
24907
25191
|
this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
|
|
24908
25192
|
const count = this.#repairPidCounts[info.packetId];
|
|
24909
25193
|
if (count <= 2 || count % 500 === 0) {
|
|
@@ -25482,7 +25766,7 @@ var MmtpRouter = class {
|
|
|
25482
25766
|
}
|
|
25483
25767
|
// ── Private: FEC recovery routing ───────────────────────────────
|
|
25484
25768
|
/** Route WASM-recovered block through appropriate path based on container kind. */
|
|
25485
|
-
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete) {
|
|
25769
|
+
#routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
25486
25770
|
const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
|
|
25487
25771
|
if (ownerPacketId === void 0) {
|
|
25488
25772
|
throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
|
|
@@ -25499,7 +25783,7 @@ var MmtpRouter = class {
|
|
|
25499
25783
|
ownerPacketId,
|
|
25500
25784
|
blockProfile
|
|
25501
25785
|
});
|
|
25502
|
-
return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete);
|
|
25786
|
+
return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted);
|
|
25503
25787
|
}
|
|
25504
25788
|
/**
|
|
25505
25789
|
* A recovered source packet of a protected flow is admitted on its own
|
|
@@ -25511,9 +25795,24 @@ var MmtpRouter = class {
|
|
|
25511
25795
|
* recovered packets is refused is unrecovered (7.2.7): its caller leaves it
|
|
25512
25796
|
* open, so it reaches its FEC deadline and fails by class. Always true
|
|
25513
25797
|
* outside a protected flow.
|
|
25514
|
-
|
|
25515
|
-
|
|
25516
|
-
|
|
25798
|
+
*
|
|
25799
|
+
* On a flow the user agent admitted, the live decoder's recoveries are kept
|
|
25800
|
+
* without a record: while the flow is protected that decoder takes only
|
|
25801
|
+
* symbols the user agent or a record verified (a flow-less multicast
|
|
25802
|
+
* datagram of a listed track is dropped or set aside in #processMMTP), so
|
|
25803
|
+
* what it recovers is as verified as they are. An `assisted` (scratch-decoder) recovery mixes in
|
|
25804
|
+
* unlisted MoQ repair and still needs a record.
|
|
25805
|
+
* ponytail: per-flow flag; per-block provenance only if a protected flow
|
|
25806
|
+
* ever mixes verified and unverified live inputs.
|
|
25807
|
+
* ponytail: a worker frame computed before the protection reset can still
|
|
25808
|
+
* arrive after it (frames carry no epoch); its inputs are MoQ copies read
|
|
25809
|
+
* and played before protection, so it admits nothing new. Drop pre-reset
|
|
25810
|
+
* frames by epoch only if pre-protection input ever matters.
|
|
25811
|
+
*/
|
|
25812
|
+
#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
|
|
25813
|
+
const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
|
|
25814
|
+
const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
|
|
25815
|
+
const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
|
|
25517
25816
|
let recovered = true;
|
|
25518
25817
|
if (receiver) {
|
|
25519
25818
|
const parsed = this.#stream(ownerPacketId, catalogTrackName).duplicates.parsedSymbols;
|
|
@@ -25525,6 +25824,8 @@ var MmtpRouter = class {
|
|
|
25525
25824
|
let admitted = false;
|
|
25526
25825
|
if (receiver === "ambiguous") {
|
|
25527
25826
|
this.#ambiguousRecoveredDrops++;
|
|
25827
|
+
} else if (userAgent) {
|
|
25828
|
+
admitted = true;
|
|
25528
25829
|
} else {
|
|
25529
25830
|
const wire = new Uint8Array(sym.rawSymbol.byteLength + 4);
|
|
25530
25831
|
wire.set(sym.rawSymbol);
|
|
@@ -25552,12 +25853,12 @@ var MmtpRouter = class {
|
|
|
25552
25853
|
catalogTrackName === void 0 || !byName ? byName?.values() : byName.has(catalogTrackName) ? [byName.get(catalogTrackName)] : []
|
|
25553
25854
|
);
|
|
25554
25855
|
if (flows.size > 1) return "ambiguous";
|
|
25555
|
-
for (const bound of flows) return this.#provenance.get(bound)
|
|
25856
|
+
for (const bound of flows) return this.#provenance.get(bound) ?? "ambiguous";
|
|
25556
25857
|
let match;
|
|
25557
|
-
for (const
|
|
25558
|
-
if (ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
|
|
25858
|
+
for (const entry of this.#provenance.values()) {
|
|
25859
|
+
if (entry.ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
|
|
25559
25860
|
if (match) return "ambiguous";
|
|
25560
|
-
match =
|
|
25861
|
+
match = entry;
|
|
25561
25862
|
}
|
|
25562
25863
|
}
|
|
25563
25864
|
return match;
|
|
@@ -26339,11 +26640,8 @@ function providerErrorOf(error) {
|
|
|
26339
26640
|
return Object.freeze(typeof code === "string" ? { name, message, code } : { name, message });
|
|
26340
26641
|
}
|
|
26341
26642
|
var MAX_TRACKED_DROPPED_PIDS = 16;
|
|
26342
|
-
function getMulticastGlobals() {
|
|
26343
|
-
return globalThis;
|
|
26344
|
-
}
|
|
26345
26643
|
function hasMulticastGateway() {
|
|
26346
|
-
return typeof
|
|
26644
|
+
return typeof resolveMulticastGateway()?.subscribe === "function";
|
|
26347
26645
|
}
|
|
26348
26646
|
function senderOf(event) {
|
|
26349
26647
|
const sourceAddress = event.sourceAddress;
|
|
@@ -26435,6 +26733,9 @@ var MulticastTransport = class {
|
|
|
26435
26733
|
// listener closure, so an in-place admission change (setContext) applies
|
|
26436
26734
|
// from the next packet on without rejoining.
|
|
26437
26735
|
#context = void 0;
|
|
26736
|
+
#deliveredContext = void 0;
|
|
26737
|
+
/** The live join's gateway, when it is the user agent's MulticastReceiver provider. */
|
|
26738
|
+
#userAgentGateway = void 0;
|
|
26438
26739
|
// The live join's provider-side filter. An in-place widening must reach it,
|
|
26439
26740
|
// or the provider never delivers the new ID; null without a live join.
|
|
26440
26741
|
#providerFilter = null;
|
|
@@ -26471,7 +26772,7 @@ var MulticastTransport = class {
|
|
|
26471
26772
|
this.#dataPlaneLive = false;
|
|
26472
26773
|
this.#sender = void 0;
|
|
26473
26774
|
if (this.#disposed) return this.#declined(generation, "disposed");
|
|
26474
|
-
const gateway = this.#gateway ??
|
|
26775
|
+
const gateway = this.#gateway ?? resolveMulticastGateway();
|
|
26475
26776
|
if (typeof gateway?.subscribe !== "function") {
|
|
26476
26777
|
console.warn("[MulticastTransport] Multicast API not available");
|
|
26477
26778
|
return this.#declined(generation, "unavailable");
|
|
@@ -26487,15 +26788,16 @@ var MulticastTransport = class {
|
|
|
26487
26788
|
console.log(
|
|
26488
26789
|
`[MulticastTransport] window.multicast.subscribe(${config.sourceAddress ?? config.senderAddress ?? "*"}, ${config.groupAddress}, ${config.port})`
|
|
26489
26790
|
);
|
|
26791
|
+
const userAgent = isUserAgentVerifiedGateway(gateway);
|
|
26490
26792
|
const subscribeConfig = toWindowMulticastSubscribeConfig({
|
|
26491
26793
|
source: config.sourceAddress,
|
|
26492
26794
|
senderAddress: config.senderAddress,
|
|
26493
26795
|
group: config.groupAddress,
|
|
26494
26796
|
port: config.port,
|
|
26495
|
-
mode: config.mode,
|
|
26496
|
-
amtRelay: config.amtRelay,
|
|
26497
|
-
amtRelayPort: config.amtRelayPort,
|
|
26498
|
-
amtDiscovery: config.amtDiscovery,
|
|
26797
|
+
mode: userAgent ? "native" : config.mode,
|
|
26798
|
+
amtRelay: userAgent ? void 0 : config.amtRelay,
|
|
26799
|
+
amtRelayPort: userAgent ? void 0 : config.amtRelayPort,
|
|
26800
|
+
amtDiscovery: userAgent ? void 0 : config.amtDiscovery,
|
|
26499
26801
|
packetIds: config.packetIds,
|
|
26500
26802
|
tracks: config.tracks,
|
|
26501
26803
|
atsc3: config.atsc3
|
|
@@ -26541,7 +26843,8 @@ var MulticastTransport = class {
|
|
|
26541
26843
|
acknowledgedUpdate: 0,
|
|
26542
26844
|
inFlight: 0
|
|
26543
26845
|
};
|
|
26544
|
-
this.#
|
|
26846
|
+
this.#userAgentGateway = userAgent ? gateway : void 0;
|
|
26847
|
+
this.#setContext(context);
|
|
26545
26848
|
if (packetIds) {
|
|
26546
26849
|
for (const packetId of packetIds) {
|
|
26547
26850
|
if (!this.#acceptedBytesByPacketId.has(packetId)) {
|
|
@@ -26549,7 +26852,7 @@ var MulticastTransport = class {
|
|
|
26549
26852
|
}
|
|
26550
26853
|
}
|
|
26551
26854
|
}
|
|
26552
|
-
this.#resolvedTransport = subscribeConfig.origin === "amt" ? "amt" : "native";
|
|
26855
|
+
this.#resolvedTransport = userAgent ? "user-agent" : subscribeConfig.origin === "amt" ? "amt" : "native";
|
|
26553
26856
|
const delivery = subscription.delivery ?? "datagram";
|
|
26554
26857
|
if (delivery !== "datagram" && delivery !== "ordered-relay") {
|
|
26555
26858
|
throw new Error(
|
|
@@ -26630,7 +26933,7 @@ var MulticastTransport = class {
|
|
|
26630
26933
|
}
|
|
26631
26934
|
if (!this.#acceptPacket(packet, sourceAddress)) return false;
|
|
26632
26935
|
const liveness = !this.#isRecordCandidate(packet);
|
|
26633
|
-
this.#onPacket(packet, this.#
|
|
26936
|
+
this.#onPacket(packet, this.#deliveredContext);
|
|
26634
26937
|
return liveness;
|
|
26635
26938
|
}
|
|
26636
26939
|
#isRecordCandidate(packet) {
|
|
@@ -26841,8 +27144,10 @@ var MulticastTransport = class {
|
|
|
26841
27144
|
return this.#subscription !== null;
|
|
26842
27145
|
}
|
|
26843
27146
|
/**
|
|
26844
|
-
*
|
|
26845
|
-
*
|
|
27147
|
+
* The live join's context as the caller gave it (the join's `context`, or
|
|
27148
|
+
* the last setContext), or undefined without a live join. On a native
|
|
27149
|
+
* provider each accepted packet is delivered with a marked copy of it
|
|
27150
|
+
* (`userAgentVerifiedIngress`); this getter never carries the mark.
|
|
26846
27151
|
*/
|
|
26847
27152
|
get context() {
|
|
26848
27153
|
return this.#context;
|
|
@@ -26866,7 +27171,16 @@ var MulticastTransport = class {
|
|
|
26866
27171
|
"[MulticastTransport] setContext needs a live join; join() carries the first context"
|
|
26867
27172
|
);
|
|
26868
27173
|
}
|
|
27174
|
+
this.#setContext(context);
|
|
27175
|
+
}
|
|
27176
|
+
/**
|
|
27177
|
+
* The join's context, and what the subscription delivers with each packet:
|
|
27178
|
+
* marked admitted by the user agent when the gateway is its native
|
|
27179
|
+
* MulticastReceiver provider. feedPacket never marks.
|
|
27180
|
+
*/
|
|
27181
|
+
#setContext(context) {
|
|
26869
27182
|
this.#context = context;
|
|
27183
|
+
this.#deliveredContext = this.#userAgentGateway !== void 0 && typeof context === "object" && context !== null ? userAgentVerifiedIngress(context, this.#userAgentGateway) : context;
|
|
26870
27184
|
}
|
|
26871
27185
|
/**
|
|
26872
27186
|
* Replace the live join's packet-ID allow-list in place, without rejoining.
|
|
@@ -26998,6 +27312,8 @@ var MulticastTransport = class {
|
|
|
26998
27312
|
this.#matchedSource = void 0;
|
|
26999
27313
|
this.#providerFilter = null;
|
|
27000
27314
|
this.#context = void 0;
|
|
27315
|
+
this.#deliveredContext = void 0;
|
|
27316
|
+
this.#userAgentGateway = void 0;
|
|
27001
27317
|
this.#withdrawnPacketIds.clear();
|
|
27002
27318
|
}
|
|
27003
27319
|
/** The ingress filter; an accepted packet is counted here, once. */
|
|
@@ -28350,8 +28666,17 @@ var MulticastManager = class {
|
|
|
28350
28666
|
* repair roles and owners are registered here; in-band owners stay the
|
|
28351
28667
|
* caller's. Joining is best effort: a group that fails to join is logged
|
|
28352
28668
|
* and leaves the media join as it was.
|
|
28353
|
-
|
|
28354
|
-
|
|
28669
|
+
*
|
|
28670
|
+
* `options.gateway` names the gateway a subscribe callback reads through.
|
|
28671
|
+
* When it is the browser's own MulticastReceiver provider, the callback's
|
|
28672
|
+
* packets are fed as admitted by the user agent
|
|
28673
|
+
* (`userAgentVerifiedIngress`); nothing the callback passes can mark them.
|
|
28674
|
+
* The mark rests on this statement, not on the data path: every packet the
|
|
28675
|
+
* callback delivers must come from a subscription opened on
|
|
28676
|
+
* `options.gateway`. A callback that also merges another source (a MoQ or
|
|
28677
|
+
* WebSocket repair feed) must not name a native provider here.
|
|
28678
|
+
*/
|
|
28679
|
+
joinRepairEndpoints(endpoints, provider, options) {
|
|
28355
28680
|
const releases = [];
|
|
28356
28681
|
let released = false;
|
|
28357
28682
|
const safely = (each) => {
|
|
@@ -28373,17 +28698,19 @@ var MulticastManager = class {
|
|
|
28373
28698
|
...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
|
|
28374
28699
|
};
|
|
28375
28700
|
let sender;
|
|
28376
|
-
const
|
|
28701
|
+
const feed = (packet, sourceAddress, ingressToFeed) => {
|
|
28377
28702
|
if (released) return;
|
|
28378
28703
|
if (sourceAddress !== void 0 && !sameDatagramSource(sourceAddress, this.#transport.sender ?? (sender ??= sourceAddress))) return;
|
|
28379
|
-
this.#router.feedPacket(packet, "multicast",
|
|
28704
|
+
this.#router.feedPacket(packet, "multicast", ingressToFeed);
|
|
28380
28705
|
};
|
|
28381
28706
|
const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
|
|
28382
28707
|
if (typeof provider === "function") {
|
|
28708
|
+
const marked = userAgentVerifiedIngress(ingress, options?.gateway);
|
|
28709
|
+
const onPacket = (packet, sourceAddress) => feed(packet, sourceAddress, marked);
|
|
28383
28710
|
Promise.resolve().then(() => provider(endpoint, onPacket)).then((release2) => released ? safely(release2) : releases.push(release2), fail);
|
|
28384
28711
|
continue;
|
|
28385
28712
|
}
|
|
28386
|
-
const transport = new MulticastTransport((packet) =>
|
|
28713
|
+
const transport = new MulticastTransport((packet, delivered) => feed(packet, void 0, delivered ?? ingress), {
|
|
28387
28714
|
gateway: provider,
|
|
28388
28715
|
lockedTo: () => this.#transport.sender
|
|
28389
28716
|
});
|
|
@@ -28395,11 +28722,12 @@ var MulticastManager = class {
|
|
|
28395
28722
|
mode: endpoint.mode,
|
|
28396
28723
|
amtRelay: endpoint.amtRelay,
|
|
28397
28724
|
amtRelayPort: endpoint.amtRelayPort ?? (endpoint.amtRelay?.trim() ? DEFAULT_AMT_RELAY_PORT : void 0),
|
|
28725
|
+
amtDiscovery: endpoint.amtDiscovery,
|
|
28398
28726
|
packetIds: endpoint.packetIds,
|
|
28399
28727
|
tracks: endpoint.tracks.map(({ name, packetId }) => ({ name, packetId })),
|
|
28400
28728
|
// A protected repair group's records ride in band on its port (7.2.3).
|
|
28401
28729
|
...endpoint.flow === void 0 ? {} : { protected: true }
|
|
28402
|
-
}).then((joined) => {
|
|
28730
|
+
}, ingress).then((joined) => {
|
|
28403
28731
|
if (!joined && !released) fail(transport.lastJoinOutcome);
|
|
28404
28732
|
});
|
|
28405
28733
|
}
|
|
@@ -31164,18 +31492,31 @@ function resolveRepairLayers(catalog, source) {
|
|
|
31164
31492
|
totalBaseRepairSymbols: binding.totalBaseRepairSymbols
|
|
31165
31493
|
};
|
|
31166
31494
|
}
|
|
31495
|
+
function valueAt(catalog, pointer) {
|
|
31496
|
+
let node = catalog;
|
|
31497
|
+
for (const segment of pointer.split("/").slice(1)) {
|
|
31498
|
+
if (typeof node !== "object" || node === null) return void 0;
|
|
31499
|
+
node = node[segment.replace(/~1/g, "/").replace(/~0/g, "~")];
|
|
31500
|
+
}
|
|
31501
|
+
return node;
|
|
31502
|
+
}
|
|
31503
|
+
function catalogTrackDeliveryMode(catalog, source) {
|
|
31504
|
+
const binding = bindingOf(catalog, source);
|
|
31505
|
+
const entry = binding ? valueAt(catalog, binding.pointer) : void 0;
|
|
31506
|
+
return entry?.deliveryMode === "stream" ? "stream" : "datagram";
|
|
31507
|
+
}
|
|
31167
31508
|
function moqtPriority2(value) {
|
|
31168
31509
|
return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
|
|
31169
31510
|
}
|
|
31170
31511
|
function catalogRepairTarget(catalog, source) {
|
|
31171
31512
|
const binding = bindingOf(catalog, source);
|
|
31172
31513
|
if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
|
|
31173
|
-
const
|
|
31514
|
+
const layerZero2 = binding.layers[0];
|
|
31174
31515
|
const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
|
|
31175
31516
|
const tracks = catalog.tracks;
|
|
31176
31517
|
const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
|
|
31177
31518
|
const sourcePriority = entry ? moqtPriority2(entry["priority"]) : null;
|
|
31178
|
-
return { repairTrack:
|
|
31519
|
+
return { repairTrack: layerZero2.repairTrackName, sourcePriority, repairPriority: layerZero2.priority };
|
|
31179
31520
|
}
|
|
31180
31521
|
function numericField(value) {
|
|
31181
31522
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
@@ -31207,6 +31548,7 @@ function fecParams(catalog, track, fps, sampleRate) {
|
|
|
31207
31548
|
repairTrackName: base.repairTrack,
|
|
31208
31549
|
repairLayers: repair.baseLayers,
|
|
31209
31550
|
repairOverlays: repair.overlays,
|
|
31551
|
+
deliveryMode: catalogTrackDeliveryMode(catalog, track),
|
|
31210
31552
|
...mediaRate
|
|
31211
31553
|
};
|
|
31212
31554
|
}
|
|
@@ -31257,7 +31599,14 @@ function resolveCatalogTrackFec(catalog, tracks) {
|
|
|
31257
31599
|
var PATH_BITS = { moq: 1, multicast: 2 };
|
|
31258
31600
|
var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
|
|
31259
31601
|
var MmtpFecSession = class {
|
|
31602
|
+
/** P_0 = B_0, the held total every repair packet is checked against. */
|
|
31603
|
+
get repairSymbols() {
|
|
31604
|
+
return this.#repairSymbols;
|
|
31605
|
+
}
|
|
31260
31606
|
#decoder;
|
|
31607
|
+
#repairSymbols;
|
|
31608
|
+
/** The message entry a catalog-less session was built from; undefined under a catalog. */
|
|
31609
|
+
#message;
|
|
31261
31610
|
/** Blocks a repair has defined and that are not settled yet, by SS_Start. */
|
|
31262
31611
|
#blocks = /* @__PURE__ */ new Map();
|
|
31263
31612
|
/** SS_Starts of settled blocks, so a later repair for one is refused. */
|
|
@@ -31295,6 +31644,24 @@ var MmtpFecSession = class {
|
|
|
31295
31644
|
* `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
|
|
31296
31645
|
*/
|
|
31297
31646
|
constructor(config) {
|
|
31647
|
+
if (config && "alFecFlow" in config) {
|
|
31648
|
+
const message = alFecRaptorQFlowConfig(config.alFecFlow);
|
|
31649
|
+
if (!message) {
|
|
31650
|
+
throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
|
|
31651
|
+
}
|
|
31652
|
+
this.#message = message;
|
|
31653
|
+
this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
|
|
31654
|
+
this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
|
|
31655
|
+
this.sourceSymbols = message.maximumK;
|
|
31656
|
+
this.#repairSymbols = message.maximumP;
|
|
31657
|
+
this.symbolSize = message.repairSymbolSize;
|
|
31658
|
+
this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
|
|
31659
|
+
this.#repairPacketId = message.repairFlowId;
|
|
31660
|
+
this.#heldLayers = layerZero(message.maximumP);
|
|
31661
|
+
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31662
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31663
|
+
return;
|
|
31664
|
+
}
|
|
31298
31665
|
const fec = config?.fec;
|
|
31299
31666
|
if (!fec || typeof fec !== "object") {
|
|
31300
31667
|
throw new Error("MmtpFecSession: the catalog fec instance is required");
|
|
@@ -31305,7 +31672,7 @@ var MmtpFecSession = class {
|
|
|
31305
31672
|
}
|
|
31306
31673
|
this.algorithm = fec.scheme;
|
|
31307
31674
|
this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
|
|
31308
|
-
this
|
|
31675
|
+
this.#repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
|
|
31309
31676
|
this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
|
|
31310
31677
|
const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
|
|
31311
31678
|
const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
|
|
@@ -31325,17 +31692,30 @@ var MmtpFecSession = class {
|
|
|
31325
31692
|
config.heldLayers
|
|
31326
31693
|
);
|
|
31327
31694
|
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31331
|
-
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
|
|
31337
|
-
|
|
31338
|
-
|
|
31695
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31696
|
+
}
|
|
31697
|
+
/**
|
|
31698
|
+
* A later version of the AL-FEC message, catalog-less sessions only. A
|
|
31699
|
+
* maximum_p_for_repair_flow above the held total raises it (`raised`): the
|
|
31700
|
+
* anchored check restarts at the new total, so a block generated under the
|
|
31701
|
+
* previous one stays valid before the first block carrying the new total
|
|
31702
|
+
* (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
|
|
31703
|
+
* (`unchanged`): no publisher lowers it, and the check catches a wire that
|
|
31704
|
+
* does. An entry with another asset, repair flow, code point, K or T, or
|
|
31705
|
+
* one outside 8.3, is `refused`: that is a new source encoding, which needs
|
|
31706
|
+
* a new session. A latched fail-closed stays latched either way.
|
|
31707
|
+
*/
|
|
31708
|
+
updateAlFecFlow(flow) {
|
|
31709
|
+
const held = this.#message;
|
|
31710
|
+
if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
|
|
31711
|
+
const next = alFecRaptorQFlowConfig(flow);
|
|
31712
|
+
if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
|
|
31713
|
+
return "refused";
|
|
31714
|
+
}
|
|
31715
|
+
if (next.maximumP <= this.#repairSymbols) return "unchanged";
|
|
31716
|
+
this.#repairSymbols = next.maximumP;
|
|
31717
|
+
this.#heldLayers = layerZero(next.maximumP);
|
|
31718
|
+
return "raised";
|
|
31339
31719
|
}
|
|
31340
31720
|
/** True once release() ran. */
|
|
31341
31721
|
get released() {
|
|
@@ -31430,7 +31810,7 @@ var MmtpFecSession = class {
|
|
|
31430
31810
|
}
|
|
31431
31811
|
if (admission.reject) {
|
|
31432
31812
|
const verdict2 = this.#heldLayers.failsClosed(
|
|
31433
|
-
repairTrackName,
|
|
31813
|
+
this.#trackFor(repairTrackName),
|
|
31434
31814
|
frame.ssStart,
|
|
31435
31815
|
frame.rsbLength,
|
|
31436
31816
|
this.#anchorHorizon,
|
|
@@ -31445,7 +31825,7 @@ var MmtpFecSession = class {
|
|
|
31445
31825
|
this.#repairPacketId ??= frame.packetId;
|
|
31446
31826
|
if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
|
|
31447
31827
|
const verdict = this.#heldLayers.admit(
|
|
31448
|
-
repairTrackName,
|
|
31828
|
+
this.#trackFor(repairTrackName),
|
|
31449
31829
|
frame.ssStart,
|
|
31450
31830
|
frame.rsbLength,
|
|
31451
31831
|
this.#anchorHorizon,
|
|
@@ -31504,6 +31884,10 @@ var MmtpFecSession = class {
|
|
|
31504
31884
|
decoder.free();
|
|
31505
31885
|
return true;
|
|
31506
31886
|
}
|
|
31887
|
+
/** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
|
|
31888
|
+
#trackFor(repairTrackName) {
|
|
31889
|
+
return this.#message ? void 0 : repairTrackName;
|
|
31890
|
+
}
|
|
31507
31891
|
#drop(reason) {
|
|
31508
31892
|
this.#stats.dropped[reason] = (this.#stats.dropped[reason] ?? 0) + 1;
|
|
31509
31893
|
return [];
|
|
@@ -31625,6 +32009,22 @@ var MmtpFecSession = class {
|
|
|
31625
32009
|
}
|
|
31626
32010
|
}
|
|
31627
32011
|
};
|
|
32012
|
+
function layerZero(total) {
|
|
32013
|
+
return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
|
|
32014
|
+
}
|
|
32015
|
+
function buildDecoder(Decoder, session) {
|
|
32016
|
+
if (typeof Decoder !== "function") {
|
|
32017
|
+
throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
|
|
32018
|
+
}
|
|
32019
|
+
const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
|
|
32020
|
+
try {
|
|
32021
|
+
decoder.configure_params(session.sourceSymbols, session.symbolSize);
|
|
32022
|
+
} catch (error) {
|
|
32023
|
+
decoder.free();
|
|
32024
|
+
throw error;
|
|
32025
|
+
}
|
|
32026
|
+
return decoder;
|
|
32027
|
+
}
|
|
31628
32028
|
function requirePositiveInteger(value, path) {
|
|
31629
32029
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
31630
32030
|
throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);
|
|
@@ -31638,6 +32038,168 @@ function requireNumber(value, path) {
|
|
|
31638
32038
|
return value;
|
|
31639
32039
|
}
|
|
31640
32040
|
|
|
32041
|
+
// src/repair-layer-subscriptions.ts
|
|
32042
|
+
var SERIAL32_HALF2 = 2147483648;
|
|
32043
|
+
var RepairLayerSubscriptions = class {
|
|
32044
|
+
#opts;
|
|
32045
|
+
#windowPackets;
|
|
32046
|
+
#margin;
|
|
32047
|
+
#dropAfterWindows;
|
|
32048
|
+
#source;
|
|
32049
|
+
#layerCount = 0;
|
|
32050
|
+
#subs = /* @__PURE__ */ new Map();
|
|
32051
|
+
#disposed = false;
|
|
32052
|
+
#lastSeq;
|
|
32053
|
+
#expected = 0;
|
|
32054
|
+
#lost = 0;
|
|
32055
|
+
#wantFewer = 0;
|
|
32056
|
+
#lossRate = null;
|
|
32057
|
+
#repairPackets = 0;
|
|
32058
|
+
#repairBytes = 0;
|
|
32059
|
+
#staleDropped = 0;
|
|
32060
|
+
constructor(opts) {
|
|
32061
|
+
this.#opts = opts;
|
|
32062
|
+
this.#source = opts.source;
|
|
32063
|
+
this.#windowPackets = opts.windowPackets ?? 500;
|
|
32064
|
+
this.#margin = opts.margin ?? 2;
|
|
32065
|
+
this.#dropAfterWindows = opts.dropAfterWindows ?? 3;
|
|
32066
|
+
}
|
|
32067
|
+
get layerCount() {
|
|
32068
|
+
return this.#layerCount;
|
|
32069
|
+
}
|
|
32070
|
+
/**
|
|
32071
|
+
* One source packet's MMTP `packet_sequence_number`, in arrival order.
|
|
32072
|
+
* The sequence is per `packet_id`: pass the packets of ONE packet_id only,
|
|
32073
|
+
* or interleaved sequences read as loss.
|
|
32074
|
+
*/
|
|
32075
|
+
noteSourceSequence(seq) {
|
|
32076
|
+
if (this.#disposed || this.#delivery() === "stream") return;
|
|
32077
|
+
seq >>>= 0;
|
|
32078
|
+
if (this.#lastSeq === void 0) {
|
|
32079
|
+
this.#lastSeq = seq;
|
|
32080
|
+
return;
|
|
32081
|
+
}
|
|
32082
|
+
const d = serial32Distance(this.#lastSeq, seq);
|
|
32083
|
+
if (d === 0) return;
|
|
32084
|
+
const back = d >= SERIAL32_HALF2 ? 2 ** 32 - d : 0;
|
|
32085
|
+
if (d > this.#windowPackets && (back === 0 || back > this.#windowPackets)) {
|
|
32086
|
+
this.#lastSeq = seq;
|
|
32087
|
+
this.#expected = 0;
|
|
32088
|
+
this.#lost = 0;
|
|
32089
|
+
return;
|
|
32090
|
+
}
|
|
32091
|
+
if (back === 0) {
|
|
32092
|
+
this.#expected += d;
|
|
32093
|
+
this.#lost += d - 1;
|
|
32094
|
+
this.#lastSeq = seq;
|
|
32095
|
+
} else if (this.#lost > 0) {
|
|
32096
|
+
this.#lost -= 1;
|
|
32097
|
+
}
|
|
32098
|
+
if (this.#expected >= this.#windowPackets) this.#closeWindow();
|
|
32099
|
+
}
|
|
32100
|
+
/** ABR switch R -> R': close R's tracks, carry the layer count to R'. */
|
|
32101
|
+
switchSource(next) {
|
|
32102
|
+
if (this.#disposed) return;
|
|
32103
|
+
this.#closeAll();
|
|
32104
|
+
this.#source = next;
|
|
32105
|
+
this.#lastSeq = void 0;
|
|
32106
|
+
this.#expected = 0;
|
|
32107
|
+
this.#lost = 0;
|
|
32108
|
+
this.#wantFewer = 0;
|
|
32109
|
+
this.#layerCount = this.#delivery() === "stream" ? 0 : Math.min(this.#layerCount, this.#layers().length);
|
|
32110
|
+
this.#reconcile();
|
|
32111
|
+
}
|
|
32112
|
+
snapshot() {
|
|
32113
|
+
return {
|
|
32114
|
+
delivery: this.#delivery(),
|
|
32115
|
+
layerCount: this.#layerCount,
|
|
32116
|
+
subscribedTracks: [...this.#subs.keys()],
|
|
32117
|
+
repairPackets: this.#repairPackets,
|
|
32118
|
+
repairBytes: this.#repairBytes,
|
|
32119
|
+
staleDropped: this.#staleDropped,
|
|
32120
|
+
lossRate: this.#lossRate
|
|
32121
|
+
};
|
|
32122
|
+
}
|
|
32123
|
+
dispose() {
|
|
32124
|
+
if (this.#disposed) return;
|
|
32125
|
+
this.#closeAll();
|
|
32126
|
+
this.#disposed = true;
|
|
32127
|
+
}
|
|
32128
|
+
#delivery() {
|
|
32129
|
+
return this.#source.deliveryMode === "stream" ? "stream" : "datagram";
|
|
32130
|
+
}
|
|
32131
|
+
#layers() {
|
|
32132
|
+
return this.#source.repairLayers ?? [];
|
|
32133
|
+
}
|
|
32134
|
+
#closeWindow() {
|
|
32135
|
+
const rate = this.#lost / this.#expected;
|
|
32136
|
+
this.#lossRate = rate;
|
|
32137
|
+
this.#expected = 0;
|
|
32138
|
+
this.#lost = 0;
|
|
32139
|
+
const target = this.#targetLayers(rate);
|
|
32140
|
+
if (target > this.#layerCount) {
|
|
32141
|
+
this.#wantFewer = 0;
|
|
32142
|
+
this.#layerCount = target;
|
|
32143
|
+
} else if (target < this.#layerCount) {
|
|
32144
|
+
this.#wantFewer += 1;
|
|
32145
|
+
if (this.#wantFewer >= this.#dropAfterWindows) {
|
|
32146
|
+
this.#wantFewer = 0;
|
|
32147
|
+
this.#layerCount -= 1;
|
|
32148
|
+
}
|
|
32149
|
+
} else {
|
|
32150
|
+
this.#wantFewer = 0;
|
|
32151
|
+
}
|
|
32152
|
+
this.#reconcile();
|
|
32153
|
+
}
|
|
32154
|
+
/** Fewest base layers whose summed P covers margin x the measured loss of K. */
|
|
32155
|
+
#targetLayers(rate) {
|
|
32156
|
+
if (rate <= 0) return 0;
|
|
32157
|
+
const needed = Math.ceil(rate * this.#source.fecK * this.#margin);
|
|
32158
|
+
const layers = this.#layers();
|
|
32159
|
+
let held = 0;
|
|
32160
|
+
for (let n = 0; n < layers.length; n++) {
|
|
32161
|
+
held += layers[n].repairSymbols;
|
|
32162
|
+
if (held >= needed) return n + 1;
|
|
32163
|
+
}
|
|
32164
|
+
return layers.length;
|
|
32165
|
+
}
|
|
32166
|
+
/** Stream delivery never leaves layer count 0, so it never subscribes. */
|
|
32167
|
+
#reconcile() {
|
|
32168
|
+
const want = /* @__PURE__ */ new Map();
|
|
32169
|
+
for (const layer of this.#layers().slice(0, this.#layerCount)) {
|
|
32170
|
+
want.set(layer.repairTrackName, layer.priority);
|
|
32171
|
+
}
|
|
32172
|
+
const overlay = this.#source.repairOverlays?.[0];
|
|
32173
|
+
if (this.#layerCount > 0 && overlay) want.set(overlay.repairTrackName, overlay.priority);
|
|
32174
|
+
for (const [name, entry] of this.#subs) {
|
|
32175
|
+
if (!want.has(name)) {
|
|
32176
|
+
entry.sub.close();
|
|
32177
|
+
this.#subs.delete(name);
|
|
32178
|
+
}
|
|
32179
|
+
}
|
|
32180
|
+
for (const [name, priority] of want) {
|
|
32181
|
+
if (this.#subs.has(name)) continue;
|
|
32182
|
+
const entry = {};
|
|
32183
|
+
entry.sub = this.#opts.subscribe(name, priority, (packet) => this.#onPacket(name, entry, packet));
|
|
32184
|
+
this.#subs.set(name, entry);
|
|
32185
|
+
}
|
|
32186
|
+
}
|
|
32187
|
+
/** Fenced on the entry itself, so a closed subscription is stale even if its track is re-added. */
|
|
32188
|
+
#onPacket(name, entry, packet) {
|
|
32189
|
+
if (this.#disposed || this.#subs.get(name) !== entry) {
|
|
32190
|
+
this.#staleDropped += 1;
|
|
32191
|
+
return;
|
|
32192
|
+
}
|
|
32193
|
+
this.#repairPackets += 1;
|
|
32194
|
+
this.#repairBytes += packet.byteLength;
|
|
32195
|
+
this.#opts.feed(packet, name);
|
|
32196
|
+
}
|
|
32197
|
+
#closeAll() {
|
|
32198
|
+
for (const entry of this.#subs.values()) entry.sub.close();
|
|
32199
|
+
this.#subs.clear();
|
|
32200
|
+
}
|
|
32201
|
+
};
|
|
32202
|
+
|
|
31641
32203
|
// src/repair-fetch.ts
|
|
31642
32204
|
var MOQT_PRIORITY_MAX = 255;
|
|
31643
32205
|
var SOURCE_MEDIA_PRIORITY_MAX = REPAIR_PRIORITY_MIN - 1;
|
|
@@ -32283,6 +32845,7 @@ export {
|
|
|
32283
32845
|
REPAIR_LIFECYCLE_LOG_PREFIX,
|
|
32284
32846
|
REPAIR_PRIORITY_MAX,
|
|
32285
32847
|
REPAIR_PRIORITY_MIN,
|
|
32848
|
+
RepairLayerSubscriptions,
|
|
32286
32849
|
SOURCE_MEDIA_PRIORITY_MAX,
|
|
32287
32850
|
SampleCombineError,
|
|
32288
32851
|
SampleCombiner,
|
|
@@ -32307,6 +32870,7 @@ export {
|
|
|
32307
32870
|
catalogGroupDurationMs,
|
|
32308
32871
|
catalogKeyframeCadenceMs,
|
|
32309
32872
|
catalogRepairTarget,
|
|
32873
|
+
catalogTrackDeliveryMode,
|
|
32310
32874
|
catalogTrackFecUnavailable,
|
|
32311
32875
|
classifyH264Keyframe,
|
|
32312
32876
|
classifyHevcKeyframe,
|