@blockcast/mmt-container 2.1.0 → 2.2.0-main.0f6aad7bb803
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 +458 -77
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.js.map +1 -1
- package/dist/catalog-fec-params.js.map +1 -1
- package/dist/catalog-network-source.js.map +1 -1
- package/dist/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 +1 -1
- package/dist/index.d.ts.map +1 -1
- 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/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/src/cmcd-producer.ts +5 -0
- package/src/index.ts +2 -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/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
|
}
|
|
@@ -31170,12 +31493,12 @@ function moqtPriority2(value) {
|
|
|
31170
31493
|
function catalogRepairTarget(catalog, source) {
|
|
31171
31494
|
const binding = bindingOf(catalog, source);
|
|
31172
31495
|
if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
|
|
31173
|
-
const
|
|
31496
|
+
const layerZero2 = binding.layers[0];
|
|
31174
31497
|
const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
|
|
31175
31498
|
const tracks = catalog.tracks;
|
|
31176
31499
|
const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
|
|
31177
31500
|
const sourcePriority = entry ? moqtPriority2(entry["priority"]) : null;
|
|
31178
|
-
return { repairTrack:
|
|
31501
|
+
return { repairTrack: layerZero2.repairTrackName, sourcePriority, repairPriority: layerZero2.priority };
|
|
31179
31502
|
}
|
|
31180
31503
|
function numericField(value) {
|
|
31181
31504
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
@@ -31257,7 +31580,14 @@ function resolveCatalogTrackFec(catalog, tracks) {
|
|
|
31257
31580
|
var PATH_BITS = { moq: 1, multicast: 2 };
|
|
31258
31581
|
var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
|
|
31259
31582
|
var MmtpFecSession = class {
|
|
31583
|
+
/** P_0 = B_0, the held total every repair packet is checked against. */
|
|
31584
|
+
get repairSymbols() {
|
|
31585
|
+
return this.#repairSymbols;
|
|
31586
|
+
}
|
|
31260
31587
|
#decoder;
|
|
31588
|
+
#repairSymbols;
|
|
31589
|
+
/** The message entry a catalog-less session was built from; undefined under a catalog. */
|
|
31590
|
+
#message;
|
|
31261
31591
|
/** Blocks a repair has defined and that are not settled yet, by SS_Start. */
|
|
31262
31592
|
#blocks = /* @__PURE__ */ new Map();
|
|
31263
31593
|
/** SS_Starts of settled blocks, so a later repair for one is refused. */
|
|
@@ -31295,6 +31625,24 @@ var MmtpFecSession = class {
|
|
|
31295
31625
|
* `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
|
|
31296
31626
|
*/
|
|
31297
31627
|
constructor(config) {
|
|
31628
|
+
if (config && "alFecFlow" in config) {
|
|
31629
|
+
const message = alFecRaptorQFlowConfig(config.alFecFlow);
|
|
31630
|
+
if (!message) {
|
|
31631
|
+
throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
|
|
31632
|
+
}
|
|
31633
|
+
this.#message = message;
|
|
31634
|
+
this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
|
|
31635
|
+
this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
|
|
31636
|
+
this.sourceSymbols = message.maximumK;
|
|
31637
|
+
this.#repairSymbols = message.maximumP;
|
|
31638
|
+
this.symbolSize = message.repairSymbolSize;
|
|
31639
|
+
this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
|
|
31640
|
+
this.#repairPacketId = message.repairFlowId;
|
|
31641
|
+
this.#heldLayers = layerZero(message.maximumP);
|
|
31642
|
+
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31643
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31644
|
+
return;
|
|
31645
|
+
}
|
|
31298
31646
|
const fec = config?.fec;
|
|
31299
31647
|
if (!fec || typeof fec !== "object") {
|
|
31300
31648
|
throw new Error("MmtpFecSession: the catalog fec instance is required");
|
|
@@ -31305,7 +31653,7 @@ var MmtpFecSession = class {
|
|
|
31305
31653
|
}
|
|
31306
31654
|
this.algorithm = fec.scheme;
|
|
31307
31655
|
this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
|
|
31308
|
-
this
|
|
31656
|
+
this.#repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
|
|
31309
31657
|
this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
|
|
31310
31658
|
const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
|
|
31311
31659
|
const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
|
|
@@ -31325,17 +31673,30 @@ var MmtpFecSession = class {
|
|
|
31325
31673
|
config.heldLayers
|
|
31326
31674
|
);
|
|
31327
31675
|
this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31331
|
-
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
|
|
31337
|
-
|
|
31338
|
-
|
|
31676
|
+
this.#decoder = buildDecoder(config.Decoder, this);
|
|
31677
|
+
}
|
|
31678
|
+
/**
|
|
31679
|
+
* A later version of the AL-FEC message, catalog-less sessions only. A
|
|
31680
|
+
* maximum_p_for_repair_flow above the held total raises it (`raised`): the
|
|
31681
|
+
* anchored check restarts at the new total, so a block generated under the
|
|
31682
|
+
* previous one stays valid before the first block carrying the new total
|
|
31683
|
+
* (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
|
|
31684
|
+
* (`unchanged`): no publisher lowers it, and the check catches a wire that
|
|
31685
|
+
* does. An entry with another asset, repair flow, code point, K or T, or
|
|
31686
|
+
* one outside 8.3, is `refused`: that is a new source encoding, which needs
|
|
31687
|
+
* a new session. A latched fail-closed stays latched either way.
|
|
31688
|
+
*/
|
|
31689
|
+
updateAlFecFlow(flow) {
|
|
31690
|
+
const held = this.#message;
|
|
31691
|
+
if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
|
|
31692
|
+
const next = alFecRaptorQFlowConfig(flow);
|
|
31693
|
+
if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
|
|
31694
|
+
return "refused";
|
|
31695
|
+
}
|
|
31696
|
+
if (next.maximumP <= this.#repairSymbols) return "unchanged";
|
|
31697
|
+
this.#repairSymbols = next.maximumP;
|
|
31698
|
+
this.#heldLayers = layerZero(next.maximumP);
|
|
31699
|
+
return "raised";
|
|
31339
31700
|
}
|
|
31340
31701
|
/** True once release() ran. */
|
|
31341
31702
|
get released() {
|
|
@@ -31430,7 +31791,7 @@ var MmtpFecSession = class {
|
|
|
31430
31791
|
}
|
|
31431
31792
|
if (admission.reject) {
|
|
31432
31793
|
const verdict2 = this.#heldLayers.failsClosed(
|
|
31433
|
-
repairTrackName,
|
|
31794
|
+
this.#trackFor(repairTrackName),
|
|
31434
31795
|
frame.ssStart,
|
|
31435
31796
|
frame.rsbLength,
|
|
31436
31797
|
this.#anchorHorizon,
|
|
@@ -31445,7 +31806,7 @@ var MmtpFecSession = class {
|
|
|
31445
31806
|
this.#repairPacketId ??= frame.packetId;
|
|
31446
31807
|
if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
|
|
31447
31808
|
const verdict = this.#heldLayers.admit(
|
|
31448
|
-
repairTrackName,
|
|
31809
|
+
this.#trackFor(repairTrackName),
|
|
31449
31810
|
frame.ssStart,
|
|
31450
31811
|
frame.rsbLength,
|
|
31451
31812
|
this.#anchorHorizon,
|
|
@@ -31504,6 +31865,10 @@ var MmtpFecSession = class {
|
|
|
31504
31865
|
decoder.free();
|
|
31505
31866
|
return true;
|
|
31506
31867
|
}
|
|
31868
|
+
/** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
|
|
31869
|
+
#trackFor(repairTrackName) {
|
|
31870
|
+
return this.#message ? void 0 : repairTrackName;
|
|
31871
|
+
}
|
|
31507
31872
|
#drop(reason) {
|
|
31508
31873
|
this.#stats.dropped[reason] = (this.#stats.dropped[reason] ?? 0) + 1;
|
|
31509
31874
|
return [];
|
|
@@ -31625,6 +31990,22 @@ var MmtpFecSession = class {
|
|
|
31625
31990
|
}
|
|
31626
31991
|
}
|
|
31627
31992
|
};
|
|
31993
|
+
function layerZero(total) {
|
|
31994
|
+
return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
|
|
31995
|
+
}
|
|
31996
|
+
function buildDecoder(Decoder, session) {
|
|
31997
|
+
if (typeof Decoder !== "function") {
|
|
31998
|
+
throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
|
|
31999
|
+
}
|
|
32000
|
+
const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
|
|
32001
|
+
try {
|
|
32002
|
+
decoder.configure_params(session.sourceSymbols, session.symbolSize);
|
|
32003
|
+
} catch (error) {
|
|
32004
|
+
decoder.free();
|
|
32005
|
+
throw error;
|
|
32006
|
+
}
|
|
32007
|
+
return decoder;
|
|
32008
|
+
}
|
|
31628
32009
|
function requirePositiveInteger(value, path) {
|
|
31629
32010
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
31630
32011
|
throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);
|