@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.
Files changed (34) hide show
  1. package/dist/browser-runtime.js +39 -1345
  2. package/dist/browser-runtime.js.map +4 -4
  3. package/dist/cmaf-block-assembler.js.map +1 -1
  4. package/dist/cmaf-group-batcher.js.map +1 -1
  5. package/dist/mfu-reassembler.d.ts +7 -11
  6. package/dist/mfu-reassembler.d.ts.map +1 -1
  7. package/dist/mfu-reassembler.js +15 -11
  8. package/dist/mfu-reassembler.js.map +2 -2
  9. package/dist/mmtp-container-parser.js +15 -11
  10. package/dist/mmtp-container-parser.js.map +2 -2
  11. package/dist/mmtp-container-parser.no-modules.js +16 -13
  12. package/dist/mmtp-container-parser.no-modules.js.map +2 -2
  13. package/dist/mmtp-decode-pipeline.js +15 -11
  14. package/dist/mmtp-decode-pipeline.js.map +2 -2
  15. package/dist/mmtp-router.js +15 -11
  16. package/dist/mmtp-router.js.map +2 -2
  17. package/dist/mmtp-to-cmaf.d.ts.map +1 -1
  18. package/dist/mmtp-to-cmaf.js +1 -2
  19. package/dist/mmtp-to-cmaf.js.map +2 -2
  20. package/dist/multicast-manager.js +38 -1346
  21. package/dist/multicast-manager.js.map +4 -4
  22. package/dist/multicast-transport.d.ts +2 -1
  23. package/dist/multicast-transport.d.ts.map +1 -1
  24. package/dist/multicast-transport.js +23 -1335
  25. package/dist/multicast-transport.js.map +4 -4
  26. package/dist/no-modules.js +16 -13
  27. package/dist/no-modules.js.map +2 -2
  28. package/dist/wasm-mfu-reassembler.js.map +2 -2
  29. package/dist/wasm-mmtp-container-parser.js +15 -11
  30. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  31. package/package.json +6 -6
  32. package/src/mfu-reassembler.ts +29 -41
  33. package/src/mmtp-to-cmaf.ts +1 -25
  34. package/src/multicast-transport.ts +27 -13
@@ -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).keys()) {
1102
- __privateMethod(this, _MFUReassembler_instances, clearPendingTimeout_fn).call(this, address);
1103
- __privateMethod(this, _MFUReassembler_instances, armHeadlessPendingTimeout_fn).call(this, address);
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 armAfterInsert = false;
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
- armAfterInsert = true;
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 (armAfterInsert) __privateMethod(this, _MFUReassembler_instances, armHeadlessPendingTimeout_fn).call(this, address);
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. Every arming site
1667
- * reaches this function, which takes that test through `parkedRunHoldsASlot`
1668
- * rather than filtering on the open run alone, which is how this engine
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
- scaledCompositionTimeOffset
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
- var _onPacket, _subscription2, _bytesListener, _resolvedTransport, _videoConfirmed, _audioConfirmed, _watchdog, _failbackWindowMs, _onTakeover, _onFailback, _disposed4, _stats3, _droppedPidCounts, _packetIds, _joinGeneration, _MulticastTransport_instances, releaseSubscription_fn, acceptPacket_fn, recordUnregisteredPidDrop_fn, checkTakeover_fn, resetWatchdog_fn;
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 subscribeConfig = toWindowMulticastSubscribeConfig({
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, subscribeConfig.origin === "amt" ? "amt" : "native");
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
  };