@blockcast/mmt-container 0.1.0-main.342ee38b60e1 → 0.1.0-main.3ca5d1ea196c
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 +39 -1345
- package/dist/browser-runtime.js.map +4 -4
- package/dist/cmaf-block-assembler.js.map +1 -1
- package/dist/cmaf-group-batcher.js.map +1 -1
- package/dist/mfu-reassembler.d.ts +7 -11
- package/dist/mfu-reassembler.d.ts.map +1 -1
- package/dist/mfu-reassembler.js +15 -11
- package/dist/mfu-reassembler.js.map +2 -2
- package/dist/mmtp-container-parser.js +15 -11
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +16 -13
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +15 -11
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.js +15 -11
- package/dist/mmtp-router.js.map +2 -2
- package/dist/mmtp-to-cmaf.d.ts.map +1 -1
- package/dist/mmtp-to-cmaf.js +1 -2
- package/dist/mmtp-to-cmaf.js.map +2 -2
- package/dist/multicast-manager.js +38 -1346
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +2 -1
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +23 -1335
- package/dist/multicast-transport.js.map +4 -4
- package/dist/no-modules.js +16 -13
- package/dist/no-modules.js.map +2 -2
- package/dist/wasm-mfu-reassembler.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +15 -11
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +6 -6
- package/src/mfu-reassembler.ts +29 -41
- package/src/mmtp-to-cmaf.ts +1 -25
- package/src/multicast-transport.ts +27 -13
package/dist/browser-runtime.js
CHANGED
|
@@ -1098,9 +1098,12 @@ var MFUReassembler = class {
|
|
|
1098
1098
|
state.timeoutId = setTimeout(() => __privateMethod(this, _MFUReassembler_instances, onTimeout_fn).call(this, bufferKey), Math.max(0, state.startedAt + ms - now));
|
|
1099
1099
|
}
|
|
1100
1100
|
}
|
|
1101
|
-
for (const address of __privateGet(this, _pendingContinuations)
|
|
1102
|
-
|
|
1103
|
-
|
|
1101
|
+
for (const [address, pending] of __privateGet(this, _pendingContinuations)) {
|
|
1102
|
+
if (pending.timeoutId !== void 0) clearTimeout(pending.timeoutId);
|
|
1103
|
+
delete pending.timeoutId;
|
|
1104
|
+
if (ms !== null && parkedRunHoldsASlot(__privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address) !== void 0, pending.fragments.size)) {
|
|
1105
|
+
pending.timeoutId = setTimeout(() => __privateMethod(this, _MFUReassembler_instances, onPendingTimeout_fn).call(this, address), Math.max(0, pending.startedAt + ms - now));
|
|
1106
|
+
}
|
|
1104
1107
|
}
|
|
1105
1108
|
}
|
|
1106
1109
|
get timeoutMs() {
|
|
@@ -1351,26 +1354,28 @@ parkContinuation_fn = function(address, fragment) {
|
|
|
1351
1354
|
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "active-data-unit-limit", "active MFU data-unit limit reached");
|
|
1352
1355
|
}
|
|
1353
1356
|
}
|
|
1354
|
-
let
|
|
1357
|
+
let reAnchored = false;
|
|
1355
1358
|
if (pending === void 0) {
|
|
1356
1359
|
pending = {
|
|
1357
1360
|
fragments: /* @__PURE__ */ new Map(),
|
|
1358
1361
|
startedAt: Date.now(),
|
|
1359
1362
|
admissionSeq: open !== void 0 ? open[1].admissionSeq : __privateWrapper(this, _nextAdmissionSeq)._++
|
|
1360
1363
|
};
|
|
1364
|
+
if (open === void 0 && __privateGet(this, _timeoutMs) !== null) {
|
|
1365
|
+
pending.timeoutId = setTimeout(() => __privateMethod(this, _MFUReassembler_instances, onPendingTimeout_fn).call(this, address), __privateGet(this, _timeoutMs));
|
|
1366
|
+
}
|
|
1361
1367
|
__privateGet(this, _pendingContinuations).set(address, pending);
|
|
1362
|
-
armAfterInsert = true;
|
|
1363
1368
|
} else if (!alreadyCharged) {
|
|
1364
1369
|
pending.admissionSeq = __privateWrapper(this, _nextAdmissionSeq)._++;
|
|
1365
1370
|
pending.startedAt = Date.now();
|
|
1366
1371
|
__privateMethod(this, _MFUReassembler_instances, clearPendingTimeout_fn).call(this, address);
|
|
1367
|
-
|
|
1372
|
+
reAnchored = true;
|
|
1368
1373
|
}
|
|
1369
1374
|
if (pending.fragments.has(fragment.fragmentCounter)) {
|
|
1370
1375
|
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "fragment-counter-mismatch", "duplicate queued continuation counter would replace retained media");
|
|
1371
1376
|
}
|
|
1372
1377
|
pending.fragments.set(fragment.fragmentCounter, fragment);
|
|
1373
|
-
if (
|
|
1378
|
+
if (reAnchored) __privateMethod(this, _MFUReassembler_instances, armHeadlessPendingTimeout_fn).call(this, address);
|
|
1374
1379
|
};
|
|
1375
1380
|
drainPendingContinuations_fn = function(address, runKey) {
|
|
1376
1381
|
while (true) {
|
|
@@ -1663,10 +1668,9 @@ clearPendingTimeout_fn = function(address) {
|
|
|
1663
1668
|
* from its own first arrival, exactly as it would had the head never come.
|
|
1664
1669
|
*
|
|
1665
1670
|
* An empty queue is not armed: it holds no data unit that could fail to
|
|
1666
|
-
* complete, so there is nothing for a deadline to expire.
|
|
1667
|
-
*
|
|
1668
|
-
*
|
|
1669
|
-
* reaches Rust's answer: its
|
|
1671
|
+
* complete, so there is nothing for a deadline to expire. Both arming sites
|
|
1672
|
+
* take that test through `parkedRunHoldsASlot` rather than filtering on the
|
|
1673
|
+
* open run alone, which is how this engine reaches Rust's answer — its
|
|
1670
1674
|
* `next_timeout_at` derives candidate deadlines from the queue's live
|
|
1671
1675
|
* entries, so an empty queue yields none (BLO-32420).
|
|
1672
1676
|
*/
|
|
@@ -7777,7 +7781,6 @@ var MmtpToCmaf = class {
|
|
|
7777
7781
|
const samplesPerFrame = __privateGet(this, _audioTimescale) !== 1e6 ? __privateGet(this, _aacFrameSamples) : sampleDuration;
|
|
7778
7782
|
const scaledTs = __privateGet(this, _audioTimescale) !== 1e6 ? __privateGet(this, _audioSampleCount) * samplesPerFrame : timestamp - __privateGet(this, _audioBaseTs);
|
|
7779
7783
|
__privateWrapper(this, _audioSampleCount)._++;
|
|
7780
|
-
const scaledCompositionTimeOffset = compositionTimeOffset === void 0 || __privateGet(this, _audioTimescale) === 1e6 ? compositionTimeOffset : Math.round(compositionTimeOffset * __privateGet(this, _audioTimescale) / 1e6);
|
|
7781
7784
|
return encodeDataSegment(
|
|
7782
7785
|
sampleData,
|
|
7783
7786
|
scaledTs,
|
|
@@ -7785,7 +7788,7 @@ var MmtpToCmaf = class {
|
|
|
7785
7788
|
true,
|
|
7786
7789
|
__privateWrapper(this, _audioSeq)._++,
|
|
7787
7790
|
1,
|
|
7788
|
-
|
|
7791
|
+
compositionTimeOffset
|
|
7789
7792
|
);
|
|
7790
7793
|
}
|
|
7791
7794
|
/** Convert an ordered audio batch using the caller-owned timeline. */
|
|
@@ -14915,1333 +14918,6 @@ var ReedSolomonCodec = class {
|
|
|
14915
14918
|
}
|
|
14916
14919
|
};
|
|
14917
14920
|
|
|
14918
|
-
// node_modules/@blockcast/mmt-transport/dist/types.js
|
|
14919
|
-
var MoqFecAlgorithm;
|
|
14920
|
-
(function(MoqFecAlgorithm2) {
|
|
14921
|
-
MoqFecAlgorithm2[MoqFecAlgorithm2["None"] = 0] = "None";
|
|
14922
|
-
MoqFecAlgorithm2[MoqFecAlgorithm2["Xor"] = 1] = "Xor";
|
|
14923
|
-
MoqFecAlgorithm2[MoqFecAlgorithm2["RaptorQ"] = 2] = "RaptorQ";
|
|
14924
|
-
})(MoqFecAlgorithm || (MoqFecAlgorithm = {}));
|
|
14925
|
-
|
|
14926
|
-
// node_modules/@blockcast/mmt-transport/dist/driad-discovery.js
|
|
14927
|
-
var DRIADDiscovery = class {
|
|
14928
|
-
constructor() {
|
|
14929
|
-
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
14930
|
-
__publicField(this, "cacheExpiry", 3e5);
|
|
14931
|
-
}
|
|
14932
|
-
// 5 minutes
|
|
14933
|
-
/**
|
|
14934
|
-
* Discover AMT relays for a multicast source address (RFC 8777)
|
|
14935
|
-
*
|
|
14936
|
-
* @param source - The multicast source IP address (NOT the group!)
|
|
14937
|
-
* @returns Array of discovered AMT relays that can tunnel traffic from this source
|
|
14938
|
-
*/
|
|
14939
|
-
async discoverRelays(source) {
|
|
14940
|
-
const cached = this.cache.get(source);
|
|
14941
|
-
if (cached) {
|
|
14942
|
-
return cached;
|
|
14943
|
-
}
|
|
14944
|
-
try {
|
|
14945
|
-
const reversedSource = source.split(".").reverse().join(".");
|
|
14946
|
-
const queryDomain = `${reversedSource}.amt.in-addr.arpa`;
|
|
14947
|
-
console.log(`[DRIAD] Discovering relays for source ${source} via ${queryDomain}`);
|
|
14948
|
-
const relays = await this.queryDRIADProxy(source);
|
|
14949
|
-
if (relays.length > 0) {
|
|
14950
|
-
this.cache.set(source, relays);
|
|
14951
|
-
setTimeout(() => this.cache.delete(source), this.cacheExpiry);
|
|
14952
|
-
}
|
|
14953
|
-
return relays;
|
|
14954
|
-
} catch (error) {
|
|
14955
|
-
console.warn(`[DRIAD] Discovery failed for source ${source}:`, error);
|
|
14956
|
-
return [];
|
|
14957
|
-
}
|
|
14958
|
-
}
|
|
14959
|
-
/**
|
|
14960
|
-
* Query DRIAD proxy service (browser-compatible)
|
|
14961
|
-
*/
|
|
14962
|
-
async queryDRIADProxy(source) {
|
|
14963
|
-
const proxyEndpoints = [
|
|
14964
|
-
"https://driad.amt.net/api/v1/relays",
|
|
14965
|
-
"https://multicast-gateway.cloudflare.com/driad"
|
|
14966
|
-
];
|
|
14967
|
-
for (const endpoint of proxyEndpoints) {
|
|
14968
|
-
try {
|
|
14969
|
-
const response = await fetch(`${endpoint}?source=${encodeURIComponent(source)}`, {
|
|
14970
|
-
method: "GET",
|
|
14971
|
-
headers: { Accept: "application/json" },
|
|
14972
|
-
signal: AbortSignal.timeout(5e3)
|
|
14973
|
-
});
|
|
14974
|
-
if (response.ok) {
|
|
14975
|
-
const data = await response.json();
|
|
14976
|
-
return data.relays || [];
|
|
14977
|
-
}
|
|
14978
|
-
} catch {
|
|
14979
|
-
}
|
|
14980
|
-
}
|
|
14981
|
-
const dohRelay = await this.queryDNSOverHTTPS(source);
|
|
14982
|
-
if (dohRelay) {
|
|
14983
|
-
return [dohRelay];
|
|
14984
|
-
}
|
|
14985
|
-
return [];
|
|
14986
|
-
}
|
|
14987
|
-
/**
|
|
14988
|
-
* Query DNS-over-HTTPS for DRIAD relay based on source address (RFC 8777)
|
|
14989
|
-
*/
|
|
14990
|
-
async queryDNSOverHTTPS(source) {
|
|
14991
|
-
try {
|
|
14992
|
-
const reversedSource = source.split(".").reverse().join(".");
|
|
14993
|
-
const queryName = `${reversedSource}.amt.in-addr.arpa`;
|
|
14994
|
-
const dohUrl = `https://cloudflare-dns.com/dns-query?name=${queryName}&type=A`;
|
|
14995
|
-
const response = await fetch(dohUrl, {
|
|
14996
|
-
headers: { Accept: "application/dns-json" },
|
|
14997
|
-
signal: AbortSignal.timeout(3e3)
|
|
14998
|
-
});
|
|
14999
|
-
if (!response.ok) {
|
|
15000
|
-
return null;
|
|
15001
|
-
}
|
|
15002
|
-
const result = await response.json();
|
|
15003
|
-
if (result.Answer && result.Answer.length > 0) {
|
|
15004
|
-
return {
|
|
15005
|
-
host: result.Answer[0].data,
|
|
15006
|
-
port: 2268,
|
|
15007
|
-
priority: 10,
|
|
15008
|
-
weight: 100,
|
|
15009
|
-
asn: 0
|
|
15010
|
-
};
|
|
15011
|
-
}
|
|
15012
|
-
return null;
|
|
15013
|
-
} catch (error) {
|
|
15014
|
-
console.warn("[DRIAD] DNS-over-HTTPS query failed:", error);
|
|
15015
|
-
return null;
|
|
15016
|
-
}
|
|
15017
|
-
}
|
|
15018
|
-
/**
|
|
15019
|
-
* Select best relay based on RTT measurements
|
|
15020
|
-
*/
|
|
15021
|
-
async selectBestRelay(relays) {
|
|
15022
|
-
if (relays.length === 0)
|
|
15023
|
-
return null;
|
|
15024
|
-
const measured = await Promise.all(relays.map(async (relay) => {
|
|
15025
|
-
const rtt = await this.measureRTT(relay.host, relay.port);
|
|
15026
|
-
return { ...relay, rtt };
|
|
15027
|
-
}));
|
|
15028
|
-
measured.sort((a, b) => {
|
|
15029
|
-
if (a.priority !== b.priority)
|
|
15030
|
-
return a.priority - b.priority;
|
|
15031
|
-
return (a.rtt || Infinity) - (b.rtt || Infinity);
|
|
15032
|
-
});
|
|
15033
|
-
console.log(`[DRIAD] Selected relay: ${measured[0].host} (RTT: ${measured[0].rtt}ms)`);
|
|
15034
|
-
return measured[0];
|
|
15035
|
-
}
|
|
15036
|
-
async measureRTT(host, _port) {
|
|
15037
|
-
const start = performance.now();
|
|
15038
|
-
try {
|
|
15039
|
-
await fetch(`https://${host}/health`, {
|
|
15040
|
-
method: "HEAD",
|
|
15041
|
-
signal: AbortSignal.timeout(2e3),
|
|
15042
|
-
mode: "no-cors"
|
|
15043
|
-
});
|
|
15044
|
-
return performance.now() - start;
|
|
15045
|
-
} catch {
|
|
15046
|
-
return Infinity;
|
|
15047
|
-
}
|
|
15048
|
-
}
|
|
15049
|
-
/**
|
|
15050
|
-
* Clear the relay cache
|
|
15051
|
-
*/
|
|
15052
|
-
clearCache() {
|
|
15053
|
-
this.cache.clear();
|
|
15054
|
-
}
|
|
15055
|
-
/**
|
|
15056
|
-
* Get cached relays for a source address
|
|
15057
|
-
*/
|
|
15058
|
-
getCached(source) {
|
|
15059
|
-
return this.cache.get(source);
|
|
15060
|
-
}
|
|
15061
|
-
};
|
|
15062
|
-
var driadDiscovery = new DRIADDiscovery();
|
|
15063
|
-
|
|
15064
|
-
// node_modules/@blockcast/mmt-transport/dist/multicast-tier.js
|
|
15065
|
-
function endpointKey(endpoint) {
|
|
15066
|
-
return `${endpoint.sourceAddress ?? ""}|${endpoint.groupAddress}|${endpoint.port}`;
|
|
15067
|
-
}
|
|
15068
|
-
function selectMulticastTier(endpoints, estimate) {
|
|
15069
|
-
if (endpoints.length <= 1)
|
|
15070
|
-
return endpoints[0];
|
|
15071
|
-
const ranked = endpoints.filter((endpoint) => typeof endpoint.bandwidth === "number" && endpoint.bandwidth > 0).sort((a, b) => a.bandwidth - b.bandwidth);
|
|
15072
|
-
if (ranked.length < 2)
|
|
15073
|
-
return endpoints[0];
|
|
15074
|
-
if (estimate === void 0)
|
|
15075
|
-
return ranked[0];
|
|
15076
|
-
const budget = estimate * 0.8;
|
|
15077
|
-
let selected;
|
|
15078
|
-
for (const endpoint of ranked) {
|
|
15079
|
-
if (endpoint.bandwidth <= budget)
|
|
15080
|
-
selected = endpoint;
|
|
15081
|
-
}
|
|
15082
|
-
return selected ?? ranked[0];
|
|
15083
|
-
}
|
|
15084
|
-
|
|
15085
|
-
// node_modules/@blockcast/mmt-transport/dist/abr-controller.js
|
|
15086
|
-
var QUALITY_LEVELS = [
|
|
15087
|
-
{ name: "240p", bitrate: 3e5, width: 426, height: 240, fps: 15 },
|
|
15088
|
-
{ name: "360p", bitrate: 5e5, width: 640, height: 360, fps: 24 },
|
|
15089
|
-
{ name: "480p", bitrate: 1e6, width: 854, height: 480, fps: 30 },
|
|
15090
|
-
{ name: "720p", bitrate: 25e5, width: 1280, height: 720, fps: 30 },
|
|
15091
|
-
{ name: "1080p", bitrate: 5e6, width: 1920, height: 1080, fps: 30 },
|
|
15092
|
-
{ name: "1440p", bitrate: 8e6, width: 2560, height: 1440, fps: 30 },
|
|
15093
|
-
{ name: "4K", bitrate: 15e6, width: 3840, height: 2160, fps: 30 }
|
|
15094
|
-
];
|
|
15095
|
-
var AdaptiveBitrateController = class {
|
|
15096
|
-
constructor(initialQuality) {
|
|
15097
|
-
__publicField(this, "currentQualityIndex");
|
|
15098
|
-
__publicField(this, "history", []);
|
|
15099
|
-
__publicField(this, "historyWindow", 1e4);
|
|
15100
|
-
// 10 second window
|
|
15101
|
-
__publicField(this, "upgradeThreshold", 0.8);
|
|
15102
|
-
// 80% of target bitrate sustained
|
|
15103
|
-
__publicField(this, "downgradeThreshold", 0.5);
|
|
15104
|
-
// 50% of target bitrate triggers downgrade
|
|
15105
|
-
__publicField(this, "stabilityPeriod", 5e3);
|
|
15106
|
-
// Wait 5s before upgrading
|
|
15107
|
-
__publicField(this, "lastQualityChange", 0);
|
|
15108
|
-
__publicField(this, "onQualityChange");
|
|
15109
|
-
// FEC stats for effective loss rate calculation
|
|
15110
|
-
__publicField(this, "fecStats", {
|
|
15111
|
-
rawLossRate: 0,
|
|
15112
|
-
effectiveLossRate: 0,
|
|
15113
|
-
recoveryRate: 1,
|
|
15114
|
-
fecEnabled: false
|
|
15115
|
-
});
|
|
15116
|
-
this.currentQualityIndex = initialQuality ? QUALITY_LEVELS.findIndex((q) => q.name === initialQuality.name) : 0;
|
|
15117
|
-
if (this.currentQualityIndex < 0)
|
|
15118
|
-
this.currentQualityIndex = 0;
|
|
15119
|
-
}
|
|
15120
|
-
setCallback(callback) {
|
|
15121
|
-
this.onQualityChange = callback;
|
|
15122
|
-
}
|
|
15123
|
-
getCurrentQuality() {
|
|
15124
|
-
return QUALITY_LEVELS[this.currentQualityIndex];
|
|
15125
|
-
}
|
|
15126
|
-
/** Selects the multicast (S,G) tier without replacing a player's unicast ABR manager. */
|
|
15127
|
-
selectMulticastTier(endpoints, estimate = this.getMetrics().measuredBitrate || void 0) {
|
|
15128
|
-
return selectMulticastTier(endpoints, estimate);
|
|
15129
|
-
}
|
|
15130
|
-
/**
|
|
15131
|
-
* Record network sample for bitrate adaptation
|
|
15132
|
-
*
|
|
15133
|
-
* @param bytesReceived - Bytes received in this sample
|
|
15134
|
-
* @param packetsLost - Packets lost (raw, before FEC recovery)
|
|
15135
|
-
*/
|
|
15136
|
-
recordSample(bytesReceived, packetsLost) {
|
|
15137
|
-
const now = Date.now();
|
|
15138
|
-
this.history.push({ timestamp: now, bytesReceived, packetsLost });
|
|
15139
|
-
this.history = this.history.filter((s) => now - s.timestamp < this.historyWindow);
|
|
15140
|
-
const { measuredBitrate, lossRate: rawLossRate } = this.calculateMetrics();
|
|
15141
|
-
const currentQuality = this.getCurrentQuality();
|
|
15142
|
-
const effectiveLossRate = this.fecStats.fecEnabled ? this.fecStats.effectiveLossRate : rawLossRate;
|
|
15143
|
-
if (now - this.lastQualityChange < this.stabilityPeriod) {
|
|
15144
|
-
return;
|
|
15145
|
-
}
|
|
15146
|
-
if (effectiveLossRate > 0.05 || measuredBitrate < currentQuality.bitrate * this.downgradeThreshold) {
|
|
15147
|
-
this.downgrade();
|
|
15148
|
-
return;
|
|
15149
|
-
}
|
|
15150
|
-
if (measuredBitrate > currentQuality.bitrate * this.upgradeThreshold && effectiveLossRate < 0.01 && this.currentQualityIndex < QUALITY_LEVELS.length - 1) {
|
|
15151
|
-
this.upgrade();
|
|
15152
|
-
}
|
|
15153
|
-
}
|
|
15154
|
-
/**
|
|
15155
|
-
* Update FEC statistics for ABR decisions
|
|
15156
|
-
*
|
|
15157
|
-
* Per draft-ramadan-moq-fec-00, ABR should use the effective loss rate
|
|
15158
|
-
* (after FEC recovery) rather than raw transport loss rate.
|
|
15159
|
-
*
|
|
15160
|
-
* @param stats - FEC statistics from the FEC decoder
|
|
15161
|
-
*/
|
|
15162
|
-
setFecStats(stats) {
|
|
15163
|
-
this.fecStats = stats;
|
|
15164
|
-
}
|
|
15165
|
-
/**
|
|
15166
|
-
* Get current FEC statistics
|
|
15167
|
-
*/
|
|
15168
|
-
getFecStats() {
|
|
15169
|
-
return { ...this.fecStats };
|
|
15170
|
-
}
|
|
15171
|
-
calculateMetrics() {
|
|
15172
|
-
if (this.history.length < 2) {
|
|
15173
|
-
return { measuredBitrate: 0, lossRate: 0 };
|
|
15174
|
-
}
|
|
15175
|
-
const totalBytes = this.history.reduce((sum, s) => sum + s.bytesReceived, 0);
|
|
15176
|
-
const totalLost = this.history.reduce((sum, s) => sum + s.packetsLost, 0);
|
|
15177
|
-
const duration = (this.history[this.history.length - 1].timestamp - this.history[0].timestamp) / 1e3;
|
|
15178
|
-
const measuredBitrate = duration > 0 ? totalBytes * 8 / duration : 0;
|
|
15179
|
-
const lossRate = totalLost / (this.history.length + totalLost);
|
|
15180
|
-
return { measuredBitrate, lossRate };
|
|
15181
|
-
}
|
|
15182
|
-
upgrade() {
|
|
15183
|
-
if (this.currentQualityIndex >= QUALITY_LEVELS.length - 1)
|
|
15184
|
-
return;
|
|
15185
|
-
this.currentQualityIndex++;
|
|
15186
|
-
this.lastQualityChange = Date.now();
|
|
15187
|
-
const newQuality = this.getCurrentQuality();
|
|
15188
|
-
console.log(`[ABR] Upgrading to ${newQuality.name} (${newQuality.bitrate / 1e6}Mbps)`);
|
|
15189
|
-
this.onQualityChange?.(newQuality);
|
|
15190
|
-
}
|
|
15191
|
-
downgrade() {
|
|
15192
|
-
if (this.currentQualityIndex <= 0)
|
|
15193
|
-
return;
|
|
15194
|
-
this.currentQualityIndex--;
|
|
15195
|
-
this.lastQualityChange = Date.now();
|
|
15196
|
-
const newQuality = this.getCurrentQuality();
|
|
15197
|
-
console.log(`[ABR] Downgrading to ${newQuality.name} (${newQuality.bitrate / 1e6}Mbps)`);
|
|
15198
|
-
this.onQualityChange?.(newQuality);
|
|
15199
|
-
}
|
|
15200
|
-
/**
|
|
15201
|
-
* Force quality level (for testing)
|
|
15202
|
-
*/
|
|
15203
|
-
setQuality(name) {
|
|
15204
|
-
const index = QUALITY_LEVELS.findIndex((q) => q.name === name);
|
|
15205
|
-
if (index >= 0) {
|
|
15206
|
-
this.currentQualityIndex = index;
|
|
15207
|
-
this.lastQualityChange = Date.now();
|
|
15208
|
-
this.onQualityChange?.(this.getCurrentQuality());
|
|
15209
|
-
}
|
|
15210
|
-
}
|
|
15211
|
-
/**
|
|
15212
|
-
* Get current metrics including both raw and effective loss rates
|
|
15213
|
-
*/
|
|
15214
|
-
getMetrics() {
|
|
15215
|
-
const { measuredBitrate, lossRate: rawLossRate } = this.calculateMetrics();
|
|
15216
|
-
return {
|
|
15217
|
-
measuredBitrate,
|
|
15218
|
-
rawLossRate,
|
|
15219
|
-
effectiveLossRate: this.fecStats.fecEnabled ? this.fecStats.effectiveLossRate : rawLossRate,
|
|
15220
|
-
fecEnabled: this.fecStats.fecEnabled,
|
|
15221
|
-
sampleCount: this.history.length
|
|
15222
|
-
};
|
|
15223
|
-
}
|
|
15224
|
-
/**
|
|
15225
|
-
* Reset history and start fresh
|
|
15226
|
-
*/
|
|
15227
|
-
reset() {
|
|
15228
|
-
this.history = [];
|
|
15229
|
-
this.lastQualityChange = 0;
|
|
15230
|
-
}
|
|
15231
|
-
};
|
|
15232
|
-
|
|
15233
|
-
// node_modules/@blockcast/mmt-transport/dist/multicast-orchestrator.js
|
|
15234
|
-
var DEFAULT_DWELL_MS = 3e3;
|
|
15235
|
-
var DEFAULT_JOIN_DEADLINE_MS = 5e3;
|
|
15236
|
-
var _state2, _current, _target, _estimate, _favored, _joinStartedAt, _lastCommittedTimestamp, _dwellMs, _joinDeadlineMs, _MulticastOrchestrator_instances, now_fn;
|
|
15237
|
-
var MulticastOrchestrator = class {
|
|
15238
|
-
constructor(options) {
|
|
15239
|
-
__privateAdd(this, _MulticastOrchestrator_instances);
|
|
15240
|
-
__publicField(this, "options");
|
|
15241
|
-
__privateAdd(this, _state2, "stable");
|
|
15242
|
-
__privateAdd(this, _current);
|
|
15243
|
-
__privateAdd(this, _target);
|
|
15244
|
-
__privateAdd(this, _estimate);
|
|
15245
|
-
__privateAdd(this, _favored);
|
|
15246
|
-
__privateAdd(this, _joinStartedAt, 0);
|
|
15247
|
-
__privateAdd(this, _lastCommittedTimestamp, Number.NEGATIVE_INFINITY);
|
|
15248
|
-
__privateAdd(this, _dwellMs);
|
|
15249
|
-
__privateAdd(this, _joinDeadlineMs);
|
|
15250
|
-
this.options = options;
|
|
15251
|
-
__privateSet(this, _dwellMs, options.dwellMs ?? DEFAULT_DWELL_MS);
|
|
15252
|
-
__privateSet(this, _joinDeadlineMs, options.joinDeadlineMs ?? DEFAULT_JOIN_DEADLINE_MS);
|
|
15253
|
-
if (!Number.isFinite(__privateGet(this, _dwellMs)) || !Number.isFinite(__privateGet(this, _joinDeadlineMs)) || __privateGet(this, _dwellMs) <= 0 || __privateGet(this, _joinDeadlineMs) <= 0) {
|
|
15254
|
-
throw new Error("Multicast dwell and join deadline must be positive");
|
|
15255
|
-
}
|
|
15256
|
-
}
|
|
15257
|
-
get state() {
|
|
15258
|
-
return __privateGet(this, _state2);
|
|
15259
|
-
}
|
|
15260
|
-
get current() {
|
|
15261
|
-
return __privateGet(this, _current);
|
|
15262
|
-
}
|
|
15263
|
-
get target() {
|
|
15264
|
-
return __privateGet(this, _target);
|
|
15265
|
-
}
|
|
15266
|
-
get estimate() {
|
|
15267
|
-
return __privateGet(this, _estimate);
|
|
15268
|
-
}
|
|
15269
|
-
start() {
|
|
15270
|
-
__privateGet(this, _current) ?? __privateSet(this, _current, this.options.initialEndpoint ?? selectMulticastTier(this.options.endpoints, void 0));
|
|
15271
|
-
return __privateGet(this, _current);
|
|
15272
|
-
}
|
|
15273
|
-
onGoodput(sample) {
|
|
15274
|
-
const bitrate = sample.bitrate;
|
|
15275
|
-
if (!__privateGet(this, _current) || this.options.adaptive === false)
|
|
15276
|
-
return void 0;
|
|
15277
|
-
if (__privateGet(this, _state2) === "joining")
|
|
15278
|
-
return this.poll();
|
|
15279
|
-
if (bitrate === void 0 || !Number.isFinite(bitrate))
|
|
15280
|
-
return void 0;
|
|
15281
|
-
__privateSet(this, _estimate, bitrate);
|
|
15282
|
-
const desired = selectMulticastTier(this.options.endpoints, bitrate);
|
|
15283
|
-
if (!desired)
|
|
15284
|
-
return void 0;
|
|
15285
|
-
const desiredKey = endpointKey(desired);
|
|
15286
|
-
if (desiredKey === endpointKey(__privateGet(this, _current))) {
|
|
15287
|
-
if (__privateGet(this, _favored)?.source === "goodput")
|
|
15288
|
-
__privateSet(this, _favored, void 0);
|
|
15289
|
-
return void 0;
|
|
15290
|
-
}
|
|
15291
|
-
if (typeof desired.bandwidth !== "number" || typeof __privateGet(this, _current).bandwidth !== "number" || desired.bandwidth >= __privateGet(this, _current).bandwidth) {
|
|
15292
|
-
if (__privateGet(this, _favored)?.source === "goodput")
|
|
15293
|
-
__privateSet(this, _favored, void 0);
|
|
15294
|
-
return void 0;
|
|
15295
|
-
}
|
|
15296
|
-
const now = __privateMethod(this, _MulticastOrchestrator_instances, now_fn).call(this);
|
|
15297
|
-
if (!__privateGet(this, _favored) || __privateGet(this, _favored).key !== desiredKey || __privateGet(this, _favored).source !== "goodput") {
|
|
15298
|
-
__privateSet(this, _favored, { key: desiredKey, since: now, source: "goodput" });
|
|
15299
|
-
return void 0;
|
|
15300
|
-
}
|
|
15301
|
-
if (now - __privateGet(this, _favored).since < __privateGet(this, _dwellMs))
|
|
15302
|
-
return void 0;
|
|
15303
|
-
__privateSet(this, _state2, "joining");
|
|
15304
|
-
__privateSet(this, _target, desired);
|
|
15305
|
-
__privateSet(this, _joinStartedAt, now);
|
|
15306
|
-
__privateSet(this, _favored, void 0);
|
|
15307
|
-
return { kind: "join-target", current: __privateGet(this, _current), target: desired };
|
|
15308
|
-
}
|
|
15309
|
-
onHeadroom(sample) {
|
|
15310
|
-
if (!__privateGet(this, _current) || this.options.adaptive === false || __privateGet(this, _state2) === "joining")
|
|
15311
|
-
return void 0;
|
|
15312
|
-
if (!sample.healthy) {
|
|
15313
|
-
if (__privateGet(this, _favored)?.source === "headroom")
|
|
15314
|
-
__privateSet(this, _favored, void 0);
|
|
15315
|
-
return void 0;
|
|
15316
|
-
}
|
|
15317
|
-
if (__privateGet(this, _favored)?.source === "goodput")
|
|
15318
|
-
return void 0;
|
|
15319
|
-
const currentBandwidth = __privateGet(this, _current).bandwidth;
|
|
15320
|
-
if (typeof currentBandwidth !== "number")
|
|
15321
|
-
return void 0;
|
|
15322
|
-
const desired = this.options.endpoints.filter((endpoint) => typeof endpoint.bandwidth === "number" && endpoint.bandwidth > currentBandwidth).sort((a, b) => a.bandwidth - b.bandwidth)[0];
|
|
15323
|
-
if (!desired) {
|
|
15324
|
-
__privateSet(this, _favored, void 0);
|
|
15325
|
-
return void 0;
|
|
15326
|
-
}
|
|
15327
|
-
const desiredKey = endpointKey(desired);
|
|
15328
|
-
if (desiredKey === endpointKey(__privateGet(this, _current))) {
|
|
15329
|
-
__privateSet(this, _favored, void 0);
|
|
15330
|
-
return void 0;
|
|
15331
|
-
}
|
|
15332
|
-
const now = __privateMethod(this, _MulticastOrchestrator_instances, now_fn).call(this);
|
|
15333
|
-
if (!__privateGet(this, _favored) || __privateGet(this, _favored).key !== desiredKey) {
|
|
15334
|
-
__privateSet(this, _favored, { key: desiredKey, since: now, source: "headroom" });
|
|
15335
|
-
return void 0;
|
|
15336
|
-
}
|
|
15337
|
-
if (now - __privateGet(this, _favored).since < __privateGet(this, _dwellMs))
|
|
15338
|
-
return void 0;
|
|
15339
|
-
__privateSet(this, _state2, "joining");
|
|
15340
|
-
__privateSet(this, _target, desired);
|
|
15341
|
-
__privateSet(this, _joinStartedAt, now);
|
|
15342
|
-
__privateSet(this, _favored, void 0);
|
|
15343
|
-
return { kind: "join-target", current: __privateGet(this, _current), target: desired };
|
|
15344
|
-
}
|
|
15345
|
-
onTierPacket(packet) {
|
|
15346
|
-
if (!__privateGet(this, _current))
|
|
15347
|
-
return void 0;
|
|
15348
|
-
if (packet.endpointKey === endpointKey(__privateGet(this, _current)) && Number.isFinite(packet.timestamp)) {
|
|
15349
|
-
__privateSet(this, _lastCommittedTimestamp, Math.max(__privateGet(this, _lastCommittedTimestamp), packet.timestamp));
|
|
15350
|
-
}
|
|
15351
|
-
if (__privateGet(this, _state2) !== "joining" || !__privateGet(this, _target))
|
|
15352
|
-
return void 0;
|
|
15353
|
-
const expired = this.poll();
|
|
15354
|
-
if (expired)
|
|
15355
|
-
return expired;
|
|
15356
|
-
if (packet.endpointKey !== endpointKey(__privateGet(this, _target)) || !packet.keyframe || !packet.hasValidCodecConfig || !Number.isFinite(packet.timestamp) || packet.timestamp < __privateGet(this, _lastCommittedTimestamp))
|
|
15357
|
-
return void 0;
|
|
15358
|
-
const command = {
|
|
15359
|
-
kind: "commit-swap",
|
|
15360
|
-
target: __privateGet(this, _target),
|
|
15361
|
-
old: __privateGet(this, _current)
|
|
15362
|
-
};
|
|
15363
|
-
__privateSet(this, _lastCommittedTimestamp, packet.timestamp);
|
|
15364
|
-
__privateSet(this, _current, __privateGet(this, _target));
|
|
15365
|
-
__privateSet(this, _target, void 0);
|
|
15366
|
-
__privateSet(this, _state2, "stable");
|
|
15367
|
-
return command;
|
|
15368
|
-
}
|
|
15369
|
-
poll() {
|
|
15370
|
-
if (__privateGet(this, _state2) !== "joining" || !__privateGet(this, _target) || !__privateGet(this, _current))
|
|
15371
|
-
return void 0;
|
|
15372
|
-
if (__privateMethod(this, _MulticastOrchestrator_instances, now_fn).call(this) - __privateGet(this, _joinStartedAt) < __privateGet(this, _joinDeadlineMs))
|
|
15373
|
-
return void 0;
|
|
15374
|
-
return this.cancel();
|
|
15375
|
-
}
|
|
15376
|
-
cancel() {
|
|
15377
|
-
if (__privateGet(this, _state2) !== "joining" || !__privateGet(this, _target) || !__privateGet(this, _current))
|
|
15378
|
-
return void 0;
|
|
15379
|
-
const command = {
|
|
15380
|
-
kind: "abandon-join",
|
|
15381
|
-
current: __privateGet(this, _current),
|
|
15382
|
-
target: __privateGet(this, _target)
|
|
15383
|
-
};
|
|
15384
|
-
__privateSet(this, _target, void 0);
|
|
15385
|
-
__privateSet(this, _state2, "stable");
|
|
15386
|
-
return command;
|
|
15387
|
-
}
|
|
15388
|
-
};
|
|
15389
|
-
_state2 = new WeakMap();
|
|
15390
|
-
_current = new WeakMap();
|
|
15391
|
-
_target = new WeakMap();
|
|
15392
|
-
_estimate = new WeakMap();
|
|
15393
|
-
_favored = new WeakMap();
|
|
15394
|
-
_joinStartedAt = new WeakMap();
|
|
15395
|
-
_lastCommittedTimestamp = new WeakMap();
|
|
15396
|
-
_dwellMs = new WeakMap();
|
|
15397
|
-
_joinDeadlineMs = new WeakMap();
|
|
15398
|
-
_MulticastOrchestrator_instances = new WeakSet();
|
|
15399
|
-
now_fn = function() {
|
|
15400
|
-
const now = this.options.now();
|
|
15401
|
-
if (!Number.isFinite(now))
|
|
15402
|
-
throw new Error("Multicast orchestrator clock must be finite");
|
|
15403
|
-
return now;
|
|
15404
|
-
};
|
|
15405
|
-
|
|
15406
|
-
// node_modules/@blockcast/mmt-transport/dist/endpoint-config.js
|
|
15407
|
-
function toWindowMulticastSubscribeConfig(input) {
|
|
15408
|
-
if (input.mode !== void 0 && !["auto", "native", "tunnel"].includes(input.mode)) {
|
|
15409
|
-
throw new TypeError(`Unsupported multicast mode: ${String(input.mode)}`);
|
|
15410
|
-
}
|
|
15411
|
-
const base = {
|
|
15412
|
-
group: input.group,
|
|
15413
|
-
port: input.port,
|
|
15414
|
-
...input.source?.trim() ? { source: input.source.trim() } : {},
|
|
15415
|
-
consume: "bytes",
|
|
15416
|
-
...input.packetIds?.length ? { packetIds: [...input.packetIds] } : {}
|
|
15417
|
-
};
|
|
15418
|
-
const relay = input.amtRelay?.trim();
|
|
15419
|
-
const wantsAmt = input.mode === "tunnel" || input.mode !== "native" && relay !== void 0 && relay !== "";
|
|
15420
|
-
if (!wantsAmt) {
|
|
15421
|
-
const native = {
|
|
15422
|
-
...base,
|
|
15423
|
-
origin: "ip-multicast",
|
|
15424
|
-
...input.tracks?.length ? { tracks: input.tracks.map(({ name, packetId }) => ({ name, packetId })) } : {}
|
|
15425
|
-
};
|
|
15426
|
-
return native;
|
|
15427
|
-
}
|
|
15428
|
-
const relayPort = input.amtRelayPort;
|
|
15429
|
-
if (!relay || relayPort === void 0 || !isPositivePort(relayPort)) {
|
|
15430
|
-
throw new TypeError("AMT requires a resolved relay and a valid relay port");
|
|
15431
|
-
}
|
|
15432
|
-
return {
|
|
15433
|
-
...base,
|
|
15434
|
-
origin: "amt",
|
|
15435
|
-
relay,
|
|
15436
|
-
relayPort
|
|
15437
|
-
};
|
|
15438
|
-
}
|
|
15439
|
-
function isValidMulticastGroupAddress(address) {
|
|
15440
|
-
const value = address?.trim().toLowerCase();
|
|
15441
|
-
if (!value)
|
|
15442
|
-
return false;
|
|
15443
|
-
if (value.startsWith("ff"))
|
|
15444
|
-
return isValidIpv6MulticastAddress(value);
|
|
15445
|
-
const parts = value.split(".");
|
|
15446
|
-
if (parts.length !== 4)
|
|
15447
|
-
return false;
|
|
15448
|
-
const octets = parts.map((part) => {
|
|
15449
|
-
if (!/^\d+$/.test(part))
|
|
15450
|
-
return Number.NaN;
|
|
15451
|
-
const octet = Number(part);
|
|
15452
|
-
return Number.isInteger(octet) && octet >= 0 && octet <= 255 ? octet : Number.NaN;
|
|
15453
|
-
});
|
|
15454
|
-
return octets.every(Number.isInteger) && octets[0] >= 224 && octets[0] <= 239;
|
|
15455
|
-
}
|
|
15456
|
-
function normalizeMulticastEndpoint(endpoint) {
|
|
15457
|
-
const groupAddress = endpoint.groupAddress.trim().toLowerCase();
|
|
15458
|
-
const sourceAddress = endpoint.sourceAddress?.trim().toLowerCase() || void 0;
|
|
15459
|
-
const amtRelay = endpoint.amtRelay?.trim().toLowerCase() || void 0;
|
|
15460
|
-
if (!isValidMulticastGroupAddress(groupAddress) || !isPositivePort(endpoint.port))
|
|
15461
|
-
return void 0;
|
|
15462
|
-
if (endpoint.mode && !["auto", "native", "tunnel"].includes(endpoint.mode))
|
|
15463
|
-
return void 0;
|
|
15464
|
-
if (endpoint.amtRelayPort !== void 0 && !isPositivePort(endpoint.amtRelayPort))
|
|
15465
|
-
return void 0;
|
|
15466
|
-
return {
|
|
15467
|
-
groupAddress,
|
|
15468
|
-
...sourceAddress ? { sourceAddress } : {},
|
|
15469
|
-
port: endpoint.port,
|
|
15470
|
-
...endpoint.mode ? { mode: endpoint.mode } : {},
|
|
15471
|
-
...amtRelay ? { amtRelay } : {},
|
|
15472
|
-
...endpoint.amtRelayPort ? { amtRelayPort: endpoint.amtRelayPort } : {}
|
|
15473
|
-
};
|
|
15474
|
-
}
|
|
15475
|
-
function multicastEndpointKey(endpoint) {
|
|
15476
|
-
const normalized = normalizeMulticastEndpoint(endpoint);
|
|
15477
|
-
if (!normalized)
|
|
15478
|
-
throw new Error("Invalid multicast endpoint");
|
|
15479
|
-
return JSON.stringify([
|
|
15480
|
-
normalized.sourceAddress ?? "",
|
|
15481
|
-
normalized.groupAddress,
|
|
15482
|
-
normalized.port,
|
|
15483
|
-
normalized.mode ?? "auto",
|
|
15484
|
-
normalized.amtRelay ?? "",
|
|
15485
|
-
normalized.amtRelayPort ?? ""
|
|
15486
|
-
]);
|
|
15487
|
-
}
|
|
15488
|
-
function isValidIpv6MulticastAddress(value) {
|
|
15489
|
-
if (!/^[0-9a-f:]+$/.test(value))
|
|
15490
|
-
return false;
|
|
15491
|
-
const compressed = value.includes("::");
|
|
15492
|
-
if (value.indexOf("::") !== value.lastIndexOf("::"))
|
|
15493
|
-
return false;
|
|
15494
|
-
const groups = value.split(":").filter(Boolean);
|
|
15495
|
-
if (groups.length === 0 || groups.length > 8)
|
|
15496
|
-
return false;
|
|
15497
|
-
if (!compressed && groups.length !== 8)
|
|
15498
|
-
return false;
|
|
15499
|
-
return groups.every((group) => /^[0-9a-f]{1,4}$/.test(group));
|
|
15500
|
-
}
|
|
15501
|
-
function isPositivePort(port) {
|
|
15502
|
-
return Number.isInteger(port) && port > 0 && port <= 65535;
|
|
15503
|
-
}
|
|
15504
|
-
|
|
15505
|
-
// node_modules/@blockcast/mmt-transport/dist/multicast-subscription-pool.js
|
|
15506
|
-
var _entries, _gatewayIds, _nextGatewayId, _MulticastSubscriptionPool_instances, createEntry_fn, gatewayId_fn, detach_fn, dispatch_fn;
|
|
15507
|
-
var MulticastSubscriptionPool = class {
|
|
15508
|
-
constructor() {
|
|
15509
|
-
__privateAdd(this, _MulticastSubscriptionPool_instances);
|
|
15510
|
-
__privateAdd(this, _entries, /* @__PURE__ */ new Map());
|
|
15511
|
-
__privateAdd(this, _gatewayIds, /* @__PURE__ */ new WeakMap());
|
|
15512
|
-
__privateAdd(this, _nextGatewayId, 1);
|
|
15513
|
-
}
|
|
15514
|
-
async acquire(gateway, config, consumer) {
|
|
15515
|
-
const endpoint = normalizeMulticastEndpoint({
|
|
15516
|
-
groupAddress: config.group,
|
|
15517
|
-
sourceAddress: config.source,
|
|
15518
|
-
port: config.port,
|
|
15519
|
-
mode: config.mode,
|
|
15520
|
-
amtRelay: config.amtRelay,
|
|
15521
|
-
amtRelayPort: config.amtRelayPort
|
|
15522
|
-
});
|
|
15523
|
-
if (!endpoint)
|
|
15524
|
-
throw new Error("Invalid multicast endpoint");
|
|
15525
|
-
if (typeof gateway.subscribe !== "function")
|
|
15526
|
-
throw new Error("window.multicast.subscribe unavailable");
|
|
15527
|
-
const key = multicastEndpointKey(endpoint);
|
|
15528
|
-
const internalKey = `${__privateMethod(this, _MulticastSubscriptionPool_instances, gatewayId_fn).call(this, gateway)}|${key}`;
|
|
15529
|
-
let record = __privateGet(this, _entries).get(internalKey);
|
|
15530
|
-
if (!record) {
|
|
15531
|
-
record = { claims: 0, entry: Promise.resolve(null) };
|
|
15532
|
-
const createdRecord = record;
|
|
15533
|
-
record.entry = __privateMethod(this, _MulticastSubscriptionPool_instances, createEntry_fn).call(this, gateway, endpoint, (entry2) => {
|
|
15534
|
-
if (__privateGet(this, _entries).get(internalKey) === createdRecord)
|
|
15535
|
-
__privateGet(this, _entries).delete(internalKey);
|
|
15536
|
-
__privateMethod(this, _MulticastSubscriptionPool_instances, detach_fn).call(this, entry2);
|
|
15537
|
-
}).catch((error) => {
|
|
15538
|
-
if (__privateGet(this, _entries).get(internalKey) === createdRecord)
|
|
15539
|
-
__privateGet(this, _entries).delete(internalKey);
|
|
15540
|
-
throw error;
|
|
15541
|
-
});
|
|
15542
|
-
__privateGet(this, _entries).set(internalKey, record);
|
|
15543
|
-
}
|
|
15544
|
-
record.claims++;
|
|
15545
|
-
let entry;
|
|
15546
|
-
try {
|
|
15547
|
-
entry = await record.entry;
|
|
15548
|
-
} catch (error) {
|
|
15549
|
-
record.claims--;
|
|
15550
|
-
throw error;
|
|
15551
|
-
}
|
|
15552
|
-
if (entry.closed) {
|
|
15553
|
-
record.claims--;
|
|
15554
|
-
return this.acquire(gateway, config, consumer);
|
|
15555
|
-
}
|
|
15556
|
-
const token = Symbol(key);
|
|
15557
|
-
entry.consumers.set(token, consumer);
|
|
15558
|
-
let released = false;
|
|
15559
|
-
return {
|
|
15560
|
-
key,
|
|
15561
|
-
release: async () => {
|
|
15562
|
-
if (released)
|
|
15563
|
-
return;
|
|
15564
|
-
released = true;
|
|
15565
|
-
entry.consumers.delete(token);
|
|
15566
|
-
record.claims--;
|
|
15567
|
-
if (record.claims > 0)
|
|
15568
|
-
return;
|
|
15569
|
-
if (__privateGet(this, _entries).get(internalKey) === record)
|
|
15570
|
-
__privateGet(this, _entries).delete(internalKey);
|
|
15571
|
-
__privateMethod(this, _MulticastSubscriptionPool_instances, detach_fn).call(this, entry);
|
|
15572
|
-
if (!entry.closed)
|
|
15573
|
-
await entry.subscription.unsubscribe();
|
|
15574
|
-
}
|
|
15575
|
-
};
|
|
15576
|
-
}
|
|
15577
|
-
get size() {
|
|
15578
|
-
return __privateGet(this, _entries).size;
|
|
15579
|
-
}
|
|
15580
|
-
};
|
|
15581
|
-
_entries = new WeakMap();
|
|
15582
|
-
_gatewayIds = new WeakMap();
|
|
15583
|
-
_nextGatewayId = new WeakMap();
|
|
15584
|
-
_MulticastSubscriptionPool_instances = new WeakSet();
|
|
15585
|
-
createEntry_fn = async function(gateway, endpoint, onClosed) {
|
|
15586
|
-
const subscription = await gateway.subscribe(toWindowMulticastSubscribeConfig({
|
|
15587
|
-
group: endpoint.groupAddress,
|
|
15588
|
-
...endpoint.sourceAddress ? { source: endpoint.sourceAddress } : {},
|
|
15589
|
-
port: endpoint.port,
|
|
15590
|
-
...endpoint.mode ? { mode: endpoint.mode } : {},
|
|
15591
|
-
...endpoint.amtRelay ? { amtRelay: endpoint.amtRelay } : {},
|
|
15592
|
-
...endpoint.amtRelayPort ? { amtRelayPort: endpoint.amtRelayPort } : {}
|
|
15593
|
-
}));
|
|
15594
|
-
const consumers = /* @__PURE__ */ new Map();
|
|
15595
|
-
let entry;
|
|
15596
|
-
const bytesListener = (event) => {
|
|
15597
|
-
__privateMethod(this, _MulticastSubscriptionPool_instances, dispatch_fn).call(this, consumers, (consumer) => consumer.onBytes(event.detail));
|
|
15598
|
-
};
|
|
15599
|
-
const stateListener = (event) => {
|
|
15600
|
-
__privateMethod(this, _MulticastSubscriptionPool_instances, dispatch_fn).call(this, consumers, (consumer) => consumer.onState?.(event.detail));
|
|
15601
|
-
if (event.detail === "closed" && !entry.closed) {
|
|
15602
|
-
entry.closed = true;
|
|
15603
|
-
onClosed(entry);
|
|
15604
|
-
}
|
|
15605
|
-
};
|
|
15606
|
-
const errorListener = (event) => {
|
|
15607
|
-
const error = event.detail instanceof Error ? event.detail : new Error(String(event.detail));
|
|
15608
|
-
__privateMethod(this, _MulticastSubscriptionPool_instances, dispatch_fn).call(this, consumers, (consumer) => consumer.onError?.(error));
|
|
15609
|
-
};
|
|
15610
|
-
entry = { subscription, consumers, bytesListener, stateListener, errorListener, closed: false };
|
|
15611
|
-
subscription.addEventListener("bytes", bytesListener);
|
|
15612
|
-
subscription.addEventListener("state", stateListener);
|
|
15613
|
-
subscription.addEventListener("error", errorListener);
|
|
15614
|
-
return entry;
|
|
15615
|
-
};
|
|
15616
|
-
gatewayId_fn = function(gateway) {
|
|
15617
|
-
const object = gateway;
|
|
15618
|
-
let id = __privateGet(this, _gatewayIds).get(object);
|
|
15619
|
-
if (!id) {
|
|
15620
|
-
id = __privateWrapper(this, _nextGatewayId)._++;
|
|
15621
|
-
__privateGet(this, _gatewayIds).set(object, id);
|
|
15622
|
-
}
|
|
15623
|
-
return id;
|
|
15624
|
-
};
|
|
15625
|
-
detach_fn = function(entry) {
|
|
15626
|
-
entry.subscription.removeEventListener("bytes", entry.bytesListener);
|
|
15627
|
-
entry.subscription.removeEventListener("state", entry.stateListener);
|
|
15628
|
-
entry.subscription.removeEventListener("error", entry.errorListener);
|
|
15629
|
-
};
|
|
15630
|
-
dispatch_fn = function(consumers, callback) {
|
|
15631
|
-
for (const consumer of consumers.values()) {
|
|
15632
|
-
try {
|
|
15633
|
-
callback(consumer);
|
|
15634
|
-
} catch (error) {
|
|
15635
|
-
console.error("[Transport] multicast consumer callback failed", error);
|
|
15636
|
-
}
|
|
15637
|
-
}
|
|
15638
|
-
};
|
|
15639
|
-
var multicastSubscriptionPool = new MulticastSubscriptionPool();
|
|
15640
|
-
|
|
15641
|
-
// node_modules/@blockcast/mmt-transport/dist/transport-manager.js
|
|
15642
|
-
var TransportManager = class {
|
|
15643
|
-
constructor() {
|
|
15644
|
-
__publicField(this, "state", {
|
|
15645
|
-
mode: "disconnected",
|
|
15646
|
-
connected: false,
|
|
15647
|
-
packetsReceived: 0,
|
|
15648
|
-
bytesReceived: 0,
|
|
15649
|
-
lastPacketTime: 0
|
|
15650
|
-
});
|
|
15651
|
-
__publicField(this, "capabilities", null);
|
|
15652
|
-
__publicField(this, "subscription", null);
|
|
15653
|
-
__publicField(this, "driad", new DRIADDiscovery());
|
|
15654
|
-
__publicField(this, "abr");
|
|
15655
|
-
__publicField(this, "multicastOrchestrator", null);
|
|
15656
|
-
__publicField(this, "onOrchestratorCommand");
|
|
15657
|
-
__publicField(this, "connectionGeneration", 0);
|
|
15658
|
-
__publicField(this, "onPacket");
|
|
15659
|
-
__publicField(this, "onError");
|
|
15660
|
-
__publicField(this, "onStateChange");
|
|
15661
|
-
this.abr = new AdaptiveBitrateController();
|
|
15662
|
-
}
|
|
15663
|
-
getWindow() {
|
|
15664
|
-
return typeof window === "undefined" ? void 0 : window;
|
|
15665
|
-
}
|
|
15666
|
-
getWindowMulticastGateway() {
|
|
15667
|
-
return this.getWindow()?.multicast;
|
|
15668
|
-
}
|
|
15669
|
-
async getGatewayCapabilities(gateway) {
|
|
15670
|
-
if (!gateway?.capabilities)
|
|
15671
|
-
return void 0;
|
|
15672
|
-
try {
|
|
15673
|
-
return typeof gateway.capabilities === "function" ? await gateway.capabilities() : gateway.capabilities;
|
|
15674
|
-
} catch (error) {
|
|
15675
|
-
console.warn("[Transport] window.multicast capabilities failed:", error);
|
|
15676
|
-
return void 0;
|
|
15677
|
-
}
|
|
15678
|
-
}
|
|
15679
|
-
async gatewaySupportsBytes(gateway) {
|
|
15680
|
-
if (typeof gateway?.subscribe !== "function")
|
|
15681
|
-
return false;
|
|
15682
|
-
if (!gateway.capabilities)
|
|
15683
|
-
return true;
|
|
15684
|
-
const capabilities = await this.getGatewayCapabilities(gateway);
|
|
15685
|
-
if (!capabilities)
|
|
15686
|
-
return false;
|
|
15687
|
-
const consumeModes = capabilities.consumeModes;
|
|
15688
|
-
return !consumeModes || consumeModes.includes("bytes");
|
|
15689
|
-
}
|
|
15690
|
-
/**
|
|
15691
|
-
* Detect available transport capabilities
|
|
15692
|
-
*/
|
|
15693
|
-
async detectCapabilities() {
|
|
15694
|
-
if (this.capabilities)
|
|
15695
|
-
return this.capabilities;
|
|
15696
|
-
const gateway = this.getWindowMulticastGateway();
|
|
15697
|
-
const hasGatewayBytes = await this.gatewaySupportsBytes(gateway);
|
|
15698
|
-
const win = this.getWindow();
|
|
15699
|
-
this.capabilities = {
|
|
15700
|
-
nativeSSM: hasGatewayBytes,
|
|
15701
|
-
nativeIGMP: hasGatewayBytes,
|
|
15702
|
-
directSockets: typeof win?.UDPSocket !== "undefined",
|
|
15703
|
-
amt: false,
|
|
15704
|
-
driad: true,
|
|
15705
|
-
// DRIAD uses HTTP proxy, always available
|
|
15706
|
-
fecDecode: false
|
|
15707
|
-
};
|
|
15708
|
-
console.log("[Transport] Capabilities:", this.capabilities);
|
|
15709
|
-
return this.capabilities;
|
|
15710
|
-
}
|
|
15711
|
-
/**
|
|
15712
|
-
* Connect using best available transport
|
|
15713
|
-
*
|
|
15714
|
-
* Promotion hierarchy:
|
|
15715
|
-
* 1. window.multicast gateway bytes
|
|
15716
|
-
* 2. AMT proxy (specified or discovered via DRIAD)
|
|
15717
|
-
* 3. MOQ WebTransport (final fallback)
|
|
15718
|
-
*/
|
|
15719
|
-
async connect(config) {
|
|
15720
|
-
const generation = ++this.connectionGeneration;
|
|
15721
|
-
this.subscription?.close();
|
|
15722
|
-
this.subscription = null;
|
|
15723
|
-
this.state = {
|
|
15724
|
-
mode: "disconnected",
|
|
15725
|
-
connected: false,
|
|
15726
|
-
packetsReceived: 0,
|
|
15727
|
-
bytesReceived: 0,
|
|
15728
|
-
lastPacketTime: 0
|
|
15729
|
-
};
|
|
15730
|
-
this.onStateChange?.(this.state);
|
|
15731
|
-
const endpoint = normalizeMulticastEndpoint({
|
|
15732
|
-
groupAddress: config.group,
|
|
15733
|
-
sourceAddress: config.source,
|
|
15734
|
-
port: config.port,
|
|
15735
|
-
mode: config.mode,
|
|
15736
|
-
amtRelay: config.amtRelay,
|
|
15737
|
-
amtRelayPort: config.amtRelayPort
|
|
15738
|
-
});
|
|
15739
|
-
if (!endpoint) {
|
|
15740
|
-
const error = new Error("Invalid multicast endpoint configuration");
|
|
15741
|
-
this.onError?.(error);
|
|
15742
|
-
return false;
|
|
15743
|
-
}
|
|
15744
|
-
config = {
|
|
15745
|
-
...config,
|
|
15746
|
-
group: endpoint.groupAddress,
|
|
15747
|
-
source: endpoint.sourceAddress ?? "",
|
|
15748
|
-
mode: endpoint.mode,
|
|
15749
|
-
amtRelay: endpoint.amtRelay,
|
|
15750
|
-
amtRelayPort: endpoint.amtRelayPort
|
|
15751
|
-
};
|
|
15752
|
-
const caps = await this.detectCapabilities();
|
|
15753
|
-
if (generation !== this.connectionGeneration)
|
|
15754
|
-
return false;
|
|
15755
|
-
if (config.initialQuality) {
|
|
15756
|
-
this.abr = new AdaptiveBitrateController(config.initialQuality);
|
|
15757
|
-
}
|
|
15758
|
-
this.abr.setCallback((quality) => {
|
|
15759
|
-
this.state.quality = quality;
|
|
15760
|
-
this.subscription?.onqualitychange?.(quality);
|
|
15761
|
-
this.onStateChange?.(this.state);
|
|
15762
|
-
});
|
|
15763
|
-
let connected = false;
|
|
15764
|
-
if (caps.nativeSSM || caps.nativeIGMP) {
|
|
15765
|
-
console.log("[Transport] Attempting window.multicast...");
|
|
15766
|
-
connected = await this.connectWindowMulticast(config, generation);
|
|
15767
|
-
if (connected) {
|
|
15768
|
-
if (generation !== this.connectionGeneration)
|
|
15769
|
-
return false;
|
|
15770
|
-
this.state.mode = "multicast";
|
|
15771
|
-
return true;
|
|
15772
|
-
}
|
|
15773
|
-
}
|
|
15774
|
-
if (!connected) {
|
|
15775
|
-
console.log("[Transport] Attempting AMT tunnel...");
|
|
15776
|
-
connected = await this.connectAmt(config, generation);
|
|
15777
|
-
if (connected) {
|
|
15778
|
-
if (generation !== this.connectionGeneration)
|
|
15779
|
-
return false;
|
|
15780
|
-
this.state.mode = "amt";
|
|
15781
|
-
return true;
|
|
15782
|
-
}
|
|
15783
|
-
}
|
|
15784
|
-
console.log("[Transport] All multicast transports failed, falling back to MOQ");
|
|
15785
|
-
return false;
|
|
15786
|
-
}
|
|
15787
|
-
/**
|
|
15788
|
-
* Connect via the lowercase window.multicast bytes gateway.
|
|
15789
|
-
*/
|
|
15790
|
-
async connectWindowMulticast(config, generation) {
|
|
15791
|
-
try {
|
|
15792
|
-
const gateway = this.getWindowMulticastGateway();
|
|
15793
|
-
if (typeof gateway?.subscribe !== "function")
|
|
15794
|
-
return false;
|
|
15795
|
-
const lease = await multicastSubscriptionPool.acquire(gateway, config, {
|
|
15796
|
-
onBytes: (data) => {
|
|
15797
|
-
if (generation === this.connectionGeneration)
|
|
15798
|
-
this.handlePacket(data);
|
|
15799
|
-
},
|
|
15800
|
-
onState: (state) => {
|
|
15801
|
-
if (generation !== this.connectionGeneration)
|
|
15802
|
-
return;
|
|
15803
|
-
this.state.connected = state !== "closed";
|
|
15804
|
-
this.onStateChange?.(this.state);
|
|
15805
|
-
},
|
|
15806
|
-
onError: (error) => {
|
|
15807
|
-
if (generation !== this.connectionGeneration)
|
|
15808
|
-
return;
|
|
15809
|
-
console.error("[Transport] window.multicast error:", error);
|
|
15810
|
-
this.onError?.(error);
|
|
15811
|
-
}
|
|
15812
|
-
});
|
|
15813
|
-
if (generation !== this.connectionGeneration) {
|
|
15814
|
-
await lease.release();
|
|
15815
|
-
return false;
|
|
15816
|
-
}
|
|
15817
|
-
this.subscription = {
|
|
15818
|
-
id: lease.key,
|
|
15819
|
-
config,
|
|
15820
|
-
close: () => {
|
|
15821
|
-
void lease.release();
|
|
15822
|
-
}
|
|
15823
|
-
};
|
|
15824
|
-
this.state = {
|
|
15825
|
-
mode: "multicast",
|
|
15826
|
-
connected: true,
|
|
15827
|
-
group: config.group,
|
|
15828
|
-
source: config.source,
|
|
15829
|
-
quality: this.abr.getCurrentQuality(),
|
|
15830
|
-
packetsReceived: 0,
|
|
15831
|
-
bytesReceived: 0,
|
|
15832
|
-
lastPacketTime: 0
|
|
15833
|
-
};
|
|
15834
|
-
console.log(`[Transport] Connected via window.multicast: (${config.source}, ${config.group})`);
|
|
15835
|
-
this.onStateChange?.(this.state);
|
|
15836
|
-
return true;
|
|
15837
|
-
} catch (error) {
|
|
15838
|
-
console.warn("[Transport] window.multicast failed:", error);
|
|
15839
|
-
return false;
|
|
15840
|
-
}
|
|
15841
|
-
}
|
|
15842
|
-
/**
|
|
15843
|
-
* Connect via AMT proxy (with optional DRIAD discovery)
|
|
15844
|
-
*/
|
|
15845
|
-
async connectAmt(config, generation) {
|
|
15846
|
-
try {
|
|
15847
|
-
let amtRelay = config.amtRelay;
|
|
15848
|
-
if (!amtRelay && config.driadEnabled !== false && config.source) {
|
|
15849
|
-
console.log(`[Transport] Discovering AMT relay via DRIAD for source ${config.source}...`);
|
|
15850
|
-
const relays = await this.driad.discoverRelays(config.source);
|
|
15851
|
-
if (generation !== this.connectionGeneration)
|
|
15852
|
-
return false;
|
|
15853
|
-
const bestRelay = await this.driad.selectBestRelay(relays);
|
|
15854
|
-
if (generation !== this.connectionGeneration)
|
|
15855
|
-
return false;
|
|
15856
|
-
if (bestRelay) {
|
|
15857
|
-
amtRelay = bestRelay.host;
|
|
15858
|
-
this.state.amtRelay = amtRelay;
|
|
15859
|
-
}
|
|
15860
|
-
}
|
|
15861
|
-
if (!amtRelay) {
|
|
15862
|
-
console.warn("[Transport] No AMT relay available (DRIAD requires source address for SSM)");
|
|
15863
|
-
return false;
|
|
15864
|
-
}
|
|
15865
|
-
return await this.connectAmtProxy(config, amtRelay, generation);
|
|
15866
|
-
} catch (error) {
|
|
15867
|
-
console.warn("[Transport] AMT connection failed:", error);
|
|
15868
|
-
return false;
|
|
15869
|
-
}
|
|
15870
|
-
}
|
|
15871
|
-
/**
|
|
15872
|
-
* Connect via WebSocket-based AMT proxy (browser fallback)
|
|
15873
|
-
*/
|
|
15874
|
-
async connectAmtProxy(config, amtRelay, generation) {
|
|
15875
|
-
try {
|
|
15876
|
-
const relayHost = amtRelay.includes(":") && !amtRelay.startsWith("[") ? `[${amtRelay}]` : amtRelay;
|
|
15877
|
-
const relayAuthority = config.amtRelayPort ? `${relayHost}:${config.amtRelayPort}` : relayHost;
|
|
15878
|
-
const wsUrl = new URL(`wss://${relayAuthority}/amt-proxy`);
|
|
15879
|
-
wsUrl.searchParams.set("group", config.group);
|
|
15880
|
-
wsUrl.searchParams.set("source", config.source || "");
|
|
15881
|
-
wsUrl.searchParams.set("port", String(config.port));
|
|
15882
|
-
const ws = new WebSocket(wsUrl);
|
|
15883
|
-
return new Promise((resolve, reject) => {
|
|
15884
|
-
ws.binaryType = "arraybuffer";
|
|
15885
|
-
const timeout = setTimeout(() => {
|
|
15886
|
-
ws.close();
|
|
15887
|
-
reject(new Error("AMT proxy connection timeout"));
|
|
15888
|
-
}, 1e4);
|
|
15889
|
-
ws.onopen = () => {
|
|
15890
|
-
clearTimeout(timeout);
|
|
15891
|
-
if (generation !== this.connectionGeneration) {
|
|
15892
|
-
ws.close();
|
|
15893
|
-
resolve(false);
|
|
15894
|
-
return;
|
|
15895
|
-
}
|
|
15896
|
-
console.log(`[Transport] Connected to AMT proxy: ${amtRelay}`);
|
|
15897
|
-
this.subscription = {
|
|
15898
|
-
id: `amt-proxy-${Date.now()}`,
|
|
15899
|
-
config,
|
|
15900
|
-
close: () => ws.close()
|
|
15901
|
-
};
|
|
15902
|
-
this.state = {
|
|
15903
|
-
mode: "amt",
|
|
15904
|
-
connected: true,
|
|
15905
|
-
group: config.group,
|
|
15906
|
-
source: config.source,
|
|
15907
|
-
amtRelay,
|
|
15908
|
-
quality: this.abr.getCurrentQuality(),
|
|
15909
|
-
packetsReceived: 0,
|
|
15910
|
-
bytesReceived: 0,
|
|
15911
|
-
lastPacketTime: 0
|
|
15912
|
-
};
|
|
15913
|
-
this.onStateChange?.(this.state);
|
|
15914
|
-
resolve(true);
|
|
15915
|
-
};
|
|
15916
|
-
ws.onmessage = (event) => {
|
|
15917
|
-
if (generation === this.connectionGeneration && event.data instanceof ArrayBuffer) {
|
|
15918
|
-
this.handlePacket(event.data);
|
|
15919
|
-
}
|
|
15920
|
-
};
|
|
15921
|
-
ws.onerror = (error) => {
|
|
15922
|
-
clearTimeout(timeout);
|
|
15923
|
-
if (generation !== this.connectionGeneration) {
|
|
15924
|
-
resolve(false);
|
|
15925
|
-
return;
|
|
15926
|
-
}
|
|
15927
|
-
console.error("[Transport] AMT proxy error:", error);
|
|
15928
|
-
reject(error);
|
|
15929
|
-
};
|
|
15930
|
-
ws.onclose = () => {
|
|
15931
|
-
clearTimeout(timeout);
|
|
15932
|
-
if (generation !== this.connectionGeneration) {
|
|
15933
|
-
resolve(false);
|
|
15934
|
-
return;
|
|
15935
|
-
}
|
|
15936
|
-
this.state.connected = false;
|
|
15937
|
-
this.onStateChange?.(this.state);
|
|
15938
|
-
resolve(false);
|
|
15939
|
-
};
|
|
15940
|
-
});
|
|
15941
|
-
} catch (error) {
|
|
15942
|
-
console.warn("[Transport] AMT proxy failed:", error);
|
|
15943
|
-
return false;
|
|
15944
|
-
}
|
|
15945
|
-
}
|
|
15946
|
-
/**
|
|
15947
|
-
* Handle received packet
|
|
15948
|
-
*/
|
|
15949
|
-
handlePacket(data) {
|
|
15950
|
-
this.state.packetsReceived++;
|
|
15951
|
-
this.state.bytesReceived += data.byteLength;
|
|
15952
|
-
this.state.lastPacketTime = Date.now();
|
|
15953
|
-
this.abr.recordSample(data.byteLength, 0);
|
|
15954
|
-
this.onPacket?.(data);
|
|
15955
|
-
}
|
|
15956
|
-
/**
|
|
15957
|
-
* Get current transport state
|
|
15958
|
-
*/
|
|
15959
|
-
getState() {
|
|
15960
|
-
return { ...this.state };
|
|
15961
|
-
}
|
|
15962
|
-
/**
|
|
15963
|
-
* Get current quality level
|
|
15964
|
-
*/
|
|
15965
|
-
getQuality() {
|
|
15966
|
-
return this.abr.getCurrentQuality();
|
|
15967
|
-
}
|
|
15968
|
-
/**
|
|
15969
|
-
* Force quality level
|
|
15970
|
-
*/
|
|
15971
|
-
setQuality(name) {
|
|
15972
|
-
this.abr.setQuality(name);
|
|
15973
|
-
}
|
|
15974
|
-
/** Configures multicast-only ABR while leaving the caller's MoQ ABR untouched. */
|
|
15975
|
-
configureMulticastTiers(options, onCommand) {
|
|
15976
|
-
this.dispatchOrchestratorCommand(this.multicastOrchestrator?.cancel());
|
|
15977
|
-
this.multicastOrchestrator = new MulticastOrchestrator(options);
|
|
15978
|
-
this.onOrchestratorCommand = onCommand;
|
|
15979
|
-
return this.multicastOrchestrator.start();
|
|
15980
|
-
}
|
|
15981
|
-
recordMulticastGoodput(bitrate) {
|
|
15982
|
-
return this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onGoodput({ bitrate }));
|
|
15983
|
-
}
|
|
15984
|
-
recordMulticastHeadroom(healthy) {
|
|
15985
|
-
return this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onHeadroom({ healthy }));
|
|
15986
|
-
}
|
|
15987
|
-
recordMulticastTierPacket(packet) {
|
|
15988
|
-
return this.dispatchOrchestratorCommand(this.multicastOrchestrator?.onTierPacket(packet));
|
|
15989
|
-
}
|
|
15990
|
-
pollMulticastTierSwitch() {
|
|
15991
|
-
return this.dispatchOrchestratorCommand(this.multicastOrchestrator?.poll());
|
|
15992
|
-
}
|
|
15993
|
-
/**
|
|
15994
|
-
* Set packet callback
|
|
15995
|
-
*/
|
|
15996
|
-
setPacketCallback(callback) {
|
|
15997
|
-
this.onPacket = callback;
|
|
15998
|
-
}
|
|
15999
|
-
/**
|
|
16000
|
-
* Set error callback
|
|
16001
|
-
*/
|
|
16002
|
-
setErrorCallback(callback) {
|
|
16003
|
-
this.onError = callback;
|
|
16004
|
-
}
|
|
16005
|
-
/**
|
|
16006
|
-
* Set state change callback
|
|
16007
|
-
*/
|
|
16008
|
-
setStateCallback(callback) {
|
|
16009
|
-
this.onStateChange = callback;
|
|
16010
|
-
}
|
|
16011
|
-
/**
|
|
16012
|
-
* Disconnect
|
|
16013
|
-
*/
|
|
16014
|
-
disconnect() {
|
|
16015
|
-
this.connectionGeneration++;
|
|
16016
|
-
this.subscription?.close();
|
|
16017
|
-
this.subscription = null;
|
|
16018
|
-
this.dispatchOrchestratorCommand(this.multicastOrchestrator?.cancel());
|
|
16019
|
-
this.multicastOrchestrator = null;
|
|
16020
|
-
this.onOrchestratorCommand = void 0;
|
|
16021
|
-
this.state = {
|
|
16022
|
-
mode: "disconnected",
|
|
16023
|
-
connected: false,
|
|
16024
|
-
packetsReceived: 0,
|
|
16025
|
-
bytesReceived: 0,
|
|
16026
|
-
lastPacketTime: 0
|
|
16027
|
-
};
|
|
16028
|
-
this.onStateChange?.(this.state);
|
|
16029
|
-
}
|
|
16030
|
-
dispatchOrchestratorCommand(command) {
|
|
16031
|
-
if (command)
|
|
16032
|
-
this.onOrchestratorCommand?.(command);
|
|
16033
|
-
return command;
|
|
16034
|
-
}
|
|
16035
|
-
};
|
|
16036
|
-
var transportManager = new TransportManager();
|
|
16037
|
-
|
|
16038
|
-
// node_modules/@blockcast/mmt-transport/dist/moqt-priority.js
|
|
16039
|
-
var MAX_MOQT_PRIORITY = 255;
|
|
16040
|
-
function moqtPriority(value, field) {
|
|
16041
|
-
if (!Number.isInteger(value) || value < 0 || value > MAX_MOQT_PRIORITY) {
|
|
16042
|
-
throw new RangeError(`${field} must be an integer in [0, ${MAX_MOQT_PRIORITY}], got ${value}`);
|
|
16043
|
-
}
|
|
16044
|
-
return value;
|
|
16045
|
-
}
|
|
16046
|
-
function moqtSubscriberPriority(value) {
|
|
16047
|
-
return moqtPriority(value, "MOQT subscriber priority");
|
|
16048
|
-
}
|
|
16049
|
-
function moqtPublisherPriority(value) {
|
|
16050
|
-
return moqtPriority(value, "MOQT publisher priority");
|
|
16051
|
-
}
|
|
16052
|
-
var MOQT_SUBSCRIBER_PRIORITY = Object.freeze({
|
|
16053
|
-
catalog: moqtSubscriberPriority(0),
|
|
16054
|
-
deadlineRepair: moqtSubscriberPriority(0),
|
|
16055
|
-
audio: moqtSubscriberPriority(1),
|
|
16056
|
-
video: moqtSubscriberPriority(2),
|
|
16057
|
-
backgroundRepair: moqtSubscriberPriority(3)
|
|
16058
|
-
});
|
|
16059
|
-
var MOQT_PUBLISHER_PRIORITY = Object.freeze({
|
|
16060
|
-
catalog: moqtPublisherPriority(32),
|
|
16061
|
-
source: moqtPublisherPriority(128),
|
|
16062
|
-
repair: moqtPublisherPriority(240)
|
|
16063
|
-
});
|
|
16064
|
-
|
|
16065
|
-
// node_modules/@blockcast/mmt-transport/dist/amt-gateway.js
|
|
16066
|
-
var DEFAULT_AMT_PORT = 2268;
|
|
16067
|
-
var AMTGatewayManager = class {
|
|
16068
|
-
constructor() {
|
|
16069
|
-
__publicField(this, "subscriptions", /* @__PURE__ */ new Map());
|
|
16070
|
-
__publicField(this, "messageHandler");
|
|
16071
|
-
__publicField(this, "pendingPackets", /* @__PURE__ */ new Map());
|
|
16072
|
-
__publicField(this, "driad", new DRIADDiscovery());
|
|
16073
|
-
this.messageHandler = (event) => {
|
|
16074
|
-
const data = event.data;
|
|
16075
|
-
console.log(`[AMT] SW message received:`, data.type);
|
|
16076
|
-
if (data.type === "UDP_PACKET") {
|
|
16077
|
-
this.handlePacket(data);
|
|
16078
|
-
} else if (data.type === "SOCKET_ERROR") {
|
|
16079
|
-
this.handleSocketError(data);
|
|
16080
|
-
} else if (data.type === "DEBUG_LOG") {
|
|
16081
|
-
console.log(`[AMT SW Debug] ${data.message}`);
|
|
16082
|
-
}
|
|
16083
|
-
};
|
|
16084
|
-
if (typeof navigator !== "undefined" && navigator.serviceWorker) {
|
|
16085
|
-
navigator.serviceWorker.addEventListener("message", this.messageHandler);
|
|
16086
|
-
console.log("[AMT] Registered service worker message handler");
|
|
16087
|
-
} else {
|
|
16088
|
-
console.warn("[AMT] Service worker not available");
|
|
16089
|
-
}
|
|
16090
|
-
}
|
|
16091
|
-
/**
|
|
16092
|
-
* Subscribe via AMT tunnel
|
|
16093
|
-
*/
|
|
16094
|
-
async subscribe(config) {
|
|
16095
|
-
const { key } = config;
|
|
16096
|
-
const keyStr = this.getKeyString(key);
|
|
16097
|
-
if (this.subscriptions.has(keyStr)) {
|
|
16098
|
-
console.log(`[AMT] Reusing existing subscription to ${keyStr}`);
|
|
16099
|
-
return keyStr;
|
|
16100
|
-
}
|
|
16101
|
-
try {
|
|
16102
|
-
let relayAddress;
|
|
16103
|
-
let discoveryMethod;
|
|
16104
|
-
const relayPort = config.relayPort || DEFAULT_AMT_PORT;
|
|
16105
|
-
if (config.relayAddress) {
|
|
16106
|
-
relayAddress = config.relayAddress;
|
|
16107
|
-
discoveryMethod = "manual";
|
|
16108
|
-
console.log(`[AMT] Using configured relay ${relayAddress}:${relayPort}`);
|
|
16109
|
-
} else if (key.source) {
|
|
16110
|
-
console.log(`[AMT] No relay configured, discovering via DRIAD for source ${key.source}...`);
|
|
16111
|
-
const relays = await this.driad.discoverRelays(key.source);
|
|
16112
|
-
const bestRelay = await this.driad.selectBestRelay(relays);
|
|
16113
|
-
if (!bestRelay) {
|
|
16114
|
-
throw new Error(`AMT relay discovery failed for source ${key.source}. Please specify a relay address in the subscription configuration.`);
|
|
16115
|
-
}
|
|
16116
|
-
relayAddress = bestRelay.host;
|
|
16117
|
-
discoveryMethod = "driad";
|
|
16118
|
-
console.log(`[AMT] Discovered relay via DRIAD: ${relayAddress}:${relayPort}`);
|
|
16119
|
-
} else {
|
|
16120
|
-
throw new Error(`AMT relay discovery requires a source address for SSM (RFC 8777). Please specify either a relay address or source address.`);
|
|
16121
|
-
}
|
|
16122
|
-
const subscriptionId = keyStr;
|
|
16123
|
-
this.pendingPackets.set(subscriptionId, []);
|
|
16124
|
-
const subscription = {
|
|
16125
|
-
key,
|
|
16126
|
-
subscriptionId,
|
|
16127
|
-
config,
|
|
16128
|
-
handshakeComplete: false,
|
|
16129
|
-
relay: {
|
|
16130
|
-
address: relayAddress,
|
|
16131
|
-
port: relayPort,
|
|
16132
|
-
discoveryMethod,
|
|
16133
|
-
discoveredAt: Date.now()
|
|
16134
|
-
}
|
|
16135
|
-
};
|
|
16136
|
-
this.subscriptions.set(keyStr, subscription);
|
|
16137
|
-
console.log(`[AMT] Subscription created for ${keyStr} via ${relayAddress}:${relayPort}`);
|
|
16138
|
-
return keyStr;
|
|
16139
|
-
} catch (error) {
|
|
16140
|
-
const subscription = this.subscriptions.get(keyStr);
|
|
16141
|
-
if (subscription) {
|
|
16142
|
-
this.subscriptions.delete(keyStr);
|
|
16143
|
-
this.pendingPackets.delete(subscription.subscriptionId);
|
|
16144
|
-
}
|
|
16145
|
-
throw new Error(`Failed to subscribe via AMT: ${error instanceof Error ? error.message : String(error)}`);
|
|
16146
|
-
}
|
|
16147
|
-
}
|
|
16148
|
-
/**
|
|
16149
|
-
* Unsubscribe from AMT tunnel
|
|
16150
|
-
*/
|
|
16151
|
-
async unsubscribe(handle) {
|
|
16152
|
-
const subscription = this.subscriptions.get(handle);
|
|
16153
|
-
if (!subscription) {
|
|
16154
|
-
throw new Error(`Subscription ${handle} not found`);
|
|
16155
|
-
}
|
|
16156
|
-
try {
|
|
16157
|
-
if (subscription.keepAliveTimer) {
|
|
16158
|
-
clearInterval(subscription.keepAliveTimer);
|
|
16159
|
-
}
|
|
16160
|
-
console.log(`[AMT] Unsubscribed from ${handle}`);
|
|
16161
|
-
} catch (error) {
|
|
16162
|
-
console.error("[AMT] Error during unsubscribe:", error);
|
|
16163
|
-
} finally {
|
|
16164
|
-
this.subscriptions.delete(handle);
|
|
16165
|
-
this.pendingPackets.delete(subscription.subscriptionId);
|
|
16166
|
-
}
|
|
16167
|
-
}
|
|
16168
|
-
/**
|
|
16169
|
-
* Get list of active subscriptions
|
|
16170
|
-
*/
|
|
16171
|
-
getSubscriptions() {
|
|
16172
|
-
return Array.from(this.subscriptions.keys());
|
|
16173
|
-
}
|
|
16174
|
-
/**
|
|
16175
|
-
* Handle packet from service worker
|
|
16176
|
-
*/
|
|
16177
|
-
handlePacket(message) {
|
|
16178
|
-
const { subscriptionId, data } = message;
|
|
16179
|
-
if (!subscriptionId)
|
|
16180
|
-
return;
|
|
16181
|
-
const subscription = this.subscriptions.get(subscriptionId);
|
|
16182
|
-
if (!subscription) {
|
|
16183
|
-
console.warn(`[AMT] Received packet for unknown subscription: ${subscriptionId}`);
|
|
16184
|
-
return;
|
|
16185
|
-
}
|
|
16186
|
-
if (!subscription.handshakeComplete) {
|
|
16187
|
-
const queue = this.pendingPackets.get(subscriptionId);
|
|
16188
|
-
const packetData2 = data;
|
|
16189
|
-
if (queue) {
|
|
16190
|
-
queue.push({
|
|
16191
|
-
data: packetData2.data,
|
|
16192
|
-
remoteAddress: packetData2.remoteAddress,
|
|
16193
|
-
remotePort: packetData2.remotePort
|
|
16194
|
-
});
|
|
16195
|
-
}
|
|
16196
|
-
return;
|
|
16197
|
-
}
|
|
16198
|
-
const packetData = data;
|
|
16199
|
-
const metadata = {
|
|
16200
|
-
sourceAddress: subscription.relay?.address || "unknown",
|
|
16201
|
-
sourcePort: subscription.relay?.port || 0,
|
|
16202
|
-
transport: "tunnel",
|
|
16203
|
-
timestamp: performance.now()
|
|
16204
|
-
};
|
|
16205
|
-
subscription.config.onPacket(packetData.data, metadata);
|
|
16206
|
-
}
|
|
16207
|
-
/**
|
|
16208
|
-
* Handle socket error from service worker
|
|
16209
|
-
*/
|
|
16210
|
-
handleSocketError(message) {
|
|
16211
|
-
const { subscriptionId, error } = message;
|
|
16212
|
-
if (!subscriptionId)
|
|
16213
|
-
return;
|
|
16214
|
-
const subscription = this.subscriptions.get(subscriptionId);
|
|
16215
|
-
if (!subscription) {
|
|
16216
|
-
console.warn(`[AMT] Received error for unknown subscription: ${subscriptionId}`);
|
|
16217
|
-
return;
|
|
16218
|
-
}
|
|
16219
|
-
subscription.config.onError(new Error(error || "Unknown error"));
|
|
16220
|
-
}
|
|
16221
|
-
/**
|
|
16222
|
-
* Cleanup on destroy
|
|
16223
|
-
*/
|
|
16224
|
-
destroy() {
|
|
16225
|
-
if (this.messageHandler && typeof navigator !== "undefined" && navigator.serviceWorker) {
|
|
16226
|
-
navigator.serviceWorker.removeEventListener("message", this.messageHandler);
|
|
16227
|
-
}
|
|
16228
|
-
const handles = Array.from(this.subscriptions.keys());
|
|
16229
|
-
for (const handle of handles) {
|
|
16230
|
-
this.unsubscribe(handle).catch((err) => {
|
|
16231
|
-
console.error(`[AMT] Error unsubscribing ${handle}:`, err);
|
|
16232
|
-
});
|
|
16233
|
-
}
|
|
16234
|
-
}
|
|
16235
|
-
/**
|
|
16236
|
-
* Get key string for HashMap
|
|
16237
|
-
*/
|
|
16238
|
-
getKeyString(key) {
|
|
16239
|
-
const source = key.source ? `@${key.source}` : "";
|
|
16240
|
-
return `${key.group}:${key.port}${source}`;
|
|
16241
|
-
}
|
|
16242
|
-
};
|
|
16243
|
-
var amtGatewayManager = new AMTGatewayManager();
|
|
16244
|
-
|
|
16245
14921
|
// src/multicast-transport.ts
|
|
16246
14922
|
var MAX_TRACKED_DROPPED_PIDS = 16;
|
|
16247
14923
|
function getMulticastGlobals() {
|
|
@@ -16250,13 +14926,19 @@ function getMulticastGlobals() {
|
|
|
16250
14926
|
function hasMulticastGateway() {
|
|
16251
14927
|
return typeof getMulticastGlobals().multicast?.subscribe === "function";
|
|
16252
14928
|
}
|
|
16253
|
-
|
|
14929
|
+
function normalizeResolvedTransport(value) {
|
|
14930
|
+
if (value === "native") return "native";
|
|
14931
|
+
if (value === "amt") return "amt";
|
|
14932
|
+
return void 0;
|
|
14933
|
+
}
|
|
14934
|
+
var _onPacket, _subscription2, _bytesListener, _transportListener, _resolvedTransport, _videoConfirmed, _audioConfirmed, _watchdog, _failbackWindowMs, _onTakeover, _onFailback, _disposed4, _stats3, _droppedPidCounts, _packetIds, _joinGeneration, _MulticastTransport_instances, releaseSubscription_fn, acceptPacket_fn, recordUnregisteredPidDrop_fn, checkTakeover_fn, resetWatchdog_fn;
|
|
16254
14935
|
var _MulticastTransport = class _MulticastTransport {
|
|
16255
14936
|
constructor(onPacket) {
|
|
16256
14937
|
__privateAdd(this, _MulticastTransport_instances);
|
|
16257
14938
|
__privateAdd(this, _onPacket);
|
|
16258
14939
|
__privateAdd(this, _subscription2, null);
|
|
16259
14940
|
__privateAdd(this, _bytesListener, null);
|
|
14941
|
+
__privateAdd(this, _transportListener, null);
|
|
16260
14942
|
__privateAdd(this, _resolvedTransport);
|
|
16261
14943
|
__privateAdd(this, _videoConfirmed, false);
|
|
16262
14944
|
__privateAdd(this, _audioConfirmed, false);
|
|
@@ -16292,17 +14974,17 @@ var _MulticastTransport = class _MulticastTransport {
|
|
|
16292
14974
|
console.log(
|
|
16293
14975
|
`[MulticastTransport] window.multicast.subscribe(${config.sourceAddress}, ${config.groupAddress}, ${config.port})`
|
|
16294
14976
|
);
|
|
16295
|
-
const
|
|
14977
|
+
const subscription = await getMulticastGlobals().multicast.subscribe({
|
|
16296
14978
|
source: config.sourceAddress,
|
|
16297
14979
|
group: config.groupAddress,
|
|
16298
14980
|
port: config.port,
|
|
14981
|
+
consume: "bytes",
|
|
16299
14982
|
mode: config.mode,
|
|
16300
14983
|
amtRelay: config.amtRelay,
|
|
16301
14984
|
amtRelayPort: config.amtRelayPort,
|
|
16302
14985
|
packetIds: config.packetIds,
|
|
16303
|
-
tracks: config.tracks
|
|
14986
|
+
tracks: config.tracks?.map(({ name, packetId }) => ({ name, packetId }))
|
|
16304
14987
|
});
|
|
16305
|
-
const subscription = await getMulticastGlobals().multicast.subscribe(subscribeConfig);
|
|
16306
14988
|
console.log(`[MulticastTransport] window.multicast.subscribe returned:`, !!subscription);
|
|
16307
14989
|
if (__privateGet(this, _disposed4) || generation !== __privateGet(this, _joinGeneration)) {
|
|
16308
14990
|
void subscription.unsubscribe();
|
|
@@ -16324,11 +15006,18 @@ var _MulticastTransport = class _MulticastTransport {
|
|
|
16324
15006
|
__privateGet(this, _onPacket).call(this, packet, context);
|
|
16325
15007
|
__privateMethod(this, _MulticastTransport_instances, resetWatchdog_fn).call(this);
|
|
16326
15008
|
};
|
|
15009
|
+
const onTransportChange = (event) => {
|
|
15010
|
+
if (__privateGet(this, _disposed4) || generation !== __privateGet(this, _joinGeneration)) return;
|
|
15011
|
+
const transport = normalizeResolvedTransport(event.detail);
|
|
15012
|
+
if (transport) __privateSet(this, _resolvedTransport, transport);
|
|
15013
|
+
};
|
|
16327
15014
|
__privateSet(this, _subscription2, subscription);
|
|
16328
15015
|
__privateSet(this, _bytesListener, onBytes);
|
|
15016
|
+
__privateSet(this, _transportListener, onTransportChange);
|
|
16329
15017
|
__privateSet(this, _packetIds, packetIds);
|
|
16330
|
-
__privateSet(this, _resolvedTransport,
|
|
15018
|
+
__privateSet(this, _resolvedTransport, normalizeResolvedTransport(subscription.transport));
|
|
16331
15019
|
subscription.addEventListener("bytes", onBytes);
|
|
15020
|
+
subscription.addEventListener("transportchange", onTransportChange);
|
|
16332
15021
|
__privateMethod(this, _MulticastTransport_instances, resetWatchdog_fn).call(this);
|
|
16333
15022
|
console.log(
|
|
16334
15023
|
`[MulticastTransport] Joined ${config.groupAddress}:${config.port}`
|
|
@@ -16396,6 +15085,7 @@ var _MulticastTransport = class _MulticastTransport {
|
|
|
16396
15085
|
_onPacket = new WeakMap();
|
|
16397
15086
|
_subscription2 = new WeakMap();
|
|
16398
15087
|
_bytesListener = new WeakMap();
|
|
15088
|
+
_transportListener = new WeakMap();
|
|
16399
15089
|
_resolvedTransport = new WeakMap();
|
|
16400
15090
|
_videoConfirmed = new WeakMap();
|
|
16401
15091
|
_audioConfirmed = new WeakMap();
|
|
@@ -16416,10 +15106,14 @@ releaseSubscription_fn = function() {
|
|
|
16416
15106
|
if (__privateGet(this, _bytesListener)) {
|
|
16417
15107
|
__privateGet(this, _subscription2).removeEventListener("bytes", __privateGet(this, _bytesListener));
|
|
16418
15108
|
}
|
|
15109
|
+
if (__privateGet(this, _transportListener)) {
|
|
15110
|
+
__privateGet(this, _subscription2).removeEventListener("transportchange", __privateGet(this, _transportListener));
|
|
15111
|
+
}
|
|
16419
15112
|
void __privateGet(this, _subscription2).unsubscribe();
|
|
16420
15113
|
}
|
|
16421
15114
|
__privateSet(this, _subscription2, null);
|
|
16422
15115
|
__privateSet(this, _bytesListener, null);
|
|
15116
|
+
__privateSet(this, _transportListener, null);
|
|
16423
15117
|
__privateSet(this, _resolvedTransport, void 0);
|
|
16424
15118
|
__privateSet(this, _packetIds, null);
|
|
16425
15119
|
};
|