@streaming-cdn/rtc-web 1.3.28 → 1.5.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.
@@ -1,4 +1,4 @@
1
- const at = 0.02, st = 1.4;
1
+ const ct = 0.02, lt = 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;
@@ -14,7 +14,7 @@ function ye(a) {
14
14
  }
15
15
  return e;
16
16
  }
17
- const V = {
17
+ const j = {
18
18
  audio: { active: !1, maxBitrate: 0, maxFramerate: 0, scaleResolutionDownBy: 4 },
19
19
  low: { active: !0, maxBitrate: 35e4, maxFramerate: 15, scaleResolutionDownBy: 4 },
20
20
  medium: { active: !0, maxBitrate: 11e5, maxFramerate: 24, scaleResolutionDownBy: 2 },
@@ -32,15 +32,15 @@ const V = {
32
32
  sustainRatio: 0.8,
33
33
  bandwidthSustainRatio: 0.7
34
34
  };
35
- function Ie(a) {
35
+ function Pe(a) {
36
36
  const e = String(a ?? "").trim().toLowerCase();
37
37
  return !e || e === "none" ? "" : e === "cpu" || e === "bandwidth" ? e : "other";
38
38
  }
39
- function Pe(a, e, t, i, n = {}) {
40
- const s = { ...Re, ...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 = Ie(e.qualityLimitationReason), p = e.packetLossPct ?? 0, S = e.rttMs ?? 0, T = e.availableOutgoingBitrateBps, y = V[a].maxBitrate, k = p >= s.criticalLossPct || S >= s.criticalRttMs || v === "cpu", f = v === "bandwidth" && (T == null || T < y * s.bandwidthSustainRatio), M = k || p >= s.poorLossPct || S >= s.poorRttMs || f, R = l < o.length - 1 ? V[o[l + 1]].maxBitrate : null, L = v === "bandwidth" && T != null && R != null && T >= R * s.bandwidthSustainRatio, P = T == null || T >= y * s.sustainRatio, A = p < s.goodLossPct && S < s.goodRttMs && (!v || L) && P, w = M ? t + 1 : 0, E = A ? i + 1 : 0, m = { quality: a, poorSamples: w, goodSamples: E, samplesSeen: d, changed: !1 };
39
+ function Ae(a, e, t, i, n = {}) {
40
+ const s = { ...Re, ...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 = Pe(e.qualityLimitationReason), p = e.packetLossPct ?? 0, S = e.rttMs ?? 0, b = e.availableOutgoingBitrateBps, g = j[a].maxBitrate, k = p >= s.criticalLossPct || S >= s.criticalRttMs || v === "cpu", f = v === "bandwidth" && (b == null || b < g * s.bandwidthSustainRatio), I = k || p >= s.poorLossPct || S >= s.poorRttMs || f, R = l < o.length - 1 ? j[o[l + 1]].maxBitrate : null, L = v === "bandwidth" && b != null && R != null && b >= R * s.bandwidthSustainRatio, P = b == null || b >= g * s.sustainRatio, A = p < s.goodLossPct && S < s.goodRttMs && (!v || L) && P, w = I ? t + 1 : 0, E = A ? i + 1 : 0, m = { quality: a, poorSamples: w, goodSamples: E, samplesSeen: d, changed: !1 };
41
41
  return l > c ? { quality: o[c], poorSamples: 0, goodSamples: 0, samplesSeen: d, changed: !0 } : u ? m : k && 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 } : E >= s.goodSamplesToStepUp && l < c ? { quality: o[l + 1], poorSamples: 0, goodSamples: 0, samplesSeen: d, changed: !0 } : m;
42
42
  }
43
- function Ae(a, e) {
43
+ function Le(a, e) {
44
44
  const t = a instanceof Map ? a : new Map(a);
45
45
  let i = 0, n = 0, s = null, o = null, r = null;
46
46
  for (const d of t.values()) {
@@ -68,18 +68,26 @@ function Ae(a, e) {
68
68
  counters: c
69
69
  };
70
70
  }
71
- function X(a, e) {
71
+ function ee(a, e) {
72
72
  const t = new URL(String(a), e);
73
73
  return t.protocol === "https:" ? t.protocol = "wss:" : t.protocol === "http:" && (t.protocol = "ws:"), t;
74
74
  }
75
- function Z(a, e) {
75
+ function te(a, e) {
76
76
  const t = new URL(String(a), e);
77
77
  return t.protocol === "wss:" ? t.protocol = "https:" : t.protocol === "ws:" && (t.protocol = "http:"), t;
78
78
  }
79
- const Le = {
79
+ function ve(a, e) {
80
+ return {
81
+ participantId: a,
82
+ connectionState: String(e.connectionState ?? "new"),
83
+ iceState: String(e.iceConnectionState ?? "new"),
84
+ signalingState: String(e.signalingState ?? "stable")
85
+ };
86
+ }
87
+ const De = {
80
88
  audio: { active: !1, maxBitrate: 0, maxFramerate: 0, scaleResolutionDownBy: 4 }
81
- }, De = { active: !0, maxBitrate: 4e6, maxFramerate: 30, scaleResolutionDownBy: 1 };
82
- class F {
89
+ }, xe = { active: !0, maxBitrate: 4e6, maxFramerate: 30, scaleResolutionDownBy: 1 };
90
+ class W {
83
91
  options;
84
92
  socket = null;
85
93
  localStream = null;
@@ -120,7 +128,7 @@ class F {
120
128
  }
121
129
  }
122
130
  async leave() {
123
- this.stopped = !0, this.clearConnectionTimers(), this.send({ type: "rtc.leave" }), this.joined = !1, this.socket?.close(1e3, "Participant left"), this.socket = null;
131
+ this.socket?.readyState === WebSocket.OPEN && this.send({ type: "rtc.leave" }), this.stopped = !0, this.clearConnectionTimers(), this.joined = !1, this.socket?.close(1e3, "Participant left"), this.socket = null;
124
132
  for (const e of this.peers.values()) e.connection.close();
125
133
  this.peers.clear(), this.stopStream(this.localStream), this.stopStream(this.screenStream), this.localStream = null, this.screenStream = null;
126
134
  }
@@ -177,7 +185,7 @@ class F {
177
185
  this.quality = e;
178
186
  for (const t of this.localStream?.getVideoTracks() ?? []) t.enabled = e !== "audio";
179
187
  for (const t of this.peers.values())
180
- t.quality = F.clampToCeiling(t.quality, e), t.poorSamples = 0, t.goodSamples = 0;
188
+ t.quality = W.clampToCeiling(t.quality, e), t.poorSamples = 0, t.goodSamples = 0;
181
189
  await this.applyVideoQuality();
182
190
  }
183
191
  static clampToCeiling(e, t) {
@@ -208,6 +216,10 @@ class F {
208
216
  getPrimaryPeerConnection() {
209
217
  return this.peers.values().next().value?.connection ?? null;
210
218
  }
219
+ /** Every peer connection's state machines at this instant, one entry per remote participant. */
220
+ getPeerStates() {
221
+ return [...this.peers.values()].map((e) => ve(e.identity.participantId, e.connection));
222
+ }
211
223
  sendChat(e, t) {
212
224
  this.send({ type: "rtc.chat", message: e, participantIds: t ?? [] });
213
225
  }
@@ -221,7 +233,7 @@ class F {
221
233
  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);
222
234
  }
223
235
  async openSocket(e) {
224
- const { credential: t } = this.options, i = X(t.signalingUrl, globalThis.location?.href), n = new Promise((c, l) => {
236
+ const { credential: t } = this.options, i = ee(t.signalingUrl, globalThis.location?.href), n = new Promise((c, l) => {
225
237
  this.resolveJoin = c, this.rejectJoin = l;
226
238
  }), s = new WebSocket(i, ["rtc-client", t.signalingToken]), o = ++this.socketGeneration;
227
239
  this.socket = s;
@@ -265,6 +277,7 @@ class F {
265
277
  this.reconnectAttempt = 0;
266
278
  const n = Array.isArray(t.peers) ? t.peers : [];
267
279
  for (const s of n) this.ensurePeer(s);
280
+ for (const s of n) this.options.onEvent("participantjoined", { ...s, initial: !0 });
268
281
  this.resolveJoin?.();
269
282
  for (const s of n)
270
283
  this.shouldInitiateOffer(s) && await this.createOffer(s);
@@ -276,27 +289,28 @@ class F {
276
289
  }
277
290
  if (i === "rtc.offer" && t.sender && t.description) {
278
291
  const n = this.ensurePeer(t.sender);
279
- n.offerTask && await n.offerTask;
292
+ this.negotiation(n, "offer-received"), n.offerTask && await n.offerTask;
280
293
  const s = n.connection.signalingState !== "stable", o = !this.shouldInitiateOffer(t.sender);
281
294
  if (s && !o) return;
282
- s && await n.connection.setLocalDescription({ type: "rollback" }), await n.connection.setRemoteDescription(t.description);
295
+ s && await n.connection.setLocalDescription({ type: "rollback" }), await n.connection.setRemoteDescription(t.description), this.negotiation(n, "remote-description-set");
283
296
  const r = await n.connection.createAnswer();
284
- await n.connection.setLocalDescription(r), this.send({
297
+ await n.connection.setLocalDescription(r), this.negotiation(n, "answer-created"), this.send({
285
298
  type: "rtc.answer",
286
299
  targetParticipantId: t.sender.participantId,
287
300
  negotiationId: t.negotiationId,
288
301
  description: n.connection.localDescription
289
- });
302
+ }), this.negotiation(n, "answer-sent");
290
303
  return;
291
304
  }
292
305
  if (i === "rtc.answer" && t.sender && t.description) {
293
306
  const n = this.peers.get(t.sender.participantId);
294
307
  if (!n || n.connection.signalingState !== "have-local-offer" || t.negotiationId && n.activeNegotiationId && t.negotiationId !== n.activeNegotiationId) return;
295
- await n.connection.setRemoteDescription(t.description), n.activeNegotiationId = null;
308
+ this.negotiation(n, "answer-received"), await n.connection.setRemoteDescription(t.description), this.negotiation(n, "remote-description-set"), n.activeNegotiationId = null;
296
309
  return;
297
310
  }
298
311
  if (i === "rtc.ice" && t.sender && t.candidate) {
299
- await this.ensurePeer(t.sender).connection.addIceCandidate(t.candidate).catch(() => {
312
+ const n = this.ensurePeer(t.sender);
313
+ this.negotiation(n, "ice-candidate-received"), await n.connection.addIceCandidate(t.candidate).catch(() => {
300
314
  });
301
315
  return;
302
316
  }
@@ -320,7 +334,7 @@ class F {
320
334
  makingOffer: !1,
321
335
  offerTask: null,
322
336
  activeNegotiationId: null,
323
- quality: F.startingQuality(this.quality),
337
+ quality: W.startingQuality(this.quality),
324
338
  poorSamples: 0,
325
339
  goodSamples: 0,
326
340
  samplesSeen: 0,
@@ -330,13 +344,20 @@ class F {
330
344
  for (const c of this.localStream?.getAudioTracks() ?? []) i.addTrack(c, this.localStream);
331
345
  const o = this.screenStream?.getVideoTracks().some((c) => c.readyState === "live") ? this.screenStream : this.localStream, r = o?.getVideoTracks()[0];
332
346
  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) => {
333
- c.candidate && this.send({ type: "rtc.ice", targetParticipantId: e.participantId, candidate: c.candidate.toJSON() });
347
+ c.candidate && (this.send({ type: "rtc.ice", targetParticipantId: e.participantId, candidate: c.candidate.toJSON() }), this.negotiation(s, "ice-candidate-sent"));
348
+ }), i.addEventListener("icegatheringstatechange", () => {
349
+ i.iceGatheringState === "complete" && this.negotiation(s, "ice-gathering-complete");
350
+ }), i.addEventListener("iceconnectionstatechange", () => {
351
+ this.options.onEvent("icestate", { participantId: e.participantId, state: i.iceConnectionState });
334
352
  }), i.addEventListener("track", (c) => {
335
353
  n.getTracks().some((l) => l.id === c.track.id) || n.addTrack(c.track), this.options.onEvent("remotestream", { participant: e, stream: n, track: c.track });
336
354
  }), i.addEventListener("connectionstatechange", () => {
337
355
  ["failed", "closed"].includes(i.connectionState) && this.removePeer(e.participantId), this.options.onEvent("peerconnectionstate", { participant: e, state: i.connectionState });
338
356
  }), s;
339
357
  }
358
+ negotiation(e, t) {
359
+ this.options.onEvent("negotiation", { participantId: e.identity.participantId, phase: t });
360
+ }
340
361
  async createOffer(e) {
341
362
  const t = this.ensurePeer(e);
342
363
  if (t.offerTask) return t.offerTask;
@@ -357,14 +378,14 @@ class F {
357
378
  offerToReceiveVideo: this.options.receiveVideo
358
379
  });
359
380
  if (e.connection.signalingState !== "stable") return;
360
- await e.connection.setLocalDescription(t);
381
+ this.negotiation(e, "offer-created"), await e.connection.setLocalDescription(t);
361
382
  const i = ie();
362
383
  e.activeNegotiationId = i, this.send({
363
384
  type: "rtc.offer",
364
385
  targetParticipantId: e.identity.participantId,
365
386
  negotiationId: i,
366
387
  description: e.connection.localDescription
367
- });
388
+ }), this.negotiation(e, "offer-sent");
368
389
  } finally {
369
390
  e.makingOffer = !1;
370
391
  }
@@ -398,7 +419,7 @@ class F {
398
419
  async applyPeerVideoQuality(e) {
399
420
  const t = this.screenStream?.getVideoTracks()[0]?.id;
400
421
  for (const i of e.connection.getSenders().filter((n) => n.track?.kind === "video")) {
401
- const n = !!(t && i.track?.id === t), s = n ? De : this.quality === "audio" ? Le.audio : V[e.quality], o = i.getParameters(), r = o.encodings?.length ? o.encodings : [{}];
422
+ const n = !!(t && i.track?.id === t), s = n ? xe : this.quality === "audio" ? De.audio : j[e.quality], o = i.getParameters(), r = o.encodings?.length ? o.encodings : [{}];
402
423
  o.encodings = r.map((c) => ({ ...c, ...s })), o.degradationPreference = n ? "maintain-resolution" : "maintain-framerate", await i.setParameters(o);
403
424
  }
404
425
  }
@@ -435,9 +456,9 @@ class F {
435
456
  } catch {
436
457
  continue;
437
458
  }
438
- const { sample: i, counters: n } = Ae(t, e.counters);
459
+ const { sample: i, counters: n } = Le(t, e.counters);
439
460
  e.counters = n;
440
- const s = Pe(e.quality, i, e.poorSamples, e.goodSamples, {
461
+ const s = Ae(e.quality, i, e.poorSamples, e.goodSamples, {
441
462
  samplesSeen: e.samplesSeen,
442
463
  ceiling: this.quality
443
464
  });
@@ -493,7 +514,11 @@ class F {
493
514
  }, e);
494
515
  }
495
516
  send(e) {
496
- this.socket?.readyState === WebSocket.OPEN && this.socket.send(JSON.stringify({
517
+ if (this.socket?.readyState !== WebSocket.OPEN) {
518
+ this.stopped || this.options.onEvent("signalingerror", { code: "send_dropped", messageType: String(e.type ?? "") });
519
+ return;
520
+ }
521
+ this.socket.send(JSON.stringify({
497
522
  clientMessageId: e.clientMessageId ?? ie(),
498
523
  ...e
499
524
  }));
@@ -505,12 +530,12 @@ class F {
505
530
  function ie() {
506
531
  return globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
507
532
  }
508
- const J = { active: !0, maxBitrate: 4e6, maxFramerate: 30, scaleResolutionDownBy: 1 }, ne = [
509
- { rid: "a", maxBitrate: V.high.maxBitrate, maxFramerate: V.high.maxFramerate },
510
- { rid: "b", maxBitrate: V.medium.maxBitrate, maxFramerate: V.medium.maxFramerate, scaleResolutionDownBy: 2 },
511
- { rid: "c", maxBitrate: V.low.maxBitrate, maxFramerate: V.low.maxFramerate, scaleResolutionDownBy: 4 }
533
+ const Y = { active: !0, maxBitrate: 4e6, maxFramerate: 30, scaleResolutionDownBy: 1 }, ne = [
534
+ { rid: "a", maxBitrate: j.high.maxBitrate, maxFramerate: j.high.maxFramerate },
535
+ { rid: "b", maxBitrate: j.medium.maxBitrate, maxFramerate: j.medium.maxFramerate, scaleResolutionDownBy: 2 },
536
+ { rid: "c", maxBitrate: j.low.maxBitrate, maxFramerate: j.low.maxFramerate, scaleResolutionDownBy: 4 }
512
537
  ], ae = { high: "a", medium: "b", low: "c" };
513
- class xe {
538
+ class Ne {
514
539
  options;
515
540
  apiBase;
516
541
  socket = null;
@@ -542,7 +567,7 @@ class xe {
542
567
  negotiationQueue = Promise.resolve();
543
568
  socketGeneration = 0;
544
569
  constructor(e) {
545
- this.options = e, this.localStream = e.localStream ?? null, this.quality = e.quality ?? "high", this.apiBase = Z(e.credential.signalingUrl, globalThis.location?.href).origin;
570
+ this.options = e, this.localStream = e.localStream ?? null, this.quality = e.quality ?? "high", this.apiBase = te(e.credential.signalingUrl, globalThis.location?.href).origin;
546
571
  }
547
572
  async initialize() {
548
573
  if (!globalThis.RTCPeerConnection || !globalThis.WebSocket)
@@ -560,7 +585,7 @@ class xe {
560
585
  await this.handleTrackAnnouncement(i.sender, i.detail);
561
586
  }
562
587
  async leave() {
563
- 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(() => {
588
+ this.socket?.readyState === WebSocket.OPEN && this.send({ type: "rtc.leave" }), this.stopped = !0, this.clearTimers(), this.joined = !1, this.socket?.close(1e3, "Participant left"), this.socket = null, await this.closePublishedTracks().catch(() => {
564
589
  }), 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;
565
590
  }
566
591
  setMuted(e) {
@@ -635,6 +660,14 @@ class xe {
635
660
  getPrimaryPeerConnection() {
636
661
  return this.connection;
637
662
  }
663
+ /**
664
+ * The single uplink/downlink connection to the SFU, reported under this
665
+ * participant's own id — the same attribution `peerconnectionstate` uses on
666
+ * this transport. Remote participants have no connection of their own here.
667
+ */
668
+ getPeerStates() {
669
+ return this.connection ? [ve(this.options.credential.participantId, this.connection)] : [];
670
+ }
638
671
  sendChat(e, t) {
639
672
  this.send({ type: "rtc.chat", message: e, participantIds: t ?? [] });
640
673
  }
@@ -643,7 +676,7 @@ class xe {
643
676
  }
644
677
  // --- signaling -----------------------------------------------------------
645
678
  async openSocket(e) {
646
- const { credential: t } = this.options, i = X(t.signalingUrl, globalThis.location?.href), n = new Promise((c, l) => {
679
+ const { credential: t } = this.options, i = ee(t.signalingUrl, globalThis.location?.href), n = new Promise((c, l) => {
647
680
  this.resolveJoin = c, this.rejectJoin = l;
648
681
  }), s = new WebSocket(i, ["rtc-client", t.signalingToken]), o = ++this.socketGeneration;
649
682
  this.socket = s;
@@ -684,7 +717,10 @@ class xe {
684
717
  return;
685
718
  }
686
719
  if (i === "rtc.room.peers") {
687
- this.reconnectAttempt = 0, this.resolveJoin?.();
720
+ this.reconnectAttempt = 0;
721
+ const n = Array.isArray(t.peers) ? t.peers : [];
722
+ for (const s of n) this.options.onEvent("participantjoined", { ...s, initial: !0 });
723
+ this.resolveJoin?.();
688
724
  return;
689
725
  }
690
726
  if (i === "rtc.room.join" && t.sender) {
@@ -717,6 +753,10 @@ class xe {
717
753
  });
718
754
  this.connection = t, t.addEventListener("track", (r) => this.handleRemoteTrack(r)), t.addEventListener("connectionstatechange", () => {
719
755
  this.options.onEvent("peerconnectionstate", { participant: this.selfIdentity(), state: t.connectionState });
756
+ }), t.addEventListener("iceconnectionstatechange", () => {
757
+ this.options.onEvent("icestate", { participantId: this.options.credential.participantId, state: t.iceConnectionState });
758
+ }), t.addEventListener("icegatheringstatechange", () => {
759
+ t.iceGatheringState === "complete" && this.negotiation("ice-gathering-complete");
720
760
  });
721
761
  const i = await this.sfuFetch("POST", "/v1/rtc/sfu/sessions");
722
762
  if (this.sessionId = i.sessionId, e.length === 0) return;
@@ -728,7 +768,7 @@ class xe {
728
768
  // quality ladder then adapts that one encoding instead of gating layers.
729
769
  ...r.kind === "video" && this.simulcastEnabled() ? { sendEncodings: ne.map((c) => ({ ...c })) } : {}
730
770
  })), s = await t.createOffer();
731
- await t.setLocalDescription(s);
771
+ this.negotiation("offer-created"), await t.setLocalDescription(s), this.negotiation("offer-sent");
732
772
  const o = await this.sfuFetch(
733
773
  "POST",
734
774
  `/v1/rtc/sfu/sessions/${encodeURIComponent(i.sessionId)}/tracks`,
@@ -741,7 +781,11 @@ class xe {
741
781
  }))
742
782
  }
743
783
  );
744
- o.sessionDescription && await t.setRemoteDescription(o.sessionDescription), this.videoSender = t.getSenders().find((r) => r.track?.kind === "video") ?? null, await this.applyVideoQuality();
784
+ o.sessionDescription && (this.negotiation("answer-received"), await t.setRemoteDescription(o.sessionDescription), this.negotiation("remote-description-set")), this.videoSender = t.getSenders().find((r) => r.track?.kind === "video") ?? null, await this.applyVideoQuality();
785
+ }
786
+ /** Negotiation on this transport is with the SFU, so it is attributed to this participant's own id. */
787
+ negotiation(e) {
788
+ this.options.onEvent("negotiation", { participantId: this.options.credential.participantId, phase: e });
745
789
  }
746
790
  trackName(e) {
747
791
  return `${this.options.credential.participantId}/${e.kind}`;
@@ -796,11 +840,11 @@ class xe {
796
840
  for (const s of n.tracks ?? [])
797
841
  s.mid && (e.mids.set(s.mid, String(s.trackName ?? "")), this.midToParticipant.set(s.mid, e.identity.participantId));
798
842
  if (e.trackNames = [.../* @__PURE__ */ new Set([...e.trackNames, ...t])], n.requiresImmediateRenegotiation && n.sessionDescription) {
799
- await i.setRemoteDescription(n.sessionDescription);
843
+ this.negotiation("offer-received"), await i.setRemoteDescription(n.sessionDescription), this.negotiation("remote-description-set");
800
844
  const s = await i.createAnswer();
801
- await i.setLocalDescription(s), await this.sfuFetch("PUT", `/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`, {
845
+ await i.setLocalDescription(s), this.negotiation("answer-created"), await this.sfuFetch("PUT", `/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`, {
802
846
  sessionDescription: i.localDescription
803
- });
847
+ }), this.negotiation("answer-sent");
804
848
  }
805
849
  }
806
850
  handleRemoteTrack(e) {
@@ -844,12 +888,12 @@ class xe {
844
888
  const o = this.quality === "audio" ? null : ae[this.quality];
845
889
  n.encodings = s.map((r) => {
846
890
  if (i)
847
- return { ...r, active: r.rid === "a", ...r.rid === "a" ? { maxBitrate: J.maxBitrate, maxFramerate: J.maxFramerate } : {} };
891
+ return { ...r, active: r.rid === "a", ...r.rid === "a" ? { maxBitrate: Y.maxBitrate, maxFramerate: Y.maxFramerate } : {} };
848
892
  const c = ne.find((d) => d.rid === r.rid), l = o != null && String(r.rid ?? "") >= o;
849
893
  return { ...r, ...c ?? {}, active: l };
850
894
  });
851
895
  } else {
852
- const o = this.quality === "audio" ? V.audio : V[this.quality], r = i ? J : o;
896
+ const o = this.quality === "audio" ? j.audio : j[this.quality], r = i ? Y : o;
853
897
  n.encodings = s.map((c) => ({ ...c, ...r }));
854
898
  }
855
899
  n.degradationPreference = i ? "maintain-resolution" : "maintain-framerate", await e.setParameters(n);
@@ -936,8 +980,12 @@ class xe {
936
980
  }, e);
937
981
  }
938
982
  send(e) {
939
- this.socket?.readyState === WebSocket.OPEN && this.socket.send(JSON.stringify({
940
- clientMessageId: e.clientMessageId ?? Ue(),
983
+ if (this.socket?.readyState !== WebSocket.OPEN) {
984
+ this.stopped || this.options.onEvent("signalingerror", { code: "send_dropped", messageType: String(e.type ?? "") });
985
+ return;
986
+ }
987
+ this.socket.send(JSON.stringify({
988
+ clientMessageId: e.clientMessageId ?? Oe(),
941
989
  ...e
942
990
  }));
943
991
  }
@@ -945,10 +993,10 @@ class xe {
945
993
  for (const t of e?.getTracks() ?? []) t.stop();
946
994
  }
947
995
  }
948
- function Ue() {
996
+ function Oe() {
949
997
  return globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
950
998
  }
951
- function rt(a, e = {}) {
999
+ function dt(a, e = {}) {
952
1000
  const t = e.document ?? globalThis.document, i = !!(t && t.pictureInPictureEnabled);
953
1001
  let n = null;
954
1002
  function s() {
@@ -976,17 +1024,17 @@ function rt(a, e = {}) {
976
1024
  }
977
1025
  };
978
1026
  }
979
- function ot(a, e) {
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);
1027
+ function ht(a, e) {
1028
+ const t = e.document ?? globalThis.document, i = e.fetchImplementation ?? globalThis.fetch.bind(globalThis), n = te(e.credential.signalingUrl, globalThis.location?.href).origin, s = e.mimeType ?? "video/webm", o = e.height ?? 720, r = Math.round(o * 16 / 9);
981
1029
  let c = null, l = null, d = null, u = null, v = 0, p = [], S = !1;
982
- async function T(y, k, f, M = "application/json") {
1030
+ async function b(g, k, f, I = "application/json") {
983
1031
  const R = await i(`${n}${k}`, {
984
- method: y,
1032
+ method: g,
985
1033
  headers: {
986
1034
  authorization: `Bearer ${e.credential.signalingToken}`,
987
- "content-type": M
1035
+ "content-type": I
988
1036
  },
989
- body: f === void 0 ? void 0 : M === "application/json" ? JSON.stringify(f) : f
1037
+ body: f === void 0 ? void 0 : I === "application/json" ? JSON.stringify(f) : f
990
1038
  });
991
1039
  if (!R.ok) throw new Error(`Recording request failed with HTTP ${R.status}`);
992
1040
  return R.json();
@@ -997,60 +1045,60 @@ function ot(a, e) {
997
1045
  },
998
1046
  async start() {
999
1047
  if (S) return;
1000
- u = (await T("POST", "/v1/rtc/recordings")).recordingId;
1048
+ u = (await b("POST", "/v1/rtc/recordings")).recordingId;
1001
1049
  const k = t.createElement("canvas");
1002
1050
  k.width = r, k.height = o;
1003
- const f = k.getContext("2d"), M = /* @__PURE__ */ new Map(), R = (b, I) => {
1004
- let C = M.get(b);
1005
- return C || (C = t.createElement("video"), C.muted = !0, C.playsInline = !0, C.srcObject = I, C.play().catch(() => {
1006
- }), M.set(b, C)), C;
1051
+ const f = k.getContext("2d"), I = /* @__PURE__ */ new Map(), R = (T, M) => {
1052
+ let C = I.get(T);
1053
+ return C || (C = t.createElement("video"), C.muted = !0, C.playsInline = !0, C.srcObject = M, C.play().catch(() => {
1054
+ }), I.set(T, C)), C;
1007
1055
  };
1008
1056
  l = setInterval(() => {
1009
- const b = a.getRemoteStreams(), I = a.getLocalStream(), C = [
1010
- ...b.map((U) => ({ key: U.participant.participantId, stream: U.stream, label: U.participant.displayName })),
1011
- ...I ? [{ key: "local", stream: I, label: "me" }] : []
1057
+ const T = a.getRemoteStreams(), M = a.getLocalStream(), C = [
1058
+ ...T.map((q) => ({ key: q.participant.participantId, stream: q.stream, label: q.participant.displayName })),
1059
+ ...M ? [{ key: "local", stream: M, label: "me" }] : []
1012
1060
  ];
1013
1061
  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);
1062
+ const O = Math.max(1, Math.ceil(Math.sqrt(C.length))), _ = Math.max(1, Math.ceil(C.length / O)), U = r / O, F = o / _;
1063
+ C.forEach((q, y) => {
1064
+ const Q = R(q.key, q.stream), J = y % O * U, G = Math.floor(y / O) * F;
1065
+ Q.videoWidth > 0 && f.drawImage(Q, J + 2, G + 2, U - 4, F - 4), f.fillStyle = "rgba(0,0,0,.6)", f.fillRect(J + 8, G + F - 30, Math.min(U - 16, 12 + q.label.length * 9), 22), f.fillStyle = "#fff", f.font = "14px system-ui", f.fillText(q.label, J + 14, G + F - 14);
1018
1066
  });
1019
1067
  }, 66);
1020
1068
  const L = k.captureStream(15);
1021
1069
  d = new AudioContext();
1022
1070
  const P = d.createMediaStreamDestination(), A = /* @__PURE__ */ new Set(), w = () => {
1023
- const b = [
1024
- ...a.getRemoteStreams().map((I) => I.stream),
1071
+ const T = [
1072
+ ...a.getRemoteStreams().map((M) => M.stream),
1025
1073
  ...a.getLocalStream() ? [a.getLocalStream()] : []
1026
1074
  ];
1027
- for (const I of b)
1028
- for (const C of I.getAudioTracks())
1075
+ for (const M of T)
1076
+ for (const C of M.getAudioTracks())
1029
1077
  A.has(C.id) || (A.add(C.id), d.createMediaStreamSource(new MediaStream([C])).connect(P));
1030
1078
  };
1031
1079
  w();
1032
1080
  const E = setInterval(w, 1e3), m = () => clearInterval(E);
1033
- for (const b of P.stream.getAudioTracks()) L.addTrack(b);
1034
- c = (e.mediaRecorderFactory ?? ((b, I) => new MediaRecorder(b, I)))(L, { mimeType: s }), p = [], c.addEventListener("dataavailable", (b) => {
1035
- b.data && b.data.size > 0 && p.push(b.data);
1081
+ for (const T of P.stream.getAudioTracks()) L.addTrack(T);
1082
+ c = (e.mediaRecorderFactory ?? ((T, M) => new MediaRecorder(T, M)))(L, { mimeType: s }), p = [], c.addEventListener("dataavailable", (T) => {
1083
+ T.data && T.data.size > 0 && p.push(T.data);
1036
1084
  }), c.addEventListener("stop", m), c.start(1e3), v = Date.now(), S = !0;
1037
1085
  },
1038
1086
  async stop() {
1039
1087
  if (!S || !c || !u) throw new Error("The recorder is not running");
1040
1088
  S = !1;
1041
- const y = c, k = new Promise((P) => y.addEventListener("stop", () => P(), { once: !0 }));
1042
- y.stop(), await k, l && clearInterval(l), l = null, await d?.close().catch(() => {
1089
+ const g = c, k = new Promise((P) => g.addEventListener("stop", () => P(), { once: !0 }));
1090
+ g.stop(), await k, l && clearInterval(l), l = null, await d?.close().catch(() => {
1043
1091
  }), d = null;
1044
1092
  const f = new Blob(p, { type: s });
1045
1093
  p = [];
1046
- const M = Math.max(1, Math.round((Date.now() - v) / 1e3)), R = await T(
1094
+ const I = Math.max(1, Math.round((Date.now() - v) / 1e3)), R = await b(
1047
1095
  "PUT",
1048
1096
  `/v1/rtc/recordings/${encodeURIComponent(u)}/media`,
1049
1097
  f,
1050
1098
  s
1051
1099
  );
1052
- await T("POST", `/v1/rtc/recordings/${encodeURIComponent(u)}/complete`, { durationSeconds: M });
1053
- const L = { recordingId: u, sizeBytes: R.sizeBytes, durationSeconds: M };
1100
+ await b("POST", `/v1/rtc/recordings/${encodeURIComponent(u)}/complete`, { durationSeconds: I });
1101
+ const L = { recordingId: u, sizeBytes: R.sizeBytes, durationSeconds: I };
1054
1102
  return u = null, c = null, L;
1055
1103
  },
1056
1104
  dispose() {
@@ -1064,7 +1112,7 @@ function ot(a, e) {
1064
1112
  }
1065
1113
  };
1066
1114
  }
1067
- const Ne = ["none", "blur", "aurora", "cybergrid", "bokeh", "sunset", "image"], qe = ["none", "snow", "confetti", "vignette"], se = {
1115
+ const Ue = ["none", "blur", "aurora", "cybergrid", "bokeh", "sunset", "image"], qe = ["none", "snow", "confetti", "vignette"], se = {
1068
1116
  aurora(a, e, t, i) {
1069
1117
  const n = a.createLinearGradient(0, 0, 0, t);
1070
1118
  n.addColorStop(0, "#050b1e"), n.addColorStop(1, "#0b2540"), a.fillStyle = n, a.fillRect(0, 0, e, t);
@@ -1101,7 +1149,7 @@ const Ne = ["none", "blur", "aurora", "cybergrid", "bokeh", "sunset", "image"],
1101
1149
  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
1150
  }
1103
1151
  };
1104
- function ct(a, e = {}) {
1152
+ function ut(a, e = {}) {
1105
1153
  const t = e.document ?? globalThis.document;
1106
1154
  let i = e.width ?? 1280, n = e.height ?? 720;
1107
1155
  const s = !!(e.width || e.height), o = e.frameRate ?? 24, r = a instanceof MediaStreamTrack ? a : a.getVideoTracks()[0];
@@ -1110,11 +1158,11 @@ function ct(a, e = {}) {
1110
1158
  const S = t.createElement("video");
1111
1159
  S.muted = !0, S.playsInline = !0, S.srcObject = new MediaStream([r]), S.play().catch(() => {
1112
1160
  });
1113
- const T = t.createElement("canvas");
1114
- T.width = i, T.height = n;
1115
- const y = T.getContext("2d"), k = t.createElement("canvas");
1161
+ const b = t.createElement("canvas");
1162
+ b.width = i, b.height = n;
1163
+ const g = b.getContext("2d"), k = t.createElement("canvas");
1116
1164
  k.width = i, k.height = n;
1117
- const f = k.getContext("2d"), M = () => Array.from({ length: 80 }, (m, h) => ({
1165
+ const f = k.getContext("2d"), I = () => Array.from({ length: 80 }, (m, h) => ({
1118
1166
  x: (Math.sin(h * 12.9898) * 0.5 + 0.5) * i,
1119
1167
  y: (Math.sin(h * 78.233) * 0.5 + 0.5) * n,
1120
1168
  speed: 0.6 + h % 5 * 0.5,
@@ -1122,30 +1170,30 @@ function ct(a, e = {}) {
1122
1170
  size: 2 + h % 4,
1123
1171
  hue: h * 47 % 360
1124
1172
  }));
1125
- let R = M();
1173
+ let R = I();
1126
1174
  const L = () => {
1127
1175
  if (s) return;
1128
1176
  const m = S.videoWidth, h = S.videoHeight;
1129
1177
  if (!m || !h) return;
1130
- const b = Math.min(1, 1280 / Math.max(m, h)), I = Math.round(m * b), C = Math.round(h * b);
1131
- I === i && C === n || (i = I, n = C, T.width = i, T.height = n, k.width = i, k.height = n, R = M());
1178
+ const T = Math.min(1, 1280 / Math.max(m, h)), M = Math.round(m * T), C = Math.round(h * T);
1179
+ M === i && C === n || (i = M, n = C, b.width = i, b.height = n, k.width = i, k.height = n, R = I());
1132
1180
  };
1133
1181
  S.addEventListener?.("loadedmetadata", L), S.addEventListener?.("resize", L);
1134
1182
  function P() {
1135
1183
  if (v) {
1136
- 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(k, 0, 0);
1184
+ 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", g.drawImage(k, 0, 0);
1137
1185
  return;
1138
1186
  }
1139
- const m = i * 0.42, h = m * 9 / 16, b = i - m - 24, I = n - h - 24;
1140
- y.save(), y.shadowColor = "rgba(0,0,0,.5)", y.shadowBlur = 24, y.beginPath(), y.roundRect(b, I, m, h, 14), y.clip(), y.drawImage(S, b, I, m, h), y.restore();
1187
+ const m = i * 0.42, h = m * 9 / 16, T = i - m - 24, M = n - h - 24;
1188
+ g.save(), g.shadowColor = "rgba(0,0,0,.5)", g.shadowBlur = 24, g.beginPath(), g.roundRect(T, M, m, h, 14), g.clip(), g.drawImage(S, T, M, m, h), g.restore();
1141
1189
  }
1142
1190
  function A(m) {
1143
- 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()) : ((se[c] ?? se.aurora)(y, i, n, m), P()), l === "snow" || l === "confetti")
1191
+ if (c === "none" ? g.drawImage(S, 0, 0, i, n) : c === "blur" ? (g.filter = "blur(14px)", g.drawImage(S, -16, -16, i + 32, n + 32), g.filter = "none", v && P()) : c === "image" && d ? (g.drawImage(d, 0, 0, i, n), P()) : ((se[c] ?? se.aurora)(g, i, n, m), P()), l === "snow" || l === "confetti")
1144
1192
  for (const h of R)
1145
- 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());
1193
+ h.y += h.speed * (l === "confetti" ? 1.6 : 1), h.x += h.drift, h.y > n && (h.y = -6, h.x = Math.random() * i), g.fillStyle = l === "snow" ? "rgba(255,255,255,.85)" : `hsl(${h.hue}, 90%, 60%)`, l === "confetti" ? g.fillRect(h.x, h.y, h.size + 2, h.size) : (g.beginPath(), g.arc(h.x, h.y, h.size / 2, 0, Math.PI * 2), g.fill());
1146
1194
  else if (l === "vignette") {
1147
- const h = y.createRadialGradient(i / 2, n / 2, n * 0.4, i / 2, n / 2, n * 0.85);
1148
- 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);
1195
+ const h = g.createRadialGradient(i / 2, n / 2, n * 0.4, i / 2, n / 2, n * 0.85);
1196
+ h.addColorStop(0, "rgba(0,0,0,0)"), h.addColorStop(1, "rgba(0,0,0,0.55)"), g.fillStyle = h, g.fillRect(0, 0, i, n);
1149
1197
  }
1150
1198
  }
1151
1199
  const w = setInterval(() => {
@@ -1156,12 +1204,12 @@ function ct(a, e = {}) {
1156
1204
  }).finally(() => {
1157
1205
  p = !1;
1158
1206
  }));
1159
- }, Math.round(1e3 / o)), E = T.captureStream(o);
1207
+ }, Math.round(1e3 / o)), E = b.captureStream(o);
1160
1208
  return {
1161
1209
  track: E.getVideoTracks()[0],
1162
1210
  stream: E,
1163
1211
  setBackground(m, h) {
1164
- c = Ne.includes(m) ? m : "none", h !== void 0 && (d = h), c === "none" && (v = null);
1212
+ c = Ue.includes(m) ? m : "none", h !== void 0 && (d = h), c === "none" && (v = null);
1165
1213
  },
1166
1214
  setOverlay(m) {
1167
1215
  l = qe.includes(m) ? m : "none";
@@ -1176,7 +1224,7 @@ function ct(a, e = {}) {
1176
1224
  }
1177
1225
  };
1178
1226
  }
1179
- function ve() {
1227
+ function Se() {
1180
1228
  return {
1181
1229
  blinkLeft: 0,
1182
1230
  blinkRight: 0,
@@ -1195,15 +1243,15 @@ function D(a, e) {
1195
1243
  const t = a.find((i) => i.categoryName === e);
1196
1244
  return t ? Math.max(0, Math.min(1, t.score)) : 0;
1197
1245
  }
1198
- function Oe(a) {
1246
+ function je(a) {
1199
1247
  if (!a || a.length < 16) return { yaw: 0, pitch: 0, roll: 0 };
1200
1248
  a[0], a[4];
1201
1249
  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);
1202
1250
  return { yaw: o, pitch: r, roll: c };
1203
1251
  }
1204
- function Ve(a, e, t = 1) {
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;
1252
+ function Be(a, e, t = 1) {
1253
+ if (!a || a.length === 0) return Se();
1254
+ const i = je(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
1255
  return {
1208
1256
  blinkLeft: d + (p - d) * v,
1209
1257
  blinkRight: u + (p - u) * v,
@@ -1234,7 +1282,7 @@ function re(a, e, t = 0.4) {
1234
1282
  tracked: e.tracked
1235
1283
  };
1236
1284
  }
1237
- function je(a, e, t, i, n) {
1285
+ function Ve(a, e, t, i, n) {
1238
1286
  n || (a.fillStyle = "#101822", a.fillRect(0, 0, t, i));
1239
1287
  const s = n ? n.x + e.headYaw * n.radius * 0.1 : t / 2 + e.headYaw * t * 0.18, o = n ? n.y + e.headPitch * n.radius * 0.1 : i / 2 + e.headPitch * i * 0.18, r = n ? n.radius : Math.min(t, i) * 0.28;
1240
1288
  a.save(), a.translate(s, o), a.rotate(e.headRoll * 0.6);
@@ -1242,26 +1290,26 @@ function je(a, e, t, i, n) {
1242
1290
  a.fillStyle = "#3a2a1c", a.beginPath(), a.ellipse(-d * r * 0.35, -r * 0.25, r * 0.98, r * 0.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) * 0.12), r * 1.12, 0, 0, Math.PI * 2), a.fill(), a.fillStyle = "#3a2a1c", a.beginPath(), a.ellipse(-d * r * 0.28, -r * 0.72 + p * 0.4, r * 0.92, r * 0.5, 0, Math.PI, Math.PI * 2), a.fill();
1243
1291
  const S = 0;
1244
1292
  a.fillStyle = "#e5b58a", a.beginPath();
1245
- const T = u(S) * 0.9, y = r * 0.12 + p;
1246
- a.ellipse(T + d * r * 0.14, y, r * 0.11, r * 0.14, 0, 0, Math.PI * 2), a.fill();
1293
+ const b = u(S) * 0.9, g = r * 0.12 + p;
1294
+ a.ellipse(b + d * r * 0.14, g, r * 0.11, r * 0.14, 0, 0, Math.PI * 2), a.fill();
1247
1295
  for (const A of [-1, 1]) {
1248
1296
  const w = A * 0.42, E = v(w);
1249
1297
  if (E <= 0.12) continue;
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;
1298
+ const m = u(w) * 0.82, h = Math.max(0.2, E), T = A < 0 ? e.blinkLeft : e.blinkRight, M = Math.max(0.06, 1 - T), C = e.browUp * r * 0.14;
1251
1299
  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(
1300
+ const O = -r * 0.18 + p;
1301
+ a.fillStyle = "#ffffff", a.beginPath(), a.ellipse(m, O, r * 0.16 * h, r * 0.12 * M, 0, 0, Math.PI * 2), a.fill(), a.fillStyle = "#22303c", a.beginPath(), a.ellipse(
1254
1302
  m + e.pupilX * r * 0.07 * h,
1255
- N + e.pupilY * r * 0.05,
1303
+ O + e.pupilY * r * 0.05,
1256
1304
  r * 0.06 * h,
1257
- r * 0.06 * I,
1305
+ r * 0.06 * M,
1258
1306
  0,
1259
1307
  0,
1260
1308
  Math.PI * 2
1261
1309
  ), a.fill();
1262
1310
  }
1263
- const k = Math.max(0.25, v(0)), f = u(0) * 0.7, M = r * 0.55 * k, R = r * (0.04 + e.jawOpen * 0.34), L = e.smile * r * 0.12, P = r * 0.42 + p;
1264
- a.fillStyle = "#8c2f2f", a.beginPath(), a.moveTo(f - M / 2, P - L), a.quadraticCurveTo(f, P + R, f + M / 2, P - L), a.quadraticCurveTo(f, P - R * 0.25, f - M / 2, P - L), a.closePath(), a.fill(), a.restore(), !e.tracked && !n && (a.fillStyle = "#9fb0bd", a.font = `${Math.round(i * 0.045)}px system-ui`, a.textAlign = "center", a.fillText("No face detected", t / 2, i * 0.9), a.textAlign = "left");
1311
+ const k = Math.max(0.25, v(0)), f = u(0) * 0.7, I = r * 0.55 * k, R = r * (0.04 + e.jawOpen * 0.34), L = e.smile * r * 0.12, P = r * 0.42 + p;
1312
+ a.fillStyle = "#8c2f2f", a.beginPath(), a.moveTo(f - I / 2, P - L), a.quadraticCurveTo(f, P + R, f + I / 2, P - L), a.quadraticCurveTo(f, P - R * 0.25, f - I / 2, P - L), a.closePath(), a.fill(), a.restore(), !e.tracked && !n && (a.fillStyle = "#9fb0bd", a.font = `${Math.round(i * 0.045)}px system-ui`, a.textAlign = "center", a.fillText("No face detected", t / 2, i * 0.9), a.textAlign = "left");
1265
1313
  }
1266
1314
  const oe = {
1267
1315
  mouthGain: 1.7,
@@ -1275,12 +1323,12 @@ const oe = {
1275
1323
  function ce() {
1276
1324
  return {
1277
1325
  render(a, e, t, i, n) {
1278
- je(a, e, t, i, n ?? void 0);
1326
+ Ve(a, e, t, i, n ?? void 0);
1279
1327
  }
1280
1328
  };
1281
1329
  }
1282
- const G = (a) => Math.max(0, Math.min(1, a));
1283
- function lt(a, e = {}) {
1330
+ const K = (a) => Math.max(0, Math.min(1, a));
1331
+ function mt(a, e = {}) {
1284
1332
  const t = e.document ?? globalThis.document;
1285
1333
  let i = e.width ?? 1280, n = e.height ?? 720;
1286
1334
  const s = !!(e.width || e.height), o = e.frameRate ?? 24, r = a instanceof MediaStreamTrack ? a : a.getVideoTracks()[0];
@@ -1291,19 +1339,19 @@ function lt(a, e = {}) {
1291
1339
  });
1292
1340
  const S = t.createElement("canvas");
1293
1341
  S.width = i, S.height = n;
1294
- const T = S.getContext("2d"), y = () => {
1342
+ const b = S.getContext("2d"), g = () => {
1295
1343
  if (s) return;
1296
1344
  const w = p.videoWidth, E = p.videoHeight;
1297
1345
  if (!w || !E) return;
1298
- const m = Math.min(1, 1280 / Math.max(w, E)), h = Math.round(w * m), b = Math.round(E * m);
1299
- (!(h === i || b === n) || h !== i || b !== n) && (i = h, n = b, S.width = i, S.height = n);
1346
+ const m = Math.min(1, 1280 / Math.max(w, E)), h = Math.round(w * m), T = Math.round(E * m);
1347
+ (!(h === i || T === n) || h !== i || T !== n) && (i = h, n = T, S.width = i, S.height = n);
1300
1348
  };
1301
- p.addEventListener?.("loadedmetadata", y), p.addEventListener?.("resize", y);
1302
- let k = ve(), f = null, M = 0;
1349
+ p.addEventListener?.("loadedmetadata", g), p.addEventListener?.("resize", g);
1350
+ let k = Se(), f = null, I = 0;
1303
1351
  function R(w) {
1304
1352
  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;
1353
+ const E = w[10], m = w[152], h = w[234], T = w[454], M = (h.x + T.x) / 2 * i, C = (h.y + T.y) / 2 * n, O = Math.hypot((m.x - E.x) * i, (m.y - E.y) * n), _ = Math.max(0.6, Math.cos(k.headPitch)), U = { x: M, y: C, radius: Math.max(24, O / _ * 0.8) };
1354
+ f = f ? { x: f.x + (U.x - f.x) * 0.4, y: f.y + (U.y - f.y) * 0.4, radius: f.radius + (U.radius - f.radius) * 0.15 } : U;
1307
1355
  }
1308
1356
  function L(w) {
1309
1357
  if (c && p.readyState >= 2) {
@@ -1312,12 +1360,12 @@ function lt(a, e = {}) {
1312
1360
  m = c.detect(p, w);
1313
1361
  } catch {
1314
1362
  }
1315
- const h = Ve(m?.categories ?? null, m?.matrix ?? null, u.eyeSync);
1316
- h.jawOpen = G(h.jawOpen * u.mouthGain), h.blinkLeft = G(h.blinkLeft * u.blinkGain), h.blinkRight = G(h.blinkRight * u.blinkGain), h.tracked ? (M = w, k = re(k, h, u.smoothing), m?.landmarks && R(m.landmarks)) : k.tracked && w - M > u.lostHoldMs && (k = re(k, { ...k, jawOpen: 0, browUp: 0, smile: k.smile * 0.5, blinkLeft: 0.1, blinkRight: 0.1 }, 0.05));
1363
+ const h = Be(m?.categories ?? null, m?.matrix ?? null, u.eyeSync);
1364
+ h.jawOpen = K(h.jawOpen * u.mouthGain), h.blinkLeft = K(h.blinkLeft * u.blinkGain), h.blinkRight = K(h.blinkRight * u.blinkGain), h.tracked ? (I = w, k = re(k, h, u.smoothing), m?.landmarks && R(m.landmarks)) : k.tracked && w - I > u.lostHoldMs && (k = re(k, { ...k, jawOpen: 0, browUp: 0, smile: k.smile * 0.5, blinkLeft: 0.1, blinkRight: 0.1 }, 0.05));
1317
1365
  }
1318
- d === "overlay" && p.readyState >= 2 ? T.drawImage(p, 0, 0, i, n) : (T.fillStyle = v, T.fillRect(0, 0, i, n));
1366
+ d === "overlay" && p.readyState >= 2 ? b.drawImage(p, 0, 0, i, n) : (b.fillStyle = v, b.fillRect(0, 0, i, n));
1319
1367
  const E = f ? { x: f.x, y: f.y + u.avatarLift * f.radius, radius: f.radius * u.avatarScale } : null;
1320
- l?.render(T, k, i, n, E);
1368
+ l?.render(b, k, i, n, E);
1321
1369
  }
1322
1370
  const P = setInterval(() => L(Date.now()), Math.round(1e3 / o)), A = S.captureStream(o);
1323
1371
  return {
@@ -1347,73 +1395,73 @@ function lt(a, e = {}) {
1347
1395
  }
1348
1396
  };
1349
1397
  }
1350
- class j extends Error {
1398
+ class B extends Error {
1351
1399
  constructor(e, t) {
1352
1400
  super(t), this.code = e, this.name = "ZipError";
1353
1401
  }
1354
1402
  code;
1355
1403
  }
1356
- const Se = 101010256, we = 33639248, ke = 67324752, K = 4294967295;
1357
- function Te(a) {
1404
+ const we = 101010256, ke = 33639248, Te = 67324752, Z = 4294967295;
1405
+ function be(a) {
1358
1406
  if (a.byteLength < 4) return !1;
1359
1407
  const e = new DataView(a).getUint32(0, !0);
1360
- return e === ke || e === we || e === Se;
1408
+ return e === Te || e === ke || e === we;
1361
1409
  }
1362
- function Be(a) {
1410
+ function Qe(a) {
1363
1411
  const e = Math.max(0, a.byteLength - 22 - 65535);
1364
1412
  for (let t = a.byteLength - 22; t >= e; t -= 1)
1365
- if (a.getUint32(t, !0) === Se) return t;
1366
- throw new j("zip_unreadable", "The archive index could not be found.");
1413
+ if (a.getUint32(t, !0) === we) return t;
1414
+ throw new B("zip_unreadable", "The archive index could not be found.");
1367
1415
  }
1368
- function be(a) {
1369
- const e = new DataView(a), t = Be(e), i = e.getUint16(t + 10, !0);
1416
+ function Ce(a) {
1417
+ const e = new DataView(a), t = Qe(e), i = e.getUint16(t + 10, !0);
1370
1418
  let n = e.getUint32(t + 16, !0);
1371
- if (n === K) throw new j("zip_unsupported", "ZIP64 archives are not supported.");
1419
+ if (n === Z) throw new B("zip_unsupported", "ZIP64 archives are not supported.");
1372
1420
  const s = new TextDecoder(), o = [];
1373
1421
  for (let r = 0; r < i; r += 1) {
1374
- if (n + 46 > a.byteLength || e.getUint32(n, !0) !== we)
1375
- throw new j("zip_unreadable", "The archive index is malformed.");
1376
- 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), T = e.getUint32(n + 42, !0), y = s.decode(new Uint8Array(a, n + 46, v));
1422
+ if (n + 46 > a.byteLength || e.getUint32(n, !0) !== ke)
1423
+ throw new B("zip_unreadable", "The archive index is malformed.");
1424
+ 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), g = s.decode(new Uint8Array(a, n + 46, v));
1377
1425
  o.push({
1378
- path: y,
1426
+ path: g,
1379
1427
  bytes: u,
1380
1428
  compressedBytes: d,
1381
1429
  compressionMethod: l,
1382
1430
  encrypted: (c & 1) !== 0,
1383
- localHeaderOffset: T
1431
+ localHeaderOffset: b
1384
1432
  }), n += 46 + v + p + S;
1385
1433
  }
1386
1434
  return o;
1387
1435
  }
1388
- async function Qe(a, e) {
1389
- if (e.encrypted) throw new j("zip_encrypted", "The archive is password protected.");
1390
- if (e.bytes === K || e.compressedBytes === K)
1391
- throw new j("zip_unsupported", "ZIP64 archives are not supported.");
1436
+ async function _e(a, e) {
1437
+ if (e.encrypted) throw new B("zip_encrypted", "The archive is password protected.");
1438
+ if (e.bytes === Z || e.compressedBytes === Z)
1439
+ throw new B("zip_unsupported", "ZIP64 archives are not supported.");
1392
1440
  const t = new DataView(a), i = e.localHeaderOffset;
1393
- if (i + 30 > a.byteLength || t.getUint32(i, !0) !== ke)
1394
- throw new j("zip_unreadable", "An archive entry header is malformed.");
1441
+ if (i + 30 > a.byteLength || t.getUint32(i, !0) !== Te)
1442
+ throw new B("zip_unreadable", "An archive entry header is malformed.");
1395
1443
  const n = t.getUint16(i + 26, !0), s = t.getUint16(i + 28, !0), o = i + 30 + n + s;
1396
1444
  if (o + e.compressedBytes > a.byteLength)
1397
- throw new j("zip_unreadable", "An archive entry runs past the end of the file.");
1445
+ throw new B("zip_unreadable", "An archive entry runs past the end of the file.");
1398
1446
  const r = a.slice(o, o + e.compressedBytes);
1399
1447
  if (e.compressionMethod === 0) return r;
1400
1448
  if (e.compressionMethod !== 8)
1401
- throw new j("zip_unsupported", `Archive compression method ${e.compressionMethod} is not supported.`);
1449
+ throw new B("zip_unsupported", `Archive compression method ${e.compressionMethod} is not supported.`);
1402
1450
  if (typeof DecompressionStream > "u")
1403
- throw new j("zip_unsupported", "This browser cannot expand compressed archives.");
1451
+ throw new B("zip_unsupported", "This browser cannot expand compressed archives.");
1404
1452
  const c = new Blob([r]).stream().pipeThrough(new DecompressionStream("deflate-raw"));
1405
1453
  return new Response(c).arrayBuffer();
1406
1454
  }
1407
- async function dt(a) {
1455
+ async function pt(a) {
1408
1456
  const e = typeof File < "u" && a instanceof File ? await a.arrayBuffer() : a;
1409
- if (!Te(e)) return [];
1457
+ if (!be(e)) return [];
1410
1458
  try {
1411
- return Ce(be(e)).map((t) => t.path);
1459
+ return Ee(Ce(e)).map((t) => t.path);
1412
1460
  } catch {
1413
1461
  return [];
1414
1462
  }
1415
1463
  }
1416
- const _e = 64 * 1024 * 1024, Fe = "streaming-cdn-avatars", z = "models", ze = 1179937895, He = 1313821514;
1464
+ const Fe = 64 * 1024 * 1024, ze = "streaming-cdn-avatars", H = "models", We = 1179937895, He = 1313821514;
1417
1465
  class x extends Error {
1418
1466
  constructor(e, t, i = []) {
1419
1467
  super(t), this.code = e, this.name = "AvatarModelError", this.choices = i;
@@ -1426,16 +1474,16 @@ class x extends Error {
1426
1474
  */
1427
1475
  choices;
1428
1476
  }
1429
- function Ce(a) {
1477
+ function Ee(a) {
1430
1478
  return a.filter((e) => /\.vrm$/i.test(e.path) && !e.path.endsWith("/") && !e.path.split("/").some((t) => t === "__MACOSX" || t.startsWith("._")));
1431
1479
  }
1432
- function q(a) {
1480
+ function N(a) {
1433
1481
  const e = typeof a == "string" ? a.trim() : "";
1434
1482
  return e.length > 0 ? e : null;
1435
1483
  }
1436
1484
  function $e(a) {
1437
1485
  const e = new DataView(a);
1438
- if (a.byteLength < 20 || e.getUint32(0, !0) !== ze)
1486
+ if (a.byteLength < 20 || e.getUint32(0, !0) !== We)
1439
1487
  throw new x("model_not_glb", "The file is not a binary glTF (.vrm / .glb) model.");
1440
1488
  let t = 12, i = null;
1441
1489
  for (; t + 8 <= a.byteLength; ) {
@@ -1457,58 +1505,58 @@ function $e(a) {
1457
1505
  if (o) {
1458
1506
  const r = Array.isArray(o.authors) ? o.authors.filter((c) => typeof c == "string") : [];
1459
1507
  return {
1460
- title: q(o.name),
1508
+ title: N(o.name),
1461
1509
  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)
1510
+ allowedUser: N(o.avatarPermission),
1511
+ commercialUse: N(o.commercialUsage),
1512
+ licenseName: N(o.licenseUrl) ? "VRM 1.0" : null,
1513
+ licenseUrl: N(o.licenseUrl) ?? N(o.otherLicenseUrl)
1466
1514
  };
1467
1515
  }
1468
1516
  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)
1517
+ title: N(s?.title),
1518
+ author: N(s?.author),
1519
+ allowedUser: N(s?.allowedUserName),
1520
+ commercialUse: N(s?.commercialUssageName),
1521
+ licenseName: N(s?.licenseName),
1522
+ licenseUrl: N(s?.otherLicenseUrl)
1475
1523
  };
1476
1524
  }
1477
- function We() {
1525
+ function Je() {
1478
1526
  return new Promise((a, e) => {
1479
1527
  if (typeof indexedDB > "u") {
1480
1528
  e(new x("storage_unavailable", "This browser cannot store imported avatar models."));
1481
1529
  return;
1482
1530
  }
1483
- const t = indexedDB.open(Fe, 1);
1531
+ const t = indexedDB.open(ze, 1);
1484
1532
  t.onupgradeneeded = () => {
1485
- t.result.objectStoreNames.contains(z) || t.result.createObjectStore(z, { keyPath: "id" });
1533
+ t.result.objectStoreNames.contains(H) || t.result.createObjectStore(H, { keyPath: "id" });
1486
1534
  }, t.onsuccess = () => a(t.result), t.onerror = () => e(new x("storage_unavailable", "Imported avatar storage could not be opened."));
1487
1535
  });
1488
1536
  }
1489
- async function H(a, e) {
1490
- const t = await We();
1537
+ async function $(a, e) {
1538
+ const t = await Je();
1491
1539
  try {
1492
1540
  return await new Promise((i, n) => {
1493
- const s = e(t.transaction(z, a).objectStore(z));
1541
+ const s = e(t.transaction(H, a).objectStore(H));
1494
1542
  s.onsuccess = () => i(s.result), s.onerror = () => n(new x("storage_unavailable", "The imported avatar could not be stored."));
1495
1543
  });
1496
1544
  } finally {
1497
1545
  t.close();
1498
1546
  }
1499
1547
  }
1500
- const Ee = (a) => ({
1548
+ const Me = (a) => ({
1501
1549
  id: a.id,
1502
1550
  name: a.name,
1503
1551
  bytes: a.bytes,
1504
1552
  importedAt: a.importedAt,
1505
1553
  license: a.license
1506
1554
  });
1507
- async function ht(a, e = {}) {
1508
- const t = e.maxBytes ?? _e, i = typeof File < "u" && a instanceof File;
1555
+ async function ft(a, e = {}) {
1556
+ const t = e.maxBytes ?? Fe, i = typeof File < "u" && a instanceof File;
1509
1557
  let n = i ? a.name : null, s = i ? await a.arrayBuffer() : a;
1510
- if (Te(s)) {
1511
- const l = await Je(s, e.entryPath, t);
1558
+ if (be(s)) {
1559
+ const l = await Ge(s, e.entryPath, t);
1512
1560
  s = l.buffer, n = l.path.split("/").pop() ?? n;
1513
1561
  }
1514
1562
  if (s.byteLength > t)
@@ -1527,14 +1575,14 @@ async function ht(a, e = {}) {
1527
1575
  license: o,
1528
1576
  data: s
1529
1577
  };
1530
- return await H("readwrite", (l) => l.put(c)), Ee(c);
1578
+ return await $("readwrite", (l) => l.put(c)), Me(c);
1531
1579
  }
1532
- async function Je(a, e, t) {
1580
+ async function Ge(a, e, t) {
1533
1581
  let i;
1534
1582
  try {
1535
- i = Ce(be(a));
1583
+ i = Ee(Ce(a));
1536
1584
  } catch (s) {
1537
- throw s instanceof j ? new x(le(s.code), s.message) : new x("archive_unreadable", "The archive could not be read.");
1585
+ throw s instanceof B ? new x(le(s.code), s.message) : new x("archive_unreadable", "The archive could not be read.");
1538
1586
  }
1539
1587
  if (i.length === 0)
1540
1588
  throw new x("archive_no_model", "The archive contains no .vrm character.");
@@ -1551,24 +1599,25 @@ async function Je(a, e, t) {
1551
1599
  "The character inside the archive is larger than the " + Math.round(t / 1024 / 1024) + " MB limit."
1552
1600
  );
1553
1601
  try {
1554
- return { buffer: await Qe(a, n), path: n.path };
1602
+ return { buffer: await _e(a, n), path: n.path };
1555
1603
  } catch (s) {
1556
- throw s instanceof j ? new x(le(s.code), s.message) : new x("archive_unreadable", "The character could not be expanded from the archive.");
1604
+ throw s instanceof B ? new x(le(s.code), s.message) : new x("archive_unreadable", "The character could not be expanded from the archive.");
1557
1605
  }
1558
1606
  }
1559
1607
  function le(a) {
1560
1608
  return a === "zip_encrypted" ? "archive_encrypted" : a === "zip_unsupported" ? "archive_unsupported" : "archive_unreadable";
1561
1609
  }
1562
- async function ut() {
1563
- return (await H("readonly", (e) => e.getAll()) ?? []).map(Ee).sort((e, t) => t.importedAt.localeCompare(e.importedAt));
1610
+ async function gt() {
1611
+ return (await $("readonly", (e) => e.getAll()) ?? []).map(Me).sort((e, t) => t.importedAt.localeCompare(e.importedAt));
1564
1612
  }
1565
- async function mt(a) {
1566
- return (await H("readonly", (t) => t.get(a)))?.data ?? null;
1613
+ async function yt(a) {
1614
+ return (await $("readonly", (t) => t.get(a)))?.data ?? null;
1567
1615
  }
1568
- async function pt(a) {
1569
- await H("readwrite", (e) => e.delete(a));
1616
+ async function vt(a) {
1617
+ await $("readwrite", (e) => e.delete(a));
1570
1618
  }
1571
- class Ge extends EventTarget {
1619
+ const Ye = 3e4;
1620
+ class Ke extends EventTarget {
1572
1621
  mode;
1573
1622
  options;
1574
1623
  client = null;
@@ -1577,7 +1626,7 @@ class Ge extends EventTarget {
1577
1626
  latestMediaScore = null;
1578
1627
  latestQuality = null;
1579
1628
  statsTimer = null;
1580
- statsRunning = !1;
1629
+ statsTask = null;
1581
1630
  previousCounters = null;
1582
1631
  lastTelemetryAt = 0;
1583
1632
  joinPromise = null;
@@ -1585,6 +1634,15 @@ class Ge extends EventTarget {
1585
1634
  effectiveQuality;
1586
1635
  poorQualitySamples = 0;
1587
1636
  goodQualitySamples = 0;
1637
+ transport = "mesh";
1638
+ // Connect phase (S9): everything below is anchored at the first join().
1639
+ joinStartedAt = null;
1640
+ connectTimer = null;
1641
+ connectTimedOut = !1;
1642
+ lastConnectTimeout = null;
1643
+ mediaConnectedPeers = /* @__PURE__ */ new Set();
1644
+ firstMediaConnectedElapsedMs = null;
1645
+ lastNegotiationPhase = /* @__PURE__ */ new Map();
1588
1646
  constructor(e) {
1589
1647
  if (super(), !e.credential?.signalingToken || !e.credential.signalingUrl || !e.credential.roomId || !e.credential.participantId)
1590
1648
  throw new Error("A valid room signaling credential is required");
@@ -1602,7 +1660,9 @@ class Ge extends EventTarget {
1602
1660
  async initialize() {
1603
1661
  if (this.client) return this;
1604
1662
  try {
1605
- const t = (this.options.transport === "auto" ? this.mode === "meeting" ? "sfu" : "mesh" : this.options.transport ?? "mesh") === "sfu" ? xe : F;
1663
+ const e = this.options.transport === "auto" ? this.mode === "meeting" ? "sfu" : "mesh" : this.options.transport ?? "mesh";
1664
+ this.transport = e;
1665
+ const t = e === "sfu" ? Ne : W;
1606
1666
  return this.client = new t({
1607
1667
  credential: {
1608
1668
  signalingToken: this.options.credential.signalingToken,
@@ -1622,27 +1682,42 @@ class Ge extends EventTarget {
1622
1682
  localStream: this.options.localStream,
1623
1683
  simulcast: this.options.simulcast ?? !0,
1624
1684
  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;
1685
+ }), await this.client.initialize(), this.startStats(), this.emit("initialized", { mode: this.mode, sdkVersion: ot }), this.options.autoJoin && await this.join(), this;
1626
1686
  } catch (e) {
1627
1687
  this.state = "failed";
1628
1688
  const t = fe(e, "RTC initialization failed");
1629
- throw this.emit("error", B(t)), t;
1689
+ throw this.emit("error", V(t)), t;
1630
1690
  }
1631
1691
  }
1632
1692
  async join() {
1633
1693
  const e = this.requireClient();
1634
- return this.joinPromise ? this.joinPromise : (this.state = "joining", this.emit("joining", {}), this.joinPromise = e.join().catch((t) => {
1635
- this.state = "failed";
1636
- const i = fe(t, "RTC connection failed");
1637
- throw this.emit("error", B(i)), i;
1694
+ if (this.joinPromise) return this.joinPromise;
1695
+ const t = this.state;
1696
+ return this.state = "joining", (this.joinStartedAt === null || t === "failed" || t === "disconnected") && (this.joinStartedAt = Date.now(), this.connectTimedOut = !1, this.lastConnectTimeout = null, this.mediaConnectedPeers.clear(), this.firstMediaConnectedElapsedMs = null, this.lastNegotiationPhase.clear(), this.armConnectTimeout()), this.emit("joining", {}), this.joinPromise = e.join().catch((i) => {
1697
+ this.state = "failed", this.clearConnectTimeout();
1698
+ const n = fe(i, "RTC connection failed");
1699
+ throw this.emit("error", V(n)), n;
1638
1700
  }).finally(() => {
1639
1701
  this.joinPromise = null;
1640
- }), this.joinPromise);
1702
+ }), this.joinPromise;
1641
1703
  }
1642
1704
  async leave() {
1643
- this.client && (await this.joinPromise?.catch(() => {
1644
- }), await this.sampleAndReport("final").catch(() => {
1645
- }), await this.client.leave(), this.state = "disconnected", this.stopStats());
1705
+ if (!this.client) return;
1706
+ this.clearConnectTimeout(), await this.joinPromise?.catch(() => {
1707
+ });
1708
+ const e = this.client.getPeerStates();
1709
+ await this.sampleAndReport("final", st(e)).catch(() => {
1710
+ }), await this.client.leave(), this.state = "disconnected", this.stopStats();
1711
+ }
1712
+ /** Where the session stands between `join()` and media. Lets a late `attachMediaClient` catch up. */
1713
+ getConnectPhase() {
1714
+ return {
1715
+ joinedAt: this.joinStartedAt === null ? null : new Date(this.joinStartedAt).toISOString(),
1716
+ mediaConnected: this.mediaConnectedPeers.size > 0,
1717
+ mediaConnectedElapsedMs: this.firstMediaConnectedElapsedMs,
1718
+ timedOut: this.connectTimedOut,
1719
+ lastConnectTimeout: this.lastConnectTimeout
1720
+ };
1646
1721
  }
1647
1722
  async setMuted(e) {
1648
1723
  this.requireClient().setMuted(e), this.emit("mediachange", { kind: "audio", enabled: !e });
@@ -1785,13 +1860,21 @@ class Ge extends EventTarget {
1785
1860
  }).catch(() => {
1786
1861
  }), d;
1787
1862
  }
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;
1790
- i?.counters && (this.previousCounters = i.counters);
1791
- const s = { ...{
1863
+ /**
1864
+ * `connectionState` names the state to report instead of reading it from the
1865
+ * connection — `leave()` passes the snapshot it took before closing anything.
1866
+ */
1867
+ async collectQualitySample(e = "runtime", t) {
1868
+ const i = this.options.statsProvider ? await this.options.statsProvider() : {}, n = this.peerConnection ? await nt(this.peerConnection, this.previousCounters) : null;
1869
+ n?.counters && (this.previousCounters = n.counters);
1870
+ const o = { ...{
1792
1871
  sampleType: e,
1793
1872
  sampledAt: (/* @__PURE__ */ new Date()).toISOString(),
1794
- connectionState: this.peerConnection?.connectionState ?? this.state,
1873
+ connectionState: t ?? this.peerConnection?.connectionState ?? this.state,
1874
+ statsTransport: this.client ? this.transport : null,
1875
+ statsConnectionCount: this.client ? this.client.getPeerStates().length : null,
1876
+ statsRowCount: n?.rowCount ?? null,
1877
+ statsRowTypes: n?.rowTypes ?? null,
1795
1878
  mediaScore: this.latestMediaScore,
1796
1879
  networkScore: this.latestMediaScore == null ? 5 : Math.max(0, Math.min(5, Math.round(this.latestMediaScore))),
1797
1880
  estimatedMos: 4.5,
@@ -1819,10 +1902,10 @@ class Ge extends EventTarget {
1819
1902
  transportProtocol: null,
1820
1903
  candidateType: null,
1821
1904
  qualityLimitationReason: null,
1822
- deviceMemoryGb: it(),
1905
+ deviceMemoryGb: rt(),
1823
1906
  effectiveConnectionType: me()?.effectiveType ?? null
1824
- }, ...i?.sample ?? {}, ...t, sampleType: e, sampledAt: (/* @__PURE__ */ new Date()).toISOString() }, o = tt(s);
1825
- return s.networkScore = o.networkScore, s.estimatedMos = o.estimatedMos, this.latestQuality = s, s;
1907
+ }, ...n?.sample ?? {}, ...i, sampleType: e, sampledAt: (/* @__PURE__ */ new Date()).toISOString() }, r = at(o);
1908
+ return o.networkScore = r.networkScore, o.estimatedMos = r.estimatedMos, this.latestQuality = o, o;
1826
1909
  }
1827
1910
  async reportQuality(e) {
1828
1911
  const t = this.options.credential.telemetryToken, i = this.options.telemetryEndpoint ?? this.options.credential.telemetryEndpoint;
@@ -1853,29 +1936,82 @@ class Ge extends EventTarget {
1853
1936
  return this.client;
1854
1937
  }
1855
1938
  destroy() {
1856
- this.stopStats(), this.client && this.client.leave(), this.client = null, this.joinPromise = null, this.peerConnection = null, this.state = "disconnected";
1939
+ this.stopStats(), this.clearConnectTimeout(), this.client && this.client.leave(), this.client = null, this.joinPromise = null, this.peerConnection = null, this.state = "disconnected";
1857
1940
  }
1858
1941
  handleNativeEvent(e, t) {
1859
- 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);
1942
+ if (e === "connected")
1943
+ this.state = "connected", this.peerConnection = this.client?.getPrimaryPeerConnection() ?? null;
1944
+ else if (e === "disconnected") this.state = "disconnected";
1945
+ else if (e === "peerconnectionstate") {
1946
+ this.peerConnection = this.client?.getPrimaryPeerConnection() ?? null;
1947
+ const { participant: i, state: n } = t;
1948
+ n === "connected" && i?.participantId && this.markMediaConnected(i.participantId);
1949
+ } else if (e === "remotestream") {
1950
+ const { participant: i } = t;
1951
+ i?.participantId && this.markMediaConnected(i.participantId);
1952
+ } else if (e === "negotiation") {
1953
+ const i = t;
1954
+ this.lastNegotiationPhase.set(i.participantId, i.phase), this.emit("negotiation", { ...i, at: this.elapsedSinceJoin() });
1955
+ return;
1956
+ }
1957
+ this.emit(e, t);
1958
+ }
1959
+ elapsedSinceJoin() {
1960
+ return this.joinStartedAt === null ? 0 : Math.max(0, Date.now() - this.joinStartedAt);
1961
+ }
1962
+ /** Fires `mediaconnected` once per peer, and disarms the connect watchdog on the first. */
1963
+ markMediaConnected(e) {
1964
+ if (this.mediaConnectedPeers.has(e)) return;
1965
+ this.mediaConnectedPeers.add(e);
1966
+ const t = this.mediaConnectedPeers.size === 1, i = this.elapsedSinceJoin();
1967
+ t && (this.firstMediaConnectedElapsedMs = i, this.clearConnectTimeout()), this.emit("mediaconnected", { participantId: e, elapsedMs: i, first: t });
1968
+ }
1969
+ armConnectTimeout() {
1970
+ this.clearConnectTimeout();
1971
+ const e = this.options.connectTimeoutMs ?? Ye;
1972
+ !(e > 0) || this.connectTimedOut || (this.connectTimer = setTimeout(() => {
1973
+ if (this.connectTimer = null, this.mediaConnectedPeers.size > 0 || this.connectTimedOut || !this.client) return;
1974
+ this.connectTimedOut = !0;
1975
+ const t = this.client.getPeerStates().map((n) => ({
1976
+ ...n,
1977
+ lastNegotiationPhase: this.lastNegotiationPhase.get(n.participantId) ?? null
1978
+ })), i = {
1979
+ elapsedMs: this.elapsedSinceJoin(),
1980
+ peers: t,
1981
+ participantCount: this.client.getParticipants().length + 1
1982
+ };
1983
+ this.lastConnectTimeout = i, this.emit("connecttimeout", i);
1984
+ }, e));
1985
+ }
1986
+ clearConnectTimeout() {
1987
+ this.connectTimer !== null && clearTimeout(this.connectTimer), this.connectTimer = null;
1860
1988
  }
1861
1989
  startStats() {
1862
1990
  this.stopStats(), this.statsTimer = setInterval(() => {
1863
1991
  this.sampleAndReport("runtime");
1864
1992
  }, this.options.statsInterval);
1865
1993
  }
1866
- async sampleAndReport(e) {
1867
- if (!this.statsRunning) {
1868
- this.statsRunning = !0;
1994
+ async sampleAndReport(e, t) {
1995
+ if (this.statsTask) {
1996
+ if (e === "runtime") return;
1997
+ await this.statsTask.catch(() => {
1998
+ });
1999
+ }
2000
+ const i = (async () => {
1869
2001
  try {
1870
- const t = await this.collectQualitySample(e);
1871
- await this.adaptQuality(t), this.emit("stats", { ...this.getStats(), latestQuality: t });
1872
- const i = Date.now(), n = e !== "runtime" || i - this.lastTelemetryAt >= this.options.telemetryInterval;
1873
- this.options.autoReportStats && n && (await this.reportQuality(t), this.lastTelemetryAt = i);
1874
- } catch (t) {
1875
- this.emit("telemetryerror", B(t));
1876
- } finally {
1877
- this.statsRunning = !1;
2002
+ const n = await this.collectQualitySample(e, t);
2003
+ await this.adaptQuality(n), this.emit("stats", { ...this.getStats(), latestQuality: n });
2004
+ const s = Date.now(), o = e !== "runtime" || s - this.lastTelemetryAt >= this.options.telemetryInterval;
2005
+ this.options.autoReportStats && o && (await this.reportQuality(n), this.lastTelemetryAt = s);
2006
+ } catch (n) {
2007
+ this.emit("telemetryerror", V(n));
1878
2008
  }
2009
+ })();
2010
+ this.statsTask = i;
2011
+ try {
2012
+ await i;
2013
+ } finally {
2014
+ this.statsTask === i && (this.statsTask = null);
1879
2015
  }
1880
2016
  }
1881
2017
  stopStats() {
@@ -1906,21 +2042,21 @@ class Ge extends EventTarget {
1906
2042
  this.dispatchEvent(new CustomEvent(e, { detail: i })), this.options.onEvent?.(i);
1907
2043
  }
1908
2044
  }
1909
- async function ft(a) {
1910
- return new Ge(a).initialize();
2045
+ async function St(a) {
2046
+ return new Ke(a).initialize();
1911
2047
  }
1912
- let _ = null;
1913
- function Me() {
1914
- if (_) return _;
2048
+ let z = null;
2049
+ function Ie() {
2050
+ if (z) return z;
1915
2051
  const a = globalThis, e = a.AudioContext ?? a.webkitAudioContext;
1916
- return e ? (_ = new e(), _) : null;
2052
+ return e ? (z = new e(), z) : null;
1917
2053
  }
1918
- async function Ye() {
1919
- const a = Me();
2054
+ async function Xe() {
2055
+ const a = Ie();
1920
2056
  return a ? (a.state === "suspended" && await a.resume().catch(() => {
1921
2057
  }), a.state === "running") : !1;
1922
2058
  }
1923
- class Ke {
2059
+ class Ze {
1924
2060
  options;
1925
2061
  media = null;
1926
2062
  cadenceTimer = null;
@@ -1929,7 +2065,7 @@ class Ke {
1929
2065
  this.options = e;
1930
2066
  }
1931
2067
  unlock() {
1932
- return Ye();
2068
+ return Xe();
1933
2069
  }
1934
2070
  startIncoming() {
1935
2071
  this.start("incoming", this.options.ringtoneUrl);
@@ -1957,7 +2093,7 @@ class Ke {
1957
2093
  i(), this.cadenceTimer = setInterval(i, e === "incoming" ? 3e3 : 3500);
1958
2094
  }
1959
2095
  playBuiltInTone(e) {
1960
- const t = Me();
2096
+ const t = Ie();
1961
2097
  if (!t || t.state !== "running") return;
1962
2098
  const i = e === "incoming" ? 1.25 : 1, n = e === "incoming" ? [440, 480] : [425], s = t.createGain(), o = t.currentTime;
1963
2099
  s.gain.setValueAtTime(1e-4, o), s.gain.exponentialRampToValueAtTime(0.045, o + 0.025), s.gain.setValueAtTime(0.045, o + i - 0.06), s.gain.exponentialRampToValueAtTime(1e-4, o + i), s.connect(t.destination);
@@ -1967,7 +2103,8 @@ class Ke {
1967
2103
  }
1968
2104
  }
1969
2105
  }
1970
- class Xe extends EventTarget {
2106
+ const et = 6e4;
2107
+ class tt extends EventTarget {
1971
2108
  options;
1972
2109
  socket = null;
1973
2110
  stopped = !1;
@@ -1985,17 +2122,20 @@ class Xe extends EventTarget {
1985
2122
  currentTokenExpiresAt;
1986
2123
  callAudio;
1987
2124
  mediaClients = /* @__PURE__ */ new Map();
2125
+ mediaSubscriptions = /* @__PURE__ */ new Map();
1988
2126
  activeCallIds = /* @__PURE__ */ new Set();
1989
2127
  seenInvitationIds = /* @__PURE__ */ new Set();
1990
2128
  invitationExpiryTimers = /* @__PURE__ */ new Map();
1991
2129
  startCallRequest = null;
2130
+ /** Bumped by disconnect() so a request still in flight cannot re-claim the call slot it just freed. */
2131
+ callGeneration = 0;
1992
2132
  lastDisconnect = null;
1993
2133
  constructor(e) {
1994
2134
  if (super(), !e.token && !e.tokenProvider) throw new Error("A client token or tokenProvider is required");
1995
2135
  if (e.token && !e.token.startsWith("rtcc_")) throw new Error("A valid RTC client token is required");
1996
2136
  if (!e.signalingUrl && !e.callActionsUrl && !e.apiBaseUrl && !globalThis.location?.origin)
1997
2137
  throw new Error("signalingUrl or callActionsUrl is required outside a browser");
1998
- this.options = e, this.currentToken = e.token ?? null, this.currentTokenExpiresAt = e.tokenExpiresAt, this.callAudio = new Ke(e), this.mount = de(e.mount), this.scheduleTokenRefresh();
2138
+ this.options = e, this.currentToken = e.token ?? null, this.currentTokenExpiresAt = e.tokenExpiresAt, this.callAudio = new Ze(e), this.mount = de(e.mount), this.scheduleTokenRefresh();
1999
2139
  }
2000
2140
  connect() {
2001
2141
  if (!this.options.signalingUrl) throw new Error("signalingUrl is required to connect signaling");
@@ -2004,12 +2144,47 @@ class Xe extends EventTarget {
2004
2144
  setMount(e) {
2005
2145
  this.clearUi(), this.mount = de(e), this.currentCall && this.renderIncoming(this.currentCall);
2006
2146
  }
2007
- /** Binds media lifetime to a call. Terminal call states automatically leave and destroy it. */
2147
+ /**
2148
+ * Binds media lifetime to a call. Terminal call states automatically leave
2149
+ * and destroy it. The call watchdog keeps running until the media client
2150
+ * reports `mediaconnected`; if it expires first, `callexpired` fires and the
2151
+ * call — media included — is released. The media client's join, media
2152
+ * establishment and connect timeout are reported to the platform through
2153
+ * the same bounded diagnostics channel as signaling outages.
2154
+ */
2008
2155
  attachMediaClient(e, t) {
2009
2156
  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);
2157
+ this.mediaSubscriptions.get(e)?.(), this.mediaClients.set(e, t);
2158
+ const i = (c) => {
2159
+ c.detail?.detail?.resumed || this.reportSignalingDiagnostic({ event: "media_joined", callId: e });
2160
+ }, n = (c) => {
2161
+ const l = c.detail.detail;
2162
+ l.first && (this.clearInvitationExpiry(e), this.reportSignalingDiagnostic({ event: "media_connected", callId: e, elapsedMs: l.elapsedMs }));
2163
+ }, s = (c) => {
2164
+ const l = c.detail.detail;
2165
+ this.reportSignalingDiagnostic({ event: "media_timeout", callId: e, ...l });
2166
+ };
2167
+ t.addEventListener("connected", i), t.addEventListener("mediaconnected", n), t.addEventListener("connecttimeout", s);
2168
+ const o = () => {
2169
+ t.removeEventListener("connected", i), t.removeEventListener("mediaconnected", n), t.removeEventListener("connecttimeout", s), this.mediaSubscriptions.get(e) === o && this.mediaSubscriptions.delete(e);
2012
2170
  };
2171
+ this.mediaSubscriptions.set(e, o);
2172
+ const r = t.getConnectPhase();
2173
+ return t.getStats().state === "connected" && this.reportSignalingDiagnostic({ event: "media_joined", callId: e }), r.mediaConnected ? (this.clearInvitationExpiry(e), this.reportSignalingDiagnostic({ event: "media_connected", callId: e, elapsedMs: r.mediaConnectedElapsedMs ?? 0 })) : r.timedOut && r.lastConnectTimeout && this.reportSignalingDiagnostic({ event: "media_timeout", callId: e, ...r.lastConnectTimeout }), () => {
2174
+ this.mediaClients.get(e) === t && (o(), this.mediaClients.delete(e), this.activeCallIds.has(e) && this.armCallWatchdog(e));
2175
+ };
2176
+ }
2177
+ /** True once the media client bound to this call has reached media on at least one peer. */
2178
+ mediaEstablished(e) {
2179
+ return this.mediaClients.get(e)?.getConnectPhase().mediaConnected === !0;
2180
+ }
2181
+ /**
2182
+ * Arms the self-release timer for a call that holds the single call slot but
2183
+ * has no media attached. Every path that claims the slot arms this, so no
2184
+ * failure can hold it for the life of the page.
2185
+ */
2186
+ armCallWatchdog(e) {
2187
+ this.scheduleInvitationExpiry(e, new Date(Date.now() + et).toISOString());
2013
2188
  }
2014
2189
  /**
2015
2190
  * Rings everyone in `targets` as a single call and connects whoever answers
@@ -2021,51 +2196,77 @@ class Xe extends EventTarget {
2021
2196
  if (this.activeCallIds.size > 0) throw new Error("Another call is already active");
2022
2197
  const t = e.targets?.length ? e.targets : e.target ? [e.target] : [];
2023
2198
  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 () => {
2199
+ const i = { mode: e.mode, targets: t, metadata: e.metadata, idempotencyKey: e.idempotencyKey }, n = this.callGeneration, s = (async () => {
2025
2200
  await this.callAudio.unlock(), this.callAudio.startRingback();
2026
- let s;
2201
+ let o;
2027
2202
  try {
2028
- s = await this.request("/v1/rtc/client/calls", {
2203
+ o = await this.request("/v1/rtc/client/calls", {
2029
2204
  method: "POST",
2030
2205
  headers: e.idempotencyKey ? { "idempotency-key": e.idempotencyKey } : void 0,
2031
2206
  body: JSON.stringify(i)
2032
2207
  });
2033
- } catch (o) {
2034
- throw this.callAudio.stop(), o;
2208
+ } catch (c) {
2209
+ throw this.callAudio.stop(), c;
2035
2210
  }
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;
2211
+ !o.credential && o.callerCredential && (o.credential = o.callerCredential);
2212
+ const r = String(o.call?.id ?? "") || null;
2213
+ if (r && n === this.callGeneration) {
2214
+ this.outgoingCallId = r, this.activeCallIds.add(r);
2215
+ const c = o.call?.expiresAt;
2216
+ this.scheduleInvitationExpiry(r, typeof c == "string" ? c : void 0);
2217
+ } else r && this.callAction(r, "cancel").catch(() => {
2218
+ });
2219
+ return this.emit("outgoingcall", o), o;
2037
2220
  })();
2038
- this.startCallRequest = n;
2221
+ this.startCallRequest = s;
2039
2222
  try {
2040
- return await n;
2223
+ return await s;
2041
2224
  } finally {
2042
- this.startCallRequest === n && (this.startCallRequest = null);
2225
+ this.startCallRequest === s && (this.startCallRequest = null);
2043
2226
  }
2044
2227
  }
2045
2228
  async accept(e = this.currentCall?.id) {
2046
2229
  if (!e) throw new Error("No incoming call is available");
2047
2230
  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;
2231
+ return this.activeCallIds.add(e), this.armCallWatchdog(e), this.callAudio.stop(), this.dismiss(e), this.emit("callaccepted", t), t;
2049
2232
  }
2233
+ // Local release runs in a finally on all four: it must never depend on a
2234
+ // network round trip. A hang-up while offline, or after the server already
2235
+ // swept the call terminal (which answers 409), used to leave the call id in
2236
+ // the active set for the life of the page, and startCall() refuses while any
2237
+ // id is held.
2050
2238
  async reject(e = this.currentCall?.id) {
2051
2239
  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;
2240
+ let t;
2241
+ try {
2242
+ return t = await this.callAction(e, "reject");
2243
+ } finally {
2244
+ this.releaseMedia(e), this.showTerminal(e, "Call declined"), this.emit("callrejected", t ?? { call: { id: e } });
2245
+ }
2054
2246
  }
2055
2247
  async busy(e = this.currentCall?.id) {
2056
2248
  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;
2249
+ try {
2250
+ return await this.callAction(e, "busy");
2251
+ } finally {
2252
+ this.releaseMedia(e), this.showTerminal(e, "Line busy");
2253
+ }
2059
2254
  }
2060
2255
  async end(e) {
2061
2256
  this.callAudio.stop();
2062
- const t = await this.callAction(e, "end");
2063
- return this.releaseMedia(e), this.showTerminal(e, "Call ended"), t;
2257
+ try {
2258
+ return await this.callAction(e, "end");
2259
+ } finally {
2260
+ this.releaseMedia(e), this.showTerminal(e, "Call ended");
2261
+ }
2064
2262
  }
2065
2263
  async cancel(e) {
2066
2264
  this.callAudio.stop();
2067
- const t = await this.callAction(e, "cancel");
2068
- return this.releaseMedia(e), this.showTerminal(e, "Call cancelled"), t;
2265
+ try {
2266
+ return await this.callAction(e, "cancel");
2267
+ } finally {
2268
+ this.releaseMedia(e), this.showTerminal(e, "Call cancelled");
2269
+ }
2069
2270
  }
2070
2271
  /** Injects an incoming call received through the application's own push or signaling channel. */
2071
2272
  handleIncomingCall(e) {
@@ -2093,7 +2294,7 @@ class Xe extends EventTarget {
2093
2294
  scheduleInvitationExpiry(e, t) {
2094
2295
  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));
2095
2296
  this.clearInvitationExpiry(e), this.invitationExpiryTimers.set(e, setTimeout(() => {
2096
- this.invitationExpiryTimers.delete(e), !(!this.activeCallIds.has(e) || this.mediaClients.has(e)) && (this.emit("callexpired", { id: e, expiresAt: t ?? null }), this.release(e));
2297
+ this.invitationExpiryTimers.delete(e), !(!this.activeCallIds.has(e) || this.mediaEstablished(e)) && (this.emit("callexpired", { id: e, expiresAt: t ?? null }), this.release(e));
2097
2298
  }, n));
2098
2299
  }
2099
2300
  clearInvitationExpiry(e) {
@@ -2113,13 +2314,13 @@ class Xe extends EventTarget {
2113
2314
  /** Applies a call state received through the application's own push or signaling channel. */
2114
2315
  handleCallUpdate(e) {
2115
2316
  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);
2317
+ i.includes(e.status) && this.clearInvitationExpiry(e.id), e.status === "accepted" && (this.mediaEstablished(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, it(e.status)), i.includes(e.status) && this.releaseMedia(e.id), this.emit("callupdated", e);
2117
2318
  }
2118
2319
  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();
2320
+ 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
2321
  for (const e of this.invitationExpiryTimers.values()) clearTimeout(e);
2121
2322
  this.invitationExpiryTimers.clear();
2122
- for (const e of this.mediaClients.keys()) this.releaseMedia(e);
2323
+ for (const e of [...this.mediaClients.keys()]) this.releaseMedia(e);
2123
2324
  this.activeCallIds.clear(), this.seenInvitationIds.clear();
2124
2325
  }
2125
2326
  destroy() {
@@ -2131,11 +2332,11 @@ class Xe extends EventTarget {
2131
2332
  try {
2132
2333
  t = await this.resolveToken(e, e === "reconnect");
2133
2334
  } catch (s) {
2134
- this.emit("tokenerror", B(s)), !this.stopped && this.options.reconnect !== !1 && this.scheduleReconnect();
2335
+ this.emit("tokenerror", V(s)), !this.stopped && this.options.reconnect !== !1 && this.scheduleReconnect();
2135
2336
  return;
2136
2337
  }
2137
2338
  if (this.stopped || this.socket) return;
2138
- const i = X(this.options.signalingUrl, globalThis.location?.href), n = new WebSocket(i, ["rtc-client", t]);
2339
+ const i = ee(this.options.signalingUrl, globalThis.location?.href), n = new WebSocket(i, ["rtc-client", t]);
2139
2340
  this.socket = n, n.addEventListener("open", () => {
2140
2341
  const s = this.reconnectAttempt > 0, o = this.lastDisconnect;
2141
2342
  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(() => {
@@ -2197,7 +2398,7 @@ class Xe extends EventTarget {
2197
2398
  async request(e, t, i = !1) {
2198
2399
  const n = this.options.callActionsUrl ?? this.options.apiBaseUrl ?? this.options.signalingUrl ?? globalThis.location?.origin;
2199
2400
  if (!n) throw new Error("callActionsUrl is required for call actions");
2200
- const s = Z(n, globalThis.location?.href), o = await this.resolveToken("initial"), r = new Headers(t.headers);
2401
+ const s = te(n, globalThis.location?.href), o = await this.resolveToken("initial"), r = new Headers(t.headers);
2201
2402
  r.set("authorization", `Bearer ${o}`), r.set("content-type", "application/json");
2202
2403
  const c = await fetch(new URL(e, s.origin), { ...t, headers: r }), l = await c.json().catch(() => ({}));
2203
2404
  if (c.status === 401 && this.options.tokenProvider && !i)
@@ -2231,9 +2432,9 @@ class Xe extends EventTarget {
2231
2432
  Object.assign(s.style, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px" });
2232
2433
  const o = he("Decline", "#fff", "#a32920", "#d9a09b"), r = he("Answer", "#fff", "#08745f", "#08745f");
2233
2434
  o.addEventListener("click", () => {
2234
- this.reject(e.id).catch((c) => this.emit("error", B(c)));
2435
+ this.reject(e.id).catch((c) => this.emit("error", V(c)));
2235
2436
  }), r.addEventListener("click", () => {
2236
- this.accept(e.id).catch((c) => this.emit("error", B(c)));
2437
+ this.accept(e.id).catch((c) => this.emit("error", V(c)));
2237
2438
  }), s.appendChild(o), s.appendChild(r), t.appendChild(i), t.appendChild(n), t.appendChild(s), this.mount.replaceChildren(t);
2238
2439
  }
2239
2440
  dismiss(e) {
@@ -2301,29 +2502,29 @@ class Xe extends EventTarget {
2301
2502
  const t = Math.max(0, e - this.refreshSkew(e));
2302
2503
  this.tokenRefreshTimer = setTimeout(() => {
2303
2504
  this.tokenRefreshTimer = null, this.resolveToken("expiring", !0).catch((i) => {
2304
- this.emit("tokenerror", B(i)), this.stopped || (this.tokenRefreshTimer = setTimeout(() => this.scheduleTokenRefresh(), 3e4));
2505
+ this.emit("tokenerror", V(i)), this.stopped || (this.tokenRefreshTimer = setTimeout(() => this.scheduleTokenRefresh(), 3e4));
2305
2506
  });
2306
2507
  }, Math.min(t, 2147e6));
2307
2508
  }
2308
2509
  releaseMedia(e) {
2309
- this.activeCallIds.delete(e), this.seenInvitationIds.delete(e);
2510
+ this.clearInvitationExpiry(e), this.activeCallIds.delete(e), this.seenInvitationIds.delete(e);
2310
2511
  const t = this.mediaClients.get(e);
2311
- t && (this.mediaClients.delete(e), t.leave().catch(() => {
2512
+ t && (this.mediaSubscriptions.get(e)?.(), this.mediaClients.delete(e), t.leave().catch(() => {
2312
2513
  }).finally(() => t.destroy()));
2313
2514
  }
2314
2515
  reportSignalingDiagnostic(e) {
2315
2516
  this.options.reportSignalingDiagnostics === !1 || this.stopped || this.request("/v1/rtc/client/signaling-events", {
2316
2517
  method: "POST",
2317
2518
  body: JSON.stringify({ ...e, occurredAt: (/* @__PURE__ */ new Date()).toISOString() })
2318
- }).catch((t) => this.emit("diagnosticserror", B(t)));
2519
+ }).catch((t) => this.emit("diagnosticserror", V(t)));
2319
2520
  }
2320
2521
  emit(e, t) {
2321
2522
  const i = { type: e, detail: t, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
2322
2523
  this.dispatchEvent(new CustomEvent(e, { detail: i })), this.options.onEvent?.(i);
2323
2524
  }
2324
2525
  }
2325
- function gt(a) {
2326
- const e = new Xe(a);
2526
+ function wt(a) {
2527
+ const e = new tt(a);
2327
2528
  return a.autoConnect !== !1 && a.signalingUrl && e.connect(), e;
2328
2529
  }
2329
2530
  function de(a) {
@@ -2342,7 +2543,7 @@ function he(a, e, t, i) {
2342
2543
  fontWeight: "700"
2343
2544
  }), n;
2344
2545
  }
2345
- function Ze(a) {
2546
+ function it(a) {
2346
2547
  return {
2347
2548
  rejected: "Call declined",
2348
2549
  busy: "Line busy",
@@ -2360,42 +2561,46 @@ function ue(a) {
2360
2561
  role: "participant"
2361
2562
  };
2362
2563
  }
2363
- async function et(a, e) {
2564
+ async function nt(a, e) {
2364
2565
  const t = await a.getStats(), i = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
2365
- 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
- t.forEach((g) => {
2367
- n.set(g.id, g), g.type === "codec" && i.set(g.id, String(g.mimeType ?? ""));
2368
- }), t.forEach((g) => {
2369
- 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
- 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;
2566
+ let s = 0, o = 0, r = 0, c = 0, l = 0, d = 0, u = 0, v = 0, p = 0, S = 0, b = 0, g = 0, k = 0, f = 0, I = 0, R = null, L = null, P = null, A = null, w = null, E = null;
2567
+ const m = /* @__PURE__ */ new Set();
2568
+ let h = 0;
2569
+ t.forEach((y) => {
2570
+ h += 1, m.add(String(y.type ?? "")), n.set(y.id, y), y.type === "codec" && i.set(y.id, String(y.mimeType ?? ""));
2571
+ }), t.forEach((y) => {
2572
+ if (y.type === "inbound-rtp" && !y.isRemote && (s += Number(y.bytesReceived ?? 0), r += Math.max(0, Number(y.packetsLost ?? 0)), c += Math.max(0, Number(y.packetsReceived ?? 0)), Number.isFinite(y.jitter) && (l += Number(y.jitter), d += 1), u = Math.max(u, Number(y.framesPerSecond ?? 0)), v = Math.max(v, Number(y.frameWidth ?? 0)), b = Math.max(b, Number(y.frameHeight ?? 0)), g += Number(y.framesDropped ?? 0), k += Number(y.freezeCount ?? 0), f += Number(y.totalFreezesDuration ?? 0), I += Number(y.concealedSamples ?? 0), w ||= i.get(String(y.codecId ?? "")) ?? null), y.type === "outbound-rtp" && !y.isRemote && (p = Math.max(p, Number(y.frameWidth ?? 0)), S = Math.max(S, Number(y.frameHeight ?? 0)), o += Number(y.bytesSent ?? 0), E ||= y.qualityLimitationReason ? String(y.qualityLimitationReason) : null, w ||= i.get(String(y.codecId ?? "")) ?? null), y.type === "candidate-pair" && (y.selected || y.nominated) && y.state === "succeeded") {
2573
+ R = Number.isFinite(y.currentRoundTripTime) ? Number(y.currentRoundTripTime) : R, L = Number.isFinite(y.availableOutgoingBitrate) ? Number(y.availableOutgoingBitrate) : L;
2574
+ const Q = n.get(y.localCandidateId);
2575
+ P = Q?.candidateType ? String(Q.candidateType) : P, A = Q?.protocol ? String(Q.protocol) : A;
2373
2576
  }
2374
2577
  });
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;
2578
+ const T = Date.now(), M = e ? Math.max(1e-3, (T - e.timestamp) / 1e3) : 0, C = e ? Math.max(0, Math.round((s - e.inboundBytes) * 8 / M)) : null, O = e ? Math.max(0, Math.round((o - e.outboundBytes) * 8 / M)) : null, _ = r + c, U = e ? Math.max(0, r - e.packetsLost) : r, F = e ? Math.max(0, c - e.packetsReceived) : c, q = U + F;
2376
2579
  return {
2377
- counters: { timestamp: m, inboundBytes: s, outboundBytes: o, packetsLost: r, packetsReceived: c },
2580
+ counters: { timestamp: T, inboundBytes: s, outboundBytes: o, packetsLost: r, packetsReceived: c },
2581
+ rowCount: h,
2582
+ rowTypes: [...m].filter(Boolean).sort().join(","),
2378
2583
  sample: {
2379
2584
  rttMs: R === null ? null : R * 1e3,
2380
2585
  jitterMs: d ? l / d * 1e3 : null,
2381
- packetLossPct: O ? N / O * 100 : null,
2382
- packetLossCumulativePct: C ? r / C * 100 : null,
2586
+ packetLossPct: q ? U / q * 100 : null,
2587
+ packetLossCumulativePct: _ ? r / _ * 100 : null,
2383
2588
  packetsLost: r,
2384
2589
  packetsReceived: c,
2385
- inboundBitrateBps: b,
2386
- outboundBitrateBps: I,
2590
+ inboundBitrateBps: C,
2591
+ outboundBitrateBps: O,
2387
2592
  availableOutgoingBitrateBps: L,
2388
2593
  framesPerSecond: u || null,
2389
2594
  frameWidth: v || null,
2390
- frameHeight: T || null,
2595
+ frameHeight: b || null,
2391
2596
  inboundFrameWidth: v || null,
2392
- inboundFrameHeight: T || null,
2597
+ inboundFrameHeight: b || null,
2393
2598
  outboundFrameWidth: p || null,
2394
2599
  outboundFrameHeight: S || null,
2395
- framesDropped: y,
2600
+ framesDropped: g,
2396
2601
  freezeCount: k,
2397
2602
  freezeDurationMs: Math.round(f * 1e3),
2398
- concealedSamples: M,
2603
+ concealedSamples: I,
2399
2604
  codec: w,
2400
2605
  transportProtocol: A,
2401
2606
  candidateType: P,
@@ -2403,64 +2608,69 @@ async function et(a, e) {
2403
2608
  }
2404
2609
  };
2405
2610
  }
2406
- function tt(a) {
2611
+ function at(a) {
2407
2612
  let e = a.mediaScore == null ? 5 : Math.round(pe(Number(a.mediaScore), 0, 5));
2408
- const t = Y(a.packetLossPct) ?? 0, i = Y(a.rttMs) ?? 0, n = Y(a.jitterMs) ?? 0, s = String(a.connectionState ?? "unknown").toLowerCase();
2613
+ const t = X(a.packetLossPct) ?? 0, i = X(a.rttMs) ?? 0, n = X(a.jitterMs) ?? 0, s = String(a.connectionState ?? "unknown").toLowerCase();
2409
2614
  ["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));
2410
2615
  const o = Number(pe(4.5 - t * 0.09 - i * 11e-4 - n * 6e-3, 1, 4.5).toFixed(2));
2411
2616
  return { networkScore: e, estimatedMos: o };
2412
2617
  }
2618
+ function st(a) {
2619
+ const e = ["failed", "disconnected", "closed", "checking", "connecting", "new", "connected"], t = a.map((i) => i.connectionState);
2620
+ return e.find((i) => t.includes(i)) ?? t[0];
2621
+ }
2413
2622
  function me() {
2414
2623
  return navigator.connection ?? null;
2415
2624
  }
2416
- function it() {
2625
+ function rt() {
2417
2626
  const a = Number(navigator.deviceMemory);
2418
2627
  return Number.isFinite(a) ? a : null;
2419
2628
  }
2420
- function Y(a) {
2629
+ function X(a) {
2421
2630
  const e = Number(a);
2422
2631
  return Number.isFinite(e) ? e : null;
2423
2632
  }
2424
2633
  function pe(a, e, t) {
2425
2634
  return Math.min(t, Math.max(e, a));
2426
2635
  }
2427
- function B(a) {
2636
+ function V(a) {
2428
2637
  return a instanceof Error ? { name: a.name, message: a.message } : { name: "ServiceError", message: String(a ?? "Service request failed") };
2429
2638
  }
2430
2639
  function fe(a, e) {
2431
2640
  return a instanceof Error ? a : new Error(e);
2432
2641
  }
2433
- const nt = "1.3.25";
2642
+ const ot = "1.5.0";
2434
2643
  export {
2435
- _e as AVATAR_MODEL_MAX_BYTES,
2644
+ Fe as AVATAR_MODEL_MAX_BYTES,
2436
2645
  oe as AVATAR_TUNING_DEFAULTS,
2437
2646
  x as AvatarModelError,
2438
- Ne as BACKGROUND_PRESETS,
2647
+ Ue as BACKGROUND_PRESETS,
2648
+ Ye as DEFAULT_CONNECT_TIMEOUT_MS,
2439
2649
  qe as OVERLAY_EFFECTS,
2440
- 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,
2450
- je as drawAvatar,
2451
- Oe as headPoseFromMatrix,
2452
- ht as importAvatarModel,
2453
- dt as listArchiveModels,
2454
- ut as listAvatarModels,
2455
- ve as neutralRig,
2650
+ Ke as RtcClient,
2651
+ tt as RtcIncomingClient,
2652
+ ct as SPEAKER_NOISE_FLOOR,
2653
+ lt as SPEAKER_TAKEOVER_RATIO,
2654
+ mt as createAvatarPipeline,
2655
+ ht as createCallRecorder,
2656
+ dt as createPictureInPictureController,
2657
+ St as createRtcClient,
2658
+ wt as createRtcIncomingClient,
2659
+ ut as createVideoEffectsPipeline,
2660
+ Ve as drawAvatar,
2661
+ je as headPoseFromMatrix,
2662
+ ft as importAvatarModel,
2663
+ pt as listArchiveModels,
2664
+ gt as listAvatarModels,
2665
+ Se as neutralRig,
2456
2666
  ge as pickActiveSpeaker,
2457
- mt as readAvatarModel,
2667
+ yt as readAvatarModel,
2458
2668
  $e as readAvatarModelMetadata,
2459
- pt as removeAvatarModel,
2460
- Ve as rigFromBlendshapes,
2669
+ vt as removeAvatarModel,
2670
+ Be as rigFromBlendshapes,
2461
2671
  se as scenePainters,
2462
2672
  re as smoothRig,
2463
2673
  ce as stylizedAvatarRenderer,
2464
- Ye as unlockRtcCallAudio,
2465
- nt as version
2674
+ Xe as unlockRtcCallAudio,
2675
+ ot as version
2466
2676
  };