@streaming-cdn/rtc-web 1.3.28 → 1.4.0

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/index.d.ts CHANGED
@@ -324,12 +324,20 @@ export declare class RtcIncomingClient extends EventTarget {
324
324
  private readonly seenInvitationIds;
325
325
  private readonly invitationExpiryTimers;
326
326
  private startCallRequest;
327
+ /** Bumped by disconnect() so a request still in flight cannot re-claim the call slot it just freed. */
328
+ private callGeneration;
327
329
  private lastDisconnect;
328
330
  constructor(options: RtcIncomingClientOptions);
329
331
  connect(): this;
330
332
  setMount(target: HTMLElement | string | null): void;
331
333
  /** Binds media lifetime to a call. Terminal call states automatically leave and destroy it. */
332
334
  attachMediaClient(callId: string, media: RtcClient): () => void;
335
+ /**
336
+ * Arms the self-release timer for a call that holds the single call slot but
337
+ * has no media attached. Every path that claims the slot arms this, so no
338
+ * failure can hold it for the life of the page.
339
+ */
340
+ private armCallWatchdog;
333
341
  /**
334
342
  * Rings everyone in `targets` as a single call and connects whoever answers
335
343
  * first; the rest stop ringing on their own. `target` still works and means a
@@ -395,4 +403,4 @@ export declare class RtcIncomingClient extends EventTarget {
395
403
  private emit;
396
404
  }
397
405
  export declare function createRtcIncomingClient(options: RtcIncomingClientOptions): RtcIncomingClient;
398
- export declare const version = "1.3.25";
406
+ export declare const version = "1.4.0";
@@ -1,4 +1,4 @@
1
- const at = 0.02, st = 1.4;
1
+ const st = 0.02, rt = 1.4;
2
2
  function ge(a, e, t = 0.02, i = 1.4) {
3
3
  const n = a.filter((r) => r.level >= t);
4
4
  if (n.length === 0) return null;
@@ -937,7 +937,7 @@ class xe {
937
937
  }
938
938
  send(e) {
939
939
  this.socket?.readyState === WebSocket.OPEN && this.socket.send(JSON.stringify({
940
- clientMessageId: e.clientMessageId ?? Ue(),
940
+ clientMessageId: e.clientMessageId ?? Ne(),
941
941
  ...e
942
942
  }));
943
943
  }
@@ -945,10 +945,10 @@ class xe {
945
945
  for (const t of e?.getTracks() ?? []) t.stop();
946
946
  }
947
947
  }
948
- function Ue() {
948
+ function Ne() {
949
949
  return globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
950
950
  }
951
- function rt(a, e = {}) {
951
+ function ot(a, e = {}) {
952
952
  const t = e.document ?? globalThis.document, i = !!(t && t.pictureInPictureEnabled);
953
953
  let n = null;
954
954
  function s() {
@@ -976,7 +976,7 @@ function rt(a, e = {}) {
976
976
  }
977
977
  };
978
978
  }
979
- function ot(a, e) {
979
+ function ct(a, e) {
980
980
  const t = e.document ?? globalThis.document, i = e.fetchImplementation ?? globalThis.fetch.bind(globalThis), n = Z(e.credential.signalingUrl, globalThis.location?.href).origin, s = e.mimeType ?? "video/webm", o = e.height ?? 720, r = Math.round(o * 16 / 9);
981
981
  let c = null, l = null, d = null, u = null, v = 0, p = [], S = !1;
982
982
  async function T(y, k, f, M = "application/json") {
@@ -1007,14 +1007,14 @@ function ot(a, e) {
1007
1007
  };
1008
1008
  l = setInterval(() => {
1009
1009
  const b = a.getRemoteStreams(), I = a.getLocalStream(), C = [
1010
- ...b.map((U) => ({ key: U.participant.participantId, stream: U.stream, label: U.participant.displayName })),
1010
+ ...b.map((N) => ({ key: N.participant.participantId, stream: N.stream, label: N.participant.displayName })),
1011
1011
  ...I ? [{ key: "local", stream: I, label: "me" }] : []
1012
1012
  ];
1013
1013
  f.fillStyle = "#101418", f.fillRect(0, 0, r, o);
1014
- const N = Math.max(1, Math.ceil(Math.sqrt(C.length))), Q = Math.max(1, Math.ceil(C.length / N)), O = r / N, g = o / Q;
1015
- C.forEach((U, ee) => {
1016
- const te = R(U.key, U.stream), $ = ee % N * O, W = Math.floor(ee / N) * g;
1017
- te.videoWidth > 0 && f.drawImage(te, $ + 2, W + 2, O - 4, g - 4), f.fillStyle = "rgba(0,0,0,.6)", f.fillRect($ + 8, W + g - 30, Math.min(O - 16, 12 + U.label.length * 9), 22), f.fillStyle = "#fff", f.font = "14px system-ui", f.fillText(U.label, $ + 14, W + g - 14);
1014
+ const U = Math.max(1, Math.ceil(Math.sqrt(C.length))), Q = Math.max(1, Math.ceil(C.length / U)), q = r / U, g = o / Q;
1015
+ C.forEach((N, ee) => {
1016
+ const te = R(N.key, N.stream), W = ee % U * q, $ = Math.floor(ee / U) * g;
1017
+ te.videoWidth > 0 && f.drawImage(te, W + 2, $ + 2, q - 4, g - 4), f.fillStyle = "rgba(0,0,0,.6)", f.fillRect(W + 8, $ + g - 30, Math.min(q - 16, 12 + N.label.length * 9), 22), f.fillStyle = "#fff", f.font = "14px system-ui", f.fillText(N.label, W + 14, $ + g - 14);
1018
1018
  });
1019
1019
  }, 66);
1020
1020
  const L = k.captureStream(15);
@@ -1064,7 +1064,7 @@ function ot(a, e) {
1064
1064
  }
1065
1065
  };
1066
1066
  }
1067
- const Ne = ["none", "blur", "aurora", "cybergrid", "bokeh", "sunset", "image"], qe = ["none", "snow", "confetti", "vignette"], se = {
1067
+ const Ue = ["none", "blur", "aurora", "cybergrid", "bokeh", "sunset", "image"], Oe = ["none", "snow", "confetti", "vignette"], se = {
1068
1068
  aurora(a, e, t, i) {
1069
1069
  const n = a.createLinearGradient(0, 0, 0, t);
1070
1070
  n.addColorStop(0, "#050b1e"), n.addColorStop(1, "#0b2540"), a.fillStyle = n, a.fillRect(0, 0, e, t);
@@ -1101,7 +1101,7 @@ const Ne = ["none", "blur", "aurora", "cybergrid", "bokeh", "sunset", "image"],
1101
1101
  n.addColorStop(0, "#2b1055"), n.addColorStop(0.55, "#d1548a"), n.addColorStop(1, "#ffb36b"), a.fillStyle = n, a.fillRect(0, 0, e, t), a.fillStyle = "#ffdf8e", a.beginPath(), a.arc(e * 0.5, t * 0.62 + Math.sin(i / 4e3) * 4, t * 0.13, 0, Math.PI * 2), a.fill(), a.fillStyle = "rgba(20,8,40,0.85)", a.fillRect(0, t * 0.78, e, t * 0.22);
1102
1102
  }
1103
1103
  };
1104
- function ct(a, e = {}) {
1104
+ function lt(a, e = {}) {
1105
1105
  const t = e.document ?? globalThis.document;
1106
1106
  let i = e.width ?? 1280, n = e.height ?? 720;
1107
1107
  const s = !!(e.width || e.height), o = e.frameRate ?? 24, r = a instanceof MediaStreamTrack ? a : a.getVideoTracks()[0];
@@ -1161,10 +1161,10 @@ function ct(a, e = {}) {
1161
1161
  track: E.getVideoTracks()[0],
1162
1162
  stream: E,
1163
1163
  setBackground(m, h) {
1164
- c = Ne.includes(m) ? m : "none", h !== void 0 && (d = h), c === "none" && (v = null);
1164
+ c = Ue.includes(m) ? m : "none", h !== void 0 && (d = h), c === "none" && (v = null);
1165
1165
  },
1166
1166
  setOverlay(m) {
1167
- l = qe.includes(m) ? m : "none";
1167
+ l = Oe.includes(m) ? m : "none";
1168
1168
  },
1169
1169
  setSegmenter(m) {
1170
1170
  u = m, m || (v = null);
@@ -1195,7 +1195,7 @@ function D(a, e) {
1195
1195
  const t = a.find((i) => i.categoryName === e);
1196
1196
  return t ? Math.max(0, Math.min(1, t.score)) : 0;
1197
1197
  }
1198
- function Oe(a) {
1198
+ function qe(a) {
1199
1199
  if (!a || a.length < 16) return { yaw: 0, pitch: 0, roll: 0 };
1200
1200
  a[0], a[4];
1201
1201
  const e = a[8], t = a[1], i = a[5], n = a[9], s = a[10], o = Math.atan2(e, s), r = Math.asin(Math.max(-1, Math.min(1, -n))), c = Math.atan2(t, i);
@@ -1203,7 +1203,7 @@ function Oe(a) {
1203
1203
  }
1204
1204
  function Ve(a, e, t = 1) {
1205
1205
  if (!a || a.length === 0) return ve();
1206
- const i = Oe(e), n = (D(a, "eyeLookOutRight") + D(a, "eyeLookInLeft")) / 2, s = (D(a, "eyeLookOutLeft") + D(a, "eyeLookInRight")) / 2, o = (D(a, "eyeLookUpLeft") + D(a, "eyeLookUpRight")) / 2, r = (D(a, "eyeLookDownLeft") + D(a, "eyeLookDownRight")) / 2, c = (D(a, "mouthSmileLeft") + D(a, "mouthSmileRight")) / 2, l = (D(a, "mouthFrownLeft") + D(a, "mouthFrownRight")) / 2, d = D(a, "eyeBlinkLeft"), u = D(a, "eyeBlinkRight"), v = Math.min(1, Math.abs(i.yaw) / 0.35) * Math.max(0, Math.min(1, t)), p = (d + u) / 2;
1206
+ const i = qe(e), n = (D(a, "eyeLookOutRight") + D(a, "eyeLookInLeft")) / 2, s = (D(a, "eyeLookOutLeft") + D(a, "eyeLookInRight")) / 2, o = (D(a, "eyeLookUpLeft") + D(a, "eyeLookUpRight")) / 2, r = (D(a, "eyeLookDownLeft") + D(a, "eyeLookDownRight")) / 2, c = (D(a, "mouthSmileLeft") + D(a, "mouthSmileRight")) / 2, l = (D(a, "mouthFrownLeft") + D(a, "mouthFrownRight")) / 2, d = D(a, "eyeBlinkLeft"), u = D(a, "eyeBlinkRight"), v = Math.min(1, Math.abs(i.yaw) / 0.35) * Math.max(0, Math.min(1, t)), p = (d + u) / 2;
1207
1207
  return {
1208
1208
  blinkLeft: d + (p - d) * v,
1209
1209
  blinkRight: u + (p - u) * v,
@@ -1249,10 +1249,10 @@ function je(a, e, t, i, n) {
1249
1249
  if (E <= 0.12) continue;
1250
1250
  const m = u(w) * 0.82, h = Math.max(0.2, E), b = A < 0 ? e.blinkLeft : e.blinkRight, I = Math.max(0.06, 1 - b), C = e.browUp * r * 0.14;
1251
1251
  a.strokeStyle = "#4a2e18", a.lineWidth = r * 0.07, a.lineCap = "round", a.beginPath(), a.moveTo(m - r * 0.2 * h, -r * 0.42 - C + p), a.quadraticCurveTo(m, -r * 0.52 - C + p, m + r * 0.2 * h, -r * 0.44 - C + p), a.stroke();
1252
- const N = -r * 0.18 + p;
1253
- a.fillStyle = "#ffffff", a.beginPath(), a.ellipse(m, N, r * 0.16 * h, r * 0.12 * I, 0, 0, Math.PI * 2), a.fill(), a.fillStyle = "#22303c", a.beginPath(), a.ellipse(
1252
+ const U = -r * 0.18 + p;
1253
+ a.fillStyle = "#ffffff", a.beginPath(), a.ellipse(m, U, r * 0.16 * h, r * 0.12 * I, 0, 0, Math.PI * 2), a.fill(), a.fillStyle = "#22303c", a.beginPath(), a.ellipse(
1254
1254
  m + e.pupilX * r * 0.07 * h,
1255
- N + e.pupilY * r * 0.05,
1255
+ U + e.pupilY * r * 0.05,
1256
1256
  r * 0.06 * h,
1257
1257
  r * 0.06 * I,
1258
1258
  0,
@@ -1280,7 +1280,7 @@ function ce() {
1280
1280
  };
1281
1281
  }
1282
1282
  const G = (a) => Math.max(0, Math.min(1, a));
1283
- function lt(a, e = {}) {
1283
+ function dt(a, e = {}) {
1284
1284
  const t = e.document ?? globalThis.document;
1285
1285
  let i = e.width ?? 1280, n = e.height ?? 720;
1286
1286
  const s = !!(e.width || e.height), o = e.frameRate ?? 24, r = a instanceof MediaStreamTrack ? a : a.getVideoTracks()[0];
@@ -1302,8 +1302,8 @@ function lt(a, e = {}) {
1302
1302
  let k = ve(), f = null, M = 0;
1303
1303
  function R(w) {
1304
1304
  if (w.length <= 454) return;
1305
- const E = w[10], m = w[152], h = w[234], b = w[454], I = (h.x + b.x) / 2 * i, C = (h.y + b.y) / 2 * n, N = Math.hypot((m.x - E.x) * i, (m.y - E.y) * n), Q = Math.max(0.6, Math.cos(k.headPitch)), O = { x: I, y: C, radius: Math.max(24, N / Q * 0.8) };
1306
- f = f ? { x: f.x + (O.x - f.x) * 0.4, y: f.y + (O.y - f.y) * 0.4, radius: f.radius + (O.radius - f.radius) * 0.15 } : O;
1305
+ const E = w[10], m = w[152], h = w[234], b = w[454], I = (h.x + b.x) / 2 * i, C = (h.y + b.y) / 2 * n, U = Math.hypot((m.x - E.x) * i, (m.y - E.y) * n), Q = Math.max(0.6, Math.cos(k.headPitch)), q = { x: I, y: C, radius: Math.max(24, U / Q * 0.8) };
1306
+ f = f ? { x: f.x + (q.x - f.x) * 0.4, y: f.y + (q.y - f.y) * 0.4, radius: f.radius + (q.radius - f.radius) * 0.15 } : q;
1307
1307
  }
1308
1308
  function L(w) {
1309
1309
  if (c && p.readyState >= 2) {
@@ -1404,7 +1404,7 @@ async function Qe(a, e) {
1404
1404
  const c = new Blob([r]).stream().pipeThrough(new DecompressionStream("deflate-raw"));
1405
1405
  return new Response(c).arrayBuffer();
1406
1406
  }
1407
- async function dt(a) {
1407
+ async function ht(a) {
1408
1408
  const e = typeof File < "u" && a instanceof File ? await a.arrayBuffer() : a;
1409
1409
  if (!Te(e)) return [];
1410
1410
  try {
@@ -1429,11 +1429,11 @@ class x extends Error {
1429
1429
  function Ce(a) {
1430
1430
  return a.filter((e) => /\.vrm$/i.test(e.path) && !e.path.endsWith("/") && !e.path.split("/").some((t) => t === "__MACOSX" || t.startsWith("._")));
1431
1431
  }
1432
- function q(a) {
1432
+ function O(a) {
1433
1433
  const e = typeof a == "string" ? a.trim() : "";
1434
1434
  return e.length > 0 ? e : null;
1435
1435
  }
1436
- function $e(a) {
1436
+ function We(a) {
1437
1437
  const e = new DataView(a);
1438
1438
  if (a.byteLength < 20 || e.getUint32(0, !0) !== ze)
1439
1439
  throw new x("model_not_glb", "The file is not a binary glTF (.vrm / .glb) model.");
@@ -1457,24 +1457,24 @@ function $e(a) {
1457
1457
  if (o) {
1458
1458
  const r = Array.isArray(o.authors) ? o.authors.filter((c) => typeof c == "string") : [];
1459
1459
  return {
1460
- title: q(o.name),
1460
+ title: O(o.name),
1461
1461
  author: r.length ? r.join(", ") : null,
1462
- allowedUser: q(o.avatarPermission),
1463
- commercialUse: q(o.commercialUsage),
1464
- licenseName: q(o.licenseUrl) ? "VRM 1.0" : null,
1465
- licenseUrl: q(o.licenseUrl) ?? q(o.otherLicenseUrl)
1462
+ allowedUser: O(o.avatarPermission),
1463
+ commercialUse: O(o.commercialUsage),
1464
+ licenseName: O(o.licenseUrl) ? "VRM 1.0" : null,
1465
+ licenseUrl: O(o.licenseUrl) ?? O(o.otherLicenseUrl)
1466
1466
  };
1467
1467
  }
1468
1468
  return {
1469
- title: q(s?.title),
1470
- author: q(s?.author),
1471
- allowedUser: q(s?.allowedUserName),
1472
- commercialUse: q(s?.commercialUssageName),
1473
- licenseName: q(s?.licenseName),
1474
- licenseUrl: q(s?.otherLicenseUrl)
1469
+ title: O(s?.title),
1470
+ author: O(s?.author),
1471
+ allowedUser: O(s?.allowedUserName),
1472
+ commercialUse: O(s?.commercialUssageName),
1473
+ licenseName: O(s?.licenseName),
1474
+ licenseUrl: O(s?.otherLicenseUrl)
1475
1475
  };
1476
1476
  }
1477
- function We() {
1477
+ function $e() {
1478
1478
  return new Promise((a, e) => {
1479
1479
  if (typeof indexedDB > "u") {
1480
1480
  e(new x("storage_unavailable", "This browser cannot store imported avatar models."));
@@ -1487,7 +1487,7 @@ function We() {
1487
1487
  });
1488
1488
  }
1489
1489
  async function H(a, e) {
1490
- const t = await We();
1490
+ const t = await $e();
1491
1491
  try {
1492
1492
  return await new Promise((i, n) => {
1493
1493
  const s = e(t.transaction(z, a).objectStore(z));
@@ -1504,7 +1504,7 @@ const Ee = (a) => ({
1504
1504
  importedAt: a.importedAt,
1505
1505
  license: a.license
1506
1506
  });
1507
- async function ht(a, e = {}) {
1507
+ async function ut(a, e = {}) {
1508
1508
  const t = e.maxBytes ?? _e, i = typeof File < "u" && a instanceof File;
1509
1509
  let n = i ? a.name : null, s = i ? await a.arrayBuffer() : a;
1510
1510
  if (Te(s)) {
@@ -1516,7 +1516,7 @@ async function ht(a, e = {}) {
1516
1516
  "model_too_large",
1517
1517
  "The model is larger than the " + Math.round(t / 1024 / 1024) + " MB limit."
1518
1518
  );
1519
- const o = $e(s);
1519
+ const o = We(s);
1520
1520
  if (!o)
1521
1521
  throw new x("model_not_vrm", "The file is a glTF model but carries no VRM character data.");
1522
1522
  const r = o.title || (n ? n.replace(/\.(vrm|glb)$/i, "") : null), c = {
@@ -1559,13 +1559,13 @@ async function Je(a, e, t) {
1559
1559
  function le(a) {
1560
1560
  return a === "zip_encrypted" ? "archive_encrypted" : a === "zip_unsupported" ? "archive_unsupported" : "archive_unreadable";
1561
1561
  }
1562
- async function ut() {
1562
+ async function mt() {
1563
1563
  return (await H("readonly", (e) => e.getAll()) ?? []).map(Ee).sort((e, t) => t.importedAt.localeCompare(e.importedAt));
1564
1564
  }
1565
- async function mt(a) {
1565
+ async function pt(a) {
1566
1566
  return (await H("readonly", (t) => t.get(a)))?.data ?? null;
1567
1567
  }
1568
- async function pt(a) {
1568
+ async function ft(a) {
1569
1569
  await H("readwrite", (e) => e.delete(a));
1570
1570
  }
1571
1571
  class Ge extends EventTarget {
@@ -1622,7 +1622,7 @@ class Ge extends EventTarget {
1622
1622
  localStream: this.options.localStream,
1623
1623
  simulcast: this.options.simulcast ?? !0,
1624
1624
  onEvent: (i, n) => this.handleNativeEvent(i, n)
1625
- }), await this.client.initialize(), this.startStats(), this.emit("initialized", { mode: this.mode, sdkVersion: nt }), this.options.autoJoin && await this.join(), this;
1625
+ }), await this.client.initialize(), this.startStats(), this.emit("initialized", { mode: this.mode, sdkVersion: at }), this.options.autoJoin && await this.join(), this;
1626
1626
  } catch (e) {
1627
1627
  this.state = "failed";
1628
1628
  const t = fe(e, "RTC initialization failed");
@@ -1786,7 +1786,7 @@ class Ge extends EventTarget {
1786
1786
  }), d;
1787
1787
  }
1788
1788
  async collectQualitySample(e = "runtime") {
1789
- const t = this.options.statsProvider ? await this.options.statsProvider() : {}, i = this.peerConnection ? await et(this.peerConnection, this.previousCounters) : null;
1789
+ const t = this.options.statsProvider ? await this.options.statsProvider() : {}, i = this.peerConnection ? await tt(this.peerConnection, this.previousCounters) : null;
1790
1790
  i?.counters && (this.previousCounters = i.counters);
1791
1791
  const s = { ...{
1792
1792
  sampleType: e,
@@ -1819,9 +1819,9 @@ class Ge extends EventTarget {
1819
1819
  transportProtocol: null,
1820
1820
  candidateType: null,
1821
1821
  qualityLimitationReason: null,
1822
- deviceMemoryGb: it(),
1822
+ deviceMemoryGb: nt(),
1823
1823
  effectiveConnectionType: me()?.effectiveType ?? null
1824
- }, ...i?.sample ?? {}, ...t, sampleType: e, sampledAt: (/* @__PURE__ */ new Date()).toISOString() }, o = tt(s);
1824
+ }, ...i?.sample ?? {}, ...t, sampleType: e, sampledAt: (/* @__PURE__ */ new Date()).toISOString() }, o = it(s);
1825
1825
  return s.networkScore = o.networkScore, s.estimatedMos = o.estimatedMos, this.latestQuality = s, s;
1826
1826
  }
1827
1827
  async reportQuality(e) {
@@ -1906,7 +1906,7 @@ class Ge extends EventTarget {
1906
1906
  this.dispatchEvent(new CustomEvent(e, { detail: i })), this.options.onEvent?.(i);
1907
1907
  }
1908
1908
  }
1909
- async function ft(a) {
1909
+ async function gt(a) {
1910
1910
  return new Ge(a).initialize();
1911
1911
  }
1912
1912
  let _ = null;
@@ -1967,7 +1967,8 @@ class Ke {
1967
1967
  }
1968
1968
  }
1969
1969
  }
