@streaming-cdn/rtc-web 1.4.0 → 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 st = 0.02, rt = 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 ?? Ne(),
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 Ne() {
996
+ function Oe() {
949
997
  return globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
950
998
  }
951
- function ot(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 ot(a, e = {}) {
976
1024
  }
977
1025
  };
978
1026
  }
979
- function ct(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 ct(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((N) => ({ key: N.participant.participantId, stream: N.stream, label: N.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 U = Math.max(1, Math.ceil(Math.sqrt(C.length))), Q = Math.max(1, Math.ceil(C.length / U)), q = r / U, g = o / Q;
1015
- C.forEach((N, ee) => {
1016
- const te = R(N.key, N.stream), W = ee % U * q, $ = Math.floor(ee / U) * g;
1017
- te.videoWidth > 0 && f.drawImage(te, W + 2, $ + 2, q - 4, g - 4), f.fillStyle = "rgba(0,0,0,.6)", f.fillRect(W + 8, $ + g - 30, Math.min(q - 16, 12 + N.label.length * 9), 22), f.fillStyle = "#fff", f.font = "14px system-ui", f.fillText(N.label, W + 14, $ + g - 14);
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 ct(a, e) {
1064
1112
  }
1065
1113
  };
1066
1114
  }
1067
- const Ue = ["none", "blur", "aurora", "cybergrid", "bokeh", "sunset", "image"], Oe = ["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 Ue = ["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 lt(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 lt(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 lt(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,7 +1204,7 @@ function lt(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,
@@ -1164,7 +1212,7 @@ function lt(a, e = {}) {
1164
1212
  c = Ue.includes(m) ? m : "none", h !== void 0 && (d = h), c === "none" && (v = null);
1165
1213
  },
1166
1214
  setOverlay(m) {
1167
- l = Oe.includes(m) ? m : "none";
1215
+ l = qe.includes(m) ? m : "none";
1168
1216
  },
1169
1217
  setSegmenter(m) {
1170
1218
  u = m, m || (v = null);
@@ -1176,7 +1224,7 @@ function lt(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 qe(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 = qe(e), n = (D(a, "eyeLookOutRight") + D(a, "eyeLookInLeft")) / 2, s = (D(a, "eyeLookOutLeft") + D(a, "eyeLookInRight")) / 2, o = (D(a, "eyeLookUpLeft") + D(a, "eyeLookUpRight")) / 2, r = (D(a, "eyeLookDownLeft") + D(a, "eyeLookDownRight")) / 2, c = (D(a, "mouthSmileLeft") + D(a, "mouthSmileRight")) / 2, l = (D(a, "mouthFrownLeft") + D(a, "mouthFrownRight")) / 2, d = D(a, "eyeBlinkLeft"), u = D(a, "eyeBlinkRight"), v = Math.min(1, Math.abs(i.yaw) / 0.35) * Math.max(0, Math.min(1, t)), p = (d + u) / 2;
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 U = -r * 0.18 + p;
1253
- a.fillStyle = "#ffffff", a.beginPath(), a.ellipse(m, U, r * 0.16 * h, r * 0.12 * I, 0, 0, Math.PI * 2), a.fill(), a.fillStyle = "#22303c", a.beginPath(), a.ellipse(
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
- U + 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 dt(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 dt(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, U = Math.hypot((m.x - E.x) * i, (m.y - E.y) * n), Q = Math.max(0.6, Math.cos(k.headPitch)), q = { x: I, y: C, radius: Math.max(24, U / Q * 0.8) };
1306
- f = f ? { x: f.x + (q.x - f.x) * 0.4, y: f.y + (q.y - f.y) * 0.4, radius: f.radius + (q.radius - f.radius) * 0.15 } : q;
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 dt(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 dt(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 ht(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 O(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
- function We(a) {
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 We(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: O(o.name),
1508
+ title: N(o.name),
1461
1509
  author: r.length ? r.join(", ") : null,
1462
- allowedUser: O(o.avatarPermission),
1463
- commercialUse: O(o.commercialUsage),
1464
- licenseName: O(o.licenseUrl) ? "VRM 1.0" : null,
1465
- licenseUrl: O(o.licenseUrl) ?? O(o.otherLicenseUrl)
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: O(s?.title),
1470
- author: O(s?.author),
1471
- allowedUser: O(s?.allowedUserName),
1472
- commercialUse: O(s?.commercialUssageName),
1473
- licenseName: O(s?.licenseName),
1474
- licenseUrl: O(s?.otherLicenseUrl)
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 $e() {
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 $e();
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 ut(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)
@@ -1516,7 +1564,7 @@ async function ut(a, e = {}) {
1516
1564
  "model_too_large",
1517
1565
  "The model is larger than the " + Math.round(t / 1024 / 1024) + " MB limit."
1518
1566
  );
1519
- const o = We(s);
1567
+ const o = $e(s);
1520
1568
  if (!o)
1521
1569
  throw new x("model_not_vrm", "The file is a glTF model but carries no VRM character data.");
1522
1570
  const r = o.title || (n ? n.replace(/\.(vrm|glb)$/i, "") : null), c = {
@@ -1527,14 +1575,14 @@ async function ut(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 mt() {
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 pt(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 ft(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: at }), 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 tt(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: nt(),
1905
+ deviceMemoryGb: rt(),
1823
1906
  effectiveConnectionType: me()?.effectiveType ?? null
1824
- }, ...i?.sample ?? {}, ...t, sampleType: e, sampledAt: (/* @__PURE__ */ new Date()).toISOString() }, o = it(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 gt(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,8 +2103,8 @@ class Ke {
1967
2103
  }
1968
2104
  }
1969
2105
  }
1970
- const Xe = 6e4;
1971
- class Ze extends EventTarget {
2106
+ const et = 6e4;
2107
+ class tt extends EventTarget {
1972
2108
  options;
1973
2109
  socket = null;
1974
2110
  stopped = !1;
@@ -1986,6 +2122,7 @@ class Ze extends EventTarget {
1986
2122
  currentTokenExpiresAt;
1987
2123
  callAudio;
1988
2124
  mediaClients = /* @__PURE__ */ new Map();
2125
+ mediaSubscriptions = /* @__PURE__ */ new Map();
1989
2126
  activeCallIds = /* @__PURE__ */ new Set();
1990
2127
  seenInvitationIds = /* @__PURE__ */ new Set();
1991
2128
  invitationExpiryTimers = /* @__PURE__ */ new Map();
@@ -1998,7 +2135,7 @@ class Ze extends EventTarget {
1998
2135
  if (e.token && !e.token.startsWith("rtcc_")) throw new Error("A valid RTC client token is required");
1999
2136
  if (!e.signalingUrl && !e.callActionsUrl && !e.apiBaseUrl && !globalThis.location?.origin)
2000
2137
  throw new Error("signalingUrl or callActionsUrl is required outside a browser");
2001
- 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();
2002
2139
  }
2003
2140
  connect() {
2004
2141
  if (!this.options.signalingUrl) throw new Error("signalingUrl is required to connect signaling");
@@ -2007,12 +2144,39 @@ class Ze extends EventTarget {
2007
2144
  setMount(e) {
2008
2145
  this.clearUi(), this.mount = de(e), this.currentCall && this.renderIncoming(this.currentCall);
2009
2146
  }
2010
- /** 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
+ */
2011
2155
  attachMediaClient(e, t) {
2012
2156
  if (!e.trim()) throw new Error("callId is required");
2013
- return this.mediaClients.set(e, t), this.clearInvitationExpiry(e), () => {
2014
- this.mediaClients.get(e) === t && (this.mediaClients.delete(e), this.activeCallIds.has(e) && this.armCallWatchdog(e));
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 });
2015
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);
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;
2016
2180
  }
2017
2181
  /**
2018
2182
  * Arms the self-release timer for a call that holds the single call slot but
@@ -2020,7 +2184,7 @@ class Ze extends EventTarget {
2020
2184
  * failure can hold it for the life of the page.
2021
2185
  */
2022
2186
  armCallWatchdog(e) {
2023
- this.scheduleInvitationExpiry(e, new Date(Date.now() + Xe).toISOString());
2187
+ this.scheduleInvitationExpiry(e, new Date(Date.now() + et).toISOString());
2024
2188
  }
2025
2189
  /**
2026
2190
  * Rings everyone in `targets` as a single call and connects whoever answers
@@ -2130,7 +2294,7 @@ class Ze extends EventTarget {
2130
2294
  scheduleInvitationExpiry(e, t) {
2131
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));
2132
2296
  this.clearInvitationExpiry(e), this.invitationExpiryTimers.set(e, setTimeout(() => {
2133
- 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));
2134
2298
  }, n));
2135
2299
  }
2136
2300
  clearInvitationExpiry(e) {
@@ -2150,13 +2314,13 @@ class Ze extends EventTarget {
2150
2314
  /** Applies a call state received through the application's own push or signaling channel. */
2151
2315
  handleCallUpdate(e) {
2152
2316
  const t = e.id === this.currentCall?.id || e.id === this.outgoingCallId, i = ["answered_elsewhere", "rejected", "busy", "cancelled", "ended", "failed", "missed"];
2153
- i.includes(e.status) && this.clearInvitationExpiry(e.id), e.status === "accepted" && (this.mediaClients.has(e.id) ? this.clearInvitationExpiry(e.id) : this.armCallWatchdog(e.id)), t && e.status === "accepted" ? (this.activeCallIds.add(e.id), this.callAudio.stop(), this.dismiss(e.id)) : t && i.includes(e.status) && this.showTerminal(e.id, et(e.status)), i.includes(e.status) && this.releaseMedia(e.id), this.emit("callupdated", e);
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);
2154
2318
  }
2155
2319
  disconnect() {
2156
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();
2157
2321
  for (const e of this.invitationExpiryTimers.values()) clearTimeout(e);
2158
2322
  this.invitationExpiryTimers.clear();
2159
- for (const e of this.mediaClients.keys()) this.releaseMedia(e);
2323
+ for (const e of [...this.mediaClients.keys()]) this.releaseMedia(e);
2160
2324
  this.activeCallIds.clear(), this.seenInvitationIds.clear();
2161
2325
  }
2162
2326
  destroy() {
@@ -2168,11 +2332,11 @@ class Ze extends EventTarget {
2168
2332
  try {
2169
2333
  t = await this.resolveToken(e, e === "reconnect");
2170
2334
  } catch (s) {
2171
- 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();
2172
2336
  return;
2173
2337
  }
2174
2338
  if (this.stopped || this.socket) return;
2175
- 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]);
2176
2340
  this.socket = n, n.addEventListener("open", () => {
2177
2341
  const s = this.reconnectAttempt > 0, o = this.lastDisconnect;
2178
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(() => {
@@ -2234,7 +2398,7 @@ class Ze extends EventTarget {
2234
2398
  async request(e, t, i = !1) {
2235
2399
  const n = this.options.callActionsUrl ?? this.options.apiBaseUrl ?? this.options.signalingUrl ?? globalThis.location?.origin;
2236
2400
  if (!n) throw new Error("callActionsUrl is required for call actions");
2237
- 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);
2238
2402
  r.set("authorization", `Bearer ${o}`), r.set("content-type", "application/json");
2239
2403
  const c = await fetch(new URL(e, s.origin), { ...t, headers: r }), l = await c.json().catch(() => ({}));
2240
2404
  if (c.status === 401 && this.options.tokenProvider && !i)
@@ -2268,9 +2432,9 @@ class Ze extends EventTarget {
2268
2432
  Object.assign(s.style, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px" });
2269
2433
  const o = he("Decline", "#fff", "#a32920", "#d9a09b"), r = he("Answer", "#fff", "#08745f", "#08745f");
2270
2434
  o.addEventListener("click", () => {
2271
- this.reject(e.id).catch((c) => this.emit("error", B(c)));
2435
+ this.reject(e.id).catch((c) => this.emit("error", V(c)));
2272
2436
  }), r.addEventListener("click", () => {
2273
- this.accept(e.id).catch((c) => this.emit("error", B(c)));
2437
+ this.accept(e.id).catch((c) => this.emit("error", V(c)));
2274
2438
  }), s.appendChild(o), s.appendChild(r), t.appendChild(i), t.appendChild(n), t.appendChild(s), this.mount.replaceChildren(t);
2275
2439
  }
2276
2440
  dismiss(e) {
@@ -2338,29 +2502,29 @@ class Ze extends EventTarget {
2338
2502
  const t = Math.max(0, e - this.refreshSkew(e));
2339
2503
  this.tokenRefreshTimer = setTimeout(() => {
2340
2504
  this.tokenRefreshTimer = null, this.resolveToken("expiring", !0).catch((i) => {
2341
- 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));
2342
2506
  });
2343
2507
  }, Math.min(t, 2147e6));
2344
2508
  }
2345
2509
  releaseMedia(e) {
2346
2510
  this.clearInvitationExpiry(e), this.activeCallIds.delete(e), this.seenInvitationIds.delete(e);
2347
2511
  const t = this.mediaClients.get(e);
2348
- t && (this.mediaClients.delete(e), t.leave().catch(() => {
2512
+ t && (this.mediaSubscriptions.get(e)?.(), this.mediaClients.delete(e), t.leave().catch(() => {
2349
2513
  }).finally(() => t.destroy()));
2350
2514
  }
2351
2515
  reportSignalingDiagnostic(e) {
2352
2516
  this.options.reportSignalingDiagnostics === !1 || this.stopped || this.request("/v1/rtc/client/signaling-events", {
2353
2517
  method: "POST",
2354
2518
  body: JSON.stringify({ ...e, occurredAt: (/* @__PURE__ */ new Date()).toISOString() })
2355
- }).catch((t) => this.emit("diagnosticserror", B(t)));
2519
+ }).catch((t) => this.emit("diagnosticserror", V(t)));
2356
2520
  }
2357
2521
  emit(e, t) {
2358
2522
  const i = { type: e, detail: t, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
2359
2523
  this.dispatchEvent(new CustomEvent(e, { detail: i })), this.options.onEvent?.(i);
2360
2524
  }
2361
2525
  }
2362
- function yt(a) {
2363
- const e = new Ze(a);
2526
+ function wt(a) {
2527
+ const e = new tt(a);
2364
2528
  return a.autoConnect !== !1 && a.signalingUrl && e.connect(), e;
2365
2529
  }
2366
2530
  function de(a) {
@@ -2379,7 +2543,7 @@ function he(a, e, t, i) {
2379
2543
  fontWeight: "700"
2380
2544
  }), n;
2381
2545
  }
2382
- function et(a) {
2546
+ function it(a) {
2383
2547
  return {
2384
2548
  rejected: "Call declined",
2385
2549
  busy: "Line busy",
@@ -2397,42 +2561,46 @@ function ue(a) {
2397
2561
  role: "participant"
2398
2562
  };
2399
2563
  }
2400
- async function tt(a, e) {
2564
+ async function nt(a, e) {
2401
2565
  const t = await a.getStats(), i = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
2402
- let s = 0, o = 0, r = 0, c = 0, l = 0, d = 0, u = 0, v = 0, p = 0, S = 0, T = 0, y = 0, k = 0, f = 0, M = 0, R = null, L = null, P = null, A = null, w = null, E = null;
2403
- t.forEach((g) => {
2404
- n.set(g.id, g), g.type === "codec" && i.set(g.id, String(g.mimeType ?? ""));
2405
- }), t.forEach((g) => {
2406
- if (g.type === "inbound-rtp" && !g.isRemote && (s += Number(g.bytesReceived ?? 0), r += Math.max(0, Number(g.packetsLost ?? 0)), c += Math.max(0, Number(g.packetsReceived ?? 0)), Number.isFinite(g.jitter) && (l += Number(g.jitter), d += 1), u = Math.max(u, Number(g.framesPerSecond ?? 0)), v = Math.max(v, Number(g.frameWidth ?? 0)), T = Math.max(T, Number(g.frameHeight ?? 0)), y += Number(g.framesDropped ?? 0), k += Number(g.freezeCount ?? 0), f += Number(g.totalFreezesDuration ?? 0), M += Number(g.concealedSamples ?? 0), w ||= i.get(String(g.codecId ?? "")) ?? null), g.type === "outbound-rtp" && !g.isRemote && (p = Math.max(p, Number(g.frameWidth ?? 0)), S = Math.max(S, Number(g.frameHeight ?? 0)), o += Number(g.bytesSent ?? 0), E ||= g.qualityLimitationReason ? String(g.qualityLimitationReason) : null, w ||= i.get(String(g.codecId ?? "")) ?? null), g.type === "candidate-pair" && (g.selected || g.nominated) && g.state === "succeeded") {
2407
- R = Number.isFinite(g.currentRoundTripTime) ? Number(g.currentRoundTripTime) : R, L = Number.isFinite(g.availableOutgoingBitrate) ? Number(g.availableOutgoingBitrate) : L;
2408
- const N = n.get(g.localCandidateId);
2409
- P = N?.candidateType ? String(N.candidateType) : P, A = N?.protocol ? String(N.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;
2410
2576
  }
2411
2577
  });
2412
- const m = Date.now(), h = e ? Math.max(1e-3, (m - e.timestamp) / 1e3) : 0, b = e ? Math.max(0, Math.round((s - e.inboundBytes) * 8 / h)) : null, I = e ? Math.max(0, Math.round((o - e.outboundBytes) * 8 / h)) : null, C = r + c, U = e ? Math.max(0, r - e.packetsLost) : r, Q = e ? Math.max(0, c - e.packetsReceived) : c, q = U + Q;
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;
2413
2579
  return {
2414
- 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(","),
2415
2583
  sample: {
2416
2584
  rttMs: R === null ? null : R * 1e3,
2417
2585
  jitterMs: d ? l / d * 1e3 : null,
2418
2586
  packetLossPct: q ? U / q * 100 : null,
2419
- packetLossCumulativePct: C ? r / C * 100 : null,
2587
+ packetLossCumulativePct: _ ? r / _ * 100 : null,
2420
2588
  packetsLost: r,
2421
2589
  packetsReceived: c,
2422
- inboundBitrateBps: b,
2423
- outboundBitrateBps: I,
2590
+ inboundBitrateBps: C,
2591
+ outboundBitrateBps: O,
2424
2592
  availableOutgoingBitrateBps: L,
2425
2593
  framesPerSecond: u || null,
2426
2594
  frameWidth: v || null,
2427
- frameHeight: T || null,
2595
+ frameHeight: b || null,
2428
2596
  inboundFrameWidth: v || null,
2429
- inboundFrameHeight: T || null,
2597
+ inboundFrameHeight: b || null,
2430
2598
  outboundFrameWidth: p || null,
2431
2599
  outboundFrameHeight: S || null,
2432
- framesDropped: y,
2600
+ framesDropped: g,
2433
2601
  freezeCount: k,
2434
2602
  freezeDurationMs: Math.round(f * 1e3),
2435
- concealedSamples: M,
2603
+ concealedSamples: I,
2436
2604
  codec: w,
2437
2605
  transportProtocol: A,
2438
2606
  candidateType: P,
@@ -2440,64 +2608,69 @@ async function tt(a, e) {
2440
2608
  }
2441
2609
  };
2442
2610
  }
2443
- function it(a) {
2611
+ function at(a) {
2444
2612
  let e = a.mediaScore == null ? 5 : Math.round(pe(Number(a.mediaScore), 0, 5));
2445
- 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();
2446
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));
2447
2615
  const o = Number(pe(4.5 - t * 0.09 - i * 11e-4 - n * 6e-3, 1, 4.5).toFixed(2));
2448
2616
  return { networkScore: e, estimatedMos: o };
2449
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
+ }
2450
2622
  function me() {
2451
2623
  return navigator.connection ?? null;
2452
2624
  }
2453
- function nt() {
2625
+ function rt() {
2454
2626
  const a = Number(navigator.deviceMemory);
2455
2627
  return Number.isFinite(a) ? a : null;
2456
2628
  }
2457
- function Y(a) {
2629
+ function X(a) {
2458
2630
  const e = Number(a);
2459
2631
  return Number.isFinite(e) ? e : null;
2460
2632
  }
2461
2633
  function pe(a, e, t) {
2462
2634
  return Math.min(t, Math.max(e, a));
2463
2635
  }
2464
- function B(a) {
2636
+ function V(a) {
2465
2637
  return a instanceof Error ? { name: a.name, message: a.message } : { name: "ServiceError", message: String(a ?? "Service request failed") };
2466
2638
  }
2467
2639
  function fe(a, e) {
2468
2640
  return a instanceof Error ? a : new Error(e);
2469
2641
  }
2470
- const at = "1.4.0";
2642
+ const ot = "1.5.0";
2471
2643
  export {
2472
- _e as AVATAR_MODEL_MAX_BYTES,
2644
+ Fe as AVATAR_MODEL_MAX_BYTES,
2473
2645
  oe as AVATAR_TUNING_DEFAULTS,
2474
2646
  x as AvatarModelError,
2475
2647
  Ue as BACKGROUND_PRESETS,
2476
- Oe as OVERLAY_EFFECTS,
2477
- Ge as RtcClient,
2478
- Ze as RtcIncomingClient,
2479
- st as SPEAKER_NOISE_FLOOR,
2480
- rt as SPEAKER_TAKEOVER_RATIO,
2481
- dt as createAvatarPipeline,
2482
- ct as createCallRecorder,
2483
- ot as createPictureInPictureController,
2484
- gt as createRtcClient,
2485
- yt as createRtcIncomingClient,
2486
- lt as createVideoEffectsPipeline,
2487
- je as drawAvatar,
2488
- qe as headPoseFromMatrix,
2489
- ut as importAvatarModel,
2490
- ht as listArchiveModels,
2491
- mt as listAvatarModels,
2492
- ve as neutralRig,
2648
+ Ye as DEFAULT_CONNECT_TIMEOUT_MS,
2649
+ qe as OVERLAY_EFFECTS,
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,
2493
2666
  ge as pickActiveSpeaker,
2494
- pt as readAvatarModel,
2495
- We as readAvatarModelMetadata,
2496
- ft as removeAvatarModel,
2497
- Ve as rigFromBlendshapes,
2667
+ yt as readAvatarModel,
2668
+ $e as readAvatarModelMetadata,
2669
+ vt as removeAvatarModel,
2670
+ Be as rigFromBlendshapes,
2498
2671
  se as scenePainters,
2499
2672
  re as smoothRig,
2500
2673
  ce as stylizedAvatarRenderer,
2501
- Ye as unlockRtcCallAudio,
2502
- at as version
2674
+ Xe as unlockRtcCallAudio,
2675
+ ot as version
2503
2676
  };