1970
- class Xe extends EventTarget {
1970
+ const Xe = 6e4;
1971
+ class Ze extends EventTarget {
1971
1972
  options;
1972
1973
  socket = null;
1973
1974
  stopped = !1;
@@ -1989,6 +1990,8 @@ class Xe extends EventTarget {
1989
1990
  seenInvitationIds = /* @__PURE__ */ new Set();
1990
1991
  invitationExpiryTimers = /* @__PURE__ */ new Map();
1991
1992
  startCallRequest = null;
1993
+ /** Bumped by disconnect() so a request still in flight cannot re-claim the call slot it just freed. */
1994
+ callGeneration = 0;
1992
1995
  lastDisconnect = null;
1993
1996
  constructor(e) {
1994
1997
  if (super(), !e.token && !e.tokenProvider) throw new Error("A client token or tokenProvider is required");
@@ -2007,10 +2010,18 @@ class Xe extends EventTarget {
2007
2010
  /** Binds media lifetime to a call. Terminal call states automatically leave and destroy it. */
2008
2011
  attachMediaClient(e, t) {
2009
2012
  if (!e.trim()) throw new Error("callId is required");
2010
- return this.mediaClients.set(e, t), () => {
2011
- this.mediaClients.get(e) === t && this.mediaClients.delete(e);
2013
+ return this.mediaClients.set(e, t), this.clearInvitationExpiry(e), () => {
2014
+ this.mediaClients.get(e) === t && (this.mediaClients.delete(e), this.activeCallIds.has(e) && this.armCallWatchdog(e));
2012
2015
  };
2013
2016
  }
2017
+ /**
2018
+ * Arms the self-release timer for a call that holds the single call slot but
2019
+ * has no media attached. Every path that claims the slot arms this, so no
2020
+ * failure can hold it for the life of the page.
2021
+ */
2022
+ armCallWatchdog(e) {
2023
+ this.scheduleInvitationExpiry(e, new Date(Date.now() + Xe).toISOString());
2024
+ }
2014
2025
  /**
2015
2026
  * Rings everyone in `targets` as a single call and connects whoever answers
2016
2027
  * first; the rest stop ringing on their own. `target` still works and means a
@@ -2021,51 +2032,77 @@ class Xe extends EventTarget {
2021
2032
  if (this.activeCallIds.size > 0) throw new Error("Another call is already active");
2022
2033
  const t = e.targets?.length ? e.targets : e.target ? [e.target] : [];
2023
2034
  if (t.length === 0) throw new Error("startCall requires at least one target");
2024
- const i = { mode: e.mode, targets: t, metadata: e.metadata, idempotencyKey: e.idempotencyKey }, n = (async () => {
2035
+ const i = { mode: e.mode, targets: t, metadata: e.metadata, idempotencyKey: e.idempotencyKey }, n = this.callGeneration, s = (async () => {
2025
2036
  await this.callAudio.unlock(), this.callAudio.startRingback();
2026
- let s;
2037
+ let o;
2027
2038
  try {
2028
- s = await this.request("/v1/rtc/client/calls", {
2039
+ o = await this.request("/v1/rtc/client/calls", {
2029
2040
  method: "POST",
2030
2041
  headers: e.idempotencyKey ? { "idempotency-key": e.idempotencyKey } : void 0,
2031
2042
  body: JSON.stringify(i)
2032
2043
  });
2033
- } catch (o) {
2034
- throw this.callAudio.stop(), o;
2044
+ } catch (c) {
2045
+ throw this.callAudio.stop(), c;
2035
2046
  }
2036
- return !s.credential && s.callerCredential && (s.credential = s.callerCredential), this.outgoingCallId = String(s.call?.id ?? "") || null, this.outgoingCallId && this.activeCallIds.add(this.outgoingCallId), this.emit("outgoingcall", s), s;
2047
+ !o.credential && o.callerCredential && (o.credential = o.callerCredential);
2048
+ const r = String(o.call?.id ?? "") || null;
2049
+ if (r && n === this.callGeneration) {
2050
+ this.outgoingCallId = r, this.activeCallIds.add(r);
2051
+ const c = o.call?.expiresAt;
2052
+ this.scheduleInvitationExpiry(r, typeof c == "string" ? c : void 0);
2053
+ } else r && this.callAction(r, "cancel").catch(() => {
2054
+ });
2055
+ return this.emit("outgoingcall", o), o;
2037
2056
  })();
2038
- this.startCallRequest = n;
2057
+ this.startCallRequest = s;
2039
2058
  try {
2040
- return await n;
2059
+ return await s;
2041
2060
  } finally {
2042
- this.startCallRequest === n && (this.startCallRequest = null);
2061
+ this.startCallRequest === s && (this.startCallRequest = null);
2043
2062
  }
2044
2063
  }
2045
2064
  async accept(e = this.currentCall?.id) {
2046
2065
  if (!e) throw new Error("No incoming call is available");
2047
2066
  const t = await this.callAction(e, "accept");
2048
- return this.activeCallIds.add(e), this.clearInvitationExpiry(e), this.callAudio.stop(), this.dismiss(e), this.emit("callaccepted", t), t;
2067
+ return this.activeCallIds.add(e), this.armCallWatchdog(e), this.callAudio.stop(), this.dismiss(e), this.emit("callaccepted", t), t;
2049
2068
  }
2069
+ // Local release runs in a finally on all four: it must never depend on a
2070
+ // network round trip. A hang-up while offline, or after the server already
2071
+ // swept the call terminal (which answers 409), used to leave the call id in
2072
+ // the active set for the life of the page, and startCall() refuses while any
2073
+ // id is held.
2050
2074
  async reject(e = this.currentCall?.id) {
2051
2075
  if (!e) throw new Error("No incoming call is available");
2052
- const t = await this.callAction(e, "reject");
2053
- return this.releaseMedia(e), this.showTerminal(e, "Call declined"), this.emit("callrejected", t), t;
2076
+ let t;
2077
+ try {
2078
+ return t = await this.callAction(e, "reject");
2079
+ } finally {
2080
+ this.releaseMedia(e), this.showTerminal(e, "Call declined"), this.emit("callrejected", t ?? { call: { id: e } });
2081
+ }
2054
2082
  }
2055
2083
  async busy(e = this.currentCall?.id) {
2056
2084
  if (!e) throw new Error("No incoming call is available");
2057
- const t = await this.callAction(e, "busy");
2058
- return this.releaseMedia(e), this.showTerminal(e, "Line busy"), t;
2085
+ try {
2086
+ return await this.callAction(e, "busy");
2087
+ } finally {
2088
+ this.releaseMedia(e), this.showTerminal(e, "Line busy");
2089
+ }
2059
2090
  }
2060
2091
  async end(e) {
2061
2092
  this.callAudio.stop();
2062
- const t = await this.callAction(e, "end");
2063
- return this.releaseMedia(e), this.showTerminal(e, "Call ended"), t;
2093
+ try {
2094
+ return await this.callAction(e, "end");
2095
+ } finally {
2096
+ this.releaseMedia(e), this.showTerminal(e, "Call ended");
2097
+ }
2064
2098
  }
2065
2099
  async cancel(e) {
2066
2100
  this.callAudio.stop();
2067
- const t = await this.callAction(e, "cancel");
2068
- return this.releaseMedia(e), this.showTerminal(e, "Call cancelled"), t;
2101
+ try {
2102
+ return await this.callAction(e, "cancel");
2103
+ } finally {
2104
+ this.releaseMedia(e), this.showTerminal(e, "Call cancelled");
2105
+ }
2069
2106
  }
2070
2107
  /** Injects an incoming call received through the application's own push or signaling channel. */
2071
2108
  handleIncomingCall(e) {
@@ -2113,10 +2150,10 @@ class Xe extends EventTarget {
2113
2150
  /** Applies a call state received through the application's own push or signaling channel. */
2114
2151
  handleCallUpdate(e) {
2115
2152
  const t = e.id === this.currentCall?.id || e.id === this.outgoingCallId, i = ["answered_elsewhere", "rejected", "busy", "cancelled", "ended", "failed", "missed"];
2116
- (e.status === "accepted" || i.includes(e.status)) && this.clearInvitationExpiry(e.id), t && e.status === "accepted" ? (this.activeCallIds.add(e.id), this.callAudio.stop(), this.dismiss(e.id)) : t && i.includes(e.status) && this.showTerminal(e.id, Ze(e.status)), i.includes(e.status) && this.releaseMedia(e.id), this.emit("callupdated", e);
2153
+ i.includes(e.status) && this.clearInvitationExpiry(e.id), e.status === "accepted" && (this.mediaClients.has(e.id) ? this.clearInvitationExpiry(e.id) : this.armCallWatchdog(e.id)), t && e.status === "accepted" ? (this.activeCallIds.add(e.id), this.callAudio.stop(), this.dismiss(e.id)) : t && i.includes(e.status) && this.showTerminal(e.id, et(e.status)), i.includes(e.status) && this.releaseMedia(e.id), this.emit("callupdated", e);
2117
2154
  }
2118
2155
  disconnect() {
2119
- this.stopped = !0, this.reconnectTimer && clearTimeout(this.reconnectTimer), this.heartbeatTimer && clearInterval(this.heartbeatTimer), this.tokenRefreshTimer && clearTimeout(this.tokenRefreshTimer), this.reconnectTimer = null, this.heartbeatTimer = null, this.tokenRefreshTimer = null, this.socket?.close(1e3, "Client disconnected"), this.socket = null, this.callAudio.stop(), this.dismiss();
2156
+ this.stopped = !0, this.callGeneration += 1, this.reconnectTimer && clearTimeout(this.reconnectTimer), this.heartbeatTimer && clearInterval(this.heartbeatTimer), this.tokenRefreshTimer && clearTimeout(this.tokenRefreshTimer), this.reconnectTimer = null, this.heartbeatTimer = null, this.tokenRefreshTimer = null, this.socket?.close(1e3, "Client disconnected"), this.socket = null, this.callAudio.stop(), this.dismiss();
2120
2157
  for (const e of this.invitationExpiryTimers.values()) clearTimeout(e);
2121
2158
  this.invitationExpiryTimers.clear();
2122
2159
  for (const e of this.mediaClients.keys()) this.releaseMedia(e);
@@ -2306,7 +2343,7 @@ class Xe extends EventTarget {
2306
2343
  }, Math.min(t, 2147e6));
2307
2344
  }
2308
2345
  releaseMedia(e) {
2309
- this.activeCallIds.delete(e), this.seenInvitationIds.delete(e);
2346
+ this.clearInvitationExpiry(e), this.activeCallIds.delete(e), this.seenInvitationIds.delete(e);
2310
2347
  const t = this.mediaClients.get(e);
2311
2348
  t && (this.mediaClients.delete(e), t.leave().catch(() => {
2312
2349
  }).finally(() => t.destroy()));
@@ -2322,8 +2359,8 @@ class Xe extends EventTarget {
2322
2359
  this.dispatchEvent(new CustomEvent(e, { detail: i })), this.options.onEvent?.(i);
2323
2360
  }
2324
2361
  }
2325
- function gt(a) {
2326
- const e = new Xe(a);
2362
+ function yt(a) {
2363
+ const e = new Ze(a);
2327
2364
  return a.autoConnect !== !1 && a.signalingUrl && e.connect(), e;
2328
2365
  }
2329
2366
  function de(a) {
@@ -2342,7 +2379,7 @@ function he(a, e, t, i) {
2342
2379
  fontWeight: "700"
2343
2380
  }), n;
2344
2381
  }
2345
- function Ze(a) {
2382
+ function et(a) {
2346
2383
  return {
2347
2384
  rejected: "Call declined",
2348
2385
  busy: "Line busy",
@@ -2360,7 +2397,7 @@ function ue(a) {
2360
2397
  role: "participant"
2361
2398
  };
2362
2399
  }
2363
- async function et(a, e) {
2400
+ async function tt(a, e) {
2364
2401
  const t = await a.getStats(), i = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
2365
2402
  let s = 0, o = 0, r = 0, c = 0, l = 0, d = 0, u = 0, v = 0, p = 0, S = 0, T = 0, y = 0, k = 0, f = 0, M = 0, R = null, L = null, P = null, A = null, w = null, E = null;
2366
2403
  t.forEach((g) => {
@@ -2368,17 +2405,17 @@ async function et(a, e) {
2368
2405
  }), t.forEach((g) => {
2369
2406
  if (g.type === "inbound-rtp" && !g.isRemote && (s += Number(g.bytesReceived ?? 0), r += Math.max(0, Number(g.packetsLost ?? 0)), c += Math.max(0, Number(g.packetsReceived ?? 0)), Number.isFinite(g.jitter) && (l += Number(g.jitter), d += 1), u = Math.max(u, Number(g.framesPerSecond ?? 0)), v = Math.max(v, Number(g.frameWidth ?? 0)), T = Math.max(T, Number(g.frameHeight ?? 0)), y += Number(g.framesDropped ?? 0), k += Number(g.freezeCount ?? 0), f += Number(g.totalFreezesDuration ?? 0), M += Number(g.concealedSamples ?? 0), w ||= i.get(String(g.codecId ?? "")) ?? null), g.type === "outbound-rtp" && !g.isRemote && (p = Math.max(p, Number(g.frameWidth ?? 0)), S = Math.max(S, Number(g.frameHeight ?? 0)), o += Number(g.bytesSent ?? 0), E ||= g.qualityLimitationReason ? String(g.qualityLimitationReason) : null, w ||= i.get(String(g.codecId ?? "")) ?? null), g.type === "candidate-pair" && (g.selected || g.nominated) && g.state === "succeeded") {
2370
2407
  R = Number.isFinite(g.currentRoundTripTime) ? Number(g.currentRoundTripTime) : R, L = Number.isFinite(g.availableOutgoingBitrate) ? Number(g.availableOutgoingBitrate) : L;
2371
- const U = n.get(g.localCandidateId);
2372
- P = U?.candidateType ? String(U.candidateType) : P, A = U?.protocol ? String(U.protocol) : A;
2408
+ const N = n.get(g.localCandidateId);
2409
+ P = N?.candidateType ? String(N.candidateType) : P, A = N?.protocol ? String(N.protocol) : A;
2373
2410
  }
2374
2411
  });
2375
- const m = Date.now(), h = e ? Math.max(1e-3, (m - e.timestamp) / 1e3) : 0, b = e ? Math.max(0, Math.round((s - e.inboundBytes) * 8 / h)) : null, I = e ? Math.max(0, Math.round((o - e.outboundBytes) * 8 / h)) : null, C = r + c, N = e ? Math.max(0, r - e.packetsLost) : r, Q = e ? Math.max(0, c - e.packetsReceived) : c, O = N + Q;
2412
+ const m = Date.now(), h = e ? Math.max(1e-3, (m - e.timestamp) / 1e3) : 0, b = e ? Math.max(0, Math.round((s - e.inboundBytes) * 8 / h)) : null, I = e ? Math.max(0, Math.round((o - e.outboundBytes) * 8 / h)) : null, C = r + c, U = e ? Math.max(0, r - e.packetsLost) : r, Q = e ? Math.max(0, c - e.packetsReceived) : c, q = U + Q;
2376
2413
  return {
2377
2414
  counters: { timestamp: m, inboundBytes: s, outboundBytes: o, packetsLost: r, packetsReceived: c },
2378
2415
  sample: {
2379
2416
  rttMs: R === null ? null : R * 1e3,
2380
2417
  jitterMs: d ? l / d * 1e3 : null,
2381
- packetLossPct: O ? N / O * 100 : null,
2418
+ packetLossPct: q ? U / q * 100 : null,
2382
2419
  packetLossCumulativePct: C ? r / C * 100 : null,
2383
2420
  packetsLost: r,
2384
2421
  packetsReceived: c,
@@ -2403,7 +2440,7 @@ async function et(a, e) {
2403
2440
  }
2404
2441
  };
2405
2442
  }
2406
- function tt(a) {
2443
+ function it(a) {
2407
2444
  let e = a.mediaScore == null ? 5 : Math.round(pe(Number(a.mediaScore), 0, 5));
2408
2445
  const t = Y(a.packetLossPct) ?? 0, i = Y(a.rttMs) ?? 0, n = Y(a.jitterMs) ?? 0, s = String(a.connectionState ?? "unknown").toLowerCase();
2409
2446
  ["failed", "closed", "disconnected"].includes(s) ? e = 0 : ["reconnecting", "checking"].includes(s) && (e = Math.min(e, 2)), t >= 15 ? e = Math.min(e, 0) : t >= 8 ? e = Math.min(e, 1) : t >= 3 ? e = Math.min(e, 2) : t >= 1 && (e = Math.min(e, 3)), i >= 1200 ? e = Math.min(e, 0) : i >= 800 ? e = Math.min(e, 1) : i >= 400 ? e = Math.min(e, 2) : i >= 250 && (e = Math.min(e, 3)), n >= 150 ? e = Math.min(e, 1) : n >= 80 ? e = Math.min(e, 2) : n >= 40 && (e = Math.min(e, 3));
@@ -2413,7 +2450,7 @@ function tt(a) {
2413
2450
  function me() {
2414
2451
  return navigator.connection ?? null;
2415
2452
  }
2416
- function it() {
2453
+ function nt() {
2417
2454
  const a = Number(navigator.deviceMemory);
2418
2455
  return Number.isFinite(a) ? a : null;
2419
2456
  }
@@ -2430,37 +2467,37 @@ function B(a) {
2430
2467
  function fe(a, e) {
2431
2468
  return a instanceof Error ? a : new Error(e);
2432
2469
  }
2433
- const nt = "1.3.25";
2470
+ const at = "1.4.0";
2434
2471
  export {
2435
2472
  _e as AVATAR_MODEL_MAX_BYTES,
2436
2473
  oe as AVATAR_TUNING_DEFAULTS,
2437
2474
  x as AvatarModelError,
2438
- Ne as BACKGROUND_PRESETS,
2439
- qe as OVERLAY_EFFECTS,
2475
+ Ue as BACKGROUND_PRESETS,
2476
+ Oe as OVERLAY_EFFECTS,
2440
2477
  Ge as RtcClient,
2441
- Xe as RtcIncomingClient,
2442
- at as SPEAKER_NOISE_FLOOR,
2443
- st as SPEAKER_TAKEOVER_RATIO,
2444
- lt as createAvatarPipeline,
2445
- ot as createCallRecorder,
2446
- rt as createPictureInPictureController,
2447
- ft as createRtcClient,
2448
- gt as createRtcIncomingClient,
2449
- ct as createVideoEffectsPipeline,
2478
+ Ze as RtcIncomingClient,
2479
+ st as SPEAKER_NOISE_FLOOR,
2480
+ rt as SPEAKER_TAKEOVER_RATIO,
2481
+ dt as createAvatarPipeline,
2482
+ ct as createCallRecorder,
2483
+ ot as createPictureInPictureController,
2484
+ gt as createRtcClient,
2485
+ yt as createRtcIncomingClient,
2486
+ lt as createVideoEffectsPipeline,
2450
2487
  je as drawAvatar,
2451
- Oe as headPoseFromMatrix,
2452
- ht as importAvatarModel,
2453
- dt as listArchiveModels,
2454
- ut as listAvatarModels,
2488
+ qe as headPoseFromMatrix,
2489
+ ut as importAvatarModel,
2490
+ ht as listArchiveModels,
2491
+ mt as listAvatarModels,
2455
2492
  ve as neutralRig,
2456
2493
  ge as pickActiveSpeaker,
2457
- mt as readAvatarModel,
2458
- $e as readAvatarModelMetadata,
2459
- pt as removeAvatarModel,
2494
+ pt as readAvatarModel,
2495
+ We as readAvatarModelMetadata,
2496
+ ft as removeAvatarModel,
2460
2497
  Ve as rigFromBlendshapes,
2461
2498
  se as scenePainters,
2462
2499
  re as smoothRig,
2463
2500
  ce as stylizedAvatarRenderer,
2464
2501
  Ye as unlockRtcCallAudio,
2465
- nt as version
2502
+ at as version
2466
2503
  };
@@ -1 +1 @@
1
- (function(k,F){typeof exports=="object"&&typeof module<"u"?F(exports):typeof define=="function"&&define.amd?define(["exports"],F):(k=typeof globalThis<"u"?globalThis:k||self,F(k.StreamingCdnRtc={}))})(this,(function(k){"use strict";function J(a,e,t=.02,i=1.4){const n=a.filter(r=>r.level>=t);if(n.length===0)return null;const s=n.reduce((r,c)=>c.level>r.level?c:r),o=n.find(r=>r.participantId===e);return o?s.participantId===e?e:s.level>=o.level*i?s.participantId:e:s.participantId}function ce(a){const e=[];for(const t of a){if(String(t.type??"")!=="inbound-rtp"||String(t.kind??t.mediaType??"")!=="audio")continue;const i=Number(t.audioLevel??NaN);Number.isFinite(i)&&e.push({mid:typeof t.mid=="string"?t.mid:null,level:i})}return e}const V={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4},low:{active:!0,maxBitrate:35e4,maxFramerate:15,scaleResolutionDownBy:4},medium:{active:!0,maxBitrate:11e5,maxFramerate:24,scaleResolutionDownBy:2},high:{active:!0,maxBitrate:28e5,maxFramerate:30,scaleResolutionDownBy:1}},je={warmupSamples:3,poorSamplesToStepDown:2,goodSamplesToStepUp:6,criticalLossPct:8,criticalRttMs:800,poorLossPct:3,poorRttMs:350,goodLossPct:1,goodRttMs:250,sustainRatio:.8,bandwidthSustainRatio:.7};function Qe(a){const e=String(a??"").trim().toLowerCase();return!e||e==="none"?"":e==="cpu"||e==="bandwidth"?e:"other"}function _e(a,e,t,i,n={}){const s={...je,...n.thresholds},o=["low","medium","high"],r=n.ceiling&&n.ceiling!=="audio"?n.ceiling:"high",c=o.indexOf(r),l=o.indexOf(a),d=(n.samplesSeen??s.warmupSamples)+1,u=d<=s.warmupSamples,v=Qe(e.qualityLimitationReason),p=e.packetLossPct??0,S=e.rttMs??0,b=e.availableOutgoingBitrateBps,y=V[a].maxBitrate,T=p>=s.criticalLossPct||S>=s.criticalRttMs||v==="cpu",f=v==="bandwidth"&&(b==null||b<y*s.bandwidthSustainRatio),M=T||p>=s.poorLossPct||S>=s.poorRttMs||f,I=l<o.length-1?V[o[l+1]].maxBitrate:null,D=v==="bandwidth"&&b!=null&&I!=null&&b>=I*s.bandwidthSustainRatio,P=b==null||b>=y*s.sustainRatio,L=p<s.goodLossPct&&S<s.goodRttMs&&(!v||D)&&P,w=M?t+1:0,R=L?i+1:0,m={quality:a,poorSamples:w,goodSamples:R,samplesSeen:d,changed:!1};return l>c?{quality:o[c],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:u?m:T&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:w>=s.poorSamplesToStepDown&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:R>=s.goodSamplesToStepUp&&l<c?{quality:o[l+1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:m}function Fe(a,e){const t=a instanceof Map?a:new Map(a);let i=0,n=0,s=null,o=null,r=null;for(const d of t.values()){const u=String(d.type??"");if(u==="outbound-rtp"&&String(d.kind??"")==="video"&&(i+=Number(d.packetsSent??0),s=d.qualityLimitationReason??s),u==="remote-inbound-rtp"&&String(d.kind??"")==="video"){n+=Number(d.packetsLost??0);const v=Number(d.roundTripTime??NaN);Number.isFinite(v)&&(o=v*1e3)}if(u==="candidate-pair"&&(d.nominated===!0||String(d.state??"")==="succeeded")){const v=Number(d.currentRoundTripTime??NaN);o==null&&Number.isFinite(v)&&(o=v*1e3);const p=Number(d.availableOutgoingBitrate??NaN);Number.isFinite(p)&&(r=p)}}const c={packetsSent:i,packetsLost:n,timestamp:Date.now()};let l=null;if(e){const d=i-e.packetsSent,u=n-e.packetsLost;d>0&&u>=0&&(l=u/(d+u)*100)}return{sample:{packetLossPct:l,rttMs:o,availableOutgoingBitrateBps:r,qualityLimitationReason:s},counters:c}}function G(a,e){const t=new URL(String(a),e);return t.protocol==="https:"?t.protocol="wss:":t.protocol==="http:"&&(t.protocol="ws:"),t}function Y(a,e){const t=new URL(String(a),e);return t.protocol==="wss:"?t.protocol="https:":t.protocol==="ws:"&&(t.protocol="http:"),t}const ze={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4}},He={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1};class z{options;socket=null;localStream=null;screenStream=null;peers=new Map;joinPromise=null;resolveJoin=null;rejectJoin=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;qualityTimer=null;speakerTimer=null;activeSpeaker=null;heartbeatDeadline=0;quality;preferredVideoCodec;signalQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.preferredVideoCodec=e.preferredVideoCodec??"auto"}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(!this.joined){if(this.joinPromise)return this.joinPromise;this.joinPromise=this.connect();try{await this.joinPromise}finally{this.joinPromise=null}}}async leave(){this.stopped=!0,this.clearConnectionTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null;for(const e of this.peers.values())e.connection.close();this.peers.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(r=>r.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(r=>r.readyState==="live")||await this.replaceVideoTrack(n);return}for(const r of this.peers.values()){const c=r.connection.getSenders().find(l=>l.track?.kind===s);c?await c.replaceTrack(n):r.connection.addTrack(n,this.localStream)}await this.applyVideoQuality()}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";for(const t of this.peers.values())t.quality=z.clampToCeiling(t.quality,e),t.poorSamples=0,t.goodSamples=0;await this.applyVideoQuality()}static clampToCeiling(e,t){if(t==="audio")return e;const i=["low","medium","high"];return i.indexOf(e)>i.indexOf(t)?t:e}async setPreferredVideoCodec(e){this.preferredVideoCodec=e;for(const t of this.peers.values())this.applyCodecPreference(t.connection);await this.renegotiateAll()}getParticipants(){return[...this.peers.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getRemoteStreams(){return[...this.peers.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.peers.values().next().value?.connection??null}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async connect(){const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1)}async openSocket(e){const{credential:t}=this.options,i=G(t.signalingUrl,globalThis.location?.href),n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.startQualitySampling(),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),this.stopQualitySampling(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.resetPeers(),this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0;const n=Array.isArray(t.peers)?t.peers:[];for(const s of n)this.ensurePeer(s);this.resolveJoin?.();for(const s of n)this.shouldInitiateOffer(s)&&await this.createOffer(s);return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.ensurePeer(t.sender),this.shouldInitiateOffer(t.sender)&&await this.createOffer(t.sender);return}if(i==="rtc.offer"&&t.sender&&t.description){const n=this.ensurePeer(t.sender);n.offerTask&&await n.offerTask;const s=n.connection.signalingState!=="stable",o=!this.shouldInitiateOffer(t.sender);if(s&&!o)return;s&&await n.connection.setLocalDescription({type:"rollback"}),await n.connection.setRemoteDescription(t.description);const r=await n.connection.createAnswer();await n.connection.setLocalDescription(r),this.send({type:"rtc.answer",targetParticipantId:t.sender.participantId,negotiationId:t.negotiationId,description:n.connection.localDescription});return}if(i==="rtc.answer"&&t.sender&&t.description){const n=this.peers.get(t.sender.participantId);if(!n||n.connection.signalingState!=="have-local-offer"||t.negotiationId&&n.activeNegotiationId&&t.negotiationId!==n.activeNegotiationId)return;await n.connection.setRemoteDescription(t.description),n.activeNegotiationId=null;return}if(i==="rtc.ice"&&t.sender&&t.candidate){await this.ensurePeer(t.sender).connection.addIceCandidate(t.candidate).catch(()=>{});return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removePeer(t.sender.participantId);return}i==="rtc.chat"?this.options.onEvent("chatupdate",t):i==="rtc.control"&&this.options.onEvent("control",t)}}ensurePeer(e){const t=this.peers.get(e.participantId);if(t)return t;const i=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"}),n=new MediaStream,s={identity:e,connection:i,stream:n,makingOffer:!1,offerTask:null,activeNegotiationId:null,quality:z.startingQuality(this.quality),poorSamples:0,goodSamples:0,samplesSeen:0,counters:null};this.peers.set(e.participantId,s);for(const c of this.localStream?.getAudioTracks()??[])i.addTrack(c,this.localStream);const o=this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")?this.screenStream:this.localStream,r=o?.getVideoTracks()[0];return r&&o&&(s.videoSender=i.addTrack(r,o)),!this.localStream?.getAudioTracks().length&&this.options.receiveAudio&&i.addTransceiver("audio",{direction:"recvonly"}),!r&&this.options.receiveVideo&&(s.videoTransceiver=i.addTransceiver("video",{direction:"recvonly"}),s.videoSender=s.videoTransceiver.sender),this.applyCodecPreference(i),this.applyVideoQuality(i),i.addEventListener("icecandidate",c=>{c.candidate&&this.send({type:"rtc.ice",targetParticipantId:e.participantId,candidate:c.candidate.toJSON()})}),i.addEventListener("track",c=>{n.getTracks().some(l=>l.id===c.track.id)||n.addTrack(c.track),this.options.onEvent("remotestream",{participant:e,stream:n,track:c.track})}),i.addEventListener("connectionstatechange",()=>{["failed","closed"].includes(i.connectionState)&&this.removePeer(e.participantId),this.options.onEvent("peerconnectionstate",{participant:e,state:i.connectionState})}),s}async createOffer(e){const t=this.ensurePeer(e);if(t.offerTask)return t.offerTask;if(t.connection.signalingState!=="stable")return;const i=this.performOffer(t);t.offerTask=i;try{await i}finally{t.offerTask===i&&(t.offerTask=null)}}async performOffer(e){e.makingOffer=!0;try{const t=await e.connection.createOffer({offerToReceiveAudio:this.options.receiveAudio,offerToReceiveVideo:this.options.receiveVideo});if(e.connection.signalingState!=="stable")return;await e.connection.setLocalDescription(t);const i=le();e.activeNegotiationId=i,this.send({type:"rtc.offer",targetParticipantId:e.identity.participantId,negotiationId:i,description:e.connection.localDescription})}finally{e.makingOffer=!1}}shouldInitiateOffer(e){return this.options.credential.participantId.localeCompare(e.participantId)<0}async renegotiateAll(){for(const e of this.peers.values())await this.createOffer(e.identity)}async replaceVideoTrack(e){let t=!1;for(const i of this.peers.values()){const n=i.videoSender??i.connection.getSenders().find(o=>o.track?.kind==="video")??i.connection.getTransceivers().find(o=>o.receiver.track.kind==="video")?.sender,s=i.videoTransceiver??i.connection.getTransceivers().find(o=>o.sender===n);if(n){const o=e?this.options.receiveVideo?"sendrecv":"sendonly":this.options.receiveVideo?"recvonly":"inactive";s&&s.direction!==o&&(s.direction=o,t=!0),await n.replaceTrack(e),i.videoSender=n,s&&(i.videoTransceiver=s)}else e&&(i.videoSender=i.connection.addTrack(e,this.screenStream??this.localStream??new MediaStream([e])),t=!0)}t&&await this.renegotiateAll(),await this.applyVideoQuality()}async applyVideoQuality(e){const t=e?[...this.peers.values()].filter(i=>i.connection===e):[...this.peers.values()];for(const i of t)await this.applyPeerVideoQuality(i)}async applyPeerVideoQuality(e){const t=this.screenStream?.getVideoTracks()[0]?.id;for(const i of e.connection.getSenders().filter(n=>n.track?.kind==="video")){const n=!!(t&&i.track?.id===t),s=n?He:this.quality==="audio"?ze.audio:V[e.quality],o=i.getParameters(),r=o.encodings?.length?o.encodings:[{}];o.encodings=r.map(c=>({...c,...s})),o.degradationPreference=n?"maintain-resolution":"maintain-framerate",await i.setParameters(o)}}startQualitySampling(){this.stopQualitySampling(),this.qualityTimer=setInterval(()=>{this.samplePeerQuality()},5e3),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopQualitySampling(){this.qualityTimer&&clearInterval(this.qualityTimer),this.qualityTimer=null,this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=[];for(const i of[...this.peers.values()])if(i.connection.connectionState==="connected")try{const n=await i.connection.getStats();for(const s of ce(n.values()))e.push({participantId:i.identity.participantId,level:s.level})}catch{}const t=J(e,this.activeSpeaker);t!==this.activeSpeaker&&(this.activeSpeaker=t,this.options.onEvent("activespeaker",{participantId:t}))}async samplePeerQuality(){if(this.quality!=="audio")for(const e of[...this.peers.values()]){if(e.connection.connectionState!=="connected")continue;let t;try{t=await e.connection.getStats()}catch{continue}const{sample:i,counters:n}=Fe(t,e.counters);e.counters=n;const s=_e(e.quality,i,e.poorSamples,e.goodSamples,{samplesSeen:e.samplesSeen,ceiling:this.quality});e.poorSamples=s.poorSamples,e.goodSamples=s.goodSamples,e.samplesSeen=s.samplesSeen,s.changed&&(e.quality=s.quality,await this.applyPeerVideoQuality(e).catch(()=>{}),this.options.onEvent("peerquality",{participantId:e.identity.participantId,quality:s.quality}))}}static startingQuality(e){return e==="low"?"low":"medium"}applyCodecPreference(e){if(this.preferredVideoCodec==="auto"||!globalThis.RTCRtpReceiver?.getCapabilities)return;const t=RTCRtpReceiver.getCapabilities("video")?.codecs??[],i=`video/${this.preferredVideoCodec}`,n=[...t.filter(s=>s.mimeType.toLowerCase()===i),...t.filter(s=>s.mimeType.toLowerCase()!==i)];for(const s of e.getTransceivers())s.receiver.track.kind==="video"&&typeof s.setCodecPreferences=="function"&&s.setCodecPreferences(n)}removePeer(e){const t=this.peers.get(e);t&&(t.connection.close(),this.peers.delete(e),this.options.onEvent("participantleft",t.identity))}resetPeers(){for(const e of this.peers.values())e.connection.close();this.peers.clear()}startHeartbeat(e){this.stopHeartbeat(),this.stopQualitySampling(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}clearConnectionTimers(){this.stopHeartbeat(),this.stopQualitySampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??le(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function le(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}const K={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1},de=[{rid:"a",maxBitrate:V.high.maxBitrate,maxFramerate:V.high.maxFramerate},{rid:"b",maxBitrate:V.medium.maxBitrate,maxFramerate:V.medium.maxFramerate,scaleResolutionDownBy:2},{rid:"c",maxBitrate:V.low.maxBitrate,maxFramerate:V.low.maxFramerate,scaleResolutionDownBy:4}],he={high:"a",medium:"b",low:"c"};class $e{options;apiBase;socket=null;connection=null;sessionId=null;localStream=null;screenStream=null;remotes=new Map;midToParticipant=new Map;pendingAnnouncements=new Map;videoSender=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;speakerTimer=null;activeSpeaker=null;quality;resolveJoin=null;rejectJoin=null;signalQueue=Promise.resolve();negotiationQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.apiBase=Y(e.credential.signalingUrl,globalThis.location?.href).origin}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(this.joined)return;const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1),await this.publish(),this.startSpeakerSampling(),this.announceTracks();const t=[...this.pendingAnnouncements.values()];this.pendingAnnouncements.clear();for(const i of t)await this.handleTrackAnnouncement(i.sender,i.detail)}async leave(){this.stopped=!0,this.clearTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null,await this.closePublishedTracks().catch(()=>{}),this.connection?.close(),this.connection=null,this.sessionId=null,this.remotes.clear(),this.midToParticipant.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(c=>c.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")||await this.replaceVideoTrack(n);return}const r=this.connection?.getSenders().find(c=>c.track?.kind==="audio");r&&await r.replaceTrack(n)}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";await this.applyVideoQuality()}async setPreferredVideoCodec(e){}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getParticipants(){return[...this.remotes.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}getRemoteStreams(){return[...this.remotes.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.connection}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async openSocket(e){const{credential:t}=this.options,i=G(t.signalingUrl,globalThis.location?.href),n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0,this.resolveJoin?.();return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.sessionId&&this.announceTracks();return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removeRemote(t.sender.participantId);return}if(i==="rtc.control"){if(String(t.action??"")==="sfu.tracks"&&t.sender){await this.handleTrackAnnouncement(t.sender,t.detail);return}this.options.onEvent("control",t);return}i==="rtc.chat"&&this.options.onEvent("chatupdate",t)}}async publish(){const e=[...this.localStream?.getAudioTracks()??[],...this.localStream?.getVideoTracks()??[]],t=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"});this.connection=t,t.addEventListener("track",r=>this.handleRemoteTrack(r)),t.addEventListener("connectionstatechange",()=>{this.options.onEvent("peerconnectionstate",{participant:this.selfIdentity(),state:t.connectionState})});const i=await this.sfuFetch("POST","/v1/rtc/sfu/sessions");if(this.sessionId=i.sessionId,e.length===0)return;const n=e.map(r=>t.addTransceiver(r,{direction:"sendonly",streams:this.localStream?[this.localStream]:[],...r.kind==="video"&&this.simulcastEnabled()?{sendEncodings:de.map(c=>({...c}))}:{}})),s=await t.createOffer();await t.setLocalDescription(s);const o=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(i.sessionId)}/tracks`,{sessionDescription:t.localDescription,tracks:n.map(r=>({location:"local",mid:r.mid,trackName:this.trackName(r.sender.track)}))});o.sessionDescription&&await t.setRemoteDescription(o.sessionDescription),this.videoSender=t.getSenders().find(r=>r.track?.kind==="video")??null,await this.applyVideoQuality()}trackName(e){return`${this.options.credential.participantId}/${e.kind}`}announceTracks(){if(!this.sessionId)return;const e=[...(this.localStream?.getAudioTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!1})),...(this.localStream?.getVideoTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:this.simulcastEnabled()}))];e.length!==0&&this.sendControl("sfu.tracks",{sessionId:this.sessionId,tracks:e})}async handleTrackAnnouncement(e,t){const i=String(t?.sessionId??""),n=(t?.tracks??[]).map(r=>({trackName:String(r.trackName??""),simulcast:r.simulcast===!0})).filter(r=>!!r.trackName),s=n.map(r=>r.trackName);if(!i||s.length===0||e.participantId===this.options.credential.participantId)return;if(!this.connection||!this.sessionId){this.pendingAnnouncements.set(e.participantId,{sender:e,detail:t??{}});return}const o=this.remotes.get(e.participantId)??{identity:e,stream:new MediaStream,sessionId:i,trackNames:[],simulcastNames:new Set,mids:new Map};o.sessionId=i;for(const r of n)r.simulcast&&o.simulcastNames.add(r.trackName);this.remotes.set(e.participantId,o),this.negotiationQueue=this.negotiationQueue.then(async()=>{const r=s.filter(c=>!o.trackNames.includes(c));r.length!==0&&await this.subscribe(o,r)}).catch(r=>{this.options.onEvent("signalingerror",{message:r instanceof Error?r.message:"SFU subscribe failed"})}),await this.negotiationQueue}async subscribe(e,t){const i=this.connection;if(!i||!this.sessionId||t.length===0)return;const n=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks`,{tracks:t.map(s=>({location:"remote",sessionId:e.sessionId,trackName:s,...e.simulcastNames.has(s)?{simulcast:{preferredRid:"a",priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}:{}}))});for(const s of n.tracks??[])s.mid&&(e.mids.set(s.mid,String(s.trackName??"")),this.midToParticipant.set(s.mid,e.identity.participantId));if(e.trackNames=[...new Set([...e.trackNames,...t])],n.requiresImmediateRenegotiation&&n.sessionDescription){await i.setRemoteDescription(n.sessionDescription);const s=await i.createAnswer();await i.setLocalDescription(s),await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`,{sessionDescription:i.localDescription})}}handleRemoteTrack(e){const t=e.transceiver?.mid??null,i=t?this.midToParticipant.get(t):void 0,n=i?this.remotes.get(i):void 0;n&&(n.stream.getTracks().some(s=>s.id===e.track.id)||n.stream.addTrack(e.track),this.options.onEvent("remotestream",{participant:n.identity,stream:n.stream,track:e.track}))}removeRemote(e){const t=this.remotes.get(e);if(t){for(const i of t.mids.keys())this.midToParticipant.delete(i);this.remotes.delete(e),this.options.onEvent("participantleft",t.identity)}}async setRemoteQuality(e,t){const i=this.remotes.get(e);if(!i||!this.sessionId)return;const n=[...i.mids.entries()].find(([,s])=>i.simulcastNames.has(s))?.[0];n&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/update`,{tracks:[{mid:n,simulcast:{preferredRid:he[t],priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}]})}async replaceVideoTrack(e){this.videoSender&&(await this.videoSender.replaceTrack(e),await this.applyVideoQuality())}simulcastEnabled(){return this.options.simulcast!==!1}async applyVideoQuality(){const e=this.videoSender;if(!e)return;const t=this.screenStream?.getVideoTracks()[0]?.id,i=!!(t&&e.track?.id===t),n=e.getParameters(),s=n.encodings?.length?n.encodings:[{}];if(s.length>1){const o=this.quality==="audio"?null:he[this.quality];n.encodings=s.map(r=>{if(i)return{...r,active:r.rid==="a",...r.rid==="a"?{maxBitrate:K.maxBitrate,maxFramerate:K.maxFramerate}:{}};const c=de.find(d=>d.rid===r.rid),l=o!=null&&String(r.rid??"")>=o;return{...r,...c??{},active:l}})}else{const o=this.quality==="audio"?V.audio:V[this.quality],r=i?K:o;n.encodings=s.map(c=>({...c,...r}))}n.degradationPreference=i?"maintain-resolution":"maintain-framerate",await e.setParameters(n)}async closePublishedTracks(){const e=this.connection;if(!e||!this.sessionId)return;const t=e.getTransceivers().filter(i=>i.sender.track&&i.mid).map(i=>({mid:i.mid}));t.length!==0&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/close`,{tracks:t,force:!0})}selfIdentity(){return{participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId??this.options.credential.participantId,displayName:this.options.credential.externalUserId??this.options.credential.participantId,platform:"web"}}async sfuFetch(e,t,i){const n=await fetch(`${this.apiBase}${t}`,{method:e,headers:{authorization:`Bearer ${this.options.credential.signalingToken}`,"content-type":"application/json"},body:i===void 0?void 0:JSON.stringify(i)});if(!n.ok)throw new Error(`SFU request failed with HTTP ${n.status}`);return n.json()}startHeartbeat(e){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}startSpeakerSampling(){this.stopSpeakerSampling(),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopSpeakerSampling(){this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=this.connection;if(!e||e.connectionState!=="connected")return;let t;try{t=await e.getStats()}catch{return}const i=[];for(const s of ce(t.values())){const o=s.mid?this.midToParticipant.get(s.mid):void 0;o&&i.push({participantId:o,level:s.level})}const n=J(i,this.activeSpeaker);n!==this.activeSpeaker&&(this.activeSpeaker=n,this.options.onEvent("activespeaker",{participantId:n}))}clearTimers(){this.stopHeartbeat(),this.stopSpeakerSampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).then(()=>this.announceTracks()).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??We(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function We(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function Je(a,e={}){const t=e.document??globalThis.document,i=!!(t&&t.pictureInPictureEnabled);let n=null;function s(){return n||(n=t.createElement("video"),n.muted=!0,n.playsInline=!0,n.style.position="fixed",n.style.width="1px",n.style.height="1px",n.style.opacity="0",n.style.pointerEvents="none",n.addEventListener("leavepictureinpicture",()=>e.onLeave?.()),t.body.appendChild(n),n)}function o(r){const c=a.getRemoteStreams();return r?c.find(l=>l.participant.participantId===r)?.stream??null:c[0]?.stream??a.getLocalStream()}return{supported:i,async enter(r){if(!i)throw new Error("This browser does not support picture-in-picture");const c=o(r);if(!c)throw new Error("No video stream is available for picture-in-picture");const l=s();l.srcObject!==c&&(l.srcObject=c),await l.play().catch(()=>{}),await l.requestPictureInPicture()},async exit(){t.pictureInPictureElement&&typeof t.exitPictureInPicture=="function"&&await t.exitPictureInPicture()},dispose(){n?.remove(),n=null}}}function Ge(a,e){const t=e.document??globalThis.document,i=e.fetchImplementation??globalThis.fetch.bind(globalThis),n=Y(e.credential.signalingUrl,globalThis.location?.href).origin,s=e.mimeType??"video/webm",o=e.height??720,r=Math.round(o*16/9);let c=null,l=null,d=null,u=null,v=0,p=[],S=!1;async function b(y,T,f,M="application/json"){const I=await i(`${n}${T}`,{method:y,headers:{authorization:`Bearer ${e.credential.signalingToken}`,"content-type":M},body:f===void 0?void 0:M==="application/json"?JSON.stringify(f):f});if(!I.ok)throw new Error(`Recording request failed with HTTP ${I.status}`);return I.json()}return{get recording(){return S},async start(){if(S)return;u=(await b("POST","/v1/rtc/recordings")).recordingId;const T=t.createElement("canvas");T.width=r,T.height=o;const f=T.getContext("2d"),M=new Map,I=(C,A)=>{let E=M.get(C);return E||(E=t.createElement("video"),E.muted=!0,E.playsInline=!0,E.srcObject=A,E.play().catch(()=>{}),M.set(C,E)),E};l=setInterval(()=>{const C=a.getRemoteStreams(),A=a.getLocalStream(),E=[...C.map(U=>({key:U.participant.participantId,stream:U.stream,label:U.participant.displayName})),...A?[{key:"local",stream:A,label:"me"}]:[]];f.fillStyle="#101418",f.fillRect(0,0,r,o);const x=Math.max(1,Math.ceil(Math.sqrt(E.length))),_=Math.max(1,Math.ceil(E.length/x)),j=r/x,g=o/_;E.forEach((U,Ve)=>{const Be=I(U.key,U.stream),re=Ve%x*j,oe=Math.floor(Ve/x)*g;Be.videoWidth>0&&f.drawImage(Be,re+2,oe+2,j-4,g-4),f.fillStyle="rgba(0,0,0,.6)",f.fillRect(re+8,oe+g-30,Math.min(j-16,12+U.label.length*9),22),f.fillStyle="#fff",f.font="14px system-ui",f.fillText(U.label,re+14,oe+g-14)})},66);const D=T.captureStream(15);d=new AudioContext;const P=d.createMediaStreamDestination(),L=new Set,w=()=>{const C=[...a.getRemoteStreams().map(A=>A.stream),...a.getLocalStream()?[a.getLocalStream()]:[]];for(const A of C)for(const E of A.getAudioTracks())L.has(E.id)||(L.add(E.id),d.createMediaStreamSource(new MediaStream([E])).connect(P))};w();const R=setInterval(w,1e3),m=()=>clearInterval(R);for(const C of P.stream.getAudioTracks())D.addTrack(C);c=(e.mediaRecorderFactory??((C,A)=>new MediaRecorder(C,A)))(D,{mimeType:s}),p=[],c.addEventListener("dataavailable",C=>{C.data&&C.data.size>0&&p.push(C.data)}),c.addEventListener("stop",m),c.start(1e3),v=Date.now(),S=!0},async stop(){if(!S||!c||!u)throw new Error("The recorder is not running");S=!1;const y=c,T=new Promise(P=>y.addEventListener("stop",()=>P(),{once:!0}));y.stop(),await T,l&&clearInterval(l),l=null,await d?.close().catch(()=>{}),d=null;const f=new Blob(p,{type:s});p=[];const M=Math.max(1,Math.round((Date.now()-v)/1e3)),I=await b("PUT",`/v1/rtc/recordings/${encodeURIComponent(u)}/media`,f,s);await b("POST",`/v1/rtc/recordings/${encodeURIComponent(u)}/complete`,{durationSeconds:M});const D={recordingId:u,sizeBytes:I.sizeBytes,durationSeconds:M};return u=null,c=null,D},dispose(){l&&clearInterval(l),l=null;try{c?.stop()}catch{}c=null,d?.close().catch(()=>{}),d=null,S=!1}}}const ue=["none","blur","aurora","cybergrid","bokeh","sunset","image"],me=["none","snow","confetti","vignette"],X={aurora(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#050b1e"),n.addColorStop(1,"#0b2540"),a.fillStyle=n,a.fillRect(0,0,e,t);for(let s=0;s<3;s+=1){a.beginPath();for(let o=0;o<=e;o+=16){const r=t*.35+s*40+Math.sin(o/140+i/900+s)*60;o===0?a.moveTo(o,r):a.lineTo(o,r)}a.lineTo(e,0),a.lineTo(0,0),a.closePath(),a.fillStyle=`hsla(${140+s*40+Math.sin(i/1300)*20}, 80%, 55%, 0.14)`,a.fill()}},cybergrid(a,e,t,i){a.fillStyle="#0a0118",a.fillRect(0,0,e,t),a.strokeStyle="rgba(255,0,180,0.5)",a.lineWidth=1.5;const n=t*.45;for(let o=0;o<12;o+=1){const r=n+(o*34+i/18)%(t-n);a.beginPath(),a.moveTo(0,r),a.lineTo(e,r),a.stroke()}for(let o=-8;o<=8;o+=1)a.beginPath(),a.moveTo(e/2+o*24,n),a.lineTo(e/2+o*e*.12,t),a.stroke();const s=a.createLinearGradient(0,t*.1,0,n);s.addColorStop(0,"#ffd34d"),s.addColorStop(1,"#ff2d78"),a.fillStyle=s,a.beginPath(),a.arc(e/2,n,t*.22,Math.PI,0),a.fill()},bokeh(a,e,t,i){a.fillStyle="#14100c",a.fillRect(0,0,e,t);for(let n=0;n<26;n+=1){const s=(Math.sin(n*12.9898)*.5+.5)*e,o=(Math.sin(n*78.233)*.5+.5)*t+Math.sin(i/1500+n)*12,r=14+n%5*12;a.fillStyle=`hsla(${35+n%4*12}, 90%, 60%, ${.1+n%3*.05})`,a.beginPath(),a.arc(s,o,r,0,Math.PI*2),a.fill()}},sunset(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#2b1055"),n.addColorStop(.55,"#d1548a"),n.addColorStop(1,"#ffb36b"),a.fillStyle=n,a.fillRect(0,0,e,t),a.fillStyle="#ffdf8e",a.beginPath(),a.arc(e*.5,t*.62+Math.sin(i/4e3)*4,t*.13,0,Math.PI*2),a.fill(),a.fillStyle="rgba(20,8,40,0.85)",a.fillRect(0,t*.78,e,t*.22)}};function Ye(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.background??"none",l=e.overlay??"none",d=e.backgroundImage??null,u=e.segmenter??null,v=null,p=!1;const S=t.createElement("video");S.muted=!0,S.playsInline=!0,S.srcObject=new MediaStream([r]),S.play().catch(()=>{});const b=t.createElement("canvas");b.width=i,b.height=n;const y=b.getContext("2d"),T=t.createElement("canvas");T.width=i,T.height=n;const f=T.getContext("2d"),M=()=>Array.from({length:80},(m,h)=>({x:(Math.sin(h*12.9898)*.5+.5)*i,y:(Math.sin(h*78.233)*.5+.5)*n,speed:.6+h%5*.5,drift:Math.sin(h)*.6,size:2+h%4,hue:h*47%360}));let I=M();const D=()=>{if(s)return;const m=S.videoWidth,h=S.videoHeight;if(!m||!h)return;const C=Math.min(1,1280/Math.max(m,h)),A=Math.round(m*C),E=Math.round(h*C);A===i&&E===n||(i=A,n=E,b.width=i,b.height=n,T.width=i,T.height=n,I=M())};S.addEventListener?.("loadedmetadata",D),S.addEventListener?.("resize",D);function P(){if(v){f.clearRect(0,0,i,n),f.drawImage(v,0,0,i,n),f.globalCompositeOperation="source-in",f.drawImage(S,0,0,i,n),f.globalCompositeOperation="source-over",y.drawImage(T,0,0);return}const m=i*.42,h=m*9/16,C=i-m-24,A=n-h-24;y.save(),y.shadowColor="rgba(0,0,0,.5)",y.shadowBlur=24,y.beginPath(),y.roundRect(C,A,m,h,14),y.clip(),y.drawImage(S,C,A,m,h),y.restore()}function L(m){if(c==="none"?y.drawImage(S,0,0,i,n):c==="blur"?(y.filter="blur(14px)",y.drawImage(S,-16,-16,i+32,n+32),y.filter="none",v&&P()):c==="image"&&d?(y.drawImage(d,0,0,i,n),P()):((X[c]??X.aurora)(y,i,n,m),P()),l==="snow"||l==="confetti")for(const h of I)h.y+=h.speed*(l==="confetti"?1.6:1),h.x+=h.drift,h.y>n&&(h.y=-6,h.x=Math.random()*i),y.fillStyle=l==="snow"?"rgba(255,255,255,.85)":`hsl(${h.hue}, 90%, 60%)`,l==="confetti"?y.fillRect(h.x,h.y,h.size+2,h.size):(y.beginPath(),y.arc(h.x,h.y,h.size/2,0,Math.PI*2),y.fill());else if(l==="vignette"){const h=y.createRadialGradient(i/2,n/2,n*.4,i/2,n/2,n*.85);h.addColorStop(0,"rgba(0,0,0,0)"),h.addColorStop(1,"rgba(0,0,0,0.55)"),y.fillStyle=h,y.fillRect(0,0,i,n)}}const w=setInterval(()=>{L(Date.now()),u&&!p&&c!=="none"&&(p=!0,u.segment(S).then(m=>{v=m}).catch(()=>{v=null}).finally(()=>{p=!1}))},Math.round(1e3/o)),R=b.captureStream(o);return{track:R.getVideoTracks()[0],stream:R,setBackground(m,h){c=ue.includes(m)?m:"none",h!==void 0&&(d=h),c==="none"&&(v=null)},setOverlay(m){l=me.includes(m)?m:"none"},setSegmenter(m){u=m,m||(v=null)},dispose(){clearInterval(w),u?.close?.();for(const m of R.getTracks())m.stop();S.srcObject=null}}}function Z(){return{blinkLeft:0,blinkRight:0,jawOpen:0,smile:0,browUp:0,pupilX:0,pupilY:0,headYaw:0,headPitch:0,headRoll:0,tracked:!1}}function O(a,e){const t=a.find(i=>i.categoryName===e);return t?Math.max(0,Math.min(1,t.score)):0}function pe(a){if(!a||a.length<16)return{yaw:0,pitch:0,roll:0};a[0],a[4];const e=a[8],t=a[1],i=a[5],n=a[9],s=a[10],o=Math.atan2(e,s),r=Math.asin(Math.max(-1,Math.min(1,-n))),c=Math.atan2(t,i);return{yaw:o,pitch:r,roll:c}}function fe(a,e,t=1){if(!a||a.length===0)return Z();const i=pe(e),n=(O(a,"eyeLookOutRight")+O(a,"eyeLookInLeft"))/2,s=(O(a,"eyeLookOutLeft")+O(a,"eyeLookInRight"))/2,o=(O(a,"eyeLookUpLeft")+O(a,"eyeLookUpRight"))/2,r=(O(a,"eyeLookDownLeft")+O(a,"eyeLookDownRight"))/2,c=(O(a,"mouthSmileLeft")+O(a,"mouthSmileRight"))/2,l=(O(a,"mouthFrownLeft")+O(a,"mouthFrownRight"))/2,d=O(a,"eyeBlinkLeft"),u=O(a,"eyeBlinkRight"),v=Math.min(1,Math.abs(i.yaw)/.35)*Math.max(0,Math.min(1,t)),p=(d+u)/2;return{blinkLeft:d+(p-d)*v,blinkRight:u+(p-u)*v,jawOpen:O(a,"jawOpen"),smile:c-l,browUp:(O(a,"browInnerUp")+O(a,"browOuterUpLeft")+O(a,"browOuterUpRight"))/3,pupilX:n-s,pupilY:r-o,headYaw:i.yaw,headPitch:i.pitch,headRoll:i.roll,tracked:!0}}function ee(a,e,t=.4){const i=(n,s)=>n+(s-n)*t;return{blinkLeft:i(a.blinkLeft,e.blinkLeft),blinkRight:i(a.blinkRight,e.blinkRight),jawOpen:i(a.jawOpen,e.jawOpen),smile:i(a.smile,e.smile),browUp:i(a.browUp,e.browUp),pupilX:i(a.pupilX,e.pupilX),pupilY:i(a.pupilY,e.pupilY),headYaw:i(a.headYaw,e.headYaw),headPitch:i(a.headPitch,e.headPitch),headRoll:i(a.headRoll,e.headRoll),tracked:e.tracked}}function ge(a,e,t,i,n){n||(a.fillStyle="#101822",a.fillRect(0,0,t,i));const s=n?n.x+e.headYaw*n.radius*.1:t/2+e.headYaw*t*.18,o=n?n.y+e.headPitch*n.radius*.1:i/2+e.headPitch*i*.18,r=n?n.radius:Math.min(t,i)*.28;a.save(),a.translate(s,o),a.rotate(e.headRoll*.6);const c=Math.max(-1.35,Math.min(1.35,e.headYaw)),l=Math.max(-.9,Math.min(.9,e.headPitch)),d=Math.sin(c),u=L=>Math.sin(L+c)*r,v=L=>Math.cos(L+c),p=Math.sin(l)*r*.22;a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.35,-r*.25,r*.98,r*.95,0,0,Math.PI*2),a.fill(),a.fillStyle=e.tracked?"#f2c9a0":"#5a6470",a.beginPath(),a.ellipse(0,0,r*(1-Math.abs(d)*.12),r*1.12,0,0,Math.PI*2),a.fill(),a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.28,-r*.72+p*.4,r*.92,r*.5,0,Math.PI,Math.PI*2),a.fill();const S=0;a.fillStyle="#e5b58a",a.beginPath();const b=u(S)*.9,y=r*.12+p;a.ellipse(b+d*r*.14,y,r*.11,r*.14,0,0,Math.PI*2),a.fill();for(const L of[-1,1]){const w=L*.42,R=v(w);if(R<=.12)continue;const m=u(w)*.82,h=Math.max(.2,R),C=L<0?e.blinkLeft:e.blinkRight,A=Math.max(.06,1-C),E=e.browUp*r*.14;a.strokeStyle="#4a2e18",a.lineWidth=r*.07,a.lineCap="round",a.beginPath(),a.moveTo(m-r*.2*h,-r*.42-E+p),a.quadraticCurveTo(m,-r*.52-E+p,m+r*.2*h,-r*.44-E+p),a.stroke();const x=-r*.18+p;a.fillStyle="#ffffff",a.beginPath(),a.ellipse(m,x,r*.16*h,r*.12*A,0,0,Math.PI*2),a.fill(),a.fillStyle="#22303c",a.beginPath(),a.ellipse(m+e.pupilX*r*.07*h,x+e.pupilY*r*.05,r*.06*h,r*.06*A,0,0,Math.PI*2),a.fill()}const T=Math.max(.25,v(0)),f=u(0)*.7,M=r*.55*T,I=r*(.04+e.jawOpen*.34),D=e.smile*r*.12,P=r*.42+p;a.fillStyle="#8c2f2f",a.beginPath(),a.moveTo(f-M/2,P-D),a.quadraticCurveTo(f,P+I,f+M/2,P-D),a.quadraticCurveTo(f,P-I*.25,f-M/2,P-D),a.closePath(),a.fill(),a.restore(),!e.tracked&&!n&&(a.fillStyle="#9fb0bd",a.font=`${Math.round(i*.045)}px system-ui`,a.textAlign="center",a.fillText("No face detected",t/2,i*.9),a.textAlign="left")}const te={mouthGain:1.7,blinkGain:1,eyeSync:1,smoothing:.4,avatarScale:1,avatarLift:0,lostHoldMs:400};function ie(){return{render(a,e,t,i,n){ge(a,e,t,i,n??void 0)}}}const ne=a=>Math.max(0,Math.min(1,a));function Ke(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.tracker??null,l=e.renderer??ie(),d=e.mode??"replace";const u={...te,...e.tuning},v=e.backdrop??"#101822",p=t.createElement("video");p.muted=!0,p.playsInline=!0,p.srcObject=new MediaStream([r]),p.play().catch(()=>{});const S=t.createElement("canvas");S.width=i,S.height=n;const b=S.getContext("2d"),y=()=>{if(s)return;const w=p.videoWidth,R=p.videoHeight;if(!w||!R)return;const m=Math.min(1,1280/Math.max(w,R)),h=Math.round(w*m),C=Math.round(R*m);(!(h===i||C===n)||h!==i||C!==n)&&(i=h,n=C,S.width=i,S.height=n)};p.addEventListener?.("loadedmetadata",y),p.addEventListener?.("resize",y);let T=Z(),f=null,M=0;function I(w){if(w.length<=454)return;const R=w[10],m=w[152],h=w[234],C=w[454],A=(h.x+C.x)/2*i,E=(h.y+C.y)/2*n,x=Math.hypot((m.x-R.x)*i,(m.y-R.y)*n),_=Math.max(.6,Math.cos(T.headPitch)),j={x:A,y:E,radius:Math.max(24,x/_*.8)};f=f?{x:f.x+(j.x-f.x)*.4,y:f.y+(j.y-f.y)*.4,radius:f.radius+(j.radius-f.radius)*.15}:j}function D(w){if(c&&p.readyState>=2){let m=null;try{m=c.detect(p,w)}catch{}const h=fe(m?.categories??null,m?.matrix??null,u.eyeSync);h.jawOpen=ne(h.jawOpen*u.mouthGain),h.blinkLeft=ne(h.blinkLeft*u.blinkGain),h.blinkRight=ne(h.blinkRight*u.blinkGain),h.tracked?(M=w,T=ee(T,h,u.smoothing),m?.landmarks&&I(m.landmarks)):T.tracked&&w-M>u.lostHoldMs&&(T=ee(T,{...T,jawOpen:0,browUp:0,smile:T.smile*.5,blinkLeft:.1,blinkRight:.1},.05))}d==="overlay"&&p.readyState>=2?b.drawImage(p,0,0,i,n):(b.fillStyle=v,b.fillRect(0,0,i,n));const R=f?{x:f.x,y:f.y+u.avatarLift*f.radius,radius:f.radius*u.avatarScale}:null;l?.render(b,T,i,n,R)}const P=setInterval(()=>D(Date.now()),Math.round(1e3/o)),L=S.captureStream(o);return{track:L.getVideoTracks()[0],stream:L,getRig:()=>({...T}),setMode(w){d=w==="overlay"?"overlay":"replace"},setTracker(w){c?.close?.(),c=w},setRenderer(w){l?.dispose?.(),l=w??ie()},setTuning(w){for(const R of Object.keys(te)){const m=w[R];typeof m=="number"&&Number.isFinite(m)&&(u[R]=m)}},getTuning:()=>({...u}),dispose(){clearInterval(P),c?.close?.(),l?.dispose?.();for(const w of L.getTracks())w.stop();p.srcObject=null}}}class B extends Error{constructor(e,t){super(t),this.code=e,this.name="ZipError"}code}const ye=101010256,ve=33639248,Se=67324752,ae=4294967295;function we(a){if(a.byteLength<4)return!1;const e=new DataView(a).getUint32(0,!0);return e===Se||e===ve||e===ye}function Xe(a){const e=Math.max(0,a.byteLength-22-65535);for(let t=a.byteLength-22;t>=e;t-=1)if(a.getUint32(t,!0)===ye)return t;throw new B("zip_unreadable","The archive index could not be found.")}function ke(a){const e=new DataView(a),t=Xe(e),i=e.getUint16(t+10,!0);let n=e.getUint32(t+16,!0);if(n===ae)throw new B("zip_unsupported","ZIP64 archives are not supported.");const s=new TextDecoder,o=[];for(let r=0;r<i;r+=1){if(n+46>a.byteLength||e.getUint32(n,!0)!==ve)throw new B("zip_unreadable","The archive index is malformed.");const c=e.getUint16(n+8,!0),l=e.getUint16(n+10,!0),d=e.getUint32(n+20,!0),u=e.getUint32(n+24,!0),v=e.getUint16(n+28,!0),p=e.getUint16(n+30,!0),S=e.getUint16(n+32,!0),b=e.getUint32(n+42,!0),y=s.decode(new Uint8Array(a,n+46,v));o.push({path:y,bytes:u,compressedBytes:d,compressionMethod:l,encrypted:(c&1)!==0,localHeaderOffset:b}),n+=46+v+p+S}return o}async function Ze(a,e){if(e.encrypted)throw new B("zip_encrypted","The archive is password protected.");if(e.bytes===ae||e.compressedBytes===ae)throw new B("zip_unsupported","ZIP64 archives are not supported.");const t=new DataView(a),i=e.localHeaderOffset;if(i+30>a.byteLength||t.getUint32(i,!0)!==Se)throw new B("zip_unreadable","An archive entry header is malformed.");const n=t.getUint16(i+26,!0),s=t.getUint16(i+28,!0),o=i+30+n+s;if(o+e.compressedBytes>a.byteLength)throw new B("zip_unreadable","An archive entry runs past the end of the file.");const r=a.slice(o,o+e.compressedBytes);if(e.compressionMethod===0)return r;if(e.compressionMethod!==8)throw new B("zip_unsupported",`Archive compression method ${e.compressionMethod} is not supported.`);if(typeof DecompressionStream>"u")throw new B("zip_unsupported","This browser cannot expand compressed archives.");const c=new Blob([r]).stream().pipeThrough(new DecompressionStream("deflate-raw"));return new Response(c).arrayBuffer()}async function et(a){const e=typeof File<"u"&&a instanceof File?await a.arrayBuffer():a;if(!we(e))return[];try{return be(ke(e)).map(t=>t.path)}catch{return[]}}const Te=64*1024*1024,tt="streaming-cdn-avatars",H="models",it=1179937895,nt=1313821514;class N extends Error{constructor(e,t,i=[]){super(t),this.code=e,this.name="AvatarModelError",this.choices=i}code;choices}function be(a){return a.filter(e=>/\.vrm$/i.test(e.path)&&!e.path.endsWith("/")&&!e.path.split("/").some(t=>t==="__MACOSX"||t.startsWith("._")))}function q(a){const e=typeof a=="string"?a.trim():"";return e.length>0?e:null}function Ce(a){const e=new DataView(a);if(a.byteLength<20||e.getUint32(0,!0)!==it)throw new N("model_not_glb","The file is not a binary glTF (.vrm / .glb) model.");let t=12,i=null;for(;t+8<=a.byteLength;){const r=e.getUint32(t,!0),c=e.getUint32(t+4,!0),l=t+8;if(l+r>a.byteLength)break;if(c===nt){try{const d=new TextDecoder().decode(new Uint8Array(a,l,r)).replace(/[\s\u0000]+$/,"");i=JSON.parse(d)}catch{throw new N("model_unreadable","The model's glTF descriptor could not be read.")}break}t=l+r+(4-r%4)%4}const n=i?.extensions??{},s=n.VRM?.meta,o=n.VRMC_vrm?.meta;if(!s&&!o)return null;if(o){const r=Array.isArray(o.authors)?o.authors.filter(c=>typeof c=="string"):[];return{title:q(o.name),author:r.length?r.join(", "):null,allowedUser:q(o.avatarPermission),commercialUse:q(o.commercialUsage),licenseName:q(o.licenseUrl)?"VRM 1.0":null,licenseUrl:q(o.licenseUrl)??q(o.otherLicenseUrl)}}return{title:q(s?.title),author:q(s?.author),allowedUser:q(s?.allowedUserName),commercialUse:q(s?.commercialUssageName),licenseName:q(s?.licenseName),licenseUrl:q(s?.otherLicenseUrl)}}function at(){return new Promise((a,e)=>{if(typeof indexedDB>"u"){e(new N("storage_unavailable","This browser cannot store imported avatar models."));return}const t=indexedDB.open(tt,1);t.onupgradeneeded=()=>{t.result.objectStoreNames.contains(H)||t.result.createObjectStore(H,{keyPath:"id"})},t.onsuccess=()=>a(t.result),t.onerror=()=>e(new N("storage_unavailable","Imported avatar storage could not be opened."))})}async function $(a,e){const t=await at();try{return await new Promise((i,n)=>{const s=e(t.transaction(H,a).objectStore(H));s.onsuccess=()=>i(s.result),s.onerror=()=>n(new N("storage_unavailable","The imported avatar could not be stored."))})}finally{t.close()}}const Ee=a=>({id:a.id,name:a.name,bytes:a.bytes,importedAt:a.importedAt,license:a.license});async function st(a,e={}){const t=e.maxBytes??Te,i=typeof File<"u"&&a instanceof File;let n=i?a.name:null,s=i?await a.arrayBuffer():a;if(we(s)){const l=await rt(s,e.entryPath,t);s=l.buffer,n=l.path.split("/").pop()??n}if(s.byteLength>t)throw new N("model_too_large","The model is larger than the "+Math.round(t/1024/1024)+" MB limit.");const o=Ce(s);if(!o)throw new N("model_not_vrm","The file is a glTF model but carries no VRM character data.");const r=o.title||(n?n.replace(/\.(vrm|glb)$/i,""):null),c={id:"avatar_"+Date.now().toString(36)+"_"+Math.random().toString(36).slice(2,8),name:e.name?.trim()||r||"Imported avatar",bytes:s.byteLength,importedAt:new Date().toISOString(),license:o,data:s};return await $("readwrite",l=>l.put(c)),Ee(c)}async function rt(a,e,t){let i;try{i=be(ke(a))}catch(s){throw s instanceof B?new N(Re(s.code),s.message):new N("archive_unreadable","The archive could not be read.")}if(i.length===0)throw new N("archive_no_model","The archive contains no .vrm character.");const n=e?i.find(s=>s.path===e):i.length===1?i[0]:null;if(!n)throw new N("archive_many_models","The archive contains several characters; choose one.",i.map(s=>s.path));if(n.bytes>t)throw new N("model_too_large","The character inside the archive is larger than the "+Math.round(t/1024/1024)+" MB limit.");try{return{buffer:await Ze(a,n),path:n.path}}catch(s){throw s instanceof B?new N(Re(s.code),s.message):new N("archive_unreadable","The character could not be expanded from the archive.")}}function Re(a){return a==="zip_encrypted"?"archive_encrypted":a==="zip_unsupported"?"archive_unsupported":"archive_unreadable"}async function ot(){return(await $("readonly",e=>e.getAll())??[]).map(Ee).sort((e,t)=>t.importedAt.localeCompare(e.importedAt))}async function ct(a){return(await $("readonly",t=>t.get(a)))?.data??null}async function lt(a){await $("readwrite",e=>e.delete(a))}class Me extends EventTarget{mode;options;client=null;peerConnection;state="initialized";latestMediaScore=null;latestQuality=null;statsTimer=null;statsRunning=!1;previousCounters=null;lastTelemetryAt=0;joinPromise=null;requestedQuality;effectiveQuality;poorQualitySamples=0;goodQualitySamples=0;constructor(e){if(super(),!e.credential?.signalingToken||!e.credential.signalingUrl||!e.credential.roomId||!e.credential.participantId)throw new Error("A valid room signaling credential is required");this.mode=e.mode,this.requestedQuality=e.mode==="voice"?"audio":e.quality??"auto",this.effectiveQuality=this.requestedQuality==="auto"?"medium":this.requestedQuality,this.peerConnection=e.peerConnection??null,this.options={...e,audio:e.audio??!0,video:e.mode==="voice"?!1:e.video??!0,receiveAudio:e.receiveAudio??!0,receiveVideo:e.mode==="voice"?!1:e.receiveVideo??!0,statsInterval:Math.max(1e3,e.statsInterval??5e3),telemetryInterval:Math.max(1e4,e.telemetryInterval??3e4),autoReportStats:e.autoReportStats??!0}}async initialize(){if(this.client)return this;try{const t=(this.options.transport==="auto"?this.mode==="meeting"?"sfu":"mesh":this.options.transport??"mesh")==="sfu"?$e:z;return this.client=new t({credential:{signalingToken:this.options.credential.signalingToken,signalingUrl:this.options.credential.signalingUrl,roomId:this.options.credential.roomId,participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId,iceServers:this.options.credential.iceServers},audio:this.options.audio,video:this.options.video,receiveAudio:this.options.receiveAudio,receiveVideo:this.options.receiveVideo,quality:this.effectiveQuality,preferredVideoCodec:this.options.preferredVideoCodec??"auto",iceTransportPolicy:this.options.iceTransportPolicy??"all",localStream:this.options.localStream,simulcast:this.options.simulcast??!0,onEvent:(i,n)=>this.handleNativeEvent(i,n)}),await this.client.initialize(),this.startStats(),this.emit("initialized",{mode:this.mode,sdkVersion:xe}),this.options.autoJoin&&await this.join(),this}catch(e){this.state="failed";const t=qe(e,"RTC initialization failed");throw this.emit("error",Q(t)),t}}async join(){const e=this.requireClient();return this.joinPromise?this.joinPromise:(this.state="joining",this.emit("joining",{}),this.joinPromise=e.join().catch(t=>{this.state="failed";const i=qe(t,"RTC connection failed");throw this.emit("error",Q(i)),i}).finally(()=>{this.joinPromise=null}),this.joinPromise)}async leave(){this.client&&(await this.joinPromise?.catch(()=>{}),await this.sampleAndReport("final").catch(()=>{}),await this.client.leave(),this.state="disconnected",this.stopStats())}async setMuted(e){this.requireClient().setMuted(e),this.emit("mediachange",{kind:"audio",enabled:!e})}async setCameraEnabled(e){if(this.mode==="voice"&&e)throw new Error("Camera is unavailable in voice mode");await this.requireClient().setCameraEnabled(e),this.emit("mediachange",{kind:"video",enabled:e})}async setVideoTrack(e){if(this.mode==="voice")throw new Error("Camera is unavailable in voice mode");await this.requireClient().setVideoTrack(e),this.emit("mediachange",{kind:"video",enabled:e.enabled,trackId:e.id})}async setScreenShareEnabled(e){await this.requireClient().setScreenShareEnabled(e),this.emit("mediachange",{kind:"screenshare",enabled:e})}async setDevice(e){await this.requireClient().setDevice(e),this.emit("devicechange",{deviceId:e.deviceId,kind:e.kind})}async setQuality(e){if(this.mode==="voice"&&e!=="audio")throw new Error("Voice calls only support audio quality");this.requestedQuality=e,this.poorQualitySamples=0,this.goodQualitySamples=0,await this.applyEffectiveQuality(e==="auto"?"medium":e,"manual")}async setPreferredVideoCodec(e){await this.requireClient().setPreferredVideoCodec(e),this.emit("codecchange",{preferredVideoCodec:e})}async getDevices(){return navigator.mediaDevices?.enumerateDevices?navigator.mediaDevices.enumerateDevices():[]}getParticipants(){return this.requireClient().getParticipants().map(Oe)}getLocalStream(){return this.client?.getLocalStream()??null}getRemoteStreams(){return(this.client?.getRemoteStreams()??[]).map(e=>({participant:Oe(e.participant),stream:e.stream}))}setPeerConnection(e){this.peerConnection=e,this.previousCounters=null}async sendChatMessage(e,t){const i=e.trim();if(!i)throw new Error("message is required");this.requireClient().sendChat(i,t)}async sendControlMessage(e,t={}){const i=e.trim();if(!i)throw new Error("action is required");this.requireClient().sendControl(i,t)}async createPoll(e,t,i={}){const n=t.map(s=>s.trim()).filter(Boolean);if(!e.trim()||n.length<2)throw new Error("A poll requires a question and at least two options");this.requireClient().sendControl("poll.create",{poll:{id:`poll-${crypto.randomUUID()}`,question:e.trim(),options:n,votes:{},...i}})}async votePoll(e,t){this.requireClient().sendControl("poll.vote",{pollId:e,optionIndex:t})}async requestStageAccess(){this.requireClient().sendControl("stage.request")}async cancelStageAccess(){this.requireClient().sendControl("stage.cancel")}async joinStage(){this.requireClient().sendControl("stage.join")}async leaveStage(){this.requireClient().sendControl("stage.leave")}async grantStageAccess(e){this.requireClient().sendControl("stage.grant",{participantIds:e})}async denyStageAccess(e){this.requireClient().sendControl("stage.deny",{participantIds:e})}async startRecording(e=!1){this.requireClient().sendControl("recording.start",{allowMultiple:e})}async stopRecording(e){this.requireClient().sendControl("recording.stop",{recordingId:e})}async pauseRecording(e){this.requireClient().sendControl("recording.pause",{recordingId:e})}async resumeRecording(e){this.requireClient().sendControl("recording.resume",{recordingId:e})}async runPreflight(e={}){const t=[],i=globalThis.isSecureContext===!0;i||t.push("secure_context_required");let n="unavailable";if(e.requestMedia!==!1&&navigator.mediaDevices?.getUserMedia)try{(await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})).getTracks().forEach(v=>v.stop()),n="granted"}catch{n="denied",t.push("media_permission_denied")}const s=navigator.mediaDevices?.enumerateDevices?await navigator.mediaDevices.enumerateDevices().catch(()=>[]):[],o=s.filter(u=>u.kind==="audioinput").length,r=s.filter(u=>u.kind==="videoinput").length;this.options.audio&&o===0&&t.push("microphone_unavailable"),this.options.video&&r===0&&t.push("camera_unavailable");const c=e.probeUrl??this.options.preflightProbeUrl;let l=null;if(c){const u=performance.now();try{const v=await fetch(c,{method:"HEAD",cache:"no-store",credentials:"omit"});l=Math.round(performance.now()-u),v.ok||t.push("network_probe_failed")}catch{t.push("network_probe_failed")}}const d={passed:t.length===0,sampledAt:new Date().toISOString(),secureContext:i,mediaPermission:n,microphoneCount:o,cameraCount:r,probeLatencyMs:l,effectiveConnectionType:Ne()?.effectiveType??null,issues:t};return this.emit("preflight",d),this.options.autoReportStats&&await this.reportQuality({sampleType:"preflight",sampledAt:d.sampledAt,connectionState:d.passed?"ready":"failed",rttMs:d.probeLatencyMs,effectiveConnectionType:d.effectiveConnectionType}).catch(()=>{}),d}async collectQualitySample(e="runtime"){const t=this.options.statsProvider?await this.options.statsProvider():{},i=this.peerConnection?await pt(this.peerConnection,this.previousCounters):null;i?.counters&&(this.previousCounters=i.counters);const s={...{sampleType:e,sampledAt:new Date().toISOString(),connectionState:this.peerConnection?.connectionState??this.state,mediaScore:this.latestMediaScore,networkScore:this.latestMediaScore==null?5:Math.max(0,Math.min(5,Math.round(this.latestMediaScore))),estimatedMos:4.5,rttMs:null,jitterMs:null,packetLossPct:null,packetLossCumulativePct:null,packetsLost:null,packetsReceived:null,inboundBitrateBps:null,outboundBitrateBps:null,availableOutgoingBitrateBps:null,framesPerSecond:null,frameWidth:null,frameHeight:null,inboundFrameWidth:null,inboundFrameHeight:null,outboundFrameWidth:null,outboundFrameHeight:null,framesDropped:null,freezeCount:null,freezeDurationMs:null,concealedSamples:null,codec:null,transportProtocol:null,candidateType:null,qualityLimitationReason:null,deviceMemoryGb:gt(),effectiveConnectionType:Ne()?.effectiveType??null},...i?.sample??{},...t,sampleType:e,sampledAt:new Date().toISOString()},o=ft(s);return s.networkScore=o.networkScore,s.estimatedMos=o.estimatedMos,this.latestQuality=s,s}async reportQuality(e){const t=this.options.credential.telemetryToken,i=this.options.telemetryEndpoint??this.options.credential.telemetryEndpoint;if(!t||!i)return;const n=await fetch(i,{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify(e),keepalive:e.sampleType==="final"});if(!n.ok)throw new Error(`RTC telemetry failed with HTTP ${n.status}`)}getStats(){const e=this.client,t=e?.getLocalStream();return{state:this.state,participantCount:e?e.getParticipants().length+1:0,audioEnabled:!!t?.getAudioTracks().some(i=>i.enabled),videoEnabled:!!t?.getVideoTracks().some(i=>i.enabled),screenShareEnabled:!1,latestMediaScore:this.latestMediaScore,latestQuality:this.latestQuality,sampledAt:new Date().toISOString()}}getNativeClient(){return this.client}destroy(){this.stopStats(),this.client&&this.client.leave(),this.client=null,this.joinPromise=null,this.peerConnection=null,this.state="disconnected"}handleNativeEvent(e,t){e==="connected"?(this.state="connected",this.peerConnection=this.client?.getPrimaryPeerConnection()??null):e==="disconnected"?this.state="disconnected":e==="peerconnectionstate"&&(this.peerConnection=this.client?.getPrimaryPeerConnection()??null),this.emit(e,t)}startStats(){this.stopStats(),this.statsTimer=setInterval(()=>{this.sampleAndReport("runtime")},this.options.statsInterval)}async sampleAndReport(e){if(!this.statsRunning){this.statsRunning=!0;try{const t=await this.collectQualitySample(e);await this.adaptQuality(t),this.emit("stats",{...this.getStats(),latestQuality:t});const i=Date.now(),n=e!=="runtime"||i-this.lastTelemetryAt>=this.options.telemetryInterval;this.options.autoReportStats&&n&&(await this.reportQuality(t),this.lastTelemetryAt=i)}catch(t){this.emit("telemetryerror",Q(t))}finally{this.statsRunning=!1}}}stopStats(){this.statsTimer!==null&&clearInterval(this.statsTimer),this.statsTimer=null}async adaptQuality(e){if(this.requestedQuality!=="auto"||this.options.adaptiveQuality===!1||this.mode==="voice")return;if((this.client?.getParticipants().length??0)>1){this.poorQualitySamples=0,this.goodQualitySamples=0,this.effectiveQuality!=="high"&&await this.applyEffectiveQuality("high","network");return}const t=String(e.qualityLimitationReason??"").toLowerCase(),i=(e.packetLossPct??0)>=8||(e.rttMs??0)>=800||t==="cpu"||t==="bandwidth",n=i||(e.packetLossPct??0)>=3||(e.rttMs??0)>=350||e.availableOutgoingBitrateBps!=null&&e.availableOutgoingBitrateBps<35e4,s=(e.packetLossPct??100)<1&&(e.rttMs??1e3)<180&&!t&&(e.availableOutgoingBitrateBps==null||e.availableOutgoingBitrateBps>1e6);this.poorQualitySamples=n?this.poorQualitySamples+1:0,this.goodQualitySamples=s?this.goodQualitySamples+1:0;const o=["low","medium","high"],r=o.indexOf(this.effectiveQuality);i&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality("low","network")):this.poorQualitySamples>=2&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality(o[r-1],"network")):this.goodQualitySamples>=6&&r>=0&&r<o.length-1&&(this.goodQualitySamples=0,await this.applyEffectiveQuality(o[r+1],"network"))}async applyEffectiveQuality(e,t){if(this.effectiveQuality===e&&this.client)return;const i=this.effectiveQuality;this.effectiveQuality=e,this.client&&await this.client.setQuality(e),this.emit("qualitychange",{requestedQuality:this.requestedQuality,quality:e,previous:i,reason:t})}requireClient(){if(!this.client)throw new Error("Call initialize() before using the RTC client");return this.client}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}async function dt(a){return new Me(a).initialize()}let W=null;function Ie(){if(W)return W;const a=globalThis,e=a.AudioContext??a.webkitAudioContext;return e?(W=new e,W):null}async function Ae(){const a=Ie();return a?(a.state==="suspended"&&await a.resume().catch(()=>{}),a.state==="running"):!1}class ht{options;media=null;cadenceTimer=null;oscillators=new Set;constructor(e){this.options=e}unlock(){return Ae()}startIncoming(){this.start("incoming",this.options.ringtoneUrl)}startRingback(){this.start("ringback",this.options.ringbackUrl)}stop(){this.cadenceTimer&&clearInterval(this.cadenceTimer),this.cadenceTimer=null,this.media?.pause(),this.media&&(this.media.currentTime=0),this.media=null;for(const e of this.oscillators)try{e.stop()}catch{}this.oscillators.clear()}start(e,t){if(this.stop(),this.options.sounds===!1)return;if(t&&typeof Audio<"u"){this.media=new Audio(t),this.media.loop=!0,this.media.play().catch(()=>{});return}const i=()=>this.playBuiltInTone(e);i(),this.cadenceTimer=setInterval(i,e==="incoming"?3e3:3500)}playBuiltInTone(e){const t=Ie();if(!t||t.state!=="running")return;const i=e==="incoming"?1.25:1,n=e==="incoming"?[440,480]:[425],s=t.createGain(),o=t.currentTime;s.gain.setValueAtTime(1e-4,o),s.gain.exponentialRampToValueAtTime(.045,o+.025),s.gain.setValueAtTime(.045,o+i-.06),s.gain.exponentialRampToValueAtTime(1e-4,o+i),s.connect(t.destination);for(const r of n){const c=t.createOscillator();c.type="sine",c.frequency.value=r,c.connect(s),this.oscillators.add(c),c.onended=()=>this.oscillators.delete(c),c.start(o),c.stop(o+i+.02)}}}class Pe extends EventTarget{options;socket=null;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;currentCall=null;outgoingCallId=null;mount=null;terminalTimer=null;tokenRefreshTimer=null;tokenRequest=null;currentToken;currentTokenExpiresAt;callAudio;mediaClients=new Map;activeCallIds=new Set;seenInvitationIds=new Set;invitationExpiryTimers=new Map;startCallRequest=null;lastDisconnect=null;constructor(e){if(super(),!e.token&&!e.tokenProvider)throw new Error("A client token or tokenProvider is required");if(e.token&&!e.token.startsWith("rtcc_"))throw new Error("A valid RTC client token is required");if(!e.signalingUrl&&!e.callActionsUrl&&!e.apiBaseUrl&&!globalThis.location?.origin)throw new Error("signalingUrl or callActionsUrl is required outside a browser");this.options=e,this.currentToken=e.token??null,this.currentTokenExpiresAt=e.tokenExpiresAt,this.callAudio=new ht(e),this.mount=Le(e.mount),this.scheduleTokenRefresh()}connect(){if(!this.options.signalingUrl)throw new Error("signalingUrl is required to connect signaling");return this.stopped=!1,this.scheduleTokenRefresh(),this.openSocket("initial"),this}setMount(e){this.clearUi(),this.mount=Le(e),this.currentCall&&this.renderIncoming(this.currentCall)}attachMediaClient(e,t){if(!e.trim())throw new Error("callId is required");return this.mediaClients.set(e,t),()=>{this.mediaClients.get(e)===t&&this.mediaClients.delete(e)}}async startCall(e){if(this.startCallRequest)return this.startCallRequest;if(this.activeCallIds.size>0)throw new Error("Another call is already active");const t=e.targets?.length?e.targets:e.target?[e.target]:[];if(t.length===0)throw new Error("startCall requires at least one target");const i={mode:e.mode,targets:t,metadata:e.metadata,idempotencyKey:e.idempotencyKey},n=(async()=>{await this.callAudio.unlock(),this.callAudio.startRingback();let s;try{s=await this.request("/v1/rtc/client/calls",{method:"POST",headers:e.idempotencyKey?{"idempotency-key":e.idempotencyKey}:void 0,body:JSON.stringify(i)})}catch(o){throw this.callAudio.stop(),o}return!s.credential&&s.callerCredential&&(s.credential=s.callerCredential),this.outgoingCallId=String(s.call?.id??"")||null,this.outgoingCallId&&this.activeCallIds.add(this.outgoingCallId),this.emit("outgoingcall",s),s})();this.startCallRequest=n;try{return await n}finally{this.startCallRequest===n&&(this.startCallRequest=null)}}async accept(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"accept");return this.activeCallIds.add(e),this.clearInvitationExpiry(e),this.callAudio.stop(),this.dismiss(e),this.emit("callaccepted",t),t}async reject(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"reject");return this.releaseMedia(e),this.showTerminal(e,"Call declined"),this.emit("callrejected",t),t}async busy(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"busy");return this.releaseMedia(e),this.showTerminal(e,"Line busy"),t}async end(e){this.callAudio.stop();const t=await this.callAction(e,"end");return this.releaseMedia(e),this.showTerminal(e,"Call ended"),t}async cancel(e){this.callAudio.stop();const t=await this.callAction(e,"cancel");return this.releaseMedia(e),this.showTerminal(e,"Call cancelled"),t}handleIncomingCall(e){if(!e?.id||!e.caller?.id||!e.caller.name||e.mode!=="voice"&&e.mode!=="video")throw new Error("Incoming call payload is invalid");if(e.expiresAt&&Date.parse(e.expiresAt)<=Date.now()){this.emit("callexpired",{id:e.id,expiresAt:e.expiresAt});return}if(this.currentCall?.id===e.id){this.currentCall=e,this.emit("callrestored",e);return}if(this.seenInvitationIds.has(e.id)){this.emit("callrestored",e);return}this.seenInvitationIds.add(e.id),this.activeCallIds.add(e.id),this.scheduleInvitationExpiry(e.id,e.expiresAt),this.currentCall=e,this.callAudio.startIncoming(),this.renderIncoming(e),this.emit("incomingcall",e)}scheduleInvitationExpiry(e,t){const i=t?Date.parse(t):Date.now()+45e3,n=Math.min(18e4,Math.max(5e3,(Number.isFinite(i)?i:Date.now()+45e3)-Date.now()+1e4));this.clearInvitationExpiry(e),this.invitationExpiryTimers.set(e,setTimeout(()=>{this.invitationExpiryTimers.delete(e),!(!this.activeCallIds.has(e)||this.mediaClients.has(e))&&(this.emit("callexpired",{id:e,expiresAt:t??null}),this.release(e))},n))}clearInvitationExpiry(e){const t=this.invitationExpiryTimers.get(e);t&&clearTimeout(t),this.invitationExpiryTimers.delete(e)}release(e){if(!e.trim())throw new Error("callId is required");this.clearInvitationExpiry(e),this.currentCall?.id===e&&(this.callAudio.stop(),this.dismiss(e)),this.outgoingCallId===e&&(this.outgoingCallId=null),this.releaseMedia(e),this.emit("callreleased",{id:e})}handleCallUpdate(e){const t=e.id===this.currentCall?.id||e.id===this.outgoingCallId,i=["answered_elsewhere","rejected","busy","cancelled","ended","failed","missed"];(e.status==="accepted"||i.includes(e.status))&&this.clearInvitationExpiry(e.id),t&&e.status==="accepted"?(this.activeCallIds.add(e.id),this.callAudio.stop(),this.dismiss(e.id)):t&&i.includes(e.status)&&this.showTerminal(e.id,mt(e.status)),i.includes(e.status)&&this.releaseMedia(e.id),this.emit("callupdated",e)}disconnect(){this.stopped=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.reconnectTimer=null,this.heartbeatTimer=null,this.tokenRefreshTimer=null,this.socket?.close(1e3,"Client disconnected"),this.socket=null,this.callAudio.stop(),this.dismiss();for(const e of this.invitationExpiryTimers.values())clearTimeout(e);this.invitationExpiryTimers.clear();for(const e of this.mediaClients.keys())this.releaseMedia(e);this.activeCallIds.clear(),this.seenInvitationIds.clear()}destroy(){this.disconnect(),this.mount=null}async openSocket(e){if(!this.options.signalingUrl||this.stopped||this.socket?.readyState===WebSocket.OPEN||this.socket?.readyState===WebSocket.CONNECTING)return;let t;try{t=await this.resolveToken(e,e==="reconnect")}catch(s){this.emit("tokenerror",Q(s)),!this.stopped&&this.options.reconnect!==!1&&this.scheduleReconnect();return}if(this.stopped||this.socket)return;const i=G(this.options.signalingUrl,globalThis.location?.href),n=new WebSocket(i,["rtc-client",t]);this.socket=n,n.addEventListener("open",()=>{const s=this.reconnectAttempt>0,o=this.lastDisconnect;this.reconnectAttempt=0,this.heartbeatDeadline=Date.now()+45e3,this.emit("signalingconnected",{resumed:s}),o&&(this.reportSignalingDiagnostic({event:"recovered",code:o.code,reason:o.reason,wasClean:o.wasClean,outageMs:Date.now()-o.at}),this.lastDisconnect=null),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{if(n.readyState===WebSocket.OPEN){if(Date.now()>this.heartbeatDeadline){n.close(4e3,"Signaling heartbeat timed out");return}n.send("ping")}},2e4)}),n.addEventListener("message",s=>{this.heartbeatDeadline=Date.now()+45e3,this.handleSignal(String(s.data))}),n.addEventListener("close",s=>{if(this.socket!==n)return;this.socket=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;const o=!this.stopped&&this.options.reconnect!==!1,r={code:s.code,reason:s.reason,wasClean:s.wasClean,willReconnect:o,attempt:this.reconnectAttempt+1};o&&(this.lastDisconnect={code:s.code,reason:s.reason,wasClean:s.wasClean,at:Date.now()}),this.emit("signalingdisconnected",r),this.reportSignalingDiagnostic({event:o?"reconnecting":"disconnected",...r}),o&&this.scheduleReconnect()}),n.addEventListener("error",()=>{const s={message:"RTC signaling connection failed",reconnecting:!this.stopped&&this.options.reconnect!==!1,attempt:this.reconnectAttempt+1};this.emit("signalingerror",s),this.reportSignalingDiagnostic({event:"error",attempt:s.attempt})})}handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i==="call.incoming"&&t.call&&typeof t.call=="object"){this.handleIncomingCall(t.call);return}if(i==="call.updated"){const n=t.call;n?.id&&n.status&&this.handleCallUpdate({id:n.id,status:n.status,actorExternalId:typeof n.actorExternalId=="string"?n.actorExternalId:null});return}i==="presence"?this.emit("presence",{users:t.users??[]}):i!=="pong"&&this.emit(i||"signal",t)}async callAction(e,t){return this.request(`/v1/rtc/client/calls/${encodeURIComponent(e)}/${t}`,{method:"POST",body:"{}"})}async request(e,t,i=!1){const n=this.options.callActionsUrl??this.options.apiBaseUrl??this.options.signalingUrl??globalThis.location?.origin;if(!n)throw new Error("callActionsUrl is required for call actions");const s=Y(n,globalThis.location?.href),o=await this.resolveToken("initial"),r=new Headers(t.headers);r.set("authorization",`Bearer ${o}`),r.set("content-type","application/json");const c=await fetch(new URL(e,s.origin),{...t,headers:r}),l=await c.json().catch(()=>({}));if(c.status===401&&this.options.tokenProvider&&!i)return await this.resolveToken("unauthorized",!0),this.request(e,t,!0);if(!c.ok){const d=l.error;throw new Error(typeof d=="string"?d:d?.message||`RTC API failed with HTTP ${c.status}`)}return l}renderIncoming(e){if(!this.mount)return;this.clearUi();const t=document.createElement("section");t.dataset.rtcIncomingCall=e.id,t.setAttribute("role","dialog"),t.setAttribute("aria-label",`Incoming ${e.mode} call from ${e.caller.name}`),Object.assign(t.style,{display:"grid",gap:"14px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const i=document.createElement("small");i.textContent=e.mode==="video"?"Incoming video call":"Incoming voice call",i.style.color="#5d7079";const n=document.createElement("strong");n.textContent=e.caller.name,n.style.fontSize="20px";const s=document.createElement("div");Object.assign(s.style,{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"});const o=De("Decline","#fff","#a32920","#d9a09b"),r=De("Answer","#fff","#08745f","#08745f");o.addEventListener("click",()=>{this.reject(e.id).catch(c=>this.emit("error",Q(c)))}),r.addEventListener("click",()=>{this.accept(e.id).catch(c=>this.emit("error",Q(c)))}),s.appendChild(o),s.appendChild(r),t.appendChild(i),t.appendChild(n),t.appendChild(s),this.mount.replaceChildren(t)}dismiss(e){e&&this.currentCall?.id!==e&&this.outgoingCallId!==e||(this.currentCall=null,(!e||this.outgoingCallId===e)&&(this.outgoingCallId=null),this.callAudio.stop(),this.clearUi())}showTerminal(e,t){if(this.callAudio.stop(),this.currentCall?.id===e&&(this.currentCall=null),this.outgoingCallId===e&&(this.outgoingCallId=null),!this.mount)return;this.clearUi();const i=document.createElement("section");i.dataset.rtcCallTerminal=e,i.setAttribute("role","status"),Object.assign(i.style,{display:"grid",placeItems:"center",gap:"10px",minHeight:"150px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const n=document.createElement("strong");n.textContent=t,n.style.fontSize="18px",i.appendChild(n),this.mount.replaceChildren(i);const s=Math.max(0,this.options.terminalStateDurationMs??1600);this.terminalTimer=setTimeout(()=>this.clearUi(),s)}clearUi(){this.terminalTimer&&clearTimeout(this.terminalTimer),this.terminalTimer=null,this.mount&&this.mount.replaceChildren()}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer);const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.openSocket("reconnect")},e)}async resolveToken(e,t=!1){if(!t&&this.currentToken&&!this.tokenNeedsRefresh())return this.currentToken;if(!this.options.tokenProvider){if(!this.currentToken)throw new Error("A valid RTC client token is required");return this.currentToken}return this.tokenRequest?this.tokenRequest:(this.tokenRequest=(async()=>{const i=await this.options.tokenProvider?.({reason:e,previousExpiresAt:this.currentTokenExpiresAt}),n=typeof i=="string"?{token:i}:i;if(!n?.token?.startsWith("rtcc_"))throw new Error("tokenProvider returned an invalid RTC client token");return this.currentToken=n.token,this.currentTokenExpiresAt=n.expiresAt,this.scheduleTokenRefresh(),this.emit("tokenrefreshed",{reason:e,expiresAt:n.expiresAt}),n.token})().finally(()=>{this.tokenRequest=null}),this.tokenRequest)}tokenNeedsRefresh(){if(!this.currentTokenExpiresAt)return!1;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();return Number.isFinite(e)?e<=this.refreshSkew(e):!0}refreshSkew(e){const t=Math.max(5e3,this.options.tokenRefreshSkewMs??3e5);return Math.min(t,Math.max(5e3,e*.2))}scheduleTokenRefresh(){if(this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.tokenRefreshTimer=null,this.stopped||!this.options.tokenProvider||!this.currentTokenExpiresAt)return;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();if(!Number.isFinite(e))return;const t=Math.max(0,e-this.refreshSkew(e));this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.resolveToken("expiring",!0).catch(i=>{this.emit("tokenerror",Q(i)),this.stopped||(this.tokenRefreshTimer=setTimeout(()=>this.scheduleTokenRefresh(),3e4))})},Math.min(t,2147e6))}releaseMedia(e){this.activeCallIds.delete(e),this.seenInvitationIds.delete(e);const t=this.mediaClients.get(e);t&&(this.mediaClients.delete(e),t.leave().catch(()=>{}).finally(()=>t.destroy()))}reportSignalingDiagnostic(e){this.options.reportSignalingDiagnostics===!1||this.stopped||this.request("/v1/rtc/client/signaling-events",{method:"POST",body:JSON.stringify({...e,occurredAt:new Date().toISOString()})}).catch(t=>this.emit("diagnosticserror",Q(t)))}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}function ut(a){const e=new Pe(a);return a.autoConnect!==!1&&a.signalingUrl&&e.connect(),e}function Le(a){return a?typeof a=="string"?document.querySelector(a):a:null}function De(a,e,t,i){const n=document.createElement("button");return n.type="button",n.textContent=a,Object.assign(n.style,{minHeight:"44px",border:`1px solid ${i}`,borderRadius:"6px",background:t,color:e,cursor:"pointer",font:"inherit",fontWeight:"700"}),n}function mt(a){return{rejected:"Call declined",busy:"Line busy",cancelled:"Call cancelled",ended:"Call ended",failed:"Call failed",missed:"No answer",answered_elsewhere:"Answered on another device"}[a]??"Call ended"}function Oe(a){return{id:a.participantId,name:a.displayName,role:"participant"}}async function pt(a,e){const t=await a.getStats(),i=new Map,n=new Map;let s=0,o=0,r=0,c=0,l=0,d=0,u=0,v=0,p=0,S=0,b=0,y=0,T=0,f=0,M=0,I=null,D=null,P=null,L=null,w=null,R=null;t.forEach(g=>{n.set(g.id,g),g.type==="codec"&&i.set(g.id,String(g.mimeType??""))}),t.forEach(g=>{if(g.type==="inbound-rtp"&&!g.isRemote&&(s+=Number(g.bytesReceived??0),r+=Math.max(0,Number(g.packetsLost??0)),c+=Math.max(0,Number(g.packetsReceived??0)),Number.isFinite(g.jitter)&&(l+=Number(g.jitter),d+=1),u=Math.max(u,Number(g.framesPerSecond??0)),v=Math.max(v,Number(g.frameWidth??0)),b=Math.max(b,Number(g.frameHeight??0)),y+=Number(g.framesDropped??0),T+=Number(g.freezeCount??0),f+=Number(g.totalFreezesDuration??0),M+=Number(g.concealedSamples??0),w||=i.get(String(g.codecId??""))??null),g.type==="outbound-rtp"&&!g.isRemote&&(p=Math.max(p,Number(g.frameWidth??0)),S=Math.max(S,Number(g.frameHeight??0)),o+=Number(g.bytesSent??0),R||=g.qualityLimitationReason?String(g.qualityLimitationReason):null,w||=i.get(String(g.codecId??""))??null),g.type==="candidate-pair"&&(g.selected||g.nominated)&&g.state==="succeeded"){I=Number.isFinite(g.currentRoundTripTime)?Number(g.currentRoundTripTime):I,D=Number.isFinite(g.availableOutgoingBitrate)?Number(g.availableOutgoingBitrate):D;const U=n.get(g.localCandidateId);P=U?.candidateType?String(U.candidateType):P,L=U?.protocol?String(U.protocol):L}});const m=Date.now(),h=e?Math.max(.001,(m-e.timestamp)/1e3):0,C=e?Math.max(0,Math.round((s-e.inboundBytes)*8/h)):null,A=e?Math.max(0,Math.round((o-e.outboundBytes)*8/h)):null,E=r+c,x=e?Math.max(0,r-e.packetsLost):r,_=e?Math.max(0,c-e.packetsReceived):c,j=x+_;return{counters:{timestamp:m,inboundBytes:s,outboundBytes:o,packetsLost:r,packetsReceived:c},sample:{rttMs:I===null?null:I*1e3,jitterMs:d?l/d*1e3:null,packetLossPct:j?x/j*100:null,packetLossCumulativePct:E?r/E*100:null,packetsLost:r,packetsReceived:c,inboundBitrateBps:C,outboundBitrateBps:A,availableOutgoingBitrateBps:D,framesPerSecond:u||null,frameWidth:v||null,frameHeight:b||null,inboundFrameWidth:v||null,inboundFrameHeight:b||null,outboundFrameWidth:p||null,outboundFrameHeight:S||null,framesDropped:y,freezeCount:T,freezeDurationMs:Math.round(f*1e3),concealedSamples:M,codec:w,transportProtocol:L,candidateType:P,qualityLimitationReason:R}}}function ft(a){let e=a.mediaScore==null?5:Math.round(Ue(Number(a.mediaScore),0,5));const t=se(a.packetLossPct)??0,i=se(a.rttMs)??0,n=se(a.jitterMs)??0,s=String(a.connectionState??"unknown").toLowerCase();["failed","closed","disconnected"].includes(s)?e=0:["reconnecting","checking"].includes(s)&&(e=Math.min(e,2)),t>=15?e=Math.min(e,0):t>=8?e=Math.min(e,1):t>=3?e=Math.min(e,2):t>=1&&(e=Math.min(e,3)),i>=1200?e=Math.min(e,0):i>=800?e=Math.min(e,1):i>=400?e=Math.min(e,2):i>=250&&(e=Math.min(e,3)),n>=150?e=Math.min(e,1):n>=80?e=Math.min(e,2):n>=40&&(e=Math.min(e,3));const o=Number(Ue(4.5-t*.09-i*.0011-n*.006,1,4.5).toFixed(2));return{networkScore:e,estimatedMos:o}}function Ne(){return navigator.connection??null}function gt(){const a=Number(navigator.deviceMemory);return Number.isFinite(a)?a:null}function se(a){const e=Number(a);return Number.isFinite(e)?e:null}function Ue(a,e,t){return Math.min(t,Math.max(e,a))}function Q(a){return a instanceof Error?{name:a.name,message:a.message}:{name:"ServiceError",message:String(a??"Service request failed")}}function qe(a,e){return a instanceof Error?a:new Error(e)}const xe="1.3.25";k.AVATAR_MODEL_MAX_BYTES=Te,k.AVATAR_TUNING_DEFAULTS=te,k.AvatarModelError=N,k.BACKGROUND_PRESETS=ue,k.OVERLAY_EFFECTS=me,k.RtcClient=Me,k.RtcIncomingClient=Pe,k.SPEAKER_NOISE_FLOOR=.02,k.SPEAKER_TAKEOVER_RATIO=1.4,k.createAvatarPipeline=Ke,k.createCallRecorder=Ge,k.createPictureInPictureController=Je,k.createRtcClient=dt,k.createRtcIncomingClient=ut,k.createVideoEffectsPipeline=Ye,k.drawAvatar=ge,k.headPoseFromMatrix=pe,k.importAvatarModel=st,k.listArchiveModels=et,k.listAvatarModels=ot,k.neutralRig=Z,k.pickActiveSpeaker=J,k.readAvatarModel=ct,k.readAvatarModelMetadata=Ce,k.removeAvatarModel=lt,k.rigFromBlendshapes=fe,k.scenePainters=X,k.smoothRig=ee,k.stylizedAvatarRenderer=ie,k.unlockRtcCallAudio=Ae,k.version=xe,Object.defineProperty(k,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(k,F){typeof exports=="object"&&typeof module<"u"?F(exports):typeof define=="function"&&define.amd?define(["exports"],F):(k=typeof globalThis<"u"?globalThis:k||self,F(k.StreamingCdnRtc={}))})(this,(function(k){"use strict";function J(a,e,t=.02,i=1.4){const n=a.filter(r=>r.level>=t);if(n.length===0)return null;const s=n.reduce((r,c)=>c.level>r.level?c:r),o=n.find(r=>r.participantId===e);return o?s.participantId===e?e:s.level>=o.level*i?s.participantId:e:s.participantId}function ce(a){const e=[];for(const t of a){if(String(t.type??"")!=="inbound-rtp"||String(t.kind??t.mediaType??"")!=="audio")continue;const i=Number(t.audioLevel??NaN);Number.isFinite(i)&&e.push({mid:typeof t.mid=="string"?t.mid:null,level:i})}return e}const V={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4},low:{active:!0,maxBitrate:35e4,maxFramerate:15,scaleResolutionDownBy:4},medium:{active:!0,maxBitrate:11e5,maxFramerate:24,scaleResolutionDownBy:2},high:{active:!0,maxBitrate:28e5,maxFramerate:30,scaleResolutionDownBy:1}},je={warmupSamples:3,poorSamplesToStepDown:2,goodSamplesToStepUp:6,criticalLossPct:8,criticalRttMs:800,poorLossPct:3,poorRttMs:350,goodLossPct:1,goodRttMs:250,sustainRatio:.8,bandwidthSustainRatio:.7};function _e(a){const e=String(a??"").trim().toLowerCase();return!e||e==="none"?"":e==="cpu"||e==="bandwidth"?e:"other"}function Qe(a,e,t,i,n={}){const s={...je,...n.thresholds},o=["low","medium","high"],r=n.ceiling&&n.ceiling!=="audio"?n.ceiling:"high",c=o.indexOf(r),l=o.indexOf(a),d=(n.samplesSeen??s.warmupSamples)+1,u=d<=s.warmupSamples,v=_e(e.qualityLimitationReason),p=e.packetLossPct??0,S=e.rttMs??0,b=e.availableOutgoingBitrateBps,y=V[a].maxBitrate,T=p>=s.criticalLossPct||S>=s.criticalRttMs||v==="cpu",f=v==="bandwidth"&&(b==null||b<y*s.bandwidthSustainRatio),M=T||p>=s.poorLossPct||S>=s.poorRttMs||f,I=l<o.length-1?V[o[l+1]].maxBitrate:null,D=v==="bandwidth"&&b!=null&&I!=null&&b>=I*s.bandwidthSustainRatio,P=b==null||b>=y*s.sustainRatio,L=p<s.goodLossPct&&S<s.goodRttMs&&(!v||D)&&P,w=M?t+1:0,R=L?i+1:0,m={quality:a,poorSamples:w,goodSamples:R,samplesSeen:d,changed:!1};return l>c?{quality:o[c],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:u?m:T&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:w>=s.poorSamplesToStepDown&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:R>=s.goodSamplesToStepUp&&l<c?{quality:o[l+1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:m}function Fe(a,e){const t=a instanceof Map?a:new Map(a);let i=0,n=0,s=null,o=null,r=null;for(const d of t.values()){const u=String(d.type??"");if(u==="outbound-rtp"&&String(d.kind??"")==="video"&&(i+=Number(d.packetsSent??0),s=d.qualityLimitationReason??s),u==="remote-inbound-rtp"&&String(d.kind??"")==="video"){n+=Number(d.packetsLost??0);const v=Number(d.roundTripTime??NaN);Number.isFinite(v)&&(o=v*1e3)}if(u==="candidate-pair"&&(d.nominated===!0||String(d.state??"")==="succeeded")){const v=Number(d.currentRoundTripTime??NaN);o==null&&Number.isFinite(v)&&(o=v*1e3);const p=Number(d.availableOutgoingBitrate??NaN);Number.isFinite(p)&&(r=p)}}const c={packetsSent:i,packetsLost:n,timestamp:Date.now()};let l=null;if(e){const d=i-e.packetsSent,u=n-e.packetsLost;d>0&&u>=0&&(l=u/(d+u)*100)}return{sample:{packetLossPct:l,rttMs:o,availableOutgoingBitrateBps:r,qualityLimitationReason:s},counters:c}}function G(a,e){const t=new URL(String(a),e);return t.protocol==="https:"?t.protocol="wss:":t.protocol==="http:"&&(t.protocol="ws:"),t}function Y(a,e){const t=new URL(String(a),e);return t.protocol==="wss:"?t.protocol="https:":t.protocol==="ws:"&&(t.protocol="http:"),t}const ze={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4}},He={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1};class z{options;socket=null;localStream=null;screenStream=null;peers=new Map;joinPromise=null;resolveJoin=null;rejectJoin=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;qualityTimer=null;speakerTimer=null;activeSpeaker=null;heartbeatDeadline=0;quality;preferredVideoCodec;signalQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.preferredVideoCodec=e.preferredVideoCodec??"auto"}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(!this.joined){if(this.joinPromise)return this.joinPromise;this.joinPromise=this.connect();try{await this.joinPromise}finally{this.joinPromise=null}}}async leave(){this.stopped=!0,this.clearConnectionTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null;for(const e of this.peers.values())e.connection.close();this.peers.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(r=>r.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(r=>r.readyState==="live")||await this.replaceVideoTrack(n);return}for(const r of this.peers.values()){const c=r.connection.getSenders().find(l=>l.track?.kind===s);c?await c.replaceTrack(n):r.connection.addTrack(n,this.localStream)}await this.applyVideoQuality()}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";for(const t of this.peers.values())t.quality=z.clampToCeiling(t.quality,e),t.poorSamples=0,t.goodSamples=0;await this.applyVideoQuality()}static clampToCeiling(e,t){if(t==="audio")return e;const i=["low","medium","high"];return i.indexOf(e)>i.indexOf(t)?t:e}async setPreferredVideoCodec(e){this.preferredVideoCodec=e;for(const t of this.peers.values())this.applyCodecPreference(t.connection);await this.renegotiateAll()}getParticipants(){return[...this.peers.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getRemoteStreams(){return[...this.peers.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.peers.values().next().value?.connection??null}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async connect(){const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1)}async openSocket(e){const{credential:t}=this.options,i=G(t.signalingUrl,globalThis.location?.href),n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.startQualitySampling(),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),this.stopQualitySampling(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.resetPeers(),this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0;const n=Array.isArray(t.peers)?t.peers:[];for(const s of n)this.ensurePeer(s);this.resolveJoin?.();for(const s of n)this.shouldInitiateOffer(s)&&await this.createOffer(s);return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.ensurePeer(t.sender),this.shouldInitiateOffer(t.sender)&&await this.createOffer(t.sender);return}if(i==="rtc.offer"&&t.sender&&t.description){const n=this.ensurePeer(t.sender);n.offerTask&&await n.offerTask;const s=n.connection.signalingState!=="stable",o=!this.shouldInitiateOffer(t.sender);if(s&&!o)return;s&&await n.connection.setLocalDescription({type:"rollback"}),await n.connection.setRemoteDescription(t.description);const r=await n.connection.createAnswer();await n.connection.setLocalDescription(r),this.send({type:"rtc.answer",targetParticipantId:t.sender.participantId,negotiationId:t.negotiationId,description:n.connection.localDescription});return}if(i==="rtc.answer"&&t.sender&&t.description){const n=this.peers.get(t.sender.participantId);if(!n||n.connection.signalingState!=="have-local-offer"||t.negotiationId&&n.activeNegotiationId&&t.negotiationId!==n.activeNegotiationId)return;await n.connection.setRemoteDescription(t.description),n.activeNegotiationId=null;return}if(i==="rtc.ice"&&t.sender&&t.candidate){await this.ensurePeer(t.sender).connection.addIceCandidate(t.candidate).catch(()=>{});return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removePeer(t.sender.participantId);return}i==="rtc.chat"?this.options.onEvent("chatupdate",t):i==="rtc.control"&&this.options.onEvent("control",t)}}ensurePeer(e){const t=this.peers.get(e.participantId);if(t)return t;const i=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"}),n=new MediaStream,s={identity:e,connection:i,stream:n,makingOffer:!1,offerTask:null,activeNegotiationId:null,quality:z.startingQuality(this.quality),poorSamples:0,goodSamples:0,samplesSeen:0,counters:null};this.peers.set(e.participantId,s);for(const c of this.localStream?.getAudioTracks()??[])i.addTrack(c,this.localStream);const o=this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")?this.screenStream:this.localStream,r=o?.getVideoTracks()[0];return r&&o&&(s.videoSender=i.addTrack(r,o)),!this.localStream?.getAudioTracks().length&&this.options.receiveAudio&&i.addTransceiver("audio",{direction:"recvonly"}),!r&&this.options.receiveVideo&&(s.videoTransceiver=i.addTransceiver("video",{direction:"recvonly"}),s.videoSender=s.videoTransceiver.sender),this.applyCodecPreference(i),this.applyVideoQuality(i),i.addEventListener("icecandidate",c=>{c.candidate&&this.send({type:"rtc.ice",targetParticipantId:e.participantId,candidate:c.candidate.toJSON()})}),i.addEventListener("track",c=>{n.getTracks().some(l=>l.id===c.track.id)||n.addTrack(c.track),this.options.onEvent("remotestream",{participant:e,stream:n,track:c.track})}),i.addEventListener("connectionstatechange",()=>{["failed","closed"].includes(i.connectionState)&&this.removePeer(e.participantId),this.options.onEvent("peerconnectionstate",{participant:e,state:i.connectionState})}),s}async createOffer(e){const t=this.ensurePeer(e);if(t.offerTask)return t.offerTask;if(t.connection.signalingState!=="stable")return;const i=this.performOffer(t);t.offerTask=i;try{await i}finally{t.offerTask===i&&(t.offerTask=null)}}async performOffer(e){e.makingOffer=!0;try{const t=await e.connection.createOffer({offerToReceiveAudio:this.options.receiveAudio,offerToReceiveVideo:this.options.receiveVideo});if(e.connection.signalingState!=="stable")return;await e.connection.setLocalDescription(t);const i=le();e.activeNegotiationId=i,this.send({type:"rtc.offer",targetParticipantId:e.identity.participantId,negotiationId:i,description:e.connection.localDescription})}finally{e.makingOffer=!1}}shouldInitiateOffer(e){return this.options.credential.participantId.localeCompare(e.participantId)<0}async renegotiateAll(){for(const e of this.peers.values())await this.createOffer(e.identity)}async replaceVideoTrack(e){let t=!1;for(const i of this.peers.values()){const n=i.videoSender??i.connection.getSenders().find(o=>o.track?.kind==="video")??i.connection.getTransceivers().find(o=>o.receiver.track.kind==="video")?.sender,s=i.videoTransceiver??i.connection.getTransceivers().find(o=>o.sender===n);if(n){const o=e?this.options.receiveVideo?"sendrecv":"sendonly":this.options.receiveVideo?"recvonly":"inactive";s&&s.direction!==o&&(s.direction=o,t=!0),await n.replaceTrack(e),i.videoSender=n,s&&(i.videoTransceiver=s)}else e&&(i.videoSender=i.connection.addTrack(e,this.screenStream??this.localStream??new MediaStream([e])),t=!0)}t&&await this.renegotiateAll(),await this.applyVideoQuality()}async applyVideoQuality(e){const t=e?[...this.peers.values()].filter(i=>i.connection===e):[...this.peers.values()];for(const i of t)await this.applyPeerVideoQuality(i)}async applyPeerVideoQuality(e){const t=this.screenStream?.getVideoTracks()[0]?.id;for(const i of e.connection.getSenders().filter(n=>n.track?.kind==="video")){const n=!!(t&&i.track?.id===t),s=n?He:this.quality==="audio"?ze.audio:V[e.quality],o=i.getParameters(),r=o.encodings?.length?o.encodings:[{}];o.encodings=r.map(c=>({...c,...s})),o.degradationPreference=n?"maintain-resolution":"maintain-framerate",await i.setParameters(o)}}startQualitySampling(){this.stopQualitySampling(),this.qualityTimer=setInterval(()=>{this.samplePeerQuality()},5e3),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopQualitySampling(){this.qualityTimer&&clearInterval(this.qualityTimer),this.qualityTimer=null,this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=[];for(const i of[...this.peers.values()])if(i.connection.connectionState==="connected")try{const n=await i.connection.getStats();for(const s of ce(n.values()))e.push({participantId:i.identity.participantId,level:s.level})}catch{}const t=J(e,this.activeSpeaker);t!==this.activeSpeaker&&(this.activeSpeaker=t,this.options.onEvent("activespeaker",{participantId:t}))}async samplePeerQuality(){if(this.quality!=="audio")for(const e of[...this.peers.values()]){if(e.connection.connectionState!=="connected")continue;let t;try{t=await e.connection.getStats()}catch{continue}const{sample:i,counters:n}=Fe(t,e.counters);e.counters=n;const s=Qe(e.quality,i,e.poorSamples,e.goodSamples,{samplesSeen:e.samplesSeen,ceiling:this.quality});e.poorSamples=s.poorSamples,e.goodSamples=s.goodSamples,e.samplesSeen=s.samplesSeen,s.changed&&(e.quality=s.quality,await this.applyPeerVideoQuality(e).catch(()=>{}),this.options.onEvent("peerquality",{participantId:e.identity.participantId,quality:s.quality}))}}static startingQuality(e){return e==="low"?"low":"medium"}applyCodecPreference(e){if(this.preferredVideoCodec==="auto"||!globalThis.RTCRtpReceiver?.getCapabilities)return;const t=RTCRtpReceiver.getCapabilities("video")?.codecs??[],i=`video/${this.preferredVideoCodec}`,n=[...t.filter(s=>s.mimeType.toLowerCase()===i),...t.filter(s=>s.mimeType.toLowerCase()!==i)];for(const s of e.getTransceivers())s.receiver.track.kind==="video"&&typeof s.setCodecPreferences=="function"&&s.setCodecPreferences(n)}removePeer(e){const t=this.peers.get(e);t&&(t.connection.close(),this.peers.delete(e),this.options.onEvent("participantleft",t.identity))}resetPeers(){for(const e of this.peers.values())e.connection.close();this.peers.clear()}startHeartbeat(e){this.stopHeartbeat(),this.stopQualitySampling(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}clearConnectionTimers(){this.stopHeartbeat(),this.stopQualitySampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??le(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function le(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}const K={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1},de=[{rid:"a",maxBitrate:V.high.maxBitrate,maxFramerate:V.high.maxFramerate},{rid:"b",maxBitrate:V.medium.maxBitrate,maxFramerate:V.medium.maxFramerate,scaleResolutionDownBy:2},{rid:"c",maxBitrate:V.low.maxBitrate,maxFramerate:V.low.maxFramerate,scaleResolutionDownBy:4}],he={high:"a",medium:"b",low:"c"};class We{options;apiBase;socket=null;connection=null;sessionId=null;localStream=null;screenStream=null;remotes=new Map;midToParticipant=new Map;pendingAnnouncements=new Map;videoSender=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;speakerTimer=null;activeSpeaker=null;quality;resolveJoin=null;rejectJoin=null;signalQueue=Promise.resolve();negotiationQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.apiBase=Y(e.credential.signalingUrl,globalThis.location?.href).origin}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(this.joined)return;const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1),await this.publish(),this.startSpeakerSampling(),this.announceTracks();const t=[...this.pendingAnnouncements.values()];this.pendingAnnouncements.clear();for(const i of t)await this.handleTrackAnnouncement(i.sender,i.detail)}async leave(){this.stopped=!0,this.clearTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null,await this.closePublishedTracks().catch(()=>{}),this.connection?.close(),this.connection=null,this.sessionId=null,this.remotes.clear(),this.midToParticipant.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(c=>c.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")||await this.replaceVideoTrack(n);return}const r=this.connection?.getSenders().find(c=>c.track?.kind==="audio");r&&await r.replaceTrack(n)}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";await this.applyVideoQuality()}async setPreferredVideoCodec(e){}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getParticipants(){return[...this.remotes.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}getRemoteStreams(){return[...this.remotes.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.connection}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async openSocket(e){const{credential:t}=this.options,i=G(t.signalingUrl,globalThis.location?.href),n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0,this.resolveJoin?.();return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.sessionId&&this.announceTracks();return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removeRemote(t.sender.participantId);return}if(i==="rtc.control"){if(String(t.action??"")==="sfu.tracks"&&t.sender){await this.handleTrackAnnouncement(t.sender,t.detail);return}this.options.onEvent("control",t);return}i==="rtc.chat"&&this.options.onEvent("chatupdate",t)}}async publish(){const e=[...this.localStream?.getAudioTracks()??[],...this.localStream?.getVideoTracks()??[]],t=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"});this.connection=t,t.addEventListener("track",r=>this.handleRemoteTrack(r)),t.addEventListener("connectionstatechange",()=>{this.options.onEvent("peerconnectionstate",{participant:this.selfIdentity(),state:t.connectionState})});const i=await this.sfuFetch("POST","/v1/rtc/sfu/sessions");if(this.sessionId=i.sessionId,e.length===0)return;const n=e.map(r=>t.addTransceiver(r,{direction:"sendonly",streams:this.localStream?[this.localStream]:[],...r.kind==="video"&&this.simulcastEnabled()?{sendEncodings:de.map(c=>({...c}))}:{}})),s=await t.createOffer();await t.setLocalDescription(s);const o=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(i.sessionId)}/tracks`,{sessionDescription:t.localDescription,tracks:n.map(r=>({location:"local",mid:r.mid,trackName:this.trackName(r.sender.track)}))});o.sessionDescription&&await t.setRemoteDescription(o.sessionDescription),this.videoSender=t.getSenders().find(r=>r.track?.kind==="video")??null,await this.applyVideoQuality()}trackName(e){return`${this.options.credential.participantId}/${e.kind}`}announceTracks(){if(!this.sessionId)return;const e=[...(this.localStream?.getAudioTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!1})),...(this.localStream?.getVideoTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:this.simulcastEnabled()}))];e.length!==0&&this.sendControl("sfu.tracks",{sessionId:this.sessionId,tracks:e})}async handleTrackAnnouncement(e,t){const i=String(t?.sessionId??""),n=(t?.tracks??[]).map(r=>({trackName:String(r.trackName??""),simulcast:r.simulcast===!0})).filter(r=>!!r.trackName),s=n.map(r=>r.trackName);if(!i||s.length===0||e.participantId===this.options.credential.participantId)return;if(!this.connection||!this.sessionId){this.pendingAnnouncements.set(e.participantId,{sender:e,detail:t??{}});return}const o=this.remotes.get(e.participantId)??{identity:e,stream:new MediaStream,sessionId:i,trackNames:[],simulcastNames:new Set,mids:new Map};o.sessionId=i;for(const r of n)r.simulcast&&o.simulcastNames.add(r.trackName);this.remotes.set(e.participantId,o),this.negotiationQueue=this.negotiationQueue.then(async()=>{const r=s.filter(c=>!o.trackNames.includes(c));r.length!==0&&await this.subscribe(o,r)}).catch(r=>{this.options.onEvent("signalingerror",{message:r instanceof Error?r.message:"SFU subscribe failed"})}),await this.negotiationQueue}async subscribe(e,t){const i=this.connection;if(!i||!this.sessionId||t.length===0)return;const n=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks`,{tracks:t.map(s=>({location:"remote",sessionId:e.sessionId,trackName:s,...e.simulcastNames.has(s)?{simulcast:{preferredRid:"a",priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}:{}}))});for(const s of n.tracks??[])s.mid&&(e.mids.set(s.mid,String(s.trackName??"")),this.midToParticipant.set(s.mid,e.identity.participantId));if(e.trackNames=[...new Set([...e.trackNames,...t])],n.requiresImmediateRenegotiation&&n.sessionDescription){await i.setRemoteDescription(n.sessionDescription);const s=await i.createAnswer();await i.setLocalDescription(s),await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`,{sessionDescription:i.localDescription})}}handleRemoteTrack(e){const t=e.transceiver?.mid??null,i=t?this.midToParticipant.get(t):void 0,n=i?this.remotes.get(i):void 0;n&&(n.stream.getTracks().some(s=>s.id===e.track.id)||n.stream.addTrack(e.track),this.options.onEvent("remotestream",{participant:n.identity,stream:n.stream,track:e.track}))}removeRemote(e){const t=this.remotes.get(e);if(t){for(const i of t.mids.keys())this.midToParticipant.delete(i);this.remotes.delete(e),this.options.onEvent("participantleft",t.identity)}}async setRemoteQuality(e,t){const i=this.remotes.get(e);if(!i||!this.sessionId)return;const n=[...i.mids.entries()].find(([,s])=>i.simulcastNames.has(s))?.[0];n&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/update`,{tracks:[{mid:n,simulcast:{preferredRid:he[t],priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}]})}async replaceVideoTrack(e){this.videoSender&&(await this.videoSender.replaceTrack(e),await this.applyVideoQuality())}simulcastEnabled(){return this.options.simulcast!==!1}async applyVideoQuality(){const e=this.videoSender;if(!e)return;const t=this.screenStream?.getVideoTracks()[0]?.id,i=!!(t&&e.track?.id===t),n=e.getParameters(),s=n.encodings?.length?n.encodings:[{}];if(s.length>1){const o=this.quality==="audio"?null:he[this.quality];n.encodings=s.map(r=>{if(i)return{...r,active:r.rid==="a",...r.rid==="a"?{maxBitrate:K.maxBitrate,maxFramerate:K.maxFramerate}:{}};const c=de.find(d=>d.rid===r.rid),l=o!=null&&String(r.rid??"")>=o;return{...r,...c??{},active:l}})}else{const o=this.quality==="audio"?V.audio:V[this.quality],r=i?K:o;n.encodings=s.map(c=>({...c,...r}))}n.degradationPreference=i?"maintain-resolution":"maintain-framerate",await e.setParameters(n)}async closePublishedTracks(){const e=this.connection;if(!e||!this.sessionId)return;const t=e.getTransceivers().filter(i=>i.sender.track&&i.mid).map(i=>({mid:i.mid}));t.length!==0&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/close`,{tracks:t,force:!0})}selfIdentity(){return{participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId??this.options.credential.participantId,displayName:this.options.credential.externalUserId??this.options.credential.participantId,platform:"web"}}async sfuFetch(e,t,i){const n=await fetch(`${this.apiBase}${t}`,{method:e,headers:{authorization:`Bearer ${this.options.credential.signalingToken}`,"content-type":"application/json"},body:i===void 0?void 0:JSON.stringify(i)});if(!n.ok)throw new Error(`SFU request failed with HTTP ${n.status}`);return n.json()}startHeartbeat(e){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}startSpeakerSampling(){this.stopSpeakerSampling(),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopSpeakerSampling(){this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=this.connection;if(!e||e.connectionState!=="connected")return;let t;try{t=await e.getStats()}catch{return}const i=[];for(const s of ce(t.values())){const o=s.mid?this.midToParticipant.get(s.mid):void 0;o&&i.push({participantId:o,level:s.level})}const n=J(i,this.activeSpeaker);n!==this.activeSpeaker&&(this.activeSpeaker=n,this.options.onEvent("activespeaker",{participantId:n}))}clearTimers(){this.stopHeartbeat(),this.stopSpeakerSampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).then(()=>this.announceTracks()).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??$e(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function $e(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function Je(a,e={}){const t=e.document??globalThis.document,i=!!(t&&t.pictureInPictureEnabled);let n=null;function s(){return n||(n=t.createElement("video"),n.muted=!0,n.playsInline=!0,n.style.position="fixed",n.style.width="1px",n.style.height="1px",n.style.opacity="0",n.style.pointerEvents="none",n.addEventListener("leavepictureinpicture",()=>e.onLeave?.()),t.body.appendChild(n),n)}function o(r){const c=a.getRemoteStreams();return r?c.find(l=>l.participant.participantId===r)?.stream??null:c[0]?.stream??a.getLocalStream()}return{supported:i,async enter(r){if(!i)throw new Error("This browser does not support picture-in-picture");const c=o(r);if(!c)throw new Error("No video stream is available for picture-in-picture");const l=s();l.srcObject!==c&&(l.srcObject=c),await l.play().catch(()=>{}),await l.requestPictureInPicture()},async exit(){t.pictureInPictureElement&&typeof t.exitPictureInPicture=="function"&&await t.exitPictureInPicture()},dispose(){n?.remove(),n=null}}}function Ge(a,e){const t=e.document??globalThis.document,i=e.fetchImplementation??globalThis.fetch.bind(globalThis),n=Y(e.credential.signalingUrl,globalThis.location?.href).origin,s=e.mimeType??"video/webm",o=e.height??720,r=Math.round(o*16/9);let c=null,l=null,d=null,u=null,v=0,p=[],S=!1;async function b(y,T,f,M="application/json"){const I=await i(`${n}${T}`,{method:y,headers:{authorization:`Bearer ${e.credential.signalingToken}`,"content-type":M},body:f===void 0?void 0:M==="application/json"?JSON.stringify(f):f});if(!I.ok)throw new Error(`Recording request failed with HTTP ${I.status}`);return I.json()}return{get recording(){return S},async start(){if(S)return;u=(await b("POST","/v1/rtc/recordings")).recordingId;const T=t.createElement("canvas");T.width=r,T.height=o;const f=T.getContext("2d"),M=new Map,I=(C,A)=>{let E=M.get(C);return E||(E=t.createElement("video"),E.muted=!0,E.playsInline=!0,E.srcObject=A,E.play().catch(()=>{}),M.set(C,E)),E};l=setInterval(()=>{const C=a.getRemoteStreams(),A=a.getLocalStream(),E=[...C.map(U=>({key:U.participant.participantId,stream:U.stream,label:U.participant.displayName})),...A?[{key:"local",stream:A,label:"me"}]:[]];f.fillStyle="#101418",f.fillRect(0,0,r,o);const x=Math.max(1,Math.ceil(Math.sqrt(E.length))),Q=Math.max(1,Math.ceil(E.length/x)),j=r/x,g=o/Q;E.forEach((U,Ve)=>{const Be=I(U.key,U.stream),re=Ve%x*j,oe=Math.floor(Ve/x)*g;Be.videoWidth>0&&f.drawImage(Be,re+2,oe+2,j-4,g-4),f.fillStyle="rgba(0,0,0,.6)",f.fillRect(re+8,oe+g-30,Math.min(j-16,12+U.label.length*9),22),f.fillStyle="#fff",f.font="14px system-ui",f.fillText(U.label,re+14,oe+g-14)})},66);const D=T.captureStream(15);d=new AudioContext;const P=d.createMediaStreamDestination(),L=new Set,w=()=>{const C=[...a.getRemoteStreams().map(A=>A.stream),...a.getLocalStream()?[a.getLocalStream()]:[]];for(const A of C)for(const E of A.getAudioTracks())L.has(E.id)||(L.add(E.id),d.createMediaStreamSource(new MediaStream([E])).connect(P))};w();const R=setInterval(w,1e3),m=()=>clearInterval(R);for(const C of P.stream.getAudioTracks())D.addTrack(C);c=(e.mediaRecorderFactory??((C,A)=>new MediaRecorder(C,A)))(D,{mimeType:s}),p=[],c.addEventListener("dataavailable",C=>{C.data&&C.data.size>0&&p.push(C.data)}),c.addEventListener("stop",m),c.start(1e3),v=Date.now(),S=!0},async stop(){if(!S||!c||!u)throw new Error("The recorder is not running");S=!1;const y=c,T=new Promise(P=>y.addEventListener("stop",()=>P(),{once:!0}));y.stop(),await T,l&&clearInterval(l),l=null,await d?.close().catch(()=>{}),d=null;const f=new Blob(p,{type:s});p=[];const M=Math.max(1,Math.round((Date.now()-v)/1e3)),I=await b("PUT",`/v1/rtc/recordings/${encodeURIComponent(u)}/media`,f,s);await b("POST",`/v1/rtc/recordings/${encodeURIComponent(u)}/complete`,{durationSeconds:M});const D={recordingId:u,sizeBytes:I.sizeBytes,durationSeconds:M};return u=null,c=null,D},dispose(){l&&clearInterval(l),l=null;try{c?.stop()}catch{}c=null,d?.close().catch(()=>{}),d=null,S=!1}}}const ue=["none","blur","aurora","cybergrid","bokeh","sunset","image"],me=["none","snow","confetti","vignette"],X={aurora(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#050b1e"),n.addColorStop(1,"#0b2540"),a.fillStyle=n,a.fillRect(0,0,e,t);for(let s=0;s<3;s+=1){a.beginPath();for(let o=0;o<=e;o+=16){const r=t*.35+s*40+Math.sin(o/140+i/900+s)*60;o===0?a.moveTo(o,r):a.lineTo(o,r)}a.lineTo(e,0),a.lineTo(0,0),a.closePath(),a.fillStyle=`hsla(${140+s*40+Math.sin(i/1300)*20}, 80%, 55%, 0.14)`,a.fill()}},cybergrid(a,e,t,i){a.fillStyle="#0a0118",a.fillRect(0,0,e,t),a.strokeStyle="rgba(255,0,180,0.5)",a.lineWidth=1.5;const n=t*.45;for(let o=0;o<12;o+=1){const r=n+(o*34+i/18)%(t-n);a.beginPath(),a.moveTo(0,r),a.lineTo(e,r),a.stroke()}for(let o=-8;o<=8;o+=1)a.beginPath(),a.moveTo(e/2+o*24,n),a.lineTo(e/2+o*e*.12,t),a.stroke();const s=a.createLinearGradient(0,t*.1,0,n);s.addColorStop(0,"#ffd34d"),s.addColorStop(1,"#ff2d78"),a.fillStyle=s,a.beginPath(),a.arc(e/2,n,t*.22,Math.PI,0),a.fill()},bokeh(a,e,t,i){a.fillStyle="#14100c",a.fillRect(0,0,e,t);for(let n=0;n<26;n+=1){const s=(Math.sin(n*12.9898)*.5+.5)*e,o=(Math.sin(n*78.233)*.5+.5)*t+Math.sin(i/1500+n)*12,r=14+n%5*12;a.fillStyle=`hsla(${35+n%4*12}, 90%, 60%, ${.1+n%3*.05})`,a.beginPath(),a.arc(s,o,r,0,Math.PI*2),a.fill()}},sunset(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#2b1055"),n.addColorStop(.55,"#d1548a"),n.addColorStop(1,"#ffb36b"),a.fillStyle=n,a.fillRect(0,0,e,t),a.fillStyle="#ffdf8e",a.beginPath(),a.arc(e*.5,t*.62+Math.sin(i/4e3)*4,t*.13,0,Math.PI*2),a.fill(),a.fillStyle="rgba(20,8,40,0.85)",a.fillRect(0,t*.78,e,t*.22)}};function Ye(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.background??"none",l=e.overlay??"none",d=e.backgroundImage??null,u=e.segmenter??null,v=null,p=!1;const S=t.createElement("video");S.muted=!0,S.playsInline=!0,S.srcObject=new MediaStream([r]),S.play().catch(()=>{});const b=t.createElement("canvas");b.width=i,b.height=n;const y=b.getContext("2d"),T=t.createElement("canvas");T.width=i,T.height=n;const f=T.getContext("2d"),M=()=>Array.from({length:80},(m,h)=>({x:(Math.sin(h*12.9898)*.5+.5)*i,y:(Math.sin(h*78.233)*.5+.5)*n,speed:.6+h%5*.5,drift:Math.sin(h)*.6,size:2+h%4,hue:h*47%360}));let I=M();const D=()=>{if(s)return;const m=S.videoWidth,h=S.videoHeight;if(!m||!h)return;const C=Math.min(1,1280/Math.max(m,h)),A=Math.round(m*C),E=Math.round(h*C);A===i&&E===n||(i=A,n=E,b.width=i,b.height=n,T.width=i,T.height=n,I=M())};S.addEventListener?.("loadedmetadata",D),S.addEventListener?.("resize",D);function P(){if(v){f.clearRect(0,0,i,n),f.drawImage(v,0,0,i,n),f.globalCompositeOperation="source-in",f.drawImage(S,0,0,i,n),f.globalCompositeOperation="source-over",y.drawImage(T,0,0);return}const m=i*.42,h=m*9/16,C=i-m-24,A=n-h-24;y.save(),y.shadowColor="rgba(0,0,0,.5)",y.shadowBlur=24,y.beginPath(),y.roundRect(C,A,m,h,14),y.clip(),y.drawImage(S,C,A,m,h),y.restore()}function L(m){if(c==="none"?y.drawImage(S,0,0,i,n):c==="blur"?(y.filter="blur(14px)",y.drawImage(S,-16,-16,i+32,n+32),y.filter="none",v&&P()):c==="image"&&d?(y.drawImage(d,0,0,i,n),P()):((X[c]??X.aurora)(y,i,n,m),P()),l==="snow"||l==="confetti")for(const h of I)h.y+=h.speed*(l==="confetti"?1.6:1),h.x+=h.drift,h.y>n&&(h.y=-6,h.x=Math.random()*i),y.fillStyle=l==="snow"?"rgba(255,255,255,.85)":`hsl(${h.hue}, 90%, 60%)`,l==="confetti"?y.fillRect(h.x,h.y,h.size+2,h.size):(y.beginPath(),y.arc(h.x,h.y,h.size/2,0,Math.PI*2),y.fill());else if(l==="vignette"){const h=y.createRadialGradient(i/2,n/2,n*.4,i/2,n/2,n*.85);h.addColorStop(0,"rgba(0,0,0,0)"),h.addColorStop(1,"rgba(0,0,0,0.55)"),y.fillStyle=h,y.fillRect(0,0,i,n)}}const w=setInterval(()=>{L(Date.now()),u&&!p&&c!=="none"&&(p=!0,u.segment(S).then(m=>{v=m}).catch(()=>{v=null}).finally(()=>{p=!1}))},Math.round(1e3/o)),R=b.captureStream(o);return{track:R.getVideoTracks()[0],stream:R,setBackground(m,h){c=ue.includes(m)?m:"none",h!==void 0&&(d=h),c==="none"&&(v=null)},setOverlay(m){l=me.includes(m)?m:"none"},setSegmenter(m){u=m,m||(v=null)},dispose(){clearInterval(w),u?.close?.();for(const m of R.getTracks())m.stop();S.srcObject=null}}}function Z(){return{blinkLeft:0,blinkRight:0,jawOpen:0,smile:0,browUp:0,pupilX:0,pupilY:0,headYaw:0,headPitch:0,headRoll:0,tracked:!1}}function O(a,e){const t=a.find(i=>i.categoryName===e);return t?Math.max(0,Math.min(1,t.score)):0}function pe(a){if(!a||a.length<16)return{yaw:0,pitch:0,roll:0};a[0],a[4];const e=a[8],t=a[1],i=a[5],n=a[9],s=a[10],o=Math.atan2(e,s),r=Math.asin(Math.max(-1,Math.min(1,-n))),c=Math.atan2(t,i);return{yaw:o,pitch:r,roll:c}}function fe(a,e,t=1){if(!a||a.length===0)return Z();const i=pe(e),n=(O(a,"eyeLookOutRight")+O(a,"eyeLookInLeft"))/2,s=(O(a,"eyeLookOutLeft")+O(a,"eyeLookInRight"))/2,o=(O(a,"eyeLookUpLeft")+O(a,"eyeLookUpRight"))/2,r=(O(a,"eyeLookDownLeft")+O(a,"eyeLookDownRight"))/2,c=(O(a,"mouthSmileLeft")+O(a,"mouthSmileRight"))/2,l=(O(a,"mouthFrownLeft")+O(a,"mouthFrownRight"))/2,d=O(a,"eyeBlinkLeft"),u=O(a,"eyeBlinkRight"),v=Math.min(1,Math.abs(i.yaw)/.35)*Math.max(0,Math.min(1,t)),p=(d+u)/2;return{blinkLeft:d+(p-d)*v,blinkRight:u+(p-u)*v,jawOpen:O(a,"jawOpen"),smile:c-l,browUp:(O(a,"browInnerUp")+O(a,"browOuterUpLeft")+O(a,"browOuterUpRight"))/3,pupilX:n-s,pupilY:r-o,headYaw:i.yaw,headPitch:i.pitch,headRoll:i.roll,tracked:!0}}function ee(a,e,t=.4){const i=(n,s)=>n+(s-n)*t;return{blinkLeft:i(a.blinkLeft,e.blinkLeft),blinkRight:i(a.blinkRight,e.blinkRight),jawOpen:i(a.jawOpen,e.jawOpen),smile:i(a.smile,e.smile),browUp:i(a.browUp,e.browUp),pupilX:i(a.pupilX,e.pupilX),pupilY:i(a.pupilY,e.pupilY),headYaw:i(a.headYaw,e.headYaw),headPitch:i(a.headPitch,e.headPitch),headRoll:i(a.headRoll,e.headRoll),tracked:e.tracked}}function ge(a,e,t,i,n){n||(a.fillStyle="#101822",a.fillRect(0,0,t,i));const s=n?n.x+e.headYaw*n.radius*.1:t/2+e.headYaw*t*.18,o=n?n.y+e.headPitch*n.radius*.1:i/2+e.headPitch*i*.18,r=n?n.radius:Math.min(t,i)*.28;a.save(),a.translate(s,o),a.rotate(e.headRoll*.6);const c=Math.max(-1.35,Math.min(1.35,e.headYaw)),l=Math.max(-.9,Math.min(.9,e.headPitch)),d=Math.sin(c),u=L=>Math.sin(L+c)*r,v=L=>Math.cos(L+c),p=Math.sin(l)*r*.22;a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.35,-r*.25,r*.98,r*.95,0,0,Math.PI*2),a.fill(),a.fillStyle=e.tracked?"#f2c9a0":"#5a6470",a.beginPath(),a.ellipse(0,0,r*(1-Math.abs(d)*.12),r*1.12,0,0,Math.PI*2),a.fill(),a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.28,-r*.72+p*.4,r*.92,r*.5,0,Math.PI,Math.PI*2),a.fill();const S=0;a.fillStyle="#e5b58a",a.beginPath();const b=u(S)*.9,y=r*.12+p;a.ellipse(b+d*r*.14,y,r*.11,r*.14,0,0,Math.PI*2),a.fill();for(const L of[-1,1]){const w=L*.42,R=v(w);if(R<=.12)continue;const m=u(w)*.82,h=Math.max(.2,R),C=L<0?e.blinkLeft:e.blinkRight,A=Math.max(.06,1-C),E=e.browUp*r*.14;a.strokeStyle="#4a2e18",a.lineWidth=r*.07,a.lineCap="round",a.beginPath(),a.moveTo(m-r*.2*h,-r*.42-E+p),a.quadraticCurveTo(m,-r*.52-E+p,m+r*.2*h,-r*.44-E+p),a.stroke();const x=-r*.18+p;a.fillStyle="#ffffff",a.beginPath(),a.ellipse(m,x,r*.16*h,r*.12*A,0,0,Math.PI*2),a.fill(),a.fillStyle="#22303c",a.beginPath(),a.ellipse(m+e.pupilX*r*.07*h,x+e.pupilY*r*.05,r*.06*h,r*.06*A,0,0,Math.PI*2),a.fill()}const T=Math.max(.25,v(0)),f=u(0)*.7,M=r*.55*T,I=r*(.04+e.jawOpen*.34),D=e.smile*r*.12,P=r*.42+p;a.fillStyle="#8c2f2f",a.beginPath(),a.moveTo(f-M/2,P-D),a.quadraticCurveTo(f,P+I,f+M/2,P-D),a.quadraticCurveTo(f,P-I*.25,f-M/2,P-D),a.closePath(),a.fill(),a.restore(),!e.tracked&&!n&&(a.fillStyle="#9fb0bd",a.font=`${Math.round(i*.045)}px system-ui`,a.textAlign="center",a.fillText("No face detected",t/2,i*.9),a.textAlign="left")}const te={mouthGain:1.7,blinkGain:1,eyeSync:1,smoothing:.4,avatarScale:1,avatarLift:0,lostHoldMs:400};function ie(){return{render(a,e,t,i,n){ge(a,e,t,i,n??void 0)}}}const ne=a=>Math.max(0,Math.min(1,a));function Ke(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.tracker??null,l=e.renderer??ie(),d=e.mode??"replace";const u={...te,...e.tuning},v=e.backdrop??"#101822",p=t.createElement("video");p.muted=!0,p.playsInline=!0,p.srcObject=new MediaStream([r]),p.play().catch(()=>{});const S=t.createElement("canvas");S.width=i,S.height=n;const b=S.getContext("2d"),y=()=>{if(s)return;const w=p.videoWidth,R=p.videoHeight;if(!w||!R)return;const m=Math.min(1,1280/Math.max(w,R)),h=Math.round(w*m),C=Math.round(R*m);(!(h===i||C===n)||h!==i||C!==n)&&(i=h,n=C,S.width=i,S.height=n)};p.addEventListener?.("loadedmetadata",y),p.addEventListener?.("resize",y);let T=Z(),f=null,M=0;function I(w){if(w.length<=454)return;const R=w[10],m=w[152],h=w[234],C=w[454],A=(h.x+C.x)/2*i,E=(h.y+C.y)/2*n,x=Math.hypot((m.x-R.x)*i,(m.y-R.y)*n),Q=Math.max(.6,Math.cos(T.headPitch)),j={x:A,y:E,radius:Math.max(24,x/Q*.8)};f=f?{x:f.x+(j.x-f.x)*.4,y:f.y+(j.y-f.y)*.4,radius:f.radius+(j.radius-f.radius)*.15}:j}function D(w){if(c&&p.readyState>=2){let m=null;try{m=c.detect(p,w)}catch{}const h=fe(m?.categories??null,m?.matrix??null,u.eyeSync);h.jawOpen=ne(h.jawOpen*u.mouthGain),h.blinkLeft=ne(h.blinkLeft*u.blinkGain),h.blinkRight=ne(h.blinkRight*u.blinkGain),h.tracked?(M=w,T=ee(T,h,u.smoothing),m?.landmarks&&I(m.landmarks)):T.tracked&&w-M>u.lostHoldMs&&(T=ee(T,{...T,jawOpen:0,browUp:0,smile:T.smile*.5,blinkLeft:.1,blinkRight:.1},.05))}d==="overlay"&&p.readyState>=2?b.drawImage(p,0,0,i,n):(b.fillStyle=v,b.fillRect(0,0,i,n));const R=f?{x:f.x,y:f.y+u.avatarLift*f.radius,radius:f.radius*u.avatarScale}:null;l?.render(b,T,i,n,R)}const P=setInterval(()=>D(Date.now()),Math.round(1e3/o)),L=S.captureStream(o);return{track:L.getVideoTracks()[0],stream:L,getRig:()=>({...T}),setMode(w){d=w==="overlay"?"overlay":"replace"},setTracker(w){c?.close?.(),c=w},setRenderer(w){l?.dispose?.(),l=w??ie()},setTuning(w){for(const R of Object.keys(te)){const m=w[R];typeof m=="number"&&Number.isFinite(m)&&(u[R]=m)}},getTuning:()=>({...u}),dispose(){clearInterval(P),c?.close?.(),l?.dispose?.();for(const w of L.getTracks())w.stop();p.srcObject=null}}}class B extends Error{constructor(e,t){super(t),this.code=e,this.name="ZipError"}code}const ye=101010256,ve=33639248,Se=67324752,ae=4294967295;function we(a){if(a.byteLength<4)return!1;const e=new DataView(a).getUint32(0,!0);return e===Se||e===ve||e===ye}function Xe(a){const e=Math.max(0,a.byteLength-22-65535);for(let t=a.byteLength-22;t>=e;t-=1)if(a.getUint32(t,!0)===ye)return t;throw new B("zip_unreadable","The archive index could not be found.")}function ke(a){const e=new DataView(a),t=Xe(e),i=e.getUint16(t+10,!0);let n=e.getUint32(t+16,!0);if(n===ae)throw new B("zip_unsupported","ZIP64 archives are not supported.");const s=new TextDecoder,o=[];for(let r=0;r<i;r+=1){if(n+46>a.byteLength||e.getUint32(n,!0)!==ve)throw new B("zip_unreadable","The archive index is malformed.");const c=e.getUint16(n+8,!0),l=e.getUint16(n+10,!0),d=e.getUint32(n+20,!0),u=e.getUint32(n+24,!0),v=e.getUint16(n+28,!0),p=e.getUint16(n+30,!0),S=e.getUint16(n+32,!0),b=e.getUint32(n+42,!0),y=s.decode(new Uint8Array(a,n+46,v));o.push({path:y,bytes:u,compressedBytes:d,compressionMethod:l,encrypted:(c&1)!==0,localHeaderOffset:b}),n+=46+v+p+S}return o}async function Ze(a,e){if(e.encrypted)throw new B("zip_encrypted","The archive is password protected.");if(e.bytes===ae||e.compressedBytes===ae)throw new B("zip_unsupported","ZIP64 archives are not supported.");const t=new DataView(a),i=e.localHeaderOffset;if(i+30>a.byteLength||t.getUint32(i,!0)!==Se)throw new B("zip_unreadable","An archive entry header is malformed.");const n=t.getUint16(i+26,!0),s=t.getUint16(i+28,!0),o=i+30+n+s;if(o+e.compressedBytes>a.byteLength)throw new B("zip_unreadable","An archive entry runs past the end of the file.");const r=a.slice(o,o+e.compressedBytes);if(e.compressionMethod===0)return r;if(e.compressionMethod!==8)throw new B("zip_unsupported",`Archive compression method ${e.compressionMethod} is not supported.`);if(typeof DecompressionStream>"u")throw new B("zip_unsupported","This browser cannot expand compressed archives.");const c=new Blob([r]).stream().pipeThrough(new DecompressionStream("deflate-raw"));return new Response(c).arrayBuffer()}async function et(a){const e=typeof File<"u"&&a instanceof File?await a.arrayBuffer():a;if(!we(e))return[];try{return be(ke(e)).map(t=>t.path)}catch{return[]}}const Te=64*1024*1024,tt="streaming-cdn-avatars",H="models",it=1179937895,nt=1313821514;class N extends Error{constructor(e,t,i=[]){super(t),this.code=e,this.name="AvatarModelError",this.choices=i}code;choices}function be(a){return a.filter(e=>/\.vrm$/i.test(e.path)&&!e.path.endsWith("/")&&!e.path.split("/").some(t=>t==="__MACOSX"||t.startsWith("._")))}function q(a){const e=typeof a=="string"?a.trim():"";return e.length>0?e:null}function Ce(a){const e=new DataView(a);if(a.byteLength<20||e.getUint32(0,!0)!==it)throw new N("model_not_glb","The file is not a binary glTF (.vrm / .glb) model.");let t=12,i=null;for(;t+8<=a.byteLength;){const r=e.getUint32(t,!0),c=e.getUint32(t+4,!0),l=t+8;if(l+r>a.byteLength)break;if(c===nt){try{const d=new TextDecoder().decode(new Uint8Array(a,l,r)).replace(/[\s\u0000]+$/,"");i=JSON.parse(d)}catch{throw new N("model_unreadable","The model's glTF descriptor could not be read.")}break}t=l+r+(4-r%4)%4}const n=i?.extensions??{},s=n.VRM?.meta,o=n.VRMC_vrm?.meta;if(!s&&!o)return null;if(o){const r=Array.isArray(o.authors)?o.authors.filter(c=>typeof c=="string"):[];return{title:q(o.name),author:r.length?r.join(", "):null,allowedUser:q(o.avatarPermission),commercialUse:q(o.commercialUsage),licenseName:q(o.licenseUrl)?"VRM 1.0":null,licenseUrl:q(o.licenseUrl)??q(o.otherLicenseUrl)}}return{title:q(s?.title),author:q(s?.author),allowedUser:q(s?.allowedUserName),commercialUse:q(s?.commercialUssageName),licenseName:q(s?.licenseName),licenseUrl:q(s?.otherLicenseUrl)}}function at(){return new Promise((a,e)=>{if(typeof indexedDB>"u"){e(new N("storage_unavailable","This browser cannot store imported avatar models."));return}const t=indexedDB.open(tt,1);t.onupgradeneeded=()=>{t.result.objectStoreNames.contains(H)||t.result.createObjectStore(H,{keyPath:"id"})},t.onsuccess=()=>a(t.result),t.onerror=()=>e(new N("storage_unavailable","Imported avatar storage could not be opened."))})}async function W(a,e){const t=await at();try{return await new Promise((i,n)=>{const s=e(t.transaction(H,a).objectStore(H));s.onsuccess=()=>i(s.result),s.onerror=()=>n(new N("storage_unavailable","The imported avatar could not be stored."))})}finally{t.close()}}const Ee=a=>({id:a.id,name:a.name,bytes:a.bytes,importedAt:a.importedAt,license:a.license});async function st(a,e={}){const t=e.maxBytes??Te,i=typeof File<"u"&&a instanceof File;let n=i?a.name:null,s=i?await a.arrayBuffer():a;if(we(s)){const l=await rt(s,e.entryPath,t);s=l.buffer,n=l.path.split("/").pop()??n}if(s.byteLength>t)throw new N("model_too_large","The model is larger than the "+Math.round(t/1024/1024)+" MB limit.");const o=Ce(s);if(!o)throw new N("model_not_vrm","The file is a glTF model but carries no VRM character data.");const r=o.title||(n?n.replace(/\.(vrm|glb)$/i,""):null),c={id:"avatar_"+Date.now().toString(36)+"_"+Math.random().toString(36).slice(2,8),name:e.name?.trim()||r||"Imported avatar",bytes:s.byteLength,importedAt:new Date().toISOString(),license:o,data:s};return await W("readwrite",l=>l.put(c)),Ee(c)}async function rt(a,e,t){let i;try{i=be(ke(a))}catch(s){throw s instanceof B?new N(Re(s.code),s.message):new N("archive_unreadable","The archive could not be read.")}if(i.length===0)throw new N("archive_no_model","The archive contains no .vrm character.");const n=e?i.find(s=>s.path===e):i.length===1?i[0]:null;if(!n)throw new N("archive_many_models","The archive contains several characters; choose one.",i.map(s=>s.path));if(n.bytes>t)throw new N("model_too_large","The character inside the archive is larger than the "+Math.round(t/1024/1024)+" MB limit.");try{return{buffer:await Ze(a,n),path:n.path}}catch(s){throw s instanceof B?new N(Re(s.code),s.message):new N("archive_unreadable","The character could not be expanded from the archive.")}}function Re(a){return a==="zip_encrypted"?"archive_encrypted":a==="zip_unsupported"?"archive_unsupported":"archive_unreadable"}async function ot(){return(await W("readonly",e=>e.getAll())??[]).map(Ee).sort((e,t)=>t.importedAt.localeCompare(e.importedAt))}async function ct(a){return(await W("readonly",t=>t.get(a)))?.data??null}async function lt(a){await W("readwrite",e=>e.delete(a))}class Me extends EventTarget{mode;options;client=null;peerConnection;state="initialized";latestMediaScore=null;latestQuality=null;statsTimer=null;statsRunning=!1;previousCounters=null;lastTelemetryAt=0;joinPromise=null;requestedQuality;effectiveQuality;poorQualitySamples=0;goodQualitySamples=0;constructor(e){if(super(),!e.credential?.signalingToken||!e.credential.signalingUrl||!e.credential.roomId||!e.credential.participantId)throw new Error("A valid room signaling credential is required");this.mode=e.mode,this.requestedQuality=e.mode==="voice"?"audio":e.quality??"auto",this.effectiveQuality=this.requestedQuality==="auto"?"medium":this.requestedQuality,this.peerConnection=e.peerConnection??null,this.options={...e,audio:e.audio??!0,video:e.mode==="voice"?!1:e.video??!0,receiveAudio:e.receiveAudio??!0,receiveVideo:e.mode==="voice"?!1:e.receiveVideo??!0,statsInterval:Math.max(1e3,e.statsInterval??5e3),telemetryInterval:Math.max(1e4,e.telemetryInterval??3e4),autoReportStats:e.autoReportStats??!0}}async initialize(){if(this.client)return this;try{const t=(this.options.transport==="auto"?this.mode==="meeting"?"sfu":"mesh":this.options.transport??"mesh")==="sfu"?We:z;return this.client=new t({credential:{signalingToken:this.options.credential.signalingToken,signalingUrl:this.options.credential.signalingUrl,roomId:this.options.credential.roomId,participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId,iceServers:this.options.credential.iceServers},audio:this.options.audio,video:this.options.video,receiveAudio:this.options.receiveAudio,receiveVideo:this.options.receiveVideo,quality:this.effectiveQuality,preferredVideoCodec:this.options.preferredVideoCodec??"auto",iceTransportPolicy:this.options.iceTransportPolicy??"all",localStream:this.options.localStream,simulcast:this.options.simulcast??!0,onEvent:(i,n)=>this.handleNativeEvent(i,n)}),await this.client.initialize(),this.startStats(),this.emit("initialized",{mode:this.mode,sdkVersion:xe}),this.options.autoJoin&&await this.join(),this}catch(e){this.state="failed";const t=qe(e,"RTC initialization failed");throw this.emit("error",_(t)),t}}async join(){const e=this.requireClient();return this.joinPromise?this.joinPromise:(this.state="joining",this.emit("joining",{}),this.joinPromise=e.join().catch(t=>{this.state="failed";const i=qe(t,"RTC connection failed");throw this.emit("error",_(i)),i}).finally(()=>{this.joinPromise=null}),this.joinPromise)}async leave(){this.client&&(await this.joinPromise?.catch(()=>{}),await this.sampleAndReport("final").catch(()=>{}),await this.client.leave(),this.state="disconnected",this.stopStats())}async setMuted(e){this.requireClient().setMuted(e),this.emit("mediachange",{kind:"audio",enabled:!e})}async setCameraEnabled(e){if(this.mode==="voice"&&e)throw new Error("Camera is unavailable in voice mode");await this.requireClient().setCameraEnabled(e),this.emit("mediachange",{kind:"video",enabled:e})}async setVideoTrack(e){if(this.mode==="voice")throw new Error("Camera is unavailable in voice mode");await this.requireClient().setVideoTrack(e),this.emit("mediachange",{kind:"video",enabled:e.enabled,trackId:e.id})}async setScreenShareEnabled(e){await this.requireClient().setScreenShareEnabled(e),this.emit("mediachange",{kind:"screenshare",enabled:e})}async setDevice(e){await this.requireClient().setDevice(e),this.emit("devicechange",{deviceId:e.deviceId,kind:e.kind})}async setQuality(e){if(this.mode==="voice"&&e!=="audio")throw new Error("Voice calls only support audio quality");this.requestedQuality=e,this.poorQualitySamples=0,this.goodQualitySamples=0,await this.applyEffectiveQuality(e==="auto"?"medium":e,"manual")}async setPreferredVideoCodec(e){await this.requireClient().setPreferredVideoCodec(e),this.emit("codecchange",{preferredVideoCodec:e})}async getDevices(){return navigator.mediaDevices?.enumerateDevices?navigator.mediaDevices.enumerateDevices():[]}getParticipants(){return this.requireClient().getParticipants().map(Oe)}getLocalStream(){return this.client?.getLocalStream()??null}getRemoteStreams(){return(this.client?.getRemoteStreams()??[]).map(e=>({participant:Oe(e.participant),stream:e.stream}))}setPeerConnection(e){this.peerConnection=e,this.previousCounters=null}async sendChatMessage(e,t){const i=e.trim();if(!i)throw new Error("message is required");this.requireClient().sendChat(i,t)}async sendControlMessage(e,t={}){const i=e.trim();if(!i)throw new Error("action is required");this.requireClient().sendControl(i,t)}async createPoll(e,t,i={}){const n=t.map(s=>s.trim()).filter(Boolean);if(!e.trim()||n.length<2)throw new Error("A poll requires a question and at least two options");this.requireClient().sendControl("poll.create",{poll:{id:`poll-${crypto.randomUUID()}`,question:e.trim(),options:n,votes:{},...i}})}async votePoll(e,t){this.requireClient().sendControl("poll.vote",{pollId:e,optionIndex:t})}async requestStageAccess(){this.requireClient().sendControl("stage.request")}async cancelStageAccess(){this.requireClient().sendControl("stage.cancel")}async joinStage(){this.requireClient().sendControl("stage.join")}async leaveStage(){this.requireClient().sendControl("stage.leave")}async grantStageAccess(e){this.requireClient().sendControl("stage.grant",{participantIds:e})}async denyStageAccess(e){this.requireClient().sendControl("stage.deny",{participantIds:e})}async startRecording(e=!1){this.requireClient().sendControl("recording.start",{allowMultiple:e})}async stopRecording(e){this.requireClient().sendControl("recording.stop",{recordingId:e})}async pauseRecording(e){this.requireClient().sendControl("recording.pause",{recordingId:e})}async resumeRecording(e){this.requireClient().sendControl("recording.resume",{recordingId:e})}async runPreflight(e={}){const t=[],i=globalThis.isSecureContext===!0;i||t.push("secure_context_required");let n="unavailable";if(e.requestMedia!==!1&&navigator.mediaDevices?.getUserMedia)try{(await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})).getTracks().forEach(v=>v.stop()),n="granted"}catch{n="denied",t.push("media_permission_denied")}const s=navigator.mediaDevices?.enumerateDevices?await navigator.mediaDevices.enumerateDevices().catch(()=>[]):[],o=s.filter(u=>u.kind==="audioinput").length,r=s.filter(u=>u.kind==="videoinput").length;this.options.audio&&o===0&&t.push("microphone_unavailable"),this.options.video&&r===0&&t.push("camera_unavailable");const c=e.probeUrl??this.options.preflightProbeUrl;let l=null;if(c){const u=performance.now();try{const v=await fetch(c,{method:"HEAD",cache:"no-store",credentials:"omit"});l=Math.round(performance.now()-u),v.ok||t.push("network_probe_failed")}catch{t.push("network_probe_failed")}}const d={passed:t.length===0,sampledAt:new Date().toISOString(),secureContext:i,mediaPermission:n,microphoneCount:o,cameraCount:r,probeLatencyMs:l,effectiveConnectionType:Ne()?.effectiveType??null,issues:t};return this.emit("preflight",d),this.options.autoReportStats&&await this.reportQuality({sampleType:"preflight",sampledAt:d.sampledAt,connectionState:d.passed?"ready":"failed",rttMs:d.probeLatencyMs,effectiveConnectionType:d.effectiveConnectionType}).catch(()=>{}),d}async collectQualitySample(e="runtime"){const t=this.options.statsProvider?await this.options.statsProvider():{},i=this.peerConnection?await ft(this.peerConnection,this.previousCounters):null;i?.counters&&(this.previousCounters=i.counters);const s={...{sampleType:e,sampledAt:new Date().toISOString(),connectionState:this.peerConnection?.connectionState??this.state,mediaScore:this.latestMediaScore,networkScore:this.latestMediaScore==null?5:Math.max(0,Math.min(5,Math.round(this.latestMediaScore))),estimatedMos:4.5,rttMs:null,jitterMs:null,packetLossPct:null,packetLossCumulativePct:null,packetsLost:null,packetsReceived:null,inboundBitrateBps:null,outboundBitrateBps:null,availableOutgoingBitrateBps:null,framesPerSecond:null,frameWidth:null,frameHeight:null,inboundFrameWidth:null,inboundFrameHeight:null,outboundFrameWidth:null,outboundFrameHeight:null,framesDropped:null,freezeCount:null,freezeDurationMs:null,concealedSamples:null,codec:null,transportProtocol:null,candidateType:null,qualityLimitationReason:null,deviceMemoryGb:yt(),effectiveConnectionType:Ne()?.effectiveType??null},...i?.sample??{},...t,sampleType:e,sampledAt:new Date().toISOString()},o=gt(s);return s.networkScore=o.networkScore,s.estimatedMos=o.estimatedMos,this.latestQuality=s,s}async reportQuality(e){const t=this.options.credential.telemetryToken,i=this.options.telemetryEndpoint??this.options.credential.telemetryEndpoint;if(!t||!i)return;const n=await fetch(i,{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify(e),keepalive:e.sampleType==="final"});if(!n.ok)throw new Error(`RTC telemetry failed with HTTP ${n.status}`)}getStats(){const e=this.client,t=e?.getLocalStream();return{state:this.state,participantCount:e?e.getParticipants().length+1:0,audioEnabled:!!t?.getAudioTracks().some(i=>i.enabled),videoEnabled:!!t?.getVideoTracks().some(i=>i.enabled),screenShareEnabled:!1,latestMediaScore:this.latestMediaScore,latestQuality:this.latestQuality,sampledAt:new Date().toISOString()}}getNativeClient(){return this.client}destroy(){this.stopStats(),this.client&&this.client.leave(),this.client=null,this.joinPromise=null,this.peerConnection=null,this.state="disconnected"}handleNativeEvent(e,t){e==="connected"?(this.state="connected",this.peerConnection=this.client?.getPrimaryPeerConnection()??null):e==="disconnected"?this.state="disconnected":e==="peerconnectionstate"&&(this.peerConnection=this.client?.getPrimaryPeerConnection()??null),this.emit(e,t)}startStats(){this.stopStats(),this.statsTimer=setInterval(()=>{this.sampleAndReport("runtime")},this.options.statsInterval)}async sampleAndReport(e){if(!this.statsRunning){this.statsRunning=!0;try{const t=await this.collectQualitySample(e);await this.adaptQuality(t),this.emit("stats",{...this.getStats(),latestQuality:t});const i=Date.now(),n=e!=="runtime"||i-this.lastTelemetryAt>=this.options.telemetryInterval;this.options.autoReportStats&&n&&(await this.reportQuality(t),this.lastTelemetryAt=i)}catch(t){this.emit("telemetryerror",_(t))}finally{this.statsRunning=!1}}}stopStats(){this.statsTimer!==null&&clearInterval(this.statsTimer),this.statsTimer=null}async adaptQuality(e){if(this.requestedQuality!=="auto"||this.options.adaptiveQuality===!1||this.mode==="voice")return;if((this.client?.getParticipants().length??0)>1){this.poorQualitySamples=0,this.goodQualitySamples=0,this.effectiveQuality!=="high"&&await this.applyEffectiveQuality("high","network");return}const t=String(e.qualityLimitationReason??"").toLowerCase(),i=(e.packetLossPct??0)>=8||(e.rttMs??0)>=800||t==="cpu"||t==="bandwidth",n=i||(e.packetLossPct??0)>=3||(e.rttMs??0)>=350||e.availableOutgoingBitrateBps!=null&&e.availableOutgoingBitrateBps<35e4,s=(e.packetLossPct??100)<1&&(e.rttMs??1e3)<180&&!t&&(e.availableOutgoingBitrateBps==null||e.availableOutgoingBitrateBps>1e6);this.poorQualitySamples=n?this.poorQualitySamples+1:0,this.goodQualitySamples=s?this.goodQualitySamples+1:0;const o=["low","medium","high"],r=o.indexOf(this.effectiveQuality);i&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality("low","network")):this.poorQualitySamples>=2&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality(o[r-1],"network")):this.goodQualitySamples>=6&&r>=0&&r<o.length-1&&(this.goodQualitySamples=0,await this.applyEffectiveQuality(o[r+1],"network"))}async applyEffectiveQuality(e,t){if(this.effectiveQuality===e&&this.client)return;const i=this.effectiveQuality;this.effectiveQuality=e,this.client&&await this.client.setQuality(e),this.emit("qualitychange",{requestedQuality:this.requestedQuality,quality:e,previous:i,reason:t})}requireClient(){if(!this.client)throw new Error("Call initialize() before using the RTC client");return this.client}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}async function dt(a){return new Me(a).initialize()}let $=null;function Ie(){if($)return $;const a=globalThis,e=a.AudioContext??a.webkitAudioContext;return e?($=new e,$):null}async function Ae(){const a=Ie();return a?(a.state==="suspended"&&await a.resume().catch(()=>{}),a.state==="running"):!1}class ht{options;media=null;cadenceTimer=null;oscillators=new Set;constructor(e){this.options=e}unlock(){return Ae()}startIncoming(){this.start("incoming",this.options.ringtoneUrl)}startRingback(){this.start("ringback",this.options.ringbackUrl)}stop(){this.cadenceTimer&&clearInterval(this.cadenceTimer),this.cadenceTimer=null,this.media?.pause(),this.media&&(this.media.currentTime=0),this.media=null;for(const e of this.oscillators)try{e.stop()}catch{}this.oscillators.clear()}start(e,t){if(this.stop(),this.options.sounds===!1)return;if(t&&typeof Audio<"u"){this.media=new Audio(t),this.media.loop=!0,this.media.play().catch(()=>{});return}const i=()=>this.playBuiltInTone(e);i(),this.cadenceTimer=setInterval(i,e==="incoming"?3e3:3500)}playBuiltInTone(e){const t=Ie();if(!t||t.state!=="running")return;const i=e==="incoming"?1.25:1,n=e==="incoming"?[440,480]:[425],s=t.createGain(),o=t.currentTime;s.gain.setValueAtTime(1e-4,o),s.gain.exponentialRampToValueAtTime(.045,o+.025),s.gain.setValueAtTime(.045,o+i-.06),s.gain.exponentialRampToValueAtTime(1e-4,o+i),s.connect(t.destination);for(const r of n){const c=t.createOscillator();c.type="sine",c.frequency.value=r,c.connect(s),this.oscillators.add(c),c.onended=()=>this.oscillators.delete(c),c.start(o),c.stop(o+i+.02)}}}const ut=6e4;class Pe extends EventTarget{options;socket=null;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;currentCall=null;outgoingCallId=null;mount=null;terminalTimer=null;tokenRefreshTimer=null;tokenRequest=null;currentToken;currentTokenExpiresAt;callAudio;mediaClients=new Map;activeCallIds=new Set;seenInvitationIds=new Set;invitationExpiryTimers=new Map;startCallRequest=null;callGeneration=0;lastDisconnect=null;constructor(e){if(super(),!e.token&&!e.tokenProvider)throw new Error("A client token or tokenProvider is required");if(e.token&&!e.token.startsWith("rtcc_"))throw new Error("A valid RTC client token is required");if(!e.signalingUrl&&!e.callActionsUrl&&!e.apiBaseUrl&&!globalThis.location?.origin)throw new Error("signalingUrl or callActionsUrl is required outside a browser");this.options=e,this.currentToken=e.token??null,this.currentTokenExpiresAt=e.tokenExpiresAt,this.callAudio=new ht(e),this.mount=Le(e.mount),this.scheduleTokenRefresh()}connect(){if(!this.options.signalingUrl)throw new Error("signalingUrl is required to connect signaling");return this.stopped=!1,this.scheduleTokenRefresh(),this.openSocket("initial"),this}setMount(e){this.clearUi(),this.mount=Le(e),this.currentCall&&this.renderIncoming(this.currentCall)}attachMediaClient(e,t){if(!e.trim())throw new Error("callId is required");return this.mediaClients.set(e,t),this.clearInvitationExpiry(e),()=>{this.mediaClients.get(e)===t&&(this.mediaClients.delete(e),this.activeCallIds.has(e)&&this.armCallWatchdog(e))}}armCallWatchdog(e){this.scheduleInvitationExpiry(e,new Date(Date.now()+ut).toISOString())}async startCall(e){if(this.startCallRequest)return this.startCallRequest;if(this.activeCallIds.size>0)throw new Error("Another call is already active");const t=e.targets?.length?e.targets:e.target?[e.target]:[];if(t.length===0)throw new Error("startCall requires at least one target");const i={mode:e.mode,targets:t,metadata:e.metadata,idempotencyKey:e.idempotencyKey},n=this.callGeneration,s=(async()=>{await this.callAudio.unlock(),this.callAudio.startRingback();let o;try{o=await this.request("/v1/rtc/client/calls",{method:"POST",headers:e.idempotencyKey?{"idempotency-key":e.idempotencyKey}:void 0,body:JSON.stringify(i)})}catch(c){throw this.callAudio.stop(),c}!o.credential&&o.callerCredential&&(o.credential=o.callerCredential);const r=String(o.call?.id??"")||null;if(r&&n===this.callGeneration){this.outgoingCallId=r,this.activeCallIds.add(r);const c=o.call?.expiresAt;this.scheduleInvitationExpiry(r,typeof c=="string"?c:void 0)}else r&&this.callAction(r,"cancel").catch(()=>{});return this.emit("outgoingcall",o),o})();this.startCallRequest=s;try{return await s}finally{this.startCallRequest===s&&(this.startCallRequest=null)}}async accept(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"accept");return this.activeCallIds.add(e),this.armCallWatchdog(e),this.callAudio.stop(),this.dismiss(e),this.emit("callaccepted",t),t}async reject(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");let t;try{return t=await this.callAction(e,"reject")}finally{this.releaseMedia(e),this.showTerminal(e,"Call declined"),this.emit("callrejected",t??{call:{id:e}})}}async busy(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");try{return await this.callAction(e,"busy")}finally{this.releaseMedia(e),this.showTerminal(e,"Line busy")}}async end(e){this.callAudio.stop();try{return await this.callAction(e,"end")}finally{this.releaseMedia(e),this.showTerminal(e,"Call ended")}}async cancel(e){this.callAudio.stop();try{return await this.callAction(e,"cancel")}finally{this.releaseMedia(e),this.showTerminal(e,"Call cancelled")}}handleIncomingCall(e){if(!e?.id||!e.caller?.id||!e.caller.name||e.mode!=="voice"&&e.mode!=="video")throw new Error("Incoming call payload is invalid");if(e.expiresAt&&Date.parse(e.expiresAt)<=Date.now()){this.emit("callexpired",{id:e.id,expiresAt:e.expiresAt});return}if(this.currentCall?.id===e.id){this.currentCall=e,this.emit("callrestored",e);return}if(this.seenInvitationIds.has(e.id)){this.emit("callrestored",e);return}this.seenInvitationIds.add(e.id),this.activeCallIds.add(e.id),this.scheduleInvitationExpiry(e.id,e.expiresAt),this.currentCall=e,this.callAudio.startIncoming(),this.renderIncoming(e),this.emit("incomingcall",e)}scheduleInvitationExpiry(e,t){const i=t?Date.parse(t):Date.now()+45e3,n=Math.min(18e4,Math.max(5e3,(Number.isFinite(i)?i:Date.now()+45e3)-Date.now()+1e4));this.clearInvitationExpiry(e),this.invitationExpiryTimers.set(e,setTimeout(()=>{this.invitationExpiryTimers.delete(e),!(!this.activeCallIds.has(e)||this.mediaClients.has(e))&&(this.emit("callexpired",{id:e,expiresAt:t??null}),this.release(e))},n))}clearInvitationExpiry(e){const t=this.invitationExpiryTimers.get(e);t&&clearTimeout(t),this.invitationExpiryTimers.delete(e)}release(e){if(!e.trim())throw new Error("callId is required");this.clearInvitationExpiry(e),this.currentCall?.id===e&&(this.callAudio.stop(),this.dismiss(e)),this.outgoingCallId===e&&(this.outgoingCallId=null),this.releaseMedia(e),this.emit("callreleased",{id:e})}handleCallUpdate(e){const t=e.id===this.currentCall?.id||e.id===this.outgoingCallId,i=["answered_elsewhere","rejected","busy","cancelled","ended","failed","missed"];i.includes(e.status)&&this.clearInvitationExpiry(e.id),e.status==="accepted"&&(this.mediaClients.has(e.id)?this.clearInvitationExpiry(e.id):this.armCallWatchdog(e.id)),t&&e.status==="accepted"?(this.activeCallIds.add(e.id),this.callAudio.stop(),this.dismiss(e.id)):t&&i.includes(e.status)&&this.showTerminal(e.id,pt(e.status)),i.includes(e.status)&&this.releaseMedia(e.id),this.emit("callupdated",e)}disconnect(){this.stopped=!0,this.callGeneration+=1,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.reconnectTimer=null,this.heartbeatTimer=null,this.tokenRefreshTimer=null,this.socket?.close(1e3,"Client disconnected"),this.socket=null,this.callAudio.stop(),this.dismiss();for(const e of this.invitationExpiryTimers.values())clearTimeout(e);this.invitationExpiryTimers.clear();for(const e of this.mediaClients.keys())this.releaseMedia(e);this.activeCallIds.clear(),this.seenInvitationIds.clear()}destroy(){this.disconnect(),this.mount=null}async openSocket(e){if(!this.options.signalingUrl||this.stopped||this.socket?.readyState===WebSocket.OPEN||this.socket?.readyState===WebSocket.CONNECTING)return;let t;try{t=await this.resolveToken(e,e==="reconnect")}catch(s){this.emit("tokenerror",_(s)),!this.stopped&&this.options.reconnect!==!1&&this.scheduleReconnect();return}if(this.stopped||this.socket)return;const i=G(this.options.signalingUrl,globalThis.location?.href),n=new WebSocket(i,["rtc-client",t]);this.socket=n,n.addEventListener("open",()=>{const s=this.reconnectAttempt>0,o=this.lastDisconnect;this.reconnectAttempt=0,this.heartbeatDeadline=Date.now()+45e3,this.emit("signalingconnected",{resumed:s}),o&&(this.reportSignalingDiagnostic({event:"recovered",code:o.code,reason:o.reason,wasClean:o.wasClean,outageMs:Date.now()-o.at}),this.lastDisconnect=null),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{if(n.readyState===WebSocket.OPEN){if(Date.now()>this.heartbeatDeadline){n.close(4e3,"Signaling heartbeat timed out");return}n.send("ping")}},2e4)}),n.addEventListener("message",s=>{this.heartbeatDeadline=Date.now()+45e3,this.handleSignal(String(s.data))}),n.addEventListener("close",s=>{if(this.socket!==n)return;this.socket=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;const o=!this.stopped&&this.options.reconnect!==!1,r={code:s.code,reason:s.reason,wasClean:s.wasClean,willReconnect:o,attempt:this.reconnectAttempt+1};o&&(this.lastDisconnect={code:s.code,reason:s.reason,wasClean:s.wasClean,at:Date.now()}),this.emit("signalingdisconnected",r),this.reportSignalingDiagnostic({event:o?"reconnecting":"disconnected",...r}),o&&this.scheduleReconnect()}),n.addEventListener("error",()=>{const s={message:"RTC signaling connection failed",reconnecting:!this.stopped&&this.options.reconnect!==!1,attempt:this.reconnectAttempt+1};this.emit("signalingerror",s),this.reportSignalingDiagnostic({event:"error",attempt:s.attempt})})}handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i==="call.incoming"&&t.call&&typeof t.call=="object"){this.handleIncomingCall(t.call);return}if(i==="call.updated"){const n=t.call;n?.id&&n.status&&this.handleCallUpdate({id:n.id,status:n.status,actorExternalId:typeof n.actorExternalId=="string"?n.actorExternalId:null});return}i==="presence"?this.emit("presence",{users:t.users??[]}):i!=="pong"&&this.emit(i||"signal",t)}async callAction(e,t){return this.request(`/v1/rtc/client/calls/${encodeURIComponent(e)}/${t}`,{method:"POST",body:"{}"})}async request(e,t,i=!1){const n=this.options.callActionsUrl??this.options.apiBaseUrl??this.options.signalingUrl??globalThis.location?.origin;if(!n)throw new Error("callActionsUrl is required for call actions");const s=Y(n,globalThis.location?.href),o=await this.resolveToken("initial"),r=new Headers(t.headers);r.set("authorization",`Bearer ${o}`),r.set("content-type","application/json");const c=await fetch(new URL(e,s.origin),{...t,headers:r}),l=await c.json().catch(()=>({}));if(c.status===401&&this.options.tokenProvider&&!i)return await this.resolveToken("unauthorized",!0),this.request(e,t,!0);if(!c.ok){const d=l.error;throw new Error(typeof d=="string"?d:d?.message||`RTC API failed with HTTP ${c.status}`)}return l}renderIncoming(e){if(!this.mount)return;this.clearUi();const t=document.createElement("section");t.dataset.rtcIncomingCall=e.id,t.setAttribute("role","dialog"),t.setAttribute("aria-label",`Incoming ${e.mode} call from ${e.caller.name}`),Object.assign(t.style,{display:"grid",gap:"14px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const i=document.createElement("small");i.textContent=e.mode==="video"?"Incoming video call":"Incoming voice call",i.style.color="#5d7079";const n=document.createElement("strong");n.textContent=e.caller.name,n.style.fontSize="20px";const s=document.createElement("div");Object.assign(s.style,{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"});const o=De("Decline","#fff","#a32920","#d9a09b"),r=De("Answer","#fff","#08745f","#08745f");o.addEventListener("click",()=>{this.reject(e.id).catch(c=>this.emit("error",_(c)))}),r.addEventListener("click",()=>{this.accept(e.id).catch(c=>this.emit("error",_(c)))}),s.appendChild(o),s.appendChild(r),t.appendChild(i),t.appendChild(n),t.appendChild(s),this.mount.replaceChildren(t)}dismiss(e){e&&this.currentCall?.id!==e&&this.outgoingCallId!==e||(this.currentCall=null,(!e||this.outgoingCallId===e)&&(this.outgoingCallId=null),this.callAudio.stop(),this.clearUi())}showTerminal(e,t){if(this.callAudio.stop(),this.currentCall?.id===e&&(this.currentCall=null),this.outgoingCallId===e&&(this.outgoingCallId=null),!this.mount)return;this.clearUi();const i=document.createElement("section");i.dataset.rtcCallTerminal=e,i.setAttribute("role","status"),Object.assign(i.style,{display:"grid",placeItems:"center",gap:"10px",minHeight:"150px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const n=document.createElement("strong");n.textContent=t,n.style.fontSize="18px",i.appendChild(n),this.mount.replaceChildren(i);const s=Math.max(0,this.options.terminalStateDurationMs??1600);this.terminalTimer=setTimeout(()=>this.clearUi(),s)}clearUi(){this.terminalTimer&&clearTimeout(this.terminalTimer),this.terminalTimer=null,this.mount&&this.mount.replaceChildren()}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer);const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.openSocket("reconnect")},e)}async resolveToken(e,t=!1){if(!t&&this.currentToken&&!this.tokenNeedsRefresh())return this.currentToken;if(!this.options.tokenProvider){if(!this.currentToken)throw new Error("A valid RTC client token is required");return this.currentToken}return this.tokenRequest?this.tokenRequest:(this.tokenRequest=(async()=>{const i=await this.options.tokenProvider?.({reason:e,previousExpiresAt:this.currentTokenExpiresAt}),n=typeof i=="string"?{token:i}:i;if(!n?.token?.startsWith("rtcc_"))throw new Error("tokenProvider returned an invalid RTC client token");return this.currentToken=n.token,this.currentTokenExpiresAt=n.expiresAt,this.scheduleTokenRefresh(),this.emit("tokenrefreshed",{reason:e,expiresAt:n.expiresAt}),n.token})().finally(()=>{this.tokenRequest=null}),this.tokenRequest)}tokenNeedsRefresh(){if(!this.currentTokenExpiresAt)return!1;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();return Number.isFinite(e)?e<=this.refreshSkew(e):!0}refreshSkew(e){const t=Math.max(5e3,this.options.tokenRefreshSkewMs??3e5);return Math.min(t,Math.max(5e3,e*.2))}scheduleTokenRefresh(){if(this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.tokenRefreshTimer=null,this.stopped||!this.options.tokenProvider||!this.currentTokenExpiresAt)return;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();if(!Number.isFinite(e))return;const t=Math.max(0,e-this.refreshSkew(e));this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.resolveToken("expiring",!0).catch(i=>{this.emit("tokenerror",_(i)),this.stopped||(this.tokenRefreshTimer=setTimeout(()=>this.scheduleTokenRefresh(),3e4))})},Math.min(t,2147e6))}releaseMedia(e){this.clearInvitationExpiry(e),this.activeCallIds.delete(e),this.seenInvitationIds.delete(e);const t=this.mediaClients.get(e);t&&(this.mediaClients.delete(e),t.leave().catch(()=>{}).finally(()=>t.destroy()))}reportSignalingDiagnostic(e){this.options.reportSignalingDiagnostics===!1||this.stopped||this.request("/v1/rtc/client/signaling-events",{method:"POST",body:JSON.stringify({...e,occurredAt:new Date().toISOString()})}).catch(t=>this.emit("diagnosticserror",_(t)))}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}function mt(a){const e=new Pe(a);return a.autoConnect!==!1&&a.signalingUrl&&e.connect(),e}function Le(a){return a?typeof a=="string"?document.querySelector(a):a:null}function De(a,e,t,i){const n=document.createElement("button");return n.type="button",n.textContent=a,Object.assign(n.style,{minHeight:"44px",border:`1px solid ${i}`,borderRadius:"6px",background:t,color:e,cursor:"pointer",font:"inherit",fontWeight:"700"}),n}function pt(a){return{rejected:"Call declined",busy:"Line busy",cancelled:"Call cancelled",ended:"Call ended",failed:"Call failed",missed:"No answer",answered_elsewhere:"Answered on another device"}[a]??"Call ended"}function Oe(a){return{id:a.participantId,name:a.displayName,role:"participant"}}async function ft(a,e){const t=await a.getStats(),i=new Map,n=new Map;let s=0,o=0,r=0,c=0,l=0,d=0,u=0,v=0,p=0,S=0,b=0,y=0,T=0,f=0,M=0,I=null,D=null,P=null,L=null,w=null,R=null;t.forEach(g=>{n.set(g.id,g),g.type==="codec"&&i.set(g.id,String(g.mimeType??""))}),t.forEach(g=>{if(g.type==="inbound-rtp"&&!g.isRemote&&(s+=Number(g.bytesReceived??0),r+=Math.max(0,Number(g.packetsLost??0)),c+=Math.max(0,Number(g.packetsReceived??0)),Number.isFinite(g.jitter)&&(l+=Number(g.jitter),d+=1),u=Math.max(u,Number(g.framesPerSecond??0)),v=Math.max(v,Number(g.frameWidth??0)),b=Math.max(b,Number(g.frameHeight??0)),y+=Number(g.framesDropped??0),T+=Number(g.freezeCount??0),f+=Number(g.totalFreezesDuration??0),M+=Number(g.concealedSamples??0),w||=i.get(String(g.codecId??""))??null),g.type==="outbound-rtp"&&!g.isRemote&&(p=Math.max(p,Number(g.frameWidth??0)),S=Math.max(S,Number(g.frameHeight??0)),o+=Number(g.bytesSent??0),R||=g.qualityLimitationReason?String(g.qualityLimitationReason):null,w||=i.get(String(g.codecId??""))??null),g.type==="candidate-pair"&&(g.selected||g.nominated)&&g.state==="succeeded"){I=Number.isFinite(g.currentRoundTripTime)?Number(g.currentRoundTripTime):I,D=Number.isFinite(g.availableOutgoingBitrate)?Number(g.availableOutgoingBitrate):D;const U=n.get(g.localCandidateId);P=U?.candidateType?String(U.candidateType):P,L=U?.protocol?String(U.protocol):L}});const m=Date.now(),h=e?Math.max(.001,(m-e.timestamp)/1e3):0,C=e?Math.max(0,Math.round((s-e.inboundBytes)*8/h)):null,A=e?Math.max(0,Math.round((o-e.outboundBytes)*8/h)):null,E=r+c,x=e?Math.max(0,r-e.packetsLost):r,Q=e?Math.max(0,c-e.packetsReceived):c,j=x+Q;return{counters:{timestamp:m,inboundBytes:s,outboundBytes:o,packetsLost:r,packetsReceived:c},sample:{rttMs:I===null?null:I*1e3,jitterMs:d?l/d*1e3:null,packetLossPct:j?x/j*100:null,packetLossCumulativePct:E?r/E*100:null,packetsLost:r,packetsReceived:c,inboundBitrateBps:C,outboundBitrateBps:A,availableOutgoingBitrateBps:D,framesPerSecond:u||null,frameWidth:v||null,frameHeight:b||null,inboundFrameWidth:v||null,inboundFrameHeight:b||null,outboundFrameWidth:p||null,outboundFrameHeight:S||null,framesDropped:y,freezeCount:T,freezeDurationMs:Math.round(f*1e3),concealedSamples:M,codec:w,transportProtocol:L,candidateType:P,qualityLimitationReason:R}}}function gt(a){let e=a.mediaScore==null?5:Math.round(Ue(Number(a.mediaScore),0,5));const t=se(a.packetLossPct)??0,i=se(a.rttMs)??0,n=se(a.jitterMs)??0,s=String(a.connectionState??"unknown").toLowerCase();["failed","closed","disconnected"].includes(s)?e=0:["reconnecting","checking"].includes(s)&&(e=Math.min(e,2)),t>=15?e=Math.min(e,0):t>=8?e=Math.min(e,1):t>=3?e=Math.min(e,2):t>=1&&(e=Math.min(e,3)),i>=1200?e=Math.min(e,0):i>=800?e=Math.min(e,1):i>=400?e=Math.min(e,2):i>=250&&(e=Math.min(e,3)),n>=150?e=Math.min(e,1):n>=80?e=Math.min(e,2):n>=40&&(e=Math.min(e,3));const o=Number(Ue(4.5-t*.09-i*.0011-n*.006,1,4.5).toFixed(2));return{networkScore:e,estimatedMos:o}}function Ne(){return navigator.connection??null}function yt(){const a=Number(navigator.deviceMemory);return Number.isFinite(a)?a:null}function se(a){const e=Number(a);return Number.isFinite(e)?e:null}function Ue(a,e,t){return Math.min(t,Math.max(e,a))}function _(a){return a instanceof Error?{name:a.name,message:a.message}:{name:"ServiceError",message:String(a??"Service request failed")}}function qe(a,e){return a instanceof Error?a:new Error(e)}const xe="1.4.0";k.AVATAR_MODEL_MAX_BYTES=Te,k.AVATAR_TUNING_DEFAULTS=te,k.AvatarModelError=N,k.BACKGROUND_PRESETS=ue,k.OVERLAY_EFFECTS=me,k.RtcClient=Me,k.RtcIncomingClient=Pe,k.SPEAKER_NOISE_FLOOR=.02,k.SPEAKER_TAKEOVER_RATIO=1.4,k.createAvatarPipeline=Ke,k.createCallRecorder=Ge,k.createPictureInPictureController=Je,k.createRtcClient=dt,k.createRtcIncomingClient=mt,k.createVideoEffectsPipeline=Ye,k.drawAvatar=ge,k.headPoseFromMatrix=pe,k.importAvatarModel=st,k.listArchiveModels=et,k.listAvatarModels=ot,k.neutralRig=Z,k.pickActiveSpeaker=J,k.readAvatarModel=ct,k.readAvatarModelMetadata=Ce,k.removeAvatarModel=lt,k.rigFromBlendshapes=fe,k.scenePainters=X,k.smoothRig=ee,k.stylizedAvatarRenderer=ie,k.unlockRtcCallAudio=Ae,k.version=xe,Object.defineProperty(k,Symbol.toStringTag,{value:"Module"})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streaming-cdn/rtc-web",
3
- "version": "1.3.28",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/rtc-web.umd.js",
6
6
  "module": "dist/rtc-web.es.js",