@streaming-cdn/rtc-web 1.3.20 → 1.3.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/index.d.ts +14 -1
- package/dist/peer-quality.d.ts +2 -0
- package/dist/rtc-web.es.js +169 -160
- package/dist/rtc-web.umd.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# RTC Web SDK 1.3.
|
|
1
|
+
# RTC Web SDK 1.3.21
|
|
2
2
|
|
|
3
3
|
The customer package contains compiled ESM/UMD JavaScript and TypeScript declarations. It intentionally excludes implementation source and source maps. Example source remains available under `examples/`.
|
|
4
4
|
|
|
@@ -356,3 +356,14 @@ Abnormal close, reconnect, and recovery duration are also sent as bounded
|
|
|
356
356
|
operational diagnostics. Tokens, SDP, media, and chat content are never part of
|
|
357
357
|
that report. Set `reportSignalingDiagnostics: false` only when replacing this
|
|
358
358
|
with an application-owned diagnostics pipeline.
|
|
359
|
+
|
|
360
|
+
## 1.3.21 notes
|
|
361
|
+
|
|
362
|
+
- Per-peer quality: a `bandwidth` limitation reason counts against a link only
|
|
363
|
+
when the measured uplink does not comfortably clear the current tier
|
|
364
|
+
(`bandwidthHeadroomRatio`, default 1.25) — encoder ramp-up during the first
|
|
365
|
+
minute no longer forces a downgrade.
|
|
366
|
+
- Quality samples carry direction-explicit resolutions:
|
|
367
|
+
`inboundFrameWidth/Height` (received; equals the historical
|
|
368
|
+
`frameWidth/Height`) and `outboundFrameWidth/Height` (what your encoder
|
|
369
|
+
sends).
|
package/dist/index.d.ts
CHANGED
|
@@ -101,8 +101,21 @@ export interface RtcQualitySample {
|
|
|
101
101
|
outboundBitrateBps: number | null;
|
|
102
102
|
availableOutgoingBitrateBps: number | null;
|
|
103
103
|
framesPerSecond: number | null;
|
|
104
|
+
/**
|
|
105
|
+
* INBOUND (received) resolution — the remote side's picture, which moves
|
|
106
|
+
* with THEIR quality tier, not yours (N3: an integrator saw "low" record a
|
|
107
|
+
* larger frame than "medium" and reasonably read it as a bug). Kept under
|
|
108
|
+
* the historical names for compatibility; prefer the direction-explicit
|
|
109
|
+
* fields below.
|
|
110
|
+
*/
|
|
104
111
|
frameWidth: number | null;
|
|
105
112
|
frameHeight: number | null;
|
|
113
|
+
/** Received (inbound) video resolution — same values as frameWidth/frameHeight. */
|
|
114
|
+
inboundFrameWidth: number | null;
|
|
115
|
+
inboundFrameHeight: number | null;
|
|
116
|
+
/** Sent (outbound) video resolution — what YOUR encoder is producing. */
|
|
117
|
+
outboundFrameWidth: number | null;
|
|
118
|
+
outboundFrameHeight: number | null;
|
|
106
119
|
framesDropped: number | null;
|
|
107
120
|
freezeCount: number | null;
|
|
108
121
|
freezeDurationMs: number | null;
|
|
@@ -364,4 +377,4 @@ export declare class RtcIncomingClient extends EventTarget {
|
|
|
364
377
|
private emit;
|
|
365
378
|
}
|
|
366
379
|
export declare function createRtcIncomingClient(options: RtcIncomingClientOptions): RtcIncomingClient;
|
|
367
|
-
export declare const version = "1.3.
|
|
380
|
+
export declare const version = "1.3.21";
|
package/dist/peer-quality.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface PeerQualityThresholds {
|
|
|
37
37
|
goodLossPct: number;
|
|
38
38
|
goodRttMs: number;
|
|
39
39
|
sustainRatio: number;
|
|
40
|
+
/** "bandwidth" counts only when the uplink estimate is below this multiple of the tier target. */
|
|
41
|
+
bandwidthHeadroomRatio: number;
|
|
40
42
|
}
|
|
41
43
|
export declare const defaultPeerQualityThresholds: PeerQualityThresholds;
|
|
42
44
|
export interface PeerQualitySample {
|
package/dist/rtc-web.es.js
CHANGED
|
@@ -14,7 +14,7 @@ function ce(a) {
|
|
|
14
14
|
}
|
|
15
15
|
return e;
|
|
16
16
|
}
|
|
17
|
-
const
|
|
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 },
|
|
@@ -29,15 +29,16 @@ const N = {
|
|
|
29
29
|
poorRttMs: 350,
|
|
30
30
|
goodLossPct: 1,
|
|
31
31
|
goodRttMs: 250,
|
|
32
|
-
sustainRatio: 0.8
|
|
32
|
+
sustainRatio: 0.8,
|
|
33
|
+
bandwidthHeadroomRatio: 1.25
|
|
33
34
|
};
|
|
34
35
|
function ue(a) {
|
|
35
36
|
const e = String(a ?? "").trim().toLowerCase();
|
|
36
37
|
return !e || e === "none" ? "" : e === "cpu" || e === "bandwidth" ? e : "other";
|
|
37
38
|
}
|
|
38
39
|
function me(a, e, t, i, n = {}) {
|
|
39
|
-
const s = { ...he, ...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,
|
|
40
|
-
return l > c ? { quality: o[c], poorSamples: 0, goodSamples: 0, samplesSeen: d, changed: !0 } : u ?
|
|
40
|
+
const s = { ...he, ...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, k = ue(e.qualityLimitationReason), f = e.packetLossPct ?? 0, v = e.rttMs ?? 0, C = e.availableOutgoingBitrateBps, y = j[a].maxBitrate, w = f >= s.criticalLossPct || v >= s.criticalRttMs || k === "cpu", p = k === "bandwidth" && (C == null || C < y * s.bandwidthHeadroomRatio), R = w || f >= s.poorLossPct || v >= s.poorRttMs || p, P = C == null || C >= y * s.sustainRatio, L = f < s.goodLossPct && v < s.goodRttMs && !k && P, I = R ? t + 1 : 0, A = L ? i + 1 : 0, S = { quality: a, poorSamples: I, goodSamples: A, samplesSeen: d, changed: !1 };
|
|
41
|
+
return l > c ? { quality: o[c], poorSamples: 0, goodSamples: 0, samplesSeen: d, changed: !0 } : u ? S : w && l > 0 ? { quality: o[l - 1], poorSamples: 0, goodSamples: 0, samplesSeen: d, changed: !0 } : I >= s.poorSamplesToStepDown && l > 0 ? { quality: o[l - 1], poorSamples: 0, goodSamples: 0, samplesSeen: d, changed: !0 } : A >= s.goodSamplesToStepUp && l < c ? { quality: o[l + 1], poorSamples: 0, goodSamples: 0, samplesSeen: d, changed: !0 } : S;
|
|
41
42
|
}
|
|
42
43
|
function pe(a, e) {
|
|
43
44
|
const t = a instanceof Map ? a : new Map(a);
|
|
@@ -46,12 +47,12 @@ function pe(a, e) {
|
|
|
46
47
|
const u = String(d.type ?? "");
|
|
47
48
|
if (u === "outbound-rtp" && String(d.kind ?? "") === "video" && (i += Number(d.packetsSent ?? 0), s = d.qualityLimitationReason ?? s), u === "remote-inbound-rtp" && String(d.kind ?? "") === "video") {
|
|
48
49
|
n += Number(d.packetsLost ?? 0);
|
|
49
|
-
const
|
|
50
|
-
Number.isFinite(
|
|
50
|
+
const k = Number(d.roundTripTime ?? NaN);
|
|
51
|
+
Number.isFinite(k) && (o = k * 1e3);
|
|
51
52
|
}
|
|
52
53
|
if (u === "candidate-pair" && (d.nominated === !0 || String(d.state ?? "") === "succeeded")) {
|
|
53
|
-
const
|
|
54
|
-
o == null && Number.isFinite(
|
|
54
|
+
const k = Number(d.currentRoundTripTime ?? NaN);
|
|
55
|
+
o == null && Number.isFinite(k) && (o = k * 1e3);
|
|
55
56
|
const f = Number(d.availableOutgoingBitrate ?? NaN);
|
|
56
57
|
Number.isFinite(f) && (r = f);
|
|
57
58
|
}
|
|
@@ -391,7 +392,7 @@ class Q {
|
|
|
391
392
|
async applyPeerVideoQuality(e) {
|
|
392
393
|
const t = this.screenStream?.getVideoTracks()[0]?.id;
|
|
393
394
|
for (const i of e.connection.getSenders().filter((n) => n.track?.kind === "video")) {
|
|
394
|
-
const n = !!(t && i.track?.id === t), s = n ? ge : this.quality === "audio" ? fe.audio :
|
|
395
|
+
const n = !!(t && i.track?.id === t), s = n ? ge : this.quality === "audio" ? fe.audio : j[e.quality], o = i.getParameters(), r = o.encodings?.length ? o.encodings : [{}];
|
|
395
396
|
o.encodings = r.map((c) => ({ ...c, ...s })), o.degradationPreference = n ? "maintain-resolution" : "maintain-framerate", await i.setParameters(o);
|
|
396
397
|
}
|
|
397
398
|
}
|
|
@@ -498,10 +499,10 @@ class Q {
|
|
|
498
499
|
function _() {
|
|
499
500
|
return globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
500
501
|
}
|
|
501
|
-
const
|
|
502
|
-
{ rid: "a", maxBitrate:
|
|
503
|
-
{ rid: "b", maxBitrate:
|
|
504
|
-
{ rid: "c", maxBitrate:
|
|
502
|
+
const H = { active: !0, maxBitrate: 4e6, maxFramerate: 30, scaleResolutionDownBy: 1 }, Y = [
|
|
503
|
+
{ rid: "a", maxBitrate: j.high.maxBitrate, maxFramerate: j.high.maxFramerate },
|
|
504
|
+
{ rid: "b", maxBitrate: j.medium.maxBitrate, maxFramerate: j.medium.maxFramerate, scaleResolutionDownBy: 2 },
|
|
505
|
+
{ rid: "c", maxBitrate: j.low.maxBitrate, maxFramerate: j.low.maxFramerate, scaleResolutionDownBy: 4 }
|
|
505
506
|
], G = { high: "a", medium: "b", low: "c" };
|
|
506
507
|
class ye {
|
|
507
508
|
options;
|
|
@@ -839,12 +840,12 @@ class ye {
|
|
|
839
840
|
const o = this.quality === "audio" ? null : G[this.quality];
|
|
840
841
|
n.encodings = s.map((r) => {
|
|
841
842
|
if (i)
|
|
842
|
-
return { ...r, active: r.rid === "a", ...r.rid === "a" ? { maxBitrate:
|
|
843
|
+
return { ...r, active: r.rid === "a", ...r.rid === "a" ? { maxBitrate: H.maxBitrate, maxFramerate: H.maxFramerate } : {} };
|
|
843
844
|
const c = Y.find((d) => d.rid === r.rid), l = o != null && String(r.rid ?? "") >= o;
|
|
844
845
|
return { ...r, ...c ?? {}, active: l };
|
|
845
846
|
});
|
|
846
847
|
} else {
|
|
847
|
-
const o = this.quality === "audio" ?
|
|
848
|
+
const o = this.quality === "audio" ? j.audio : j[this.quality], r = i ? H : o;
|
|
848
849
|
n.encodings = s.map((c) => ({ ...c, ...r }));
|
|
849
850
|
}
|
|
850
851
|
n.degradationPreference = i ? "maintain-resolution" : "maintain-framerate", await e.setParameters(n);
|
|
@@ -973,15 +974,15 @@ function Ne(a, e = {}) {
|
|
|
973
974
|
}
|
|
974
975
|
function je(a, e) {
|
|
975
976
|
const t = e.document ?? globalThis.document, i = e.fetchImplementation ?? globalThis.fetch.bind(globalThis), n = new URL(e.credential.signalingUrl, globalThis.location?.href).origin, s = e.mimeType ?? "video/webm", o = e.height ?? 720, r = Math.round(o * 16 / 9);
|
|
976
|
-
let c = null, l = null, d = null, u = null,
|
|
977
|
-
async function
|
|
978
|
-
const P = await i(`${n}${
|
|
977
|
+
let c = null, l = null, d = null, u = null, k = 0, f = [], v = !1;
|
|
978
|
+
async function C(y, w, p, R = "application/json") {
|
|
979
|
+
const P = await i(`${n}${w}`, {
|
|
979
980
|
method: y,
|
|
980
981
|
headers: {
|
|
981
982
|
authorization: `Bearer ${e.credential.signalingToken}`,
|
|
982
|
-
"content-type":
|
|
983
|
+
"content-type": R
|
|
983
984
|
},
|
|
984
|
-
body: p === void 0 ? void 0 :
|
|
985
|
+
body: p === void 0 ? void 0 : R === "application/json" ? JSON.stringify(p) : p
|
|
985
986
|
});
|
|
986
987
|
if (!P.ok) throw new Error(`Recording request failed with HTTP ${P.status}`);
|
|
987
988
|
return P.json();
|
|
@@ -992,60 +993,60 @@ function je(a, e) {
|
|
|
992
993
|
},
|
|
993
994
|
async start() {
|
|
994
995
|
if (v) return;
|
|
995
|
-
u = (await
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
const p =
|
|
999
|
-
let b =
|
|
1000
|
-
return b || (b = t.createElement("video"), b.muted = !0, b.playsInline = !0, b.srcObject =
|
|
1001
|
-
}),
|
|
996
|
+
u = (await C("POST", "/v1/rtc/recordings")).recordingId;
|
|
997
|
+
const w = t.createElement("canvas");
|
|
998
|
+
w.width = r, w.height = o;
|
|
999
|
+
const p = w.getContext("2d"), R = /* @__PURE__ */ new Map(), P = (T, M) => {
|
|
1000
|
+
let b = R.get(T);
|
|
1001
|
+
return b || (b = t.createElement("video"), b.muted = !0, b.playsInline = !0, b.srcObject = M, b.play().catch(() => {
|
|
1002
|
+
}), R.set(T, b)), b;
|
|
1002
1003
|
};
|
|
1003
1004
|
l = setInterval(() => {
|
|
1004
|
-
const T = a.getRemoteStreams(),
|
|
1005
|
-
...T.map((
|
|
1006
|
-
...
|
|
1005
|
+
const T = a.getRemoteStreams(), M = a.getLocalStream(), b = [
|
|
1006
|
+
...T.map((q) => ({ key: q.participant.participantId, stream: q.stream, label: q.participant.displayName })),
|
|
1007
|
+
...M ? [{ key: "local", stream: M, label: "me" }] : []
|
|
1007
1008
|
];
|
|
1008
1009
|
p.fillStyle = "#101418", p.fillRect(0, 0, r, o);
|
|
1009
|
-
const O = Math.max(1, Math.ceil(Math.sqrt(b.length))),
|
|
1010
|
-
b.forEach((
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1010
|
+
const O = Math.max(1, Math.ceil(Math.sqrt(b.length))), V = Math.max(1, Math.ceil(b.length / O)), N = r / O, g = o / V;
|
|
1011
|
+
b.forEach((q, $) => {
|
|
1012
|
+
const J = P(q.key, q.stream), B = $ % O * N, F = Math.floor($ / O) * g;
|
|
1013
|
+
J.videoWidth > 0 && p.drawImage(J, B + 2, F + 2, N - 4, g - 4), p.fillStyle = "rgba(0,0,0,.6)", p.fillRect(B + 8, F + g - 30, Math.min(N - 16, 12 + q.label.length * 9), 22), p.fillStyle = "#fff", p.font = "14px system-ui", p.fillText(q.label, B + 14, F + g - 14);
|
|
1013
1014
|
});
|
|
1014
1015
|
}, 66);
|
|
1015
|
-
const L =
|
|
1016
|
+
const L = w.captureStream(15);
|
|
1016
1017
|
d = new AudioContext();
|
|
1017
|
-
const
|
|
1018
|
+
const I = d.createMediaStreamDestination(), A = /* @__PURE__ */ new Set(), S = () => {
|
|
1018
1019
|
const T = [
|
|
1019
|
-
...a.getRemoteStreams().map((
|
|
1020
|
+
...a.getRemoteStreams().map((M) => M.stream),
|
|
1020
1021
|
...a.getLocalStream() ? [a.getLocalStream()] : []
|
|
1021
1022
|
];
|
|
1022
|
-
for (const
|
|
1023
|
-
for (const b of
|
|
1024
|
-
A.has(b.id) || (A.add(b.id), d.createMediaStreamSource(new MediaStream([b])).connect(
|
|
1023
|
+
for (const M of T)
|
|
1024
|
+
for (const b of M.getAudioTracks())
|
|
1025
|
+
A.has(b.id) || (A.add(b.id), d.createMediaStreamSource(new MediaStream([b])).connect(I));
|
|
1025
1026
|
};
|
|
1026
|
-
|
|
1027
|
-
const
|
|
1028
|
-
for (const T of
|
|
1029
|
-
c = (e.mediaRecorderFactory ?? ((T,
|
|
1027
|
+
S();
|
|
1028
|
+
const E = setInterval(S, 1e3), m = () => clearInterval(E);
|
|
1029
|
+
for (const T of I.stream.getAudioTracks()) L.addTrack(T);
|
|
1030
|
+
c = (e.mediaRecorderFactory ?? ((T, M) => new MediaRecorder(T, M)))(L, { mimeType: s }), f = [], c.addEventListener("dataavailable", (T) => {
|
|
1030
1031
|
T.data && T.data.size > 0 && f.push(T.data);
|
|
1031
|
-
}), c.addEventListener("stop", m), c.start(1e3),
|
|
1032
|
+
}), c.addEventListener("stop", m), c.start(1e3), k = Date.now(), v = !0;
|
|
1032
1033
|
},
|
|
1033
1034
|
async stop() {
|
|
1034
1035
|
if (!v || !c || !u) throw new Error("The recorder is not running");
|
|
1035
1036
|
v = !1;
|
|
1036
|
-
const y = c,
|
|
1037
|
-
y.stop(), await
|
|
1037
|
+
const y = c, w = new Promise((I) => y.addEventListener("stop", () => I(), { once: !0 }));
|
|
1038
|
+
y.stop(), await w, l && clearInterval(l), l = null, await d?.close().catch(() => {
|
|
1038
1039
|
}), d = null;
|
|
1039
1040
|
const p = new Blob(f, { type: s });
|
|
1040
1041
|
f = [];
|
|
1041
|
-
const
|
|
1042
|
+
const R = Math.max(1, Math.round((Date.now() - k) / 1e3)), P = await C(
|
|
1042
1043
|
"PUT",
|
|
1043
1044
|
`/v1/rtc/recordings/${encodeURIComponent(u)}/media`,
|
|
1044
1045
|
p,
|
|
1045
1046
|
s
|
|
1046
1047
|
);
|
|
1047
|
-
await
|
|
1048
|
-
const L = { recordingId: u, sizeBytes: P.sizeBytes, durationSeconds:
|
|
1048
|
+
await C("POST", `/v1/rtc/recordings/${encodeURIComponent(u)}/complete`, { durationSeconds: R });
|
|
1049
|
+
const L = { recordingId: u, sizeBytes: P.sizeBytes, durationSeconds: R };
|
|
1049
1050
|
return u = null, c = null, L;
|
|
1050
1051
|
},
|
|
1051
1052
|
dispose() {
|
|
@@ -1096,20 +1097,20 @@ const Se = ["none", "blur", "aurora", "cybergrid", "bokeh", "sunset", "image"],
|
|
|
1096
1097
|
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);
|
|
1097
1098
|
}
|
|
1098
1099
|
};
|
|
1099
|
-
function
|
|
1100
|
+
function xe(a, e = {}) {
|
|
1100
1101
|
const t = e.document ?? globalThis.document;
|
|
1101
1102
|
let i = e.width ?? 1280, n = e.height ?? 720;
|
|
1102
1103
|
const s = !!(e.width || e.height), o = e.frameRate ?? 24, r = a instanceof MediaStreamTrack ? a : a.getVideoTracks()[0];
|
|
1103
1104
|
if (!r) throw new Error("A video track is required");
|
|
1104
|
-
let c = e.background ?? "none", l = e.overlay ?? "none", d = e.backgroundImage ?? null, u = e.segmenter ?? null,
|
|
1105
|
+
let c = e.background ?? "none", l = e.overlay ?? "none", d = e.backgroundImage ?? null, u = e.segmenter ?? null, k = null, f = !1;
|
|
1105
1106
|
const v = t.createElement("video");
|
|
1106
1107
|
v.muted = !0, v.playsInline = !0, v.srcObject = new MediaStream([r]), v.play().catch(() => {
|
|
1107
1108
|
});
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
const y =
|
|
1111
|
-
|
|
1112
|
-
const p =
|
|
1109
|
+
const C = t.createElement("canvas");
|
|
1110
|
+
C.width = i, C.height = n;
|
|
1111
|
+
const y = C.getContext("2d"), w = t.createElement("canvas");
|
|
1112
|
+
w.width = i, w.height = n;
|
|
1113
|
+
const p = w.getContext("2d"), R = () => Array.from({ length: 80 }, (m, h) => ({
|
|
1113
1114
|
x: (Math.sin(h * 12.9898) * 0.5 + 0.5) * i,
|
|
1114
1115
|
y: (Math.sin(h * 78.233) * 0.5 + 0.5) * n,
|
|
1115
1116
|
speed: 0.6 + h % 5 * 0.5,
|
|
@@ -1117,25 +1118,25 @@ function Ve(a, e = {}) {
|
|
|
1117
1118
|
size: 2 + h % 4,
|
|
1118
1119
|
hue: h * 47 % 360
|
|
1119
1120
|
}));
|
|
1120
|
-
let P =
|
|
1121
|
+
let P = R();
|
|
1121
1122
|
const L = () => {
|
|
1122
1123
|
if (s) return;
|
|
1123
1124
|
const m = v.videoWidth, h = v.videoHeight;
|
|
1124
1125
|
if (!m || !h) return;
|
|
1125
|
-
const T = Math.min(1, 1280 / Math.max(m, h)),
|
|
1126
|
-
|
|
1126
|
+
const T = Math.min(1, 1280 / Math.max(m, h)), M = Math.round(m * T), b = Math.round(h * T);
|
|
1127
|
+
M === i && b === n || (i = M, n = b, C.width = i, C.height = n, w.width = i, w.height = n, P = R());
|
|
1127
1128
|
};
|
|
1128
1129
|
v.addEventListener?.("loadedmetadata", L), v.addEventListener?.("resize", L);
|
|
1129
|
-
function
|
|
1130
|
-
if (
|
|
1131
|
-
p.clearRect(0, 0, i, n), p.drawImage(
|
|
1130
|
+
function I() {
|
|
1131
|
+
if (k) {
|
|
1132
|
+
p.clearRect(0, 0, i, n), p.drawImage(k, 0, 0, i, n), p.globalCompositeOperation = "source-in", p.drawImage(v, 0, 0, i, n), p.globalCompositeOperation = "source-over", y.drawImage(w, 0, 0);
|
|
1132
1133
|
return;
|
|
1133
1134
|
}
|
|
1134
|
-
const m = i * 0.42, h = m * 9 / 16, T = i - m - 24,
|
|
1135
|
-
y.save(), y.shadowColor = "rgba(0,0,0,.5)", y.shadowBlur = 24, y.beginPath(), y.roundRect(T,
|
|
1135
|
+
const m = i * 0.42, h = m * 9 / 16, T = i - m - 24, M = n - h - 24;
|
|
1136
|
+
y.save(), y.shadowColor = "rgba(0,0,0,.5)", y.shadowBlur = 24, y.beginPath(), y.roundRect(T, M, m, h, 14), y.clip(), y.drawImage(v, T, M, m, h), y.restore();
|
|
1136
1137
|
}
|
|
1137
1138
|
function A(m) {
|
|
1138
|
-
if (c === "none" ? y.drawImage(v, 0, 0, i, n) : c === "blur" ? (y.filter = "blur(14px)", y.drawImage(v, -16, -16, i + 32, n + 32), y.filter = "none",
|
|
1139
|
+
if (c === "none" ? y.drawImage(v, 0, 0, i, n) : c === "blur" ? (y.filter = "blur(14px)", y.drawImage(v, -16, -16, i + 32, n + 32), y.filter = "none", k && I()) : c === "image" && d ? (y.drawImage(d, 0, 0, i, n), I()) : ((K[c] ?? K.aurora)(y, i, n, m), I()), l === "snow" || l === "confetti")
|
|
1139
1140
|
for (const h of P)
|
|
1140
1141
|
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());
|
|
1141
1142
|
else if (l === "vignette") {
|
|
@@ -1143,30 +1144,30 @@ function Ve(a, e = {}) {
|
|
|
1143
1144
|
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);
|
|
1144
1145
|
}
|
|
1145
1146
|
}
|
|
1146
|
-
const
|
|
1147
|
+
const S = setInterval(() => {
|
|
1147
1148
|
A(Date.now()), u && !f && c !== "none" && (f = !0, u.segment(v).then((m) => {
|
|
1148
|
-
|
|
1149
|
+
k = m;
|
|
1149
1150
|
}).catch(() => {
|
|
1150
|
-
|
|
1151
|
+
k = null;
|
|
1151
1152
|
}).finally(() => {
|
|
1152
1153
|
f = !1;
|
|
1153
1154
|
}));
|
|
1154
|
-
}, Math.round(1e3 / o)),
|
|
1155
|
+
}, Math.round(1e3 / o)), E = C.captureStream(o);
|
|
1155
1156
|
return {
|
|
1156
|
-
track:
|
|
1157
|
-
stream:
|
|
1157
|
+
track: E.getVideoTracks()[0],
|
|
1158
|
+
stream: E,
|
|
1158
1159
|
setBackground(m, h) {
|
|
1159
|
-
c = Se.includes(m) ? m : "none", h !== void 0 && (d = h), c === "none" && (
|
|
1160
|
+
c = Se.includes(m) ? m : "none", h !== void 0 && (d = h), c === "none" && (k = null);
|
|
1160
1161
|
},
|
|
1161
1162
|
setOverlay(m) {
|
|
1162
1163
|
l = ke.includes(m) ? m : "none";
|
|
1163
1164
|
},
|
|
1164
1165
|
setSegmenter(m) {
|
|
1165
|
-
u = m, m || (
|
|
1166
|
+
u = m, m || (k = null);
|
|
1166
1167
|
},
|
|
1167
1168
|
dispose() {
|
|
1168
|
-
clearInterval(
|
|
1169
|
-
for (const m of
|
|
1169
|
+
clearInterval(S), u?.close?.();
|
|
1170
|
+
for (const m of E.getTracks()) m.stop();
|
|
1170
1171
|
v.srcObject = null;
|
|
1171
1172
|
}
|
|
1172
1173
|
};
|
|
@@ -1198,10 +1199,10 @@ function we(a) {
|
|
|
1198
1199
|
}
|
|
1199
1200
|
function Te(a, e, t = 1) {
|
|
1200
1201
|
if (!a || a.length === 0) return le();
|
|
1201
|
-
const i = we(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"),
|
|
1202
|
+
const i = we(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"), k = Math.min(1, Math.abs(i.yaw) / 0.35) * Math.max(0, Math.min(1, t)), f = (d + u) / 2;
|
|
1202
1203
|
return {
|
|
1203
|
-
blinkLeft: d + (f - d) *
|
|
1204
|
-
blinkRight: u + (f - u) *
|
|
1204
|
+
blinkLeft: d + (f - d) * k,
|
|
1205
|
+
blinkRight: u + (f - u) * k,
|
|
1205
1206
|
jawOpen: D(a, "jawOpen"),
|
|
1206
1207
|
smile: c - l,
|
|
1207
1208
|
browUp: (D(a, "browInnerUp") + D(a, "browOuterUpLeft") + D(a, "browOuterUpRight")) / 3,
|
|
@@ -1233,30 +1234,30 @@ function be(a, e, t, i, n) {
|
|
|
1233
1234
|
n || (a.fillStyle = "#101822", a.fillRect(0, 0, t, i));
|
|
1234
1235
|
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;
|
|
1235
1236
|
a.save(), a.translate(s, o), a.rotate(e.headRoll * 0.6);
|
|
1236
|
-
const c = Math.max(-1.35, Math.min(1.35, e.headYaw)), l = Math.max(-0.9, Math.min(0.9, e.headPitch)), d = Math.sin(c), u = (A) => Math.sin(A + c) * r,
|
|
1237
|
+
const c = Math.max(-1.35, Math.min(1.35, e.headYaw)), l = Math.max(-0.9, Math.min(0.9, e.headPitch)), d = Math.sin(c), u = (A) => Math.sin(A + c) * r, k = (A) => Math.cos(A + c), f = Math.sin(l) * r * 0.22;
|
|
1237
1238
|
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 + f * 0.4, r * 0.92, r * 0.5, 0, Math.PI, Math.PI * 2), a.fill();
|
|
1238
1239
|
const v = 0;
|
|
1239
1240
|
a.fillStyle = "#e5b58a", a.beginPath();
|
|
1240
|
-
const
|
|
1241
|
-
a.ellipse(
|
|
1241
|
+
const C = u(v) * 0.9, y = r * 0.12 + f;
|
|
1242
|
+
a.ellipse(C + d * r * 0.14, y, r * 0.11, r * 0.14, 0, 0, Math.PI * 2), a.fill();
|
|
1242
1243
|
for (const A of [-1, 1]) {
|
|
1243
|
-
const
|
|
1244
|
-
if (
|
|
1245
|
-
const m = u(
|
|
1244
|
+
const S = A * 0.42, E = k(S);
|
|
1245
|
+
if (E <= 0.12) continue;
|
|
1246
|
+
const m = u(S) * 0.82, h = Math.max(0.2, E), T = A < 0 ? e.blinkLeft : e.blinkRight, M = Math.max(0.06, 1 - T), b = e.browUp * r * 0.14;
|
|
1246
1247
|
a.strokeStyle = "#4a2e18", a.lineWidth = r * 0.07, a.lineCap = "round", a.beginPath(), a.moveTo(m - r * 0.2 * h, -r * 0.42 - b + f), a.quadraticCurveTo(m, -r * 0.52 - b + f, m + r * 0.2 * h, -r * 0.44 - b + f), a.stroke();
|
|
1247
1248
|
const O = -r * 0.18 + f;
|
|
1248
|
-
a.fillStyle = "#ffffff", a.beginPath(), a.ellipse(m, O, r * 0.16 * h, r * 0.12 *
|
|
1249
|
+
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(
|
|
1249
1250
|
m + e.pupilX * r * 0.07 * h,
|
|
1250
1251
|
O + e.pupilY * r * 0.05,
|
|
1251
1252
|
r * 0.06 * h,
|
|
1252
|
-
r * 0.06 *
|
|
1253
|
+
r * 0.06 * M,
|
|
1253
1254
|
0,
|
|
1254
1255
|
0,
|
|
1255
1256
|
Math.PI * 2
|
|
1256
1257
|
), a.fill();
|
|
1257
1258
|
}
|
|
1258
|
-
const
|
|
1259
|
-
a.fillStyle = "#8c2f2f", a.beginPath(), a.moveTo(p -
|
|
1259
|
+
const w = Math.max(0.25, k(0)), p = u(0) * 0.7, R = r * 0.55 * w, P = r * (0.04 + e.jawOpen * 0.34), L = e.smile * r * 0.12, I = r * 0.42 + f;
|
|
1260
|
+
a.fillStyle = "#8c2f2f", a.beginPath(), a.moveTo(p - R / 2, I - L), a.quadraticCurveTo(p, I + P, p + R / 2, I - L), a.quadraticCurveTo(p, I - P * 0.25, p - R / 2, I - 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");
|
|
1260
1261
|
}
|
|
1261
1262
|
const Z = {
|
|
1262
1263
|
mouthGain: 1.7,
|
|
@@ -1274,70 +1275,70 @@ function ee() {
|
|
|
1274
1275
|
}
|
|
1275
1276
|
};
|
|
1276
1277
|
}
|
|
1277
|
-
const
|
|
1278
|
-
function
|
|
1278
|
+
const z = (a) => Math.max(0, Math.min(1, a));
|
|
1279
|
+
function Ve(a, e = {}) {
|
|
1279
1280
|
const t = e.document ?? globalThis.document;
|
|
1280
1281
|
let i = e.width ?? 1280, n = e.height ?? 720;
|
|
1281
1282
|
const s = !!(e.width || e.height), o = e.frameRate ?? 24, r = a instanceof MediaStreamTrack ? a : a.getVideoTracks()[0];
|
|
1282
1283
|
if (!r) throw new Error("A video track is required");
|
|
1283
1284
|
let c = e.tracker ?? null, l = e.renderer ?? ee(), d = e.mode ?? "replace";
|
|
1284
|
-
const u = { ...Z, ...e.tuning },
|
|
1285
|
+
const u = { ...Z, ...e.tuning }, k = e.backdrop ?? "#101822", f = t.createElement("video");
|
|
1285
1286
|
f.muted = !0, f.playsInline = !0, f.srcObject = new MediaStream([r]), f.play().catch(() => {
|
|
1286
1287
|
});
|
|
1287
1288
|
const v = t.createElement("canvas");
|
|
1288
1289
|
v.width = i, v.height = n;
|
|
1289
|
-
const
|
|
1290
|
+
const C = v.getContext("2d"), y = () => {
|
|
1290
1291
|
if (s) return;
|
|
1291
|
-
const
|
|
1292
|
-
if (!
|
|
1293
|
-
const m = Math.min(1, 1280 / Math.max(
|
|
1292
|
+
const S = f.videoWidth, E = f.videoHeight;
|
|
1293
|
+
if (!S || !E) return;
|
|
1294
|
+
const m = Math.min(1, 1280 / Math.max(S, E)), h = Math.round(S * m), T = Math.round(E * m);
|
|
1294
1295
|
(!(h === i || T === n) || h !== i || T !== n) && (i = h, n = T, v.width = i, v.height = n);
|
|
1295
1296
|
};
|
|
1296
1297
|
f.addEventListener?.("loadedmetadata", y), f.addEventListener?.("resize", y);
|
|
1297
|
-
let
|
|
1298
|
-
function P(
|
|
1299
|
-
if (
|
|
1300
|
-
const
|
|
1301
|
-
p = p ? { x: p.x + (
|
|
1298
|
+
let w = le(), p = null, R = 0;
|
|
1299
|
+
function P(S) {
|
|
1300
|
+
if (S.length <= 454) return;
|
|
1301
|
+
const E = S[10], m = S[152], h = S[234], T = S[454], M = (h.x + T.x) / 2 * i, b = (h.y + T.y) / 2 * n, O = Math.hypot((m.x - E.x) * i, (m.y - E.y) * n), V = Math.max(0.6, Math.cos(w.headPitch)), N = { x: M, y: b, radius: Math.max(24, O / V * 0.8) };
|
|
1302
|
+
p = p ? { x: p.x + (N.x - p.x) * 0.4, y: p.y + (N.y - p.y) * 0.4, radius: p.radius + (N.radius - p.radius) * 0.15 } : N;
|
|
1302
1303
|
}
|
|
1303
|
-
function L(
|
|
1304
|
+
function L(S) {
|
|
1304
1305
|
if (c && f.readyState >= 2) {
|
|
1305
1306
|
let m = null;
|
|
1306
1307
|
try {
|
|
1307
|
-
m = c.detect(f,
|
|
1308
|
+
m = c.detect(f, S);
|
|
1308
1309
|
} catch {
|
|
1309
1310
|
}
|
|
1310
1311
|
const h = Te(m?.categories ?? null, m?.matrix ?? null, u.eyeSync);
|
|
1311
|
-
h.jawOpen =
|
|
1312
|
+
h.jawOpen = z(h.jawOpen * u.mouthGain), h.blinkLeft = z(h.blinkLeft * u.blinkGain), h.blinkRight = z(h.blinkRight * u.blinkGain), h.tracked ? (R = S, w = X(w, h, u.smoothing), m?.landmarks && P(m.landmarks)) : w.tracked && S - R > u.lostHoldMs && (w = X(w, { ...w, jawOpen: 0, browUp: 0, smile: w.smile * 0.5, blinkLeft: 0.1, blinkRight: 0.1 }, 0.05));
|
|
1312
1313
|
}
|
|
1313
|
-
d === "overlay" && f.readyState >= 2 ?
|
|
1314
|
-
const
|
|
1315
|
-
l?.render(
|
|
1314
|
+
d === "overlay" && f.readyState >= 2 ? C.drawImage(f, 0, 0, i, n) : (C.fillStyle = k, C.fillRect(0, 0, i, n));
|
|
1315
|
+
const E = p ? { x: p.x, y: p.y + u.avatarLift * p.radius, radius: p.radius * u.avatarScale } : null;
|
|
1316
|
+
l?.render(C, w, i, n, E);
|
|
1316
1317
|
}
|
|
1317
|
-
const
|
|
1318
|
+
const I = setInterval(() => L(Date.now()), Math.round(1e3 / o)), A = v.captureStream(o);
|
|
1318
1319
|
return {
|
|
1319
1320
|
track: A.getVideoTracks()[0],
|
|
1320
1321
|
stream: A,
|
|
1321
|
-
getRig: () => ({ ...
|
|
1322
|
-
setMode(
|
|
1323
|
-
d =
|
|
1322
|
+
getRig: () => ({ ...w }),
|
|
1323
|
+
setMode(S) {
|
|
1324
|
+
d = S === "overlay" ? "overlay" : "replace";
|
|
1324
1325
|
},
|
|
1325
|
-
setTracker(
|
|
1326
|
-
c?.close?.(), c =
|
|
1326
|
+
setTracker(S) {
|
|
1327
|
+
c?.close?.(), c = S;
|
|
1327
1328
|
},
|
|
1328
|
-
setRenderer(
|
|
1329
|
-
l?.dispose?.(), l =
|
|
1329
|
+
setRenderer(S) {
|
|
1330
|
+
l?.dispose?.(), l = S ?? ee();
|
|
1330
1331
|
},
|
|
1331
|
-
setTuning(
|
|
1332
|
-
for (const
|
|
1333
|
-
const m =
|
|
1334
|
-
typeof m == "number" && Number.isFinite(m) && (u[
|
|
1332
|
+
setTuning(S) {
|
|
1333
|
+
for (const E of Object.keys(Z)) {
|
|
1334
|
+
const m = S[E];
|
|
1335
|
+
typeof m == "number" && Number.isFinite(m) && (u[E] = m);
|
|
1335
1336
|
}
|
|
1336
1337
|
},
|
|
1337
1338
|
getTuning: () => ({ ...u }),
|
|
1338
1339
|
dispose() {
|
|
1339
|
-
clearInterval(
|
|
1340
|
-
for (const
|
|
1340
|
+
clearInterval(I), c?.close?.(), l?.dispose?.();
|
|
1341
|
+
for (const S of A.getTracks()) S.stop();
|
|
1341
1342
|
f.srcObject = null;
|
|
1342
1343
|
}
|
|
1343
1344
|
};
|
|
@@ -1400,7 +1401,7 @@ class Ce extends EventTarget {
|
|
|
1400
1401
|
} catch (e) {
|
|
1401
1402
|
this.state = "failed";
|
|
1402
1403
|
const t = re(e, "RTC initialization failed");
|
|
1403
|
-
throw this.emit("error",
|
|
1404
|
+
throw this.emit("error", x(t)), t;
|
|
1404
1405
|
}
|
|
1405
1406
|
}
|
|
1406
1407
|
async join() {
|
|
@@ -1408,7 +1409,7 @@ class Ce extends EventTarget {
|
|
|
1408
1409
|
return this.joinPromise ? this.joinPromise : (this.state = "joining", this.emit("joining", {}), this.joinPromise = e.join().catch((t) => {
|
|
1409
1410
|
this.state = "failed";
|
|
1410
1411
|
const i = re(t, "RTC connection failed");
|
|
1411
|
-
throw this.emit("error",
|
|
1412
|
+
throw this.emit("error", x(i)), i;
|
|
1412
1413
|
}).finally(() => {
|
|
1413
1414
|
this.joinPromise = null;
|
|
1414
1415
|
}), this.joinPromise);
|
|
@@ -1522,7 +1523,7 @@ class Ce extends EventTarget {
|
|
|
1522
1523
|
let n = "unavailable";
|
|
1523
1524
|
if (e.requestMedia !== !1 && navigator.mediaDevices?.getUserMedia)
|
|
1524
1525
|
try {
|
|
1525
|
-
(await navigator.mediaDevices.getUserMedia({ audio: this.options.audio, video: this.options.video })).getTracks().forEach((
|
|
1526
|
+
(await navigator.mediaDevices.getUserMedia({ audio: this.options.audio, video: this.options.video })).getTracks().forEach((k) => k.stop()), n = "granted";
|
|
1526
1527
|
} catch {
|
|
1527
1528
|
n = "denied", t.push("media_permission_denied");
|
|
1528
1529
|
}
|
|
@@ -1533,8 +1534,8 @@ class Ce extends EventTarget {
|
|
|
1533
1534
|
if (c) {
|
|
1534
1535
|
const u = performance.now();
|
|
1535
1536
|
try {
|
|
1536
|
-
const
|
|
1537
|
-
l = Math.round(performance.now() - u),
|
|
1537
|
+
const k = await fetch(c, { method: "HEAD", cache: "no-store", credentials: "omit" });
|
|
1538
|
+
l = Math.round(performance.now() - u), k.ok || t.push("network_probe_failed");
|
|
1538
1539
|
} catch {
|
|
1539
1540
|
t.push("network_probe_failed");
|
|
1540
1541
|
}
|
|
@@ -1581,6 +1582,10 @@ class Ce extends EventTarget {
|
|
|
1581
1582
|
framesPerSecond: null,
|
|
1582
1583
|
frameWidth: null,
|
|
1583
1584
|
frameHeight: null,
|
|
1585
|
+
inboundFrameWidth: null,
|
|
1586
|
+
inboundFrameHeight: null,
|
|
1587
|
+
outboundFrameWidth: null,
|
|
1588
|
+
outboundFrameHeight: null,
|
|
1584
1589
|
framesDropped: null,
|
|
1585
1590
|
freezeCount: null,
|
|
1586
1591
|
freezeDurationMs: null,
|
|
@@ -1642,7 +1647,7 @@ class Ce extends EventTarget {
|
|
|
1642
1647
|
const i = Date.now(), n = e !== "runtime" || i - this.lastTelemetryAt >= this.options.telemetryInterval;
|
|
1643
1648
|
this.options.autoReportStats && n && (await this.reportQuality(t), this.lastTelemetryAt = i);
|
|
1644
1649
|
} catch (t) {
|
|
1645
|
-
this.emit("telemetryerror",
|
|
1650
|
+
this.emit("telemetryerror", x(t));
|
|
1646
1651
|
} finally {
|
|
1647
1652
|
this.statsRunning = !1;
|
|
1648
1653
|
}
|
|
@@ -1873,7 +1878,7 @@ class Ee extends EventTarget {
|
|
|
1873
1878
|
try {
|
|
1874
1879
|
t = await this.resolveToken(e, e === "reconnect");
|
|
1875
1880
|
} catch (s) {
|
|
1876
|
-
this.emit("tokenerror",
|
|
1881
|
+
this.emit("tokenerror", x(s)), !this.stopped && this.options.reconnect !== !1 && this.scheduleReconnect();
|
|
1877
1882
|
return;
|
|
1878
1883
|
}
|
|
1879
1884
|
if (this.stopped || this.socket) return;
|
|
@@ -1971,9 +1976,9 @@ class Ee extends EventTarget {
|
|
|
1971
1976
|
Object.assign(s.style, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px" });
|
|
1972
1977
|
const o = ie("Decline", "#fff", "#a32920", "#d9a09b"), r = ie("Answer", "#fff", "#08745f", "#08745f");
|
|
1973
1978
|
o.addEventListener("click", () => {
|
|
1974
|
-
this.reject(e.id).catch((c) => this.emit("error",
|
|
1979
|
+
this.reject(e.id).catch((c) => this.emit("error", x(c)));
|
|
1975
1980
|
}), r.addEventListener("click", () => {
|
|
1976
|
-
this.accept(e.id).catch((c) => this.emit("error",
|
|
1981
|
+
this.accept(e.id).catch((c) => this.emit("error", x(c)));
|
|
1977
1982
|
}), s.appendChild(o), s.appendChild(r), t.appendChild(i), t.appendChild(n), t.appendChild(s), this.mount.replaceChildren(t);
|
|
1978
1983
|
}
|
|
1979
1984
|
dismiss(e) {
|
|
@@ -2041,7 +2046,7 @@ class Ee extends EventTarget {
|
|
|
2041
2046
|
const t = Math.max(0, e - this.refreshSkew(e));
|
|
2042
2047
|
this.tokenRefreshTimer = setTimeout(() => {
|
|
2043
2048
|
this.tokenRefreshTimer = null, this.resolveToken("expiring", !0).catch((i) => {
|
|
2044
|
-
this.emit("tokenerror",
|
|
2049
|
+
this.emit("tokenerror", x(i)), this.stopped || (this.tokenRefreshTimer = setTimeout(() => this.scheduleTokenRefresh(), 3e4));
|
|
2045
2050
|
});
|
|
2046
2051
|
}, Math.min(t, 2147e6));
|
|
2047
2052
|
}
|
|
@@ -2055,7 +2060,7 @@ class Ee extends EventTarget {
|
|
|
2055
2060
|
this.options.reportSignalingDiagnostics === !1 || this.stopped || this.request("/v1/rtc/client/signaling-events", {
|
|
2056
2061
|
method: "POST",
|
|
2057
2062
|
body: JSON.stringify({ ...e, occurredAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
2058
|
-
}).catch((t) => this.emit("diagnosticserror",
|
|
2063
|
+
}).catch((t) => this.emit("diagnosticserror", x(t)));
|
|
2059
2064
|
}
|
|
2060
2065
|
emit(e, t) {
|
|
2061
2066
|
const i = { type: e, detail: t, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -2101,46 +2106,50 @@ function ne(a) {
|
|
|
2101
2106
|
}
|
|
2102
2107
|
async function Pe(a, e) {
|
|
2103
2108
|
const t = await a.getStats(), i = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
|
|
2104
|
-
let s = 0, o = 0, r = 0, c = 0, l = 0, d = 0, u = 0,
|
|
2109
|
+
let s = 0, o = 0, r = 0, c = 0, l = 0, d = 0, u = 0, k = 0, f = 0, v = 0, C = 0, y = 0, w = 0, p = 0, R = 0, P = null, L = null, I = null, A = null, S = null, E = null;
|
|
2105
2110
|
t.forEach((g) => {
|
|
2106
2111
|
n.set(g.id, g), g.type === "codec" && i.set(g.id, String(g.mimeType ?? ""));
|
|
2107
2112
|
}), t.forEach((g) => {
|
|
2108
|
-
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)),
|
|
2109
|
-
|
|
2113
|
+
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)), k = Math.max(k, Number(g.frameWidth ?? 0)), C = Math.max(C, Number(g.frameHeight ?? 0)), y += Number(g.framesDropped ?? 0), w += Number(g.freezeCount ?? 0), p += Number(g.totalFreezesDuration ?? 0), R += Number(g.concealedSamples ?? 0), S ||= i.get(String(g.codecId ?? "")) ?? null), g.type === "outbound-rtp" && !g.isRemote && (f = Math.max(f, Number(g.frameWidth ?? 0)), v = Math.max(v, Number(g.frameHeight ?? 0)), o += Number(g.bytesSent ?? 0), E ||= g.qualityLimitationReason ? String(g.qualityLimitationReason) : null, S ||= i.get(String(g.codecId ?? "")) ?? null), g.type === "candidate-pair" && (g.selected || g.nominated) && g.state === "succeeded") {
|
|
2114
|
+
P = Number.isFinite(g.currentRoundTripTime) ? Number(g.currentRoundTripTime) : P, L = Number.isFinite(g.availableOutgoingBitrate) ? Number(g.availableOutgoingBitrate) : L;
|
|
2110
2115
|
const q = n.get(g.localCandidateId);
|
|
2111
|
-
|
|
2116
|
+
I = q?.candidateType ? String(q.candidateType) : I, A = q?.protocol ? String(q.protocol) : A;
|
|
2112
2117
|
}
|
|
2113
2118
|
});
|
|
2114
|
-
const
|
|
2119
|
+
const m = Date.now(), h = e ? Math.max(1e-3, (m - e.timestamp) / 1e3) : 0, T = e ? Math.max(0, Math.round((s - e.inboundBytes) * 8 / h)) : null, M = e ? Math.max(0, Math.round((o - e.outboundBytes) * 8 / h)) : null, b = r + c, O = e ? Math.max(0, r - e.packetsLost) : r, V = e ? Math.max(0, c - e.packetsReceived) : c, N = O + V;
|
|
2115
2120
|
return {
|
|
2116
|
-
counters: { timestamp:
|
|
2121
|
+
counters: { timestamp: m, inboundBytes: s, outboundBytes: o, packetsLost: r, packetsReceived: c },
|
|
2117
2122
|
sample: {
|
|
2118
|
-
rttMs:
|
|
2123
|
+
rttMs: P === null ? null : P * 1e3,
|
|
2119
2124
|
jitterMs: d ? l / d * 1e3 : null,
|
|
2120
|
-
packetLossPct:
|
|
2121
|
-
packetLossCumulativePct:
|
|
2125
|
+
packetLossPct: N ? O / N * 100 : null,
|
|
2126
|
+
packetLossCumulativePct: b ? r / b * 100 : null,
|
|
2122
2127
|
packetsLost: r,
|
|
2123
2128
|
packetsReceived: c,
|
|
2124
|
-
inboundBitrateBps:
|
|
2125
|
-
outboundBitrateBps:
|
|
2126
|
-
availableOutgoingBitrateBps:
|
|
2129
|
+
inboundBitrateBps: T,
|
|
2130
|
+
outboundBitrateBps: M,
|
|
2131
|
+
availableOutgoingBitrateBps: L,
|
|
2127
2132
|
framesPerSecond: u || null,
|
|
2128
|
-
frameWidth:
|
|
2129
|
-
frameHeight:
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2133
|
+
frameWidth: k || null,
|
|
2134
|
+
frameHeight: C || null,
|
|
2135
|
+
inboundFrameWidth: k || null,
|
|
2136
|
+
inboundFrameHeight: C || null,
|
|
2137
|
+
outboundFrameWidth: f || null,
|
|
2138
|
+
outboundFrameHeight: v || null,
|
|
2139
|
+
framesDropped: y,
|
|
2140
|
+
freezeCount: w,
|
|
2141
|
+
freezeDurationMs: Math.round(p * 1e3),
|
|
2142
|
+
concealedSamples: R,
|
|
2143
|
+
codec: S,
|
|
2144
|
+
transportProtocol: A,
|
|
2145
|
+
candidateType: I,
|
|
2146
|
+
qualityLimitationReason: E
|
|
2138
2147
|
}
|
|
2139
2148
|
};
|
|
2140
2149
|
}
|
|
2141
2150
|
function Ae(a) {
|
|
2142
2151
|
let e = a.mediaScore == null ? 5 : Math.round(se(Number(a.mediaScore), 0, 5));
|
|
2143
|
-
const t =
|
|
2152
|
+
const t = W(a.packetLossPct) ?? 0, i = W(a.rttMs) ?? 0, n = W(a.jitterMs) ?? 0, s = String(a.connectionState ?? "unknown").toLowerCase();
|
|
2144
2153
|
["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));
|
|
2145
2154
|
const o = Number(se(4.5 - t * 0.09 - i * 11e-4 - n * 6e-3, 1, 4.5).toFixed(2));
|
|
2146
2155
|
return { networkScore: e, estimatedMos: o };
|
|
@@ -2152,20 +2161,20 @@ function Le() {
|
|
|
2152
2161
|
const a = Number(navigator.deviceMemory);
|
|
2153
2162
|
return Number.isFinite(a) ? a : null;
|
|
2154
2163
|
}
|
|
2155
|
-
function
|
|
2164
|
+
function W(a) {
|
|
2156
2165
|
const e = Number(a);
|
|
2157
2166
|
return Number.isFinite(e) ? e : null;
|
|
2158
2167
|
}
|
|
2159
2168
|
function se(a, e, t) {
|
|
2160
2169
|
return Math.min(t, Math.max(e, a));
|
|
2161
2170
|
}
|
|
2162
|
-
function
|
|
2171
|
+
function x(a) {
|
|
2163
2172
|
return a instanceof Error ? { name: a.name, message: a.message } : { name: "ServiceError", message: String(a ?? "Service request failed") };
|
|
2164
2173
|
}
|
|
2165
2174
|
function re(a, e) {
|
|
2166
2175
|
return a instanceof Error ? a : new Error(e);
|
|
2167
2176
|
}
|
|
2168
|
-
const De = "1.3.
|
|
2177
|
+
const De = "1.3.21";
|
|
2169
2178
|
export {
|
|
2170
2179
|
Z as AVATAR_TUNING_DEFAULTS,
|
|
2171
2180
|
Se as BACKGROUND_PRESETS,
|
|
@@ -2174,12 +2183,12 @@ export {
|
|
|
2174
2183
|
Ee as RtcIncomingClient,
|
|
2175
2184
|
qe as SPEAKER_NOISE_FLOOR,
|
|
2176
2185
|
Oe as SPEAKER_TAKEOVER_RATIO,
|
|
2177
|
-
|
|
2186
|
+
Ve as createAvatarPipeline,
|
|
2178
2187
|
je as createCallRecorder,
|
|
2179
2188
|
Ne as createPictureInPictureController,
|
|
2180
2189
|
Ue as createRtcClient,
|
|
2181
2190
|
Qe as createRtcIncomingClient,
|
|
2182
|
-
|
|
2191
|
+
xe as createVideoEffectsPipeline,
|
|
2183
2192
|
be as drawAvatar,
|
|
2184
2193
|
we as headPoseFromMatrix,
|
|
2185
2194
|
le as neutralRig,
|
package/dist/rtc-web.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(b,Q){typeof exports=="object"&&typeof module<"u"?Q(exports):typeof define=="function"&&define.amd?define(["exports"],Q):(b=typeof globalThis<"u"?globalThis:b||self,Q(b.StreamingCdnRtc={}))})(this,(function(b){"use strict";function z(a,e,t=.02,i=1.4){const n=a.filter(r=>r.level>=t);if(n.length===0)return null;const s=n.reduce((r,c)=>c.level>r.level?c:r),o=n.find(r=>r.participantId===e);return o?s.participantId===e?e:s.level>=o.level*i?s.participantId:e:s.participantId}function ee(a){const e=[];for(const t of a){if(String(t.type??"")!=="inbound-rtp"||String(t.kind??t.mediaType??"")!=="audio")continue;const i=Number(t.audioLevel??NaN);Number.isFinite(i)&&e.push({mid:typeof t.mid=="string"?t.mid:null,level:i})}return e}const j={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4},low:{active:!0,maxBitrate:35e4,maxFramerate:15,scaleResolutionDownBy:4},medium:{active:!0,maxBitrate:11e5,maxFramerate:24,scaleResolutionDownBy:2},high:{active:!0,maxBitrate:28e5,maxFramerate:30,scaleResolutionDownBy:1}},Te={warmupSamples:3,poorSamplesToStepDown:2,goodSamplesToStepUp:6,criticalLossPct:8,criticalRttMs:800,poorLossPct:3,poorRttMs:350,goodLossPct:1,goodRttMs:250,sustainRatio:.8};function be(a){const e=String(a??"").trim().toLowerCase();return!e||e==="none"?"":e==="cpu"||e==="bandwidth"?e:"other"}function Ce(a,e,t,i,n={}){const s={...Te,...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,S=be(e.qualityLimitationReason),f=e.packetLossPct??0,v=e.rttMs??0,E=e.availableOutgoingBitrateBps,y=j[a].maxBitrate,k=f>=s.criticalLossPct||v>=s.criticalRttMs||S==="cpu",p=k||f>=s.poorLossPct||v>=s.poorRttMs||S==="bandwidth",R=E==null||E>=y*s.sustainRatio,A=f<s.goodLossPct&&v<s.goodRttMs&&!S&&R,D=p?t+1:0,M=A?i+1:0,L={quality:a,poorSamples:D,goodSamples:M,samplesSeen:d,changed:!1};return l>c?{quality:o[c],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:u?L:k&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:D>=s.poorSamplesToStepDown&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:M>=s.goodSamplesToStepUp&&l<c?{quality:o[l+1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:L}function Re(a,e){const t=a instanceof Map?a:new Map(a);let i=0,n=0,s=null,o=null,r=null;for(const d of t.values()){const u=String(d.type??"");if(u==="outbound-rtp"&&String(d.kind??"")==="video"&&(i+=Number(d.packetsSent??0),s=d.qualityLimitationReason??s),u==="remote-inbound-rtp"&&String(d.kind??"")==="video"){n+=Number(d.packetsLost??0);const S=Number(d.roundTripTime??NaN);Number.isFinite(S)&&(o=S*1e3)}if(u==="candidate-pair"&&(d.nominated===!0||String(d.state??"")==="succeeded")){const S=Number(d.currentRoundTripTime??NaN);o==null&&Number.isFinite(S)&&(o=S*1e3);const f=Number(d.availableOutgoingBitrate??NaN);Number.isFinite(f)&&(r=f)}}const c={packetsSent:i,packetsLost:n,timestamp:Date.now()};let l=null;if(e){const d=i-e.packetsSent,u=n-e.packetsLost;d>0&&u>=0&&(l=u/(d+u)*100)}return{sample:{packetLossPct:l,rttMs:o,availableOutgoingBitrateBps:r,qualityLimitationReason:s},counters:c}}const Ie={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4}},Ee={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1};class B{options;socket=null;localStream=null;screenStream=null;peers=new Map;joinPromise=null;resolveJoin=null;rejectJoin=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;qualityTimer=null;speakerTimer=null;activeSpeaker=null;heartbeatDeadline=0;quality;preferredVideoCodec;signalQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.preferredVideoCodec=e.preferredVideoCodec??"auto"}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(!this.joined){if(this.joinPromise)return this.joinPromise;this.joinPromise=this.connect();try{await this.joinPromise}finally{this.joinPromise=null}}}async leave(){this.stopped=!0,this.clearConnectionTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null;for(const e of this.peers.values())e.connection.close();this.peers.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(r=>r.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(r=>r.readyState==="live")||await this.replaceVideoTrack(n);return}for(const r of this.peers.values()){const c=r.connection.getSenders().find(l=>l.track?.kind===s);c?await c.replaceTrack(n):r.connection.addTrack(n,this.localStream)}await this.applyVideoQuality()}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";for(const t of this.peers.values())t.quality=B.clampToCeiling(t.quality,e),t.poorSamples=0,t.goodSamples=0;await this.applyVideoQuality()}static clampToCeiling(e,t){if(t==="audio")return e;const i=["low","medium","high"];return i.indexOf(e)>i.indexOf(t)?t:e}async setPreferredVideoCodec(e){this.preferredVideoCodec=e;for(const t of this.peers.values())this.applyCodecPreference(t.connection);await this.renegotiateAll()}getParticipants(){return[...this.peers.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getRemoteStreams(){return[...this.peers.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.peers.values().next().value?.connection??null}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async connect(){const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1)}async openSocket(e){const{credential:t}=this.options,i=new URL(t.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.startQualitySampling(),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),this.stopQualitySampling(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.resetPeers(),this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0;const n=Array.isArray(t.peers)?t.peers:[];for(const s of n)this.ensurePeer(s);this.resolveJoin?.();for(const s of n)this.shouldInitiateOffer(s)&&await this.createOffer(s);return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.ensurePeer(t.sender),this.shouldInitiateOffer(t.sender)&&await this.createOffer(t.sender);return}if(i==="rtc.offer"&&t.sender&&t.description){const n=this.ensurePeer(t.sender);n.offerTask&&await n.offerTask;const s=n.connection.signalingState!=="stable",o=!this.shouldInitiateOffer(t.sender);if(s&&!o)return;s&&await n.connection.setLocalDescription({type:"rollback"}),await n.connection.setRemoteDescription(t.description);const r=await n.connection.createAnswer();await n.connection.setLocalDescription(r),this.send({type:"rtc.answer",targetParticipantId:t.sender.participantId,negotiationId:t.negotiationId,description:n.connection.localDescription});return}if(i==="rtc.answer"&&t.sender&&t.description){const n=this.peers.get(t.sender.participantId);if(!n||n.connection.signalingState!=="have-local-offer"||t.negotiationId&&n.activeNegotiationId&&t.negotiationId!==n.activeNegotiationId)return;await n.connection.setRemoteDescription(t.description),n.activeNegotiationId=null;return}if(i==="rtc.ice"&&t.sender&&t.candidate){await this.ensurePeer(t.sender).connection.addIceCandidate(t.candidate).catch(()=>{});return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removePeer(t.sender.participantId);return}i==="rtc.chat"?this.options.onEvent("chatupdate",t):i==="rtc.control"&&this.options.onEvent("control",t)}}ensurePeer(e){const t=this.peers.get(e.participantId);if(t)return t;const i=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"}),n=new MediaStream,s={identity:e,connection:i,stream:n,makingOffer:!1,offerTask:null,activeNegotiationId:null,quality:B.startingQuality(this.quality),poorSamples:0,goodSamples:0,samplesSeen:0,counters:null};this.peers.set(e.participantId,s);for(const c of this.localStream?.getAudioTracks()??[])i.addTrack(c,this.localStream);const o=this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")?this.screenStream:this.localStream,r=o?.getVideoTracks()[0];return r&&o&&(s.videoSender=i.addTrack(r,o)),!this.localStream?.getAudioTracks().length&&this.options.receiveAudio&&i.addTransceiver("audio",{direction:"recvonly"}),!r&&this.options.receiveVideo&&(s.videoTransceiver=i.addTransceiver("video",{direction:"recvonly"}),s.videoSender=s.videoTransceiver.sender),this.applyCodecPreference(i),this.applyVideoQuality(i),i.addEventListener("icecandidate",c=>{c.candidate&&this.send({type:"rtc.ice",targetParticipantId:e.participantId,candidate:c.candidate.toJSON()})}),i.addEventListener("track",c=>{n.getTracks().some(l=>l.id===c.track.id)||n.addTrack(c.track),this.options.onEvent("remotestream",{participant:e,stream:n,track:c.track})}),i.addEventListener("connectionstatechange",()=>{["failed","closed"].includes(i.connectionState)&&this.removePeer(e.participantId),this.options.onEvent("peerconnectionstate",{participant:e,state:i.connectionState})}),s}async createOffer(e){const t=this.ensurePeer(e);if(t.offerTask)return t.offerTask;if(t.connection.signalingState!=="stable")return;const i=this.performOffer(t);t.offerTask=i;try{await i}finally{t.offerTask===i&&(t.offerTask=null)}}async performOffer(e){e.makingOffer=!0;try{const t=await e.connection.createOffer({offerToReceiveAudio:this.options.receiveAudio,offerToReceiveVideo:this.options.receiveVideo});if(e.connection.signalingState!=="stable")return;await e.connection.setLocalDescription(t);const i=te();e.activeNegotiationId=i,this.send({type:"rtc.offer",targetParticipantId:e.identity.participantId,negotiationId:i,description:e.connection.localDescription})}finally{e.makingOffer=!1}}shouldInitiateOffer(e){return this.options.credential.participantId.localeCompare(e.participantId)<0}async renegotiateAll(){for(const e of this.peers.values())await this.createOffer(e.identity)}async replaceVideoTrack(e){let t=!1;for(const i of this.peers.values()){const n=i.videoSender??i.connection.getSenders().find(o=>o.track?.kind==="video")??i.connection.getTransceivers().find(o=>o.receiver.track.kind==="video")?.sender,s=i.videoTransceiver??i.connection.getTransceivers().find(o=>o.sender===n);if(n){const o=e?this.options.receiveVideo?"sendrecv":"sendonly":this.options.receiveVideo?"recvonly":"inactive";s&&s.direction!==o&&(s.direction=o,t=!0),await n.replaceTrack(e),i.videoSender=n,s&&(i.videoTransceiver=s)}else e&&(i.videoSender=i.connection.addTrack(e,this.screenStream??this.localStream??new MediaStream([e])),t=!0)}t&&await this.renegotiateAll(),await this.applyVideoQuality()}async applyVideoQuality(e){const t=e?[...this.peers.values()].filter(i=>i.connection===e):[...this.peers.values()];for(const i of t)await this.applyPeerVideoQuality(i)}async applyPeerVideoQuality(e){const t=this.screenStream?.getVideoTracks()[0]?.id;for(const i of e.connection.getSenders().filter(n=>n.track?.kind==="video")){const n=!!(t&&i.track?.id===t),s=n?Ee:this.quality==="audio"?Ie.audio:j[e.quality],o=i.getParameters(),r=o.encodings?.length?o.encodings:[{}];o.encodings=r.map(c=>({...c,...s})),o.degradationPreference=n?"maintain-resolution":"maintain-framerate",await i.setParameters(o)}}startQualitySampling(){this.stopQualitySampling(),this.qualityTimer=setInterval(()=>{this.samplePeerQuality()},5e3),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopQualitySampling(){this.qualityTimer&&clearInterval(this.qualityTimer),this.qualityTimer=null,this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=[];for(const i of[...this.peers.values()])if(i.connection.connectionState==="connected")try{const n=await i.connection.getStats();for(const s of ee(n.values()))e.push({participantId:i.identity.participantId,level:s.level})}catch{}const t=z(e,this.activeSpeaker);t!==this.activeSpeaker&&(this.activeSpeaker=t,this.options.onEvent("activespeaker",{participantId:t}))}async samplePeerQuality(){if(this.quality!=="audio")for(const e of[...this.peers.values()]){if(e.connection.connectionState!=="connected")continue;let t;try{t=await e.connection.getStats()}catch{continue}const{sample:i,counters:n}=Re(t,e.counters);e.counters=n;const s=Ce(e.quality,i,e.poorSamples,e.goodSamples,{samplesSeen:e.samplesSeen,ceiling:this.quality});e.poorSamples=s.poorSamples,e.goodSamples=s.goodSamples,e.samplesSeen=s.samplesSeen,s.changed&&(e.quality=s.quality,await this.applyPeerVideoQuality(e).catch(()=>{}),this.options.onEvent("peerquality",{participantId:e.identity.participantId,quality:s.quality}))}}static startingQuality(e){return e==="low"?"low":"medium"}applyCodecPreference(e){if(this.preferredVideoCodec==="auto"||!globalThis.RTCRtpReceiver?.getCapabilities)return;const t=RTCRtpReceiver.getCapabilities("video")?.codecs??[],i=`video/${this.preferredVideoCodec}`,n=[...t.filter(s=>s.mimeType.toLowerCase()===i),...t.filter(s=>s.mimeType.toLowerCase()!==i)];for(const s of e.getTransceivers())s.receiver.track.kind==="video"&&typeof s.setCodecPreferences=="function"&&s.setCodecPreferences(n)}removePeer(e){const t=this.peers.get(e);t&&(t.connection.close(),this.peers.delete(e),this.options.onEvent("participantleft",t.identity))}resetPeers(){for(const e of this.peers.values())e.connection.close();this.peers.clear()}startHeartbeat(e){this.stopHeartbeat(),this.stopQualitySampling(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}clearConnectionTimers(){this.stopHeartbeat(),this.stopQualitySampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??te(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function te(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}const _={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1},ie=[{rid:"a",maxBitrate:j.high.maxBitrate,maxFramerate:j.high.maxFramerate},{rid:"b",maxBitrate:j.medium.maxBitrate,maxFramerate:j.medium.maxFramerate,scaleResolutionDownBy:2},{rid:"c",maxBitrate:j.low.maxBitrate,maxFramerate:j.low.maxFramerate,scaleResolutionDownBy:4}],ne={high:"a",medium:"b",low:"c"};class Me{options;apiBase;socket=null;connection=null;sessionId=null;localStream=null;screenStream=null;remotes=new Map;midToParticipant=new Map;pendingAnnouncements=new Map;videoSender=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;speakerTimer=null;activeSpeaker=null;quality;resolveJoin=null;rejectJoin=null;signalQueue=Promise.resolve();negotiationQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.apiBase=new URL(e.credential.signalingUrl,globalThis.location?.href).origin}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(this.joined)return;const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1),await this.publish(),this.startSpeakerSampling(),this.announceTracks();const t=[...this.pendingAnnouncements.values()];this.pendingAnnouncements.clear();for(const i of t)await this.handleTrackAnnouncement(i.sender,i.detail)}async leave(){this.stopped=!0,this.clearTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null,await this.closePublishedTracks().catch(()=>{}),this.connection?.close(),this.connection=null,this.sessionId=null,this.remotes.clear(),this.midToParticipant.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(c=>c.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")||await this.replaceVideoTrack(n);return}const r=this.connection?.getSenders().find(c=>c.track?.kind==="audio");r&&await r.replaceTrack(n)}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";await this.applyVideoQuality()}async setPreferredVideoCodec(e){}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getParticipants(){return[...this.remotes.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}getRemoteStreams(){return[...this.remotes.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.connection}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async openSocket(e){const{credential:t}=this.options,i=new URL(t.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0,this.resolveJoin?.();return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.sessionId&&this.announceTracks();return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removeRemote(t.sender.participantId);return}if(i==="rtc.control"){if(String(t.action??"")==="sfu.tracks"&&t.sender){await this.handleTrackAnnouncement(t.sender,t.detail);return}this.options.onEvent("control",t);return}i==="rtc.chat"&&this.options.onEvent("chatupdate",t)}}async publish(){const e=[...this.localStream?.getAudioTracks()??[],...this.localStream?.getVideoTracks()??[]],t=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"});this.connection=t,t.addEventListener("track",r=>this.handleRemoteTrack(r)),t.addEventListener("connectionstatechange",()=>{this.options.onEvent("peerconnectionstate",{participant:this.selfIdentity(),state:t.connectionState})});const i=await this.sfuFetch("POST","/v1/rtc/sfu/sessions");if(this.sessionId=i.sessionId,e.length===0)return;const n=e.map(r=>t.addTransceiver(r,{direction:"sendonly",streams:this.localStream?[this.localStream]:[],...r.kind==="video"&&this.simulcastEnabled()?{sendEncodings:ie.map(c=>({...c}))}:{}})),s=await t.createOffer();await t.setLocalDescription(s);const o=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(i.sessionId)}/tracks`,{sessionDescription:t.localDescription,tracks:n.map(r=>({location:"local",mid:r.mid,trackName:this.trackName(r.sender.track)}))});o.sessionDescription&&await t.setRemoteDescription(o.sessionDescription),this.videoSender=t.getSenders().find(r=>r.track?.kind==="video")??null,await this.applyVideoQuality()}trackName(e){return`${this.options.credential.participantId}/${e.kind}`}announceTracks(){if(!this.sessionId)return;const e=[...(this.localStream?.getAudioTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!1})),...(this.localStream?.getVideoTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:this.simulcastEnabled()}))];e.length!==0&&this.sendControl("sfu.tracks",{sessionId:this.sessionId,tracks:e})}async handleTrackAnnouncement(e,t){const i=String(t?.sessionId??""),n=(t?.tracks??[]).map(r=>({trackName:String(r.trackName??""),simulcast:r.simulcast===!0})).filter(r=>!!r.trackName),s=n.map(r=>r.trackName);if(!i||s.length===0||e.participantId===this.options.credential.participantId)return;if(!this.connection||!this.sessionId){this.pendingAnnouncements.set(e.participantId,{sender:e,detail:t??{}});return}const o=this.remotes.get(e.participantId)??{identity:e,stream:new MediaStream,sessionId:i,trackNames:[],simulcastNames:new Set,mids:new Map};o.sessionId=i;for(const r of n)r.simulcast&&o.simulcastNames.add(r.trackName);this.remotes.set(e.participantId,o),this.negotiationQueue=this.negotiationQueue.then(async()=>{const r=s.filter(c=>!o.trackNames.includes(c));r.length!==0&&await this.subscribe(o,r)}).catch(r=>{this.options.onEvent("signalingerror",{message:r instanceof Error?r.message:"SFU subscribe failed"})}),await this.negotiationQueue}async subscribe(e,t){const i=this.connection;if(!i||!this.sessionId||t.length===0)return;const n=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks`,{tracks:t.map(s=>({location:"remote",sessionId:e.sessionId,trackName:s,...e.simulcastNames.has(s)?{simulcast:{preferredRid:"a",priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}:{}}))});for(const s of n.tracks??[])s.mid&&(e.mids.set(s.mid,String(s.trackName??"")),this.midToParticipant.set(s.mid,e.identity.participantId));if(e.trackNames=[...new Set([...e.trackNames,...t])],n.requiresImmediateRenegotiation&&n.sessionDescription){await i.setRemoteDescription(n.sessionDescription);const s=await i.createAnswer();await i.setLocalDescription(s),await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`,{sessionDescription:i.localDescription})}}handleRemoteTrack(e){const t=e.transceiver?.mid??null,i=t?this.midToParticipant.get(t):void 0,n=i?this.remotes.get(i):void 0;n&&(n.stream.getTracks().some(s=>s.id===e.track.id)||n.stream.addTrack(e.track),this.options.onEvent("remotestream",{participant:n.identity,stream:n.stream,track:e.track}))}removeRemote(e){const t=this.remotes.get(e);if(t){for(const i of t.mids.keys())this.midToParticipant.delete(i);this.remotes.delete(e),this.options.onEvent("participantleft",t.identity)}}async setRemoteQuality(e,t){const i=this.remotes.get(e);if(!i||!this.sessionId)return;const n=[...i.mids.entries()].find(([,s])=>i.simulcastNames.has(s))?.[0];n&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/update`,{tracks:[{mid:n,simulcast:{preferredRid:ne[t],priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}]})}async replaceVideoTrack(e){this.videoSender&&(await this.videoSender.replaceTrack(e),await this.applyVideoQuality())}simulcastEnabled(){return this.options.simulcast!==!1}async applyVideoQuality(){const e=this.videoSender;if(!e)return;const t=this.screenStream?.getVideoTracks()[0]?.id,i=!!(t&&e.track?.id===t),n=e.getParameters(),s=n.encodings?.length?n.encodings:[{}];if(s.length>1){const o=this.quality==="audio"?null:ne[this.quality];n.encodings=s.map(r=>{if(i)return{...r,active:r.rid==="a",...r.rid==="a"?{maxBitrate:_.maxBitrate,maxFramerate:_.maxFramerate}:{}};const c=ie.find(d=>d.rid===r.rid),l=o!=null&&String(r.rid??"")>=o;return{...r,...c??{},active:l}})}else{const o=this.quality==="audio"?j.audio:j[this.quality],r=i?_:o;n.encodings=s.map(c=>({...c,...r}))}n.degradationPreference=i?"maintain-resolution":"maintain-framerate",await e.setParameters(n)}async closePublishedTracks(){const e=this.connection;if(!e||!this.sessionId)return;const t=e.getTransceivers().filter(i=>i.sender.track&&i.mid).map(i=>({mid:i.mid}));t.length!==0&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/close`,{tracks:t,force:!0})}selfIdentity(){return{participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId??this.options.credential.participantId,displayName:this.options.credential.externalUserId??this.options.credential.participantId,platform:"web"}}async sfuFetch(e,t,i){const n=await fetch(`${this.apiBase}${t}`,{method:e,headers:{authorization:`Bearer ${this.options.credential.signalingToken}`,"content-type":"application/json"},body:i===void 0?void 0:JSON.stringify(i)});if(!n.ok)throw new Error(`SFU request failed with HTTP ${n.status}`);return n.json()}startHeartbeat(e){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}startSpeakerSampling(){this.stopSpeakerSampling(),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopSpeakerSampling(){this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=this.connection;if(!e||e.connectionState!=="connected")return;let t;try{t=await e.getStats()}catch{return}const i=[];for(const s of ee(t.values())){const o=s.mid?this.midToParticipant.get(s.mid):void 0;o&&i.push({participantId:o,level:s.level})}const n=z(i,this.activeSpeaker);n!==this.activeSpeaker&&(this.activeSpeaker=n,this.options.onEvent("activespeaker",{participantId:n}))}clearTimers(){this.stopHeartbeat(),this.stopSpeakerSampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).then(()=>this.announceTracks()).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??Pe(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function Pe(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function Ae(a,e={}){const t=e.document??globalThis.document,i=!!(t&&t.pictureInPictureEnabled);let n=null;function s(){return n||(n=t.createElement("video"),n.muted=!0,n.playsInline=!0,n.style.position="fixed",n.style.width="1px",n.style.height="1px",n.style.opacity="0",n.style.pointerEvents="none",n.addEventListener("leavepictureinpicture",()=>e.onLeave?.()),t.body.appendChild(n),n)}function o(r){const c=a.getRemoteStreams();return r?c.find(l=>l.participant.participantId===r)?.stream??null:c[0]?.stream??a.getLocalStream()}return{supported:i,async enter(r){if(!i)throw new Error("This browser does not support picture-in-picture");const c=o(r);if(!c)throw new Error("No video stream is available for picture-in-picture");const l=s();l.srcObject!==c&&(l.srcObject=c),await l.play().catch(()=>{}),await l.requestPictureInPicture()},async exit(){t.pictureInPictureElement&&typeof t.exitPictureInPicture=="function"&&await t.exitPictureInPicture()},dispose(){n?.remove(),n=null}}}function Le(a,e){const t=e.document??globalThis.document,i=e.fetchImplementation??globalThis.fetch.bind(globalThis),n=new URL(e.credential.signalingUrl,globalThis.location?.href).origin,s=e.mimeType??"video/webm",o=e.height??720,r=Math.round(o*16/9);let c=null,l=null,d=null,u=null,S=0,f=[],v=!1;async function E(y,k,p,R="application/json"){const A=await i(`${n}${k}`,{method:y,headers:{authorization:`Bearer ${e.credential.signalingToken}`,"content-type":R},body:p===void 0?void 0:R==="application/json"?JSON.stringify(p):p});if(!A.ok)throw new Error(`Recording request failed with HTTP ${A.status}`);return A.json()}return{get recording(){return v},async start(){if(v)return;u=(await E("POST","/v1/rtc/recordings")).recordingId;const k=t.createElement("canvas");k.width=r,k.height=o;const p=k.getContext("2d"),R=new Map,A=(T,I)=>{let C=R.get(T);return C||(C=t.createElement("video"),C.muted=!0,C.playsInline=!0,C.srcObject=I,C.play().catch(()=>{}),R.set(T,C)),C};l=setInterval(()=>{const T=a.getRemoteStreams(),I=a.getLocalStream(),C=[...T.map(U=>({key:U.participant.participantId,stream:U.stream,label:U.participant.displayName})),...I?[{key:"local",stream:I,label:"me"}]:[]];p.fillStyle="#101418",p.fillRect(0,0,r,o);const N=Math.max(1,Math.ceil(Math.sqrt(C.length))),g=Math.max(1,Math.ceil(C.length/N)),q=r/N,F=o/g;C.forEach((U,ke)=>{const we=A(U.key,U.stream),X=ke%N*q,Z=Math.floor(ke/N)*F;we.videoWidth>0&&p.drawImage(we,X+2,Z+2,q-4,F-4),p.fillStyle="rgba(0,0,0,.6)",p.fillRect(X+8,Z+F-30,Math.min(q-16,12+U.label.length*9),22),p.fillStyle="#fff",p.font="14px system-ui",p.fillText(U.label,X+14,Z+F-14)})},66);const D=k.captureStream(15);d=new AudioContext;const M=d.createMediaStreamDestination(),L=new Set,w=()=>{const T=[...a.getRemoteStreams().map(I=>I.stream),...a.getLocalStream()?[a.getLocalStream()]:[]];for(const I of T)for(const C of I.getAudioTracks())L.has(C.id)||(L.add(C.id),d.createMediaStreamSource(new MediaStream([C])).connect(M))};w();const P=setInterval(w,1e3),m=()=>clearInterval(P);for(const T of M.stream.getAudioTracks())D.addTrack(T);c=(e.mediaRecorderFactory??((T,I)=>new MediaRecorder(T,I)))(D,{mimeType:s}),f=[],c.addEventListener("dataavailable",T=>{T.data&&T.data.size>0&&f.push(T.data)}),c.addEventListener("stop",m),c.start(1e3),S=Date.now(),v=!0},async stop(){if(!v||!c||!u)throw new Error("The recorder is not running");v=!1;const y=c,k=new Promise(M=>y.addEventListener("stop",()=>M(),{once:!0}));y.stop(),await k,l&&clearInterval(l),l=null,await d?.close().catch(()=>{}),d=null;const p=new Blob(f,{type:s});f=[];const R=Math.max(1,Math.round((Date.now()-S)/1e3)),A=await E("PUT",`/v1/rtc/recordings/${encodeURIComponent(u)}/media`,p,s);await E("POST",`/v1/rtc/recordings/${encodeURIComponent(u)}/complete`,{durationSeconds:R});const D={recordingId:u,sizeBytes:A.sizeBytes,durationSeconds:R};return u=null,c=null,D},dispose(){l&&clearInterval(l),l=null;try{c?.stop()}catch{}c=null,d?.close().catch(()=>{}),d=null,v=!1}}}const ae=["none","blur","aurora","cybergrid","bokeh","sunset","image"],se=["none","snow","confetti","vignette"],$={aurora(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#050b1e"),n.addColorStop(1,"#0b2540"),a.fillStyle=n,a.fillRect(0,0,e,t);for(let s=0;s<3;s+=1){a.beginPath();for(let o=0;o<=e;o+=16){const r=t*.35+s*40+Math.sin(o/140+i/900+s)*60;o===0?a.moveTo(o,r):a.lineTo(o,r)}a.lineTo(e,0),a.lineTo(0,0),a.closePath(),a.fillStyle=`hsla(${140+s*40+Math.sin(i/1300)*20}, 80%, 55%, 0.14)`,a.fill()}},cybergrid(a,e,t,i){a.fillStyle="#0a0118",a.fillRect(0,0,e,t),a.strokeStyle="rgba(255,0,180,0.5)",a.lineWidth=1.5;const n=t*.45;for(let o=0;o<12;o+=1){const r=n+(o*34+i/18)%(t-n);a.beginPath(),a.moveTo(0,r),a.lineTo(e,r),a.stroke()}for(let o=-8;o<=8;o+=1)a.beginPath(),a.moveTo(e/2+o*24,n),a.lineTo(e/2+o*e*.12,t),a.stroke();const s=a.createLinearGradient(0,t*.1,0,n);s.addColorStop(0,"#ffd34d"),s.addColorStop(1,"#ff2d78"),a.fillStyle=s,a.beginPath(),a.arc(e/2,n,t*.22,Math.PI,0),a.fill()},bokeh(a,e,t,i){a.fillStyle="#14100c",a.fillRect(0,0,e,t);for(let n=0;n<26;n+=1){const s=(Math.sin(n*12.9898)*.5+.5)*e,o=(Math.sin(n*78.233)*.5+.5)*t+Math.sin(i/1500+n)*12,r=14+n%5*12;a.fillStyle=`hsla(${35+n%4*12}, 90%, 60%, ${.1+n%3*.05})`,a.beginPath(),a.arc(s,o,r,0,Math.PI*2),a.fill()}},sunset(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#2b1055"),n.addColorStop(.55,"#d1548a"),n.addColorStop(1,"#ffb36b"),a.fillStyle=n,a.fillRect(0,0,e,t),a.fillStyle="#ffdf8e",a.beginPath(),a.arc(e*.5,t*.62+Math.sin(i/4e3)*4,t*.13,0,Math.PI*2),a.fill(),a.fillStyle="rgba(20,8,40,0.85)",a.fillRect(0,t*.78,e,t*.22)}};function De(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.background??"none",l=e.overlay??"none",d=e.backgroundImage??null,u=e.segmenter??null,S=null,f=!1;const v=t.createElement("video");v.muted=!0,v.playsInline=!0,v.srcObject=new MediaStream([r]),v.play().catch(()=>{});const E=t.createElement("canvas");E.width=i,E.height=n;const y=E.getContext("2d"),k=t.createElement("canvas");k.width=i,k.height=n;const p=k.getContext("2d"),R=()=>Array.from({length:80},(m,h)=>({x:(Math.sin(h*12.9898)*.5+.5)*i,y:(Math.sin(h*78.233)*.5+.5)*n,speed:.6+h%5*.5,drift:Math.sin(h)*.6,size:2+h%4,hue:h*47%360}));let A=R();const D=()=>{if(s)return;const m=v.videoWidth,h=v.videoHeight;if(!m||!h)return;const T=Math.min(1,1280/Math.max(m,h)),I=Math.round(m*T),C=Math.round(h*T);I===i&&C===n||(i=I,n=C,E.width=i,E.height=n,k.width=i,k.height=n,A=R())};v.addEventListener?.("loadedmetadata",D),v.addEventListener?.("resize",D);function M(){if(S){p.clearRect(0,0,i,n),p.drawImage(S,0,0,i,n),p.globalCompositeOperation="source-in",p.drawImage(v,0,0,i,n),p.globalCompositeOperation="source-over",y.drawImage(k,0,0);return}const m=i*.42,h=m*9/16,T=i-m-24,I=n-h-24;y.save(),y.shadowColor="rgba(0,0,0,.5)",y.shadowBlur=24,y.beginPath(),y.roundRect(T,I,m,h,14),y.clip(),y.drawImage(v,T,I,m,h),y.restore()}function L(m){if(c==="none"?y.drawImage(v,0,0,i,n):c==="blur"?(y.filter="blur(14px)",y.drawImage(v,-16,-16,i+32,n+32),y.filter="none",S&&M()):c==="image"&&d?(y.drawImage(d,0,0,i,n),M()):(($[c]??$.aurora)(y,i,n,m),M()),l==="snow"||l==="confetti")for(const h of A)h.y+=h.speed*(l==="confetti"?1.6:1),h.x+=h.drift,h.y>n&&(h.y=-6,h.x=Math.random()*i),y.fillStyle=l==="snow"?"rgba(255,255,255,.85)":`hsl(${h.hue}, 90%, 60%)`,l==="confetti"?y.fillRect(h.x,h.y,h.size+2,h.size):(y.beginPath(),y.arc(h.x,h.y,h.size/2,0,Math.PI*2),y.fill());else if(l==="vignette"){const h=y.createRadialGradient(i/2,n/2,n*.4,i/2,n/2,n*.85);h.addColorStop(0,"rgba(0,0,0,0)"),h.addColorStop(1,"rgba(0,0,0,0.55)"),y.fillStyle=h,y.fillRect(0,0,i,n)}}const w=setInterval(()=>{L(Date.now()),u&&!f&&c!=="none"&&(f=!0,u.segment(v).then(m=>{S=m}).catch(()=>{S=null}).finally(()=>{f=!1}))},Math.round(1e3/o)),P=E.captureStream(o);return{track:P.getVideoTracks()[0],stream:P,setBackground(m,h){c=ae.includes(m)?m:"none",h!==void 0&&(d=h),c==="none"&&(S=null)},setOverlay(m){l=se.includes(m)?m:"none"},setSegmenter(m){u=m,m||(S=null)},dispose(){clearInterval(w),u?.close?.();for(const m of P.getTracks())m.stop();v.srcObject=null}}}function H(){return{blinkLeft:0,blinkRight:0,jawOpen:0,smile:0,browUp:0,pupilX:0,pupilY:0,headYaw:0,headPitch:0,headRoll:0,tracked:!1}}function O(a,e){const t=a.find(i=>i.categoryName===e);return t?Math.max(0,Math.min(1,t.score)):0}function re(a){if(!a||a.length<16)return{yaw:0,pitch:0,roll:0};a[0],a[4];const e=a[8],t=a[1],i=a[5],n=a[9],s=a[10],o=Math.atan2(e,s),r=Math.asin(Math.max(-1,Math.min(1,-n))),c=Math.atan2(t,i);return{yaw:o,pitch:r,roll:c}}function oe(a,e,t=1){if(!a||a.length===0)return H();const i=re(e),n=(O(a,"eyeLookOutRight")+O(a,"eyeLookInLeft"))/2,s=(O(a,"eyeLookOutLeft")+O(a,"eyeLookInRight"))/2,o=(O(a,"eyeLookUpLeft")+O(a,"eyeLookUpRight"))/2,r=(O(a,"eyeLookDownLeft")+O(a,"eyeLookDownRight"))/2,c=(O(a,"mouthSmileLeft")+O(a,"mouthSmileRight"))/2,l=(O(a,"mouthFrownLeft")+O(a,"mouthFrownRight"))/2,d=O(a,"eyeBlinkLeft"),u=O(a,"eyeBlinkRight"),S=Math.min(1,Math.abs(i.yaw)/.35)*Math.max(0,Math.min(1,t)),f=(d+u)/2;return{blinkLeft:d+(f-d)*S,blinkRight:u+(f-u)*S,jawOpen:O(a,"jawOpen"),smile:c-l,browUp:(O(a,"browInnerUp")+O(a,"browOuterUpLeft")+O(a,"browOuterUpRight"))/3,pupilX:n-s,pupilY:r-o,headYaw:i.yaw,headPitch:i.pitch,headRoll:i.roll,tracked:!0}}function J(a,e,t=.4){const i=(n,s)=>n+(s-n)*t;return{blinkLeft:i(a.blinkLeft,e.blinkLeft),blinkRight:i(a.blinkRight,e.blinkRight),jawOpen:i(a.jawOpen,e.jawOpen),smile:i(a.smile,e.smile),browUp:i(a.browUp,e.browUp),pupilX:i(a.pupilX,e.pupilX),pupilY:i(a.pupilY,e.pupilY),headYaw:i(a.headYaw,e.headYaw),headPitch:i(a.headPitch,e.headPitch),headRoll:i(a.headRoll,e.headRoll),tracked:e.tracked}}function ce(a,e,t,i,n){n||(a.fillStyle="#101822",a.fillRect(0,0,t,i));const s=n?n.x+e.headYaw*n.radius*.1:t/2+e.headYaw*t*.18,o=n?n.y+e.headPitch*n.radius*.1:i/2+e.headPitch*i*.18,r=n?n.radius:Math.min(t,i)*.28;a.save(),a.translate(s,o),a.rotate(e.headRoll*.6);const c=Math.max(-1.35,Math.min(1.35,e.headYaw)),l=Math.max(-.9,Math.min(.9,e.headPitch)),d=Math.sin(c),u=L=>Math.sin(L+c)*r,S=L=>Math.cos(L+c),f=Math.sin(l)*r*.22;a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.35,-r*.25,r*.98,r*.95,0,0,Math.PI*2),a.fill(),a.fillStyle=e.tracked?"#f2c9a0":"#5a6470",a.beginPath(),a.ellipse(0,0,r*(1-Math.abs(d)*.12),r*1.12,0,0,Math.PI*2),a.fill(),a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.28,-r*.72+f*.4,r*.92,r*.5,0,Math.PI,Math.PI*2),a.fill();const v=0;a.fillStyle="#e5b58a",a.beginPath();const E=u(v)*.9,y=r*.12+f;a.ellipse(E+d*r*.14,y,r*.11,r*.14,0,0,Math.PI*2),a.fill();for(const L of[-1,1]){const w=L*.42,P=S(w);if(P<=.12)continue;const m=u(w)*.82,h=Math.max(.2,P),T=L<0?e.blinkLeft:e.blinkRight,I=Math.max(.06,1-T),C=e.browUp*r*.14;a.strokeStyle="#4a2e18",a.lineWidth=r*.07,a.lineCap="round",a.beginPath(),a.moveTo(m-r*.2*h,-r*.42-C+f),a.quadraticCurveTo(m,-r*.52-C+f,m+r*.2*h,-r*.44-C+f),a.stroke();const N=-r*.18+f;a.fillStyle="#ffffff",a.beginPath(),a.ellipse(m,N,r*.16*h,r*.12*I,0,0,Math.PI*2),a.fill(),a.fillStyle="#22303c",a.beginPath(),a.ellipse(m+e.pupilX*r*.07*h,N+e.pupilY*r*.05,r*.06*h,r*.06*I,0,0,Math.PI*2),a.fill()}const k=Math.max(.25,S(0)),p=u(0)*.7,R=r*.55*k,A=r*(.04+e.jawOpen*.34),D=e.smile*r*.12,M=r*.42+f;a.fillStyle="#8c2f2f",a.beginPath(),a.moveTo(p-R/2,M-D),a.quadraticCurveTo(p,M+A,p+R/2,M-D),a.quadraticCurveTo(p,M-A*.25,p-R/2,M-D),a.closePath(),a.fill(),a.restore(),!e.tracked&&!n&&(a.fillStyle="#9fb0bd",a.font=`${Math.round(i*.045)}px system-ui`,a.textAlign="center",a.fillText("No face detected",t/2,i*.9),a.textAlign="left")}const W={mouthGain:1.7,blinkGain:1,eyeSync:1,smoothing:.4,avatarScale:1,avatarLift:0,lostHoldMs:400};function G(){return{render(a,e,t,i,n){ce(a,e,t,i,n??void 0)}}}const Y=a=>Math.max(0,Math.min(1,a));function Oe(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.tracker??null,l=e.renderer??G(),d=e.mode??"replace";const u={...W,...e.tuning},S=e.backdrop??"#101822",f=t.createElement("video");f.muted=!0,f.playsInline=!0,f.srcObject=new MediaStream([r]),f.play().catch(()=>{});const v=t.createElement("canvas");v.width=i,v.height=n;const E=v.getContext("2d"),y=()=>{if(s)return;const w=f.videoWidth,P=f.videoHeight;if(!w||!P)return;const m=Math.min(1,1280/Math.max(w,P)),h=Math.round(w*m),T=Math.round(P*m);(!(h===i||T===n)||h!==i||T!==n)&&(i=h,n=T,v.width=i,v.height=n)};f.addEventListener?.("loadedmetadata",y),f.addEventListener?.("resize",y);let k=H(),p=null,R=0;function A(w){if(w.length<=454)return;const P=w[10],m=w[152],h=w[234],T=w[454],I=(h.x+T.x)/2*i,C=(h.y+T.y)/2*n,N=Math.hypot((m.x-P.x)*i,(m.y-P.y)*n),g=Math.max(.6,Math.cos(k.headPitch)),q={x:I,y:C,radius:Math.max(24,N/g*.8)};p=p?{x:p.x+(q.x-p.x)*.4,y:p.y+(q.y-p.y)*.4,radius:p.radius+(q.radius-p.radius)*.15}:q}function D(w){if(c&&f.readyState>=2){let m=null;try{m=c.detect(f,w)}catch{}const h=oe(m?.categories??null,m?.matrix??null,u.eyeSync);h.jawOpen=Y(h.jawOpen*u.mouthGain),h.blinkLeft=Y(h.blinkLeft*u.blinkGain),h.blinkRight=Y(h.blinkRight*u.blinkGain),h.tracked?(R=w,k=J(k,h,u.smoothing),m?.landmarks&&A(m.landmarks)):k.tracked&&w-R>u.lostHoldMs&&(k=J(k,{...k,jawOpen:0,browUp:0,smile:k.smile*.5,blinkLeft:.1,blinkRight:.1},.05))}d==="overlay"&&f.readyState>=2?E.drawImage(f,0,0,i,n):(E.fillStyle=S,E.fillRect(0,0,i,n));const P=p?{x:p.x,y:p.y+u.avatarLift*p.radius,radius:p.radius*u.avatarScale}:null;l?.render(E,k,i,n,P)}const M=setInterval(()=>D(Date.now()),Math.round(1e3/o)),L=v.captureStream(o);return{track:L.getVideoTracks()[0],stream:L,getRig:()=>({...k}),setMode(w){d=w==="overlay"?"overlay":"replace"},setTracker(w){c?.close?.(),c=w},setRenderer(w){l?.dispose?.(),l=w??G()},setTuning(w){for(const P of Object.keys(W)){const m=w[P];typeof m=="number"&&Number.isFinite(m)&&(u[P]=m)}},getTuning:()=>({...u}),dispose(){clearInterval(M),c?.close?.(),l?.dispose?.();for(const w of L.getTracks())w.stop();f.srcObject=null}}}class le extends EventTarget{mode;options;client=null;peerConnection;state="initialized";latestMediaScore=null;latestQuality=null;statsTimer=null;statsRunning=!1;previousCounters=null;lastTelemetryAt=0;joinPromise=null;requestedQuality;effectiveQuality;poorQualitySamples=0;goodQualitySamples=0;constructor(e){if(super(),!e.credential?.signalingToken||!e.credential.signalingUrl||!e.credential.roomId||!e.credential.participantId)throw new Error("A valid room signaling credential is required");this.mode=e.mode,this.requestedQuality=e.mode==="voice"?"audio":e.quality??"auto",this.effectiveQuality=this.requestedQuality==="auto"?"medium":this.requestedQuality,this.peerConnection=e.peerConnection??null,this.options={...e,audio:e.audio??!0,video:e.mode==="voice"?!1:e.video??!0,receiveAudio:e.receiveAudio??!0,receiveVideo:e.mode==="voice"?!1:e.receiveVideo??!0,statsInterval:Math.max(1e3,e.statsInterval??5e3),telemetryInterval:Math.max(1e4,e.telemetryInterval??3e4),autoReportStats:e.autoReportStats??!0}}async initialize(){if(this.client)return this;try{const t=(this.options.transport==="auto"?this.mode==="meeting"?"sfu":"mesh":this.options.transport??"mesh")==="sfu"?Me:B;return this.client=new t({credential:{signalingToken:this.options.credential.signalingToken,signalingUrl:this.options.credential.signalingUrl,roomId:this.options.credential.roomId,participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId,iceServers:this.options.credential.iceServers},audio:this.options.audio,video:this.options.video,receiveAudio:this.options.receiveAudio,receiveVideo:this.options.receiveVideo,quality:this.effectiveQuality,preferredVideoCodec:this.options.preferredVideoCodec??"auto",iceTransportPolicy:this.options.iceTransportPolicy??"all",localStream:this.options.localStream,simulcast:this.options.simulcast??!0,onEvent:(i,n)=>this.handleNativeEvent(i,n)}),await this.client.initialize(),this.startStats(),this.emit("initialized",{mode:this.mode,sdkVersion:Se}),this.options.autoJoin&&await this.join(),this}catch(e){this.state="failed";const t=ve(e,"RTC initialization failed");throw this.emit("error",V(t)),t}}async join(){const e=this.requireClient();return this.joinPromise?this.joinPromise:(this.state="joining",this.emit("joining",{}),this.joinPromise=e.join().catch(t=>{this.state="failed";const i=ve(t,"RTC connection failed");throw this.emit("error",V(i)),i}).finally(()=>{this.joinPromise=null}),this.joinPromise)}async leave(){this.client&&(await this.joinPromise?.catch(()=>{}),await this.sampleAndReport("final").catch(()=>{}),await this.client.leave(),this.state="disconnected",this.stopStats())}async setMuted(e){this.requireClient().setMuted(e),this.emit("mediachange",{kind:"audio",enabled:!e})}async setCameraEnabled(e){if(this.mode==="voice"&&e)throw new Error("Camera is unavailable in voice mode");await this.requireClient().setCameraEnabled(e),this.emit("mediachange",{kind:"video",enabled:e})}async setVideoTrack(e){if(this.mode==="voice")throw new Error("Camera is unavailable in voice mode");await this.requireClient().setVideoTrack(e),this.emit("mediachange",{kind:"video",enabled:e.enabled,trackId:e.id})}async setScreenShareEnabled(e){await this.requireClient().setScreenShareEnabled(e),this.emit("mediachange",{kind:"screenshare",enabled:e})}async setDevice(e){await this.requireClient().setDevice(e),this.emit("devicechange",{deviceId:e.deviceId,kind:e.kind})}async setQuality(e){if(this.mode==="voice"&&e!=="audio")throw new Error("Voice calls only support audio quality");this.requestedQuality=e,this.poorQualitySamples=0,this.goodQualitySamples=0,await this.applyEffectiveQuality(e==="auto"?"medium":e,"manual")}async setPreferredVideoCodec(e){await this.requireClient().setPreferredVideoCodec(e),this.emit("codecchange",{preferredVideoCodec:e})}async getDevices(){return navigator.mediaDevices?.enumerateDevices?navigator.mediaDevices.enumerateDevices():[]}getParticipants(){return this.requireClient().getParticipants().map(fe)}getLocalStream(){return this.client?.getLocalStream()??null}getRemoteStreams(){return(this.client?.getRemoteStreams()??[]).map(e=>({participant:fe(e.participant),stream:e.stream}))}setPeerConnection(e){this.peerConnection=e,this.previousCounters=null}async sendChatMessage(e,t){const i=e.trim();if(!i)throw new Error("message is required");this.requireClient().sendChat(i,t)}async sendControlMessage(e,t={}){const i=e.trim();if(!i)throw new Error("action is required");this.requireClient().sendControl(i,t)}async createPoll(e,t,i={}){const n=t.map(s=>s.trim()).filter(Boolean);if(!e.trim()||n.length<2)throw new Error("A poll requires a question and at least two options");this.requireClient().sendControl("poll.create",{poll:{id:`poll-${crypto.randomUUID()}`,question:e.trim(),options:n,votes:{},...i}})}async votePoll(e,t){this.requireClient().sendControl("poll.vote",{pollId:e,optionIndex:t})}async requestStageAccess(){this.requireClient().sendControl("stage.request")}async cancelStageAccess(){this.requireClient().sendControl("stage.cancel")}async joinStage(){this.requireClient().sendControl("stage.join")}async leaveStage(){this.requireClient().sendControl("stage.leave")}async grantStageAccess(e){this.requireClient().sendControl("stage.grant",{participantIds:e})}async denyStageAccess(e){this.requireClient().sendControl("stage.deny",{participantIds:e})}async startRecording(e=!1){this.requireClient().sendControl("recording.start",{allowMultiple:e})}async stopRecording(e){this.requireClient().sendControl("recording.stop",{recordingId:e})}async pauseRecording(e){this.requireClient().sendControl("recording.pause",{recordingId:e})}async resumeRecording(e){this.requireClient().sendControl("recording.resume",{recordingId:e})}async runPreflight(e={}){const t=[],i=globalThis.isSecureContext===!0;i||t.push("secure_context_required");let n="unavailable";if(e.requestMedia!==!1&&navigator.mediaDevices?.getUserMedia)try{(await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})).getTracks().forEach(S=>S.stop()),n="granted"}catch{n="denied",t.push("media_permission_denied")}const s=navigator.mediaDevices?.enumerateDevices?await navigator.mediaDevices.enumerateDevices().catch(()=>[]):[],o=s.filter(u=>u.kind==="audioinput").length,r=s.filter(u=>u.kind==="videoinput").length;this.options.audio&&o===0&&t.push("microphone_unavailable"),this.options.video&&r===0&&t.push("camera_unavailable");const c=e.probeUrl??this.options.preflightProbeUrl;let l=null;if(c){const u=performance.now();try{const S=await fetch(c,{method:"HEAD",cache:"no-store",credentials:"omit"});l=Math.round(performance.now()-u),S.ok||t.push("network_probe_failed")}catch{t.push("network_probe_failed")}}const d={passed:t.length===0,sampledAt:new Date().toISOString(),secureContext:i,mediaPermission:n,microphoneCount:o,cameraCount:r,probeLatencyMs:l,effectiveConnectionType:ge()?.effectiveType??null,issues:t};return this.emit("preflight",d),this.options.autoReportStats&&await this.reportQuality({sampleType:"preflight",sampledAt:d.sampledAt,connectionState:d.passed?"ready":"failed",rttMs:d.probeLatencyMs,effectiveConnectionType:d.effectiveConnectionType}).catch(()=>{}),d}async collectQualitySample(e="runtime"){const t=this.options.statsProvider?await this.options.statsProvider():{},i=this.peerConnection?await Ue(this.peerConnection,this.previousCounters):null;i?.counters&&(this.previousCounters=i.counters);const s={...{sampleType:e,sampledAt:new Date().toISOString(),connectionState:this.peerConnection?.connectionState??this.state,mediaScore:this.latestMediaScore,networkScore:this.latestMediaScore==null?5:Math.max(0,Math.min(5,Math.round(this.latestMediaScore))),estimatedMos:4.5,rttMs:null,jitterMs:null,packetLossPct:null,packetLossCumulativePct:null,packetsLost:null,packetsReceived:null,inboundBitrateBps:null,outboundBitrateBps:null,availableOutgoingBitrateBps:null,framesPerSecond:null,frameWidth:null,frameHeight:null,framesDropped:null,freezeCount:null,freezeDurationMs:null,concealedSamples:null,codec:null,transportProtocol:null,candidateType:null,qualityLimitationReason:null,deviceMemoryGb:Be(),effectiveConnectionType:ge()?.effectiveType??null},...i?.sample??{},...t,sampleType:e,sampledAt:new Date().toISOString()},o=Qe(s);return s.networkScore=o.networkScore,s.estimatedMos=o.estimatedMos,this.latestQuality=s,s}async reportQuality(e){const t=this.options.credential.telemetryToken,i=this.options.telemetryEndpoint??this.options.credential.telemetryEndpoint;if(!t||!i)return;const n=await fetch(i,{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify(e),keepalive:e.sampleType==="final"});if(!n.ok)throw new Error(`RTC telemetry failed with HTTP ${n.status}`)}getStats(){const e=this.client,t=e?.getLocalStream();return{state:this.state,participantCount:e?e.getParticipants().length+1:0,audioEnabled:!!t?.getAudioTracks().some(i=>i.enabled),videoEnabled:!!t?.getVideoTracks().some(i=>i.enabled),screenShareEnabled:!1,latestMediaScore:this.latestMediaScore,latestQuality:this.latestQuality,sampledAt:new Date().toISOString()}}getNativeClient(){return this.client}destroy(){this.stopStats(),this.client&&this.client.leave(),this.client=null,this.joinPromise=null,this.peerConnection=null,this.state="disconnected"}handleNativeEvent(e,t){e==="connected"?(this.state="connected",this.peerConnection=this.client?.getPrimaryPeerConnection()??null):e==="disconnected"?this.state="disconnected":e==="peerconnectionstate"&&(this.peerConnection=this.client?.getPrimaryPeerConnection()??null),this.emit(e,t)}startStats(){this.stopStats(),this.statsTimer=setInterval(()=>{this.sampleAndReport("runtime")},this.options.statsInterval)}async sampleAndReport(e){if(!this.statsRunning){this.statsRunning=!0;try{const t=await this.collectQualitySample(e);await this.adaptQuality(t),this.emit("stats",{...this.getStats(),latestQuality:t});const i=Date.now(),n=e!=="runtime"||i-this.lastTelemetryAt>=this.options.telemetryInterval;this.options.autoReportStats&&n&&(await this.reportQuality(t),this.lastTelemetryAt=i)}catch(t){this.emit("telemetryerror",V(t))}finally{this.statsRunning=!1}}}stopStats(){this.statsTimer!==null&&clearInterval(this.statsTimer),this.statsTimer=null}async adaptQuality(e){if(this.requestedQuality!=="auto"||this.options.adaptiveQuality===!1||this.mode==="voice")return;if((this.client?.getParticipants().length??0)>1){this.poorQualitySamples=0,this.goodQualitySamples=0,this.effectiveQuality!=="high"&&await this.applyEffectiveQuality("high","network");return}const t=String(e.qualityLimitationReason??"").toLowerCase(),i=(e.packetLossPct??0)>=8||(e.rttMs??0)>=800||t==="cpu"||t==="bandwidth",n=i||(e.packetLossPct??0)>=3||(e.rttMs??0)>=350||e.availableOutgoingBitrateBps!=null&&e.availableOutgoingBitrateBps<35e4,s=(e.packetLossPct??100)<1&&(e.rttMs??1e3)<180&&!t&&(e.availableOutgoingBitrateBps==null||e.availableOutgoingBitrateBps>1e6);this.poorQualitySamples=n?this.poorQualitySamples+1:0,this.goodQualitySamples=s?this.goodQualitySamples+1:0;const o=["low","medium","high"],r=o.indexOf(this.effectiveQuality);i&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality("low","network")):this.poorQualitySamples>=2&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality(o[r-1],"network")):this.goodQualitySamples>=6&&r>=0&&r<o.length-1&&(this.goodQualitySamples=0,await this.applyEffectiveQuality(o[r+1],"network"))}async applyEffectiveQuality(e,t){if(this.effectiveQuality===e&&this.client)return;const i=this.effectiveQuality;this.effectiveQuality=e,this.client&&await this.client.setQuality(e),this.emit("qualitychange",{requestedQuality:this.requestedQuality,quality:e,previous:i,reason:t})}requireClient(){if(!this.client)throw new Error("Call initialize() before using the RTC client");return this.client}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}async function qe(a){return new le(a).initialize()}let x=null;function de(){if(x)return x;const a=globalThis,e=a.AudioContext??a.webkitAudioContext;return e?(x=new e,x):null}async function he(){const a=de();return a?(a.state==="suspended"&&await a.resume().catch(()=>{}),a.state==="running"):!1}class Ne{options;media=null;cadenceTimer=null;oscillators=new Set;constructor(e){this.options=e}unlock(){return he()}startIncoming(){this.start("incoming",this.options.ringtoneUrl)}startRingback(){this.start("ringback",this.options.ringbackUrl)}stop(){this.cadenceTimer&&clearInterval(this.cadenceTimer),this.cadenceTimer=null,this.media?.pause(),this.media&&(this.media.currentTime=0),this.media=null;for(const e of this.oscillators)try{e.stop()}catch{}this.oscillators.clear()}start(e,t){if(this.stop(),this.options.sounds===!1)return;if(t&&typeof Audio<"u"){this.media=new Audio(t),this.media.loop=!0,this.media.play().catch(()=>{});return}const i=()=>this.playBuiltInTone(e);i(),this.cadenceTimer=setInterval(i,e==="incoming"?3e3:3500)}playBuiltInTone(e){const t=de();if(!t||t.state!=="running")return;const i=e==="incoming"?1.25:1,n=e==="incoming"?[440,480]:[425],s=t.createGain(),o=t.currentTime;s.gain.setValueAtTime(1e-4,o),s.gain.exponentialRampToValueAtTime(.045,o+.025),s.gain.setValueAtTime(.045,o+i-.06),s.gain.exponentialRampToValueAtTime(1e-4,o+i),s.connect(t.destination);for(const r of n){const c=t.createOscillator();c.type="sine",c.frequency.value=r,c.connect(s),this.oscillators.add(c),c.onended=()=>this.oscillators.delete(c),c.start(o),c.stop(o+i+.02)}}}class ue extends EventTarget{options;socket=null;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;currentCall=null;outgoingCallId=null;mount=null;terminalTimer=null;tokenRefreshTimer=null;tokenRequest=null;currentToken;currentTokenExpiresAt;callAudio;mediaClients=new Map;activeCallIds=new Set;seenInvitationIds=new Set;startCallRequest=null;lastDisconnect=null;constructor(e){if(super(),!e.token&&!e.tokenProvider)throw new Error("A client token or tokenProvider is required");if(e.token&&!e.token.startsWith("rtcc_"))throw new Error("A valid RTC client token is required");if(!e.signalingUrl&&!e.callActionsUrl&&!e.apiBaseUrl&&!globalThis.location?.origin)throw new Error("signalingUrl or callActionsUrl is required outside a browser");this.options=e,this.currentToken=e.token??null,this.currentTokenExpiresAt=e.tokenExpiresAt,this.callAudio=new Ne(e),this.mount=me(e.mount),this.scheduleTokenRefresh()}connect(){if(!this.options.signalingUrl)throw new Error("signalingUrl is required to connect signaling");return this.stopped=!1,this.scheduleTokenRefresh(),this.openSocket("initial"),this}setMount(e){this.clearUi(),this.mount=me(e),this.currentCall&&this.renderIncoming(this.currentCall)}attachMediaClient(e,t){if(!e.trim())throw new Error("callId is required");return this.mediaClients.set(e,t),()=>{this.mediaClients.get(e)===t&&this.mediaClients.delete(e)}}async startCall(e){if(this.startCallRequest)return this.startCallRequest;if(this.activeCallIds.size>0)throw new Error("Another call is already active");const t=e.targets?.length?e.targets:e.target?[e.target]:[];if(t.length===0)throw new Error("startCall requires at least one target");const i={mode:e.mode,targets:t,metadata:e.metadata,idempotencyKey:e.idempotencyKey},n=(async()=>{await this.callAudio.unlock(),this.callAudio.startRingback();let s;try{s=await this.request("/v1/rtc/client/calls",{method:"POST",headers:e.idempotencyKey?{"idempotency-key":e.idempotencyKey}:void 0,body:JSON.stringify(i)})}catch(o){throw this.callAudio.stop(),o}return!s.credential&&s.callerCredential&&(s.credential=s.callerCredential),this.outgoingCallId=String(s.call?.id??"")||null,this.outgoingCallId&&this.activeCallIds.add(this.outgoingCallId),this.emit("outgoingcall",s),s})();this.startCallRequest=n;try{return await n}finally{this.startCallRequest===n&&(this.startCallRequest=null)}}async accept(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"accept");return this.activeCallIds.add(e),this.callAudio.stop(),this.dismiss(e),this.emit("callaccepted",t),t}async reject(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"reject");return this.releaseMedia(e),this.showTerminal(e,"Call declined"),this.emit("callrejected",t),t}async busy(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"busy");return this.releaseMedia(e),this.showTerminal(e,"Line busy"),t}async end(e){this.callAudio.stop();const t=await this.callAction(e,"end");return this.releaseMedia(e),this.showTerminal(e,"Call ended"),t}async cancel(e){this.callAudio.stop();const t=await this.callAction(e,"cancel");return this.releaseMedia(e),this.showTerminal(e,"Call cancelled"),t}handleIncomingCall(e){if(!e?.id||!e.caller?.id||!e.caller.name||e.mode!=="voice"&&e.mode!=="video")throw new Error("Incoming call payload is invalid");if(e.expiresAt&&Date.parse(e.expiresAt)<=Date.now()){this.emit("callexpired",{id:e.id,expiresAt:e.expiresAt});return}if(this.currentCall?.id===e.id){this.currentCall=e,this.emit("callrestored",e);return}if(this.seenInvitationIds.has(e.id)){this.emit("callrestored",e);return}this.seenInvitationIds.add(e.id),this.activeCallIds.add(e.id),this.currentCall=e,this.callAudio.startIncoming(),this.renderIncoming(e),this.emit("incomingcall",e)}handleCallUpdate(e){const t=e.id===this.currentCall?.id||e.id===this.outgoingCallId;t&&e.status==="accepted"?(this.activeCallIds.add(e.id),this.callAudio.stop(),this.dismiss(e.id)):t&&["rejected","busy","cancelled","ended","failed","missed"].includes(e.status)&&this.showTerminal(e.id,Ve(e.status)),["rejected","busy","cancelled","ended","failed","missed"].includes(e.status)&&this.releaseMedia(e.id),this.emit("callupdated",e)}disconnect(){this.stopped=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.reconnectTimer=null,this.heartbeatTimer=null,this.tokenRefreshTimer=null,this.socket?.close(1e3,"Client disconnected"),this.socket=null,this.callAudio.stop(),this.dismiss();for(const e of this.mediaClients.keys())this.releaseMedia(e);this.activeCallIds.clear(),this.seenInvitationIds.clear()}destroy(){this.disconnect(),this.mount=null}async openSocket(e){if(!this.options.signalingUrl||this.stopped||this.socket?.readyState===WebSocket.OPEN||this.socket?.readyState===WebSocket.CONNECTING)return;let t;try{t=await this.resolveToken(e,e==="reconnect")}catch(s){this.emit("tokenerror",V(s)),!this.stopped&&this.options.reconnect!==!1&&this.scheduleReconnect();return}if(this.stopped||this.socket)return;const i=new URL(this.options.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new WebSocket(i,["rtc-client",t]);this.socket=n,n.addEventListener("open",()=>{const s=this.reconnectAttempt>0,o=this.lastDisconnect;this.reconnectAttempt=0,this.heartbeatDeadline=Date.now()+45e3,this.emit("signalingconnected",{resumed:s}),o&&(this.reportSignalingDiagnostic({event:"recovered",code:o.code,reason:o.reason,wasClean:o.wasClean,outageMs:Date.now()-o.at}),this.lastDisconnect=null),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{if(n.readyState===WebSocket.OPEN){if(Date.now()>this.heartbeatDeadline){n.close(4e3,"Signaling heartbeat timed out");return}n.send("ping")}},2e4)}),n.addEventListener("message",s=>{this.heartbeatDeadline=Date.now()+45e3,this.handleSignal(String(s.data))}),n.addEventListener("close",s=>{if(this.socket!==n)return;this.socket=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;const o=!this.stopped&&this.options.reconnect!==!1,r={code:s.code,reason:s.reason,wasClean:s.wasClean,willReconnect:o,attempt:this.reconnectAttempt+1};o&&(this.lastDisconnect={code:s.code,reason:s.reason,wasClean:s.wasClean,at:Date.now()}),this.emit("signalingdisconnected",r),this.reportSignalingDiagnostic({event:o?"reconnecting":"disconnected",...r}),o&&this.scheduleReconnect()}),n.addEventListener("error",()=>{const s={message:"RTC signaling connection failed",reconnecting:!this.stopped&&this.options.reconnect!==!1,attempt:this.reconnectAttempt+1};this.emit("signalingerror",s),this.reportSignalingDiagnostic({event:"error",attempt:s.attempt})})}handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i==="call.incoming"&&t.call&&typeof t.call=="object"){this.handleIncomingCall(t.call);return}if(i==="call.updated"){const n=t.call;n?.id&&n.status&&this.handleCallUpdate({id:n.id,status:n.status});return}i==="presence"?this.emit("presence",{users:t.users??[]}):i!=="pong"&&this.emit(i||"signal",t)}async callAction(e,t){return this.request(`/v1/rtc/client/calls/${encodeURIComponent(e)}/${t}`,{method:"POST",body:"{}"})}async request(e,t,i=!1){const n=this.options.callActionsUrl??this.options.apiBaseUrl??this.options.signalingUrl??globalThis.location?.origin;if(!n)throw new Error("callActionsUrl is required for call actions");const s=new URL(n,globalThis.location?.href),o=await this.resolveToken("initial"),r=new Headers(t.headers);r.set("authorization",`Bearer ${o}`),r.set("content-type","application/json");const c=await fetch(new URL(e,s.origin),{...t,headers:r}),l=await c.json().catch(()=>({}));if(c.status===401&&this.options.tokenProvider&&!i)return await this.resolveToken("unauthorized",!0),this.request(e,t,!0);if(!c.ok){const d=l.error;throw new Error(typeof d=="string"?d:d?.message||`RTC API failed with HTTP ${c.status}`)}return l}renderIncoming(e){if(!this.mount)return;this.clearUi();const t=document.createElement("section");t.dataset.rtcIncomingCall=e.id,t.setAttribute("role","dialog"),t.setAttribute("aria-label",`Incoming ${e.mode} call from ${e.caller.name}`),Object.assign(t.style,{display:"grid",gap:"14px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const i=document.createElement("small");i.textContent=e.mode==="video"?"Incoming video call":"Incoming voice call",i.style.color="#5d7079";const n=document.createElement("strong");n.textContent=e.caller.name,n.style.fontSize="20px";const s=document.createElement("div");Object.assign(s.style,{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"});const o=pe("Decline","#fff","#a32920","#d9a09b"),r=pe("Answer","#fff","#08745f","#08745f");o.addEventListener("click",()=>{this.reject(e.id).catch(c=>this.emit("error",V(c)))}),r.addEventListener("click",()=>{this.accept(e.id).catch(c=>this.emit("error",V(c)))}),s.appendChild(o),s.appendChild(r),t.appendChild(i),t.appendChild(n),t.appendChild(s),this.mount.replaceChildren(t)}dismiss(e){e&&this.currentCall?.id!==e&&this.outgoingCallId!==e||(this.currentCall=null,(!e||this.outgoingCallId===e)&&(this.outgoingCallId=null),this.callAudio.stop(),this.clearUi())}showTerminal(e,t){if(this.callAudio.stop(),this.currentCall?.id===e&&(this.currentCall=null),this.outgoingCallId===e&&(this.outgoingCallId=null),!this.mount)return;this.clearUi();const i=document.createElement("section");i.dataset.rtcCallTerminal=e,i.setAttribute("role","status"),Object.assign(i.style,{display:"grid",placeItems:"center",gap:"10px",minHeight:"150px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const n=document.createElement("strong");n.textContent=t,n.style.fontSize="18px",i.appendChild(n),this.mount.replaceChildren(i);const s=Math.max(0,this.options.terminalStateDurationMs??1600);this.terminalTimer=setTimeout(()=>this.clearUi(),s)}clearUi(){this.terminalTimer&&clearTimeout(this.terminalTimer),this.terminalTimer=null,this.mount&&this.mount.replaceChildren()}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer);const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.openSocket("reconnect")},e)}async resolveToken(e,t=!1){if(!t&&this.currentToken&&!this.tokenNeedsRefresh())return this.currentToken;if(!this.options.tokenProvider){if(!this.currentToken)throw new Error("A valid RTC client token is required");return this.currentToken}return this.tokenRequest?this.tokenRequest:(this.tokenRequest=(async()=>{const i=await this.options.tokenProvider?.({reason:e,previousExpiresAt:this.currentTokenExpiresAt}),n=typeof i=="string"?{token:i}:i;if(!n?.token?.startsWith("rtcc_"))throw new Error("tokenProvider returned an invalid RTC client token");return this.currentToken=n.token,this.currentTokenExpiresAt=n.expiresAt,this.scheduleTokenRefresh(),this.emit("tokenrefreshed",{reason:e,expiresAt:n.expiresAt}),n.token})().finally(()=>{this.tokenRequest=null}),this.tokenRequest)}tokenNeedsRefresh(){if(!this.currentTokenExpiresAt)return!1;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();return Number.isFinite(e)?e<=this.refreshSkew(e):!0}refreshSkew(e){const t=Math.max(5e3,this.options.tokenRefreshSkewMs??3e5);return Math.min(t,Math.max(5e3,e*.2))}scheduleTokenRefresh(){if(this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.tokenRefreshTimer=null,this.stopped||!this.options.tokenProvider||!this.currentTokenExpiresAt)return;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();if(!Number.isFinite(e))return;const t=Math.max(0,e-this.refreshSkew(e));this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.resolveToken("expiring",!0).catch(i=>{this.emit("tokenerror",V(i)),this.stopped||(this.tokenRefreshTimer=setTimeout(()=>this.scheduleTokenRefresh(),3e4))})},Math.min(t,2147e6))}releaseMedia(e){this.activeCallIds.delete(e),this.seenInvitationIds.delete(e);const t=this.mediaClients.get(e);t&&(this.mediaClients.delete(e),t.leave().catch(()=>{}).finally(()=>t.destroy()))}reportSignalingDiagnostic(e){this.options.reportSignalingDiagnostics===!1||this.stopped||this.request("/v1/rtc/client/signaling-events",{method:"POST",body:JSON.stringify({...e,occurredAt:new Date().toISOString()})}).catch(t=>this.emit("diagnosticserror",V(t)))}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}function je(a){const e=new ue(a);return a.autoConnect!==!1&&a.signalingUrl&&e.connect(),e}function me(a){return a?typeof a=="string"?document.querySelector(a):a:null}function pe(a,e,t,i){const n=document.createElement("button");return n.type="button",n.textContent=a,Object.assign(n.style,{minHeight:"44px",border:`1px solid ${i}`,borderRadius:"6px",background:t,color:e,cursor:"pointer",font:"inherit",fontWeight:"700"}),n}function Ve(a){return{rejected:"Call declined",busy:"Line busy",cancelled:"Call cancelled",ended:"Call ended",failed:"Call failed",missed:"No answer"}[a]??"Call ended"}function fe(a){return{id:a.participantId,name:a.displayName,role:"participant"}}async function Ue(a,e){const t=await a.getStats(),i=new Map,n=new Map;let s=0,o=0,r=0,c=0,l=0,d=0,u=0,S=0,f=0,v=0,E=0,y=0,k=0,p=null,R=null,A=null,D=null,M=null,L=null;t.forEach(g=>{n.set(g.id,g),g.type==="codec"&&i.set(g.id,String(g.mimeType??""))}),t.forEach(g=>{if(g.type==="inbound-rtp"&&!g.isRemote&&(s+=Number(g.bytesReceived??0),r+=Math.max(0,Number(g.packetsLost??0)),c+=Math.max(0,Number(g.packetsReceived??0)),Number.isFinite(g.jitter)&&(l+=Number(g.jitter),d+=1),u=Math.max(u,Number(g.framesPerSecond??0)),S=Math.max(S,Number(g.frameWidth??0)),f=Math.max(f,Number(g.frameHeight??0)),v+=Number(g.framesDropped??0),E+=Number(g.freezeCount??0),y+=Number(g.totalFreezesDuration??0),k+=Number(g.concealedSamples??0),M||=i.get(String(g.codecId??""))??null),g.type==="outbound-rtp"&&!g.isRemote&&(o+=Number(g.bytesSent??0),L||=g.qualityLimitationReason?String(g.qualityLimitationReason):null,M||=i.get(String(g.codecId??""))??null),g.type==="candidate-pair"&&(g.selected||g.nominated)&&g.state==="succeeded"){p=Number.isFinite(g.currentRoundTripTime)?Number(g.currentRoundTripTime):p,R=Number.isFinite(g.availableOutgoingBitrate)?Number(g.availableOutgoingBitrate):R;const q=n.get(g.localCandidateId);A=q?.candidateType?String(q.candidateType):A,D=q?.protocol?String(q.protocol):D}});const w=Date.now(),P=e?Math.max(.001,(w-e.timestamp)/1e3):0,m=e?Math.max(0,Math.round((s-e.inboundBytes)*8/P)):null,h=e?Math.max(0,Math.round((o-e.outboundBytes)*8/P)):null,T=r+c,I=e?Math.max(0,r-e.packetsLost):r,C=e?Math.max(0,c-e.packetsReceived):c,N=I+C;return{counters:{timestamp:w,inboundBytes:s,outboundBytes:o,packetsLost:r,packetsReceived:c},sample:{rttMs:p===null?null:p*1e3,jitterMs:d?l/d*1e3:null,packetLossPct:N?I/N*100:null,packetLossCumulativePct:T?r/T*100:null,packetsLost:r,packetsReceived:c,inboundBitrateBps:m,outboundBitrateBps:h,availableOutgoingBitrateBps:R,framesPerSecond:u||null,frameWidth:S||null,frameHeight:f||null,framesDropped:v,freezeCount:E,freezeDurationMs:Math.round(y*1e3),concealedSamples:k,codec:M,transportProtocol:D,candidateType:A,qualityLimitationReason:L}}}function Qe(a){let e=a.mediaScore==null?5:Math.round(ye(Number(a.mediaScore),0,5));const t=K(a.packetLossPct)??0,i=K(a.rttMs)??0,n=K(a.jitterMs)??0,s=String(a.connectionState??"unknown").toLowerCase();["failed","closed","disconnected"].includes(s)?e=0:["reconnecting","checking"].includes(s)&&(e=Math.min(e,2)),t>=15?e=Math.min(e,0):t>=8?e=Math.min(e,1):t>=3?e=Math.min(e,2):t>=1&&(e=Math.min(e,3)),i>=1200?e=Math.min(e,0):i>=800?e=Math.min(e,1):i>=400?e=Math.min(e,2):i>=250&&(e=Math.min(e,3)),n>=150?e=Math.min(e,1):n>=80?e=Math.min(e,2):n>=40&&(e=Math.min(e,3));const o=Number(ye(4.5-t*.09-i*.0011-n*.006,1,4.5).toFixed(2));return{networkScore:e,estimatedMos:o}}function ge(){return navigator.connection??null}function Be(){const a=Number(navigator.deviceMemory);return Number.isFinite(a)?a:null}function K(a){const e=Number(a);return Number.isFinite(e)?e:null}function ye(a,e,t){return Math.min(t,Math.max(e,a))}function V(a){return a instanceof Error?{name:a.name,message:a.message}:{name:"ServiceError",message:String(a??"Service request failed")}}function ve(a,e){return a instanceof Error?a:new Error(e)}const Se="1.3.20";b.AVATAR_TUNING_DEFAULTS=W,b.BACKGROUND_PRESETS=ae,b.OVERLAY_EFFECTS=se,b.RtcClient=le,b.RtcIncomingClient=ue,b.SPEAKER_NOISE_FLOOR=.02,b.SPEAKER_TAKEOVER_RATIO=1.4,b.createAvatarPipeline=Oe,b.createCallRecorder=Le,b.createPictureInPictureController=Ae,b.createRtcClient=qe,b.createRtcIncomingClient=je,b.createVideoEffectsPipeline=De,b.drawAvatar=ce,b.headPoseFromMatrix=re,b.neutralRig=H,b.pickActiveSpeaker=z,b.rigFromBlendshapes=oe,b.scenePainters=$,b.smoothRig=J,b.stylizedAvatarRenderer=G,b.unlockRtcCallAudio=he,b.version=Se,Object.defineProperty(b,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(b,B){typeof exports=="object"&&typeof module<"u"?B(exports):typeof define=="function"&&define.amd?define(["exports"],B):(b=typeof globalThis<"u"?globalThis:b||self,B(b.StreamingCdnRtc={}))})(this,(function(b){"use strict";function H(a,e,t=.02,i=1.4){const n=a.filter(r=>r.level>=t);if(n.length===0)return null;const s=n.reduce((r,c)=>c.level>r.level?c:r),o=n.find(r=>r.participantId===e);return o?s.participantId===e?e:s.level>=o.level*i?s.participantId:e:s.participantId}function ee(a){const e=[];for(const t of a){if(String(t.type??"")!=="inbound-rtp"||String(t.kind??t.mediaType??"")!=="audio")continue;const i=Number(t.audioLevel??NaN);Number.isFinite(i)&&e.push({mid:typeof t.mid=="string"?t.mid:null,level:i})}return e}const V={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4},low:{active:!0,maxBitrate:35e4,maxFramerate:15,scaleResolutionDownBy:4},medium:{active:!0,maxBitrate:11e5,maxFramerate:24,scaleResolutionDownBy:2},high:{active:!0,maxBitrate:28e5,maxFramerate:30,scaleResolutionDownBy:1}},Te={warmupSamples:3,poorSamplesToStepDown:2,goodSamplesToStepUp:6,criticalLossPct:8,criticalRttMs:800,poorLossPct:3,poorRttMs:350,goodLossPct:1,goodRttMs:250,sustainRatio:.8,bandwidthHeadroomRatio:1.25};function be(a){const e=String(a??"").trim().toLowerCase();return!e||e==="none"?"":e==="cpu"||e==="bandwidth"?e:"other"}function Ce(a,e,t,i,n={}){const s={...Te,...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,k=be(e.qualityLimitationReason),f=e.packetLossPct??0,v=e.rttMs??0,R=e.availableOutgoingBitrateBps,y=V[a].maxBitrate,w=f>=s.criticalLossPct||v>=s.criticalRttMs||k==="cpu",p=k==="bandwidth"&&(R==null||R<y*s.bandwidthHeadroomRatio),I=w||f>=s.poorLossPct||v>=s.poorRttMs||p,A=R==null||R>=y*s.sustainRatio,D=f<s.goodLossPct&&v<s.goodRttMs&&!k&&A,E=I?t+1:0,L=D?i+1:0,S={quality:a,poorSamples:E,goodSamples:L,samplesSeen:d,changed:!1};return l>c?{quality:o[c],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:u?S:w&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:E>=s.poorSamplesToStepDown&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:L>=s.goodSamplesToStepUp&&l<c?{quality:o[l+1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:S}function Re(a,e){const t=a instanceof Map?a:new Map(a);let i=0,n=0,s=null,o=null,r=null;for(const d of t.values()){const u=String(d.type??"");if(u==="outbound-rtp"&&String(d.kind??"")==="video"&&(i+=Number(d.packetsSent??0),s=d.qualityLimitationReason??s),u==="remote-inbound-rtp"&&String(d.kind??"")==="video"){n+=Number(d.packetsLost??0);const k=Number(d.roundTripTime??NaN);Number.isFinite(k)&&(o=k*1e3)}if(u==="candidate-pair"&&(d.nominated===!0||String(d.state??"")==="succeeded")){const k=Number(d.currentRoundTripTime??NaN);o==null&&Number.isFinite(k)&&(o=k*1e3);const f=Number(d.availableOutgoingBitrate??NaN);Number.isFinite(f)&&(r=f)}}const c={packetsSent:i,packetsLost:n,timestamp:Date.now()};let l=null;if(e){const d=i-e.packetsSent,u=n-e.packetsLost;d>0&&u>=0&&(l=u/(d+u)*100)}return{sample:{packetLossPct:l,rttMs:o,availableOutgoingBitrateBps:r,qualityLimitationReason:s},counters:c}}const Ie={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4}},Ee={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1};class x{options;socket=null;localStream=null;screenStream=null;peers=new Map;joinPromise=null;resolveJoin=null;rejectJoin=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;qualityTimer=null;speakerTimer=null;activeSpeaker=null;heartbeatDeadline=0;quality;preferredVideoCodec;signalQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.preferredVideoCodec=e.preferredVideoCodec??"auto"}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(!this.joined){if(this.joinPromise)return this.joinPromise;this.joinPromise=this.connect();try{await this.joinPromise}finally{this.joinPromise=null}}}async leave(){this.stopped=!0,this.clearConnectionTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null;for(const e of this.peers.values())e.connection.close();this.peers.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(r=>r.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(r=>r.readyState==="live")||await this.replaceVideoTrack(n);return}for(const r of this.peers.values()){const c=r.connection.getSenders().find(l=>l.track?.kind===s);c?await c.replaceTrack(n):r.connection.addTrack(n,this.localStream)}await this.applyVideoQuality()}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";for(const t of this.peers.values())t.quality=x.clampToCeiling(t.quality,e),t.poorSamples=0,t.goodSamples=0;await this.applyVideoQuality()}static clampToCeiling(e,t){if(t==="audio")return e;const i=["low","medium","high"];return i.indexOf(e)>i.indexOf(t)?t:e}async setPreferredVideoCodec(e){this.preferredVideoCodec=e;for(const t of this.peers.values())this.applyCodecPreference(t.connection);await this.renegotiateAll()}getParticipants(){return[...this.peers.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getRemoteStreams(){return[...this.peers.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.peers.values().next().value?.connection??null}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async connect(){const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1)}async openSocket(e){const{credential:t}=this.options,i=new URL(t.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.startQualitySampling(),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),this.stopQualitySampling(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.resetPeers(),this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0;const n=Array.isArray(t.peers)?t.peers:[];for(const s of n)this.ensurePeer(s);this.resolveJoin?.();for(const s of n)this.shouldInitiateOffer(s)&&await this.createOffer(s);return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.ensurePeer(t.sender),this.shouldInitiateOffer(t.sender)&&await this.createOffer(t.sender);return}if(i==="rtc.offer"&&t.sender&&t.description){const n=this.ensurePeer(t.sender);n.offerTask&&await n.offerTask;const s=n.connection.signalingState!=="stable",o=!this.shouldInitiateOffer(t.sender);if(s&&!o)return;s&&await n.connection.setLocalDescription({type:"rollback"}),await n.connection.setRemoteDescription(t.description);const r=await n.connection.createAnswer();await n.connection.setLocalDescription(r),this.send({type:"rtc.answer",targetParticipantId:t.sender.participantId,negotiationId:t.negotiationId,description:n.connection.localDescription});return}if(i==="rtc.answer"&&t.sender&&t.description){const n=this.peers.get(t.sender.participantId);if(!n||n.connection.signalingState!=="have-local-offer"||t.negotiationId&&n.activeNegotiationId&&t.negotiationId!==n.activeNegotiationId)return;await n.connection.setRemoteDescription(t.description),n.activeNegotiationId=null;return}if(i==="rtc.ice"&&t.sender&&t.candidate){await this.ensurePeer(t.sender).connection.addIceCandidate(t.candidate).catch(()=>{});return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removePeer(t.sender.participantId);return}i==="rtc.chat"?this.options.onEvent("chatupdate",t):i==="rtc.control"&&this.options.onEvent("control",t)}}ensurePeer(e){const t=this.peers.get(e.participantId);if(t)return t;const i=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"}),n=new MediaStream,s={identity:e,connection:i,stream:n,makingOffer:!1,offerTask:null,activeNegotiationId:null,quality:x.startingQuality(this.quality),poorSamples:0,goodSamples:0,samplesSeen:0,counters:null};this.peers.set(e.participantId,s);for(const c of this.localStream?.getAudioTracks()??[])i.addTrack(c,this.localStream);const o=this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")?this.screenStream:this.localStream,r=o?.getVideoTracks()[0];return r&&o&&(s.videoSender=i.addTrack(r,o)),!this.localStream?.getAudioTracks().length&&this.options.receiveAudio&&i.addTransceiver("audio",{direction:"recvonly"}),!r&&this.options.receiveVideo&&(s.videoTransceiver=i.addTransceiver("video",{direction:"recvonly"}),s.videoSender=s.videoTransceiver.sender),this.applyCodecPreference(i),this.applyVideoQuality(i),i.addEventListener("icecandidate",c=>{c.candidate&&this.send({type:"rtc.ice",targetParticipantId:e.participantId,candidate:c.candidate.toJSON()})}),i.addEventListener("track",c=>{n.getTracks().some(l=>l.id===c.track.id)||n.addTrack(c.track),this.options.onEvent("remotestream",{participant:e,stream:n,track:c.track})}),i.addEventListener("connectionstatechange",()=>{["failed","closed"].includes(i.connectionState)&&this.removePeer(e.participantId),this.options.onEvent("peerconnectionstate",{participant:e,state:i.connectionState})}),s}async createOffer(e){const t=this.ensurePeer(e);if(t.offerTask)return t.offerTask;if(t.connection.signalingState!=="stable")return;const i=this.performOffer(t);t.offerTask=i;try{await i}finally{t.offerTask===i&&(t.offerTask=null)}}async performOffer(e){e.makingOffer=!0;try{const t=await e.connection.createOffer({offerToReceiveAudio:this.options.receiveAudio,offerToReceiveVideo:this.options.receiveVideo});if(e.connection.signalingState!=="stable")return;await e.connection.setLocalDescription(t);const i=te();e.activeNegotiationId=i,this.send({type:"rtc.offer",targetParticipantId:e.identity.participantId,negotiationId:i,description:e.connection.localDescription})}finally{e.makingOffer=!1}}shouldInitiateOffer(e){return this.options.credential.participantId.localeCompare(e.participantId)<0}async renegotiateAll(){for(const e of this.peers.values())await this.createOffer(e.identity)}async replaceVideoTrack(e){let t=!1;for(const i of this.peers.values()){const n=i.videoSender??i.connection.getSenders().find(o=>o.track?.kind==="video")??i.connection.getTransceivers().find(o=>o.receiver.track.kind==="video")?.sender,s=i.videoTransceiver??i.connection.getTransceivers().find(o=>o.sender===n);if(n){const o=e?this.options.receiveVideo?"sendrecv":"sendonly":this.options.receiveVideo?"recvonly":"inactive";s&&s.direction!==o&&(s.direction=o,t=!0),await n.replaceTrack(e),i.videoSender=n,s&&(i.videoTransceiver=s)}else e&&(i.videoSender=i.connection.addTrack(e,this.screenStream??this.localStream??new MediaStream([e])),t=!0)}t&&await this.renegotiateAll(),await this.applyVideoQuality()}async applyVideoQuality(e){const t=e?[...this.peers.values()].filter(i=>i.connection===e):[...this.peers.values()];for(const i of t)await this.applyPeerVideoQuality(i)}async applyPeerVideoQuality(e){const t=this.screenStream?.getVideoTracks()[0]?.id;for(const i of e.connection.getSenders().filter(n=>n.track?.kind==="video")){const n=!!(t&&i.track?.id===t),s=n?Ee:this.quality==="audio"?Ie.audio:V[e.quality],o=i.getParameters(),r=o.encodings?.length?o.encodings:[{}];o.encodings=r.map(c=>({...c,...s})),o.degradationPreference=n?"maintain-resolution":"maintain-framerate",await i.setParameters(o)}}startQualitySampling(){this.stopQualitySampling(),this.qualityTimer=setInterval(()=>{this.samplePeerQuality()},5e3),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopQualitySampling(){this.qualityTimer&&clearInterval(this.qualityTimer),this.qualityTimer=null,this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=[];for(const i of[...this.peers.values()])if(i.connection.connectionState==="connected")try{const n=await i.connection.getStats();for(const s of ee(n.values()))e.push({participantId:i.identity.participantId,level:s.level})}catch{}const t=H(e,this.activeSpeaker);t!==this.activeSpeaker&&(this.activeSpeaker=t,this.options.onEvent("activespeaker",{participantId:t}))}async samplePeerQuality(){if(this.quality!=="audio")for(const e of[...this.peers.values()]){if(e.connection.connectionState!=="connected")continue;let t;try{t=await e.connection.getStats()}catch{continue}const{sample:i,counters:n}=Re(t,e.counters);e.counters=n;const s=Ce(e.quality,i,e.poorSamples,e.goodSamples,{samplesSeen:e.samplesSeen,ceiling:this.quality});e.poorSamples=s.poorSamples,e.goodSamples=s.goodSamples,e.samplesSeen=s.samplesSeen,s.changed&&(e.quality=s.quality,await this.applyPeerVideoQuality(e).catch(()=>{}),this.options.onEvent("peerquality",{participantId:e.identity.participantId,quality:s.quality}))}}static startingQuality(e){return e==="low"?"low":"medium"}applyCodecPreference(e){if(this.preferredVideoCodec==="auto"||!globalThis.RTCRtpReceiver?.getCapabilities)return;const t=RTCRtpReceiver.getCapabilities("video")?.codecs??[],i=`video/${this.preferredVideoCodec}`,n=[...t.filter(s=>s.mimeType.toLowerCase()===i),...t.filter(s=>s.mimeType.toLowerCase()!==i)];for(const s of e.getTransceivers())s.receiver.track.kind==="video"&&typeof s.setCodecPreferences=="function"&&s.setCodecPreferences(n)}removePeer(e){const t=this.peers.get(e);t&&(t.connection.close(),this.peers.delete(e),this.options.onEvent("participantleft",t.identity))}resetPeers(){for(const e of this.peers.values())e.connection.close();this.peers.clear()}startHeartbeat(e){this.stopHeartbeat(),this.stopQualitySampling(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}clearConnectionTimers(){this.stopHeartbeat(),this.stopQualitySampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??te(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function te(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}const z={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1},ie=[{rid:"a",maxBitrate:V.high.maxBitrate,maxFramerate:V.high.maxFramerate},{rid:"b",maxBitrate:V.medium.maxBitrate,maxFramerate:V.medium.maxFramerate,scaleResolutionDownBy:2},{rid:"c",maxBitrate:V.low.maxBitrate,maxFramerate:V.low.maxFramerate,scaleResolutionDownBy:4}],ne={high:"a",medium:"b",low:"c"};class Me{options;apiBase;socket=null;connection=null;sessionId=null;localStream=null;screenStream=null;remotes=new Map;midToParticipant=new Map;pendingAnnouncements=new Map;videoSender=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;speakerTimer=null;activeSpeaker=null;quality;resolveJoin=null;rejectJoin=null;signalQueue=Promise.resolve();negotiationQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.apiBase=new URL(e.credential.signalingUrl,globalThis.location?.href).origin}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(this.joined)return;const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1),await this.publish(),this.startSpeakerSampling(),this.announceTracks();const t=[...this.pendingAnnouncements.values()];this.pendingAnnouncements.clear();for(const i of t)await this.handleTrackAnnouncement(i.sender,i.detail)}async leave(){this.stopped=!0,this.clearTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null,await this.closePublishedTracks().catch(()=>{}),this.connection?.close(),this.connection=null,this.sessionId=null,this.remotes.clear(),this.midToParticipant.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(c=>c.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")||await this.replaceVideoTrack(n);return}const r=this.connection?.getSenders().find(c=>c.track?.kind==="audio");r&&await r.replaceTrack(n)}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";await this.applyVideoQuality()}async setPreferredVideoCodec(e){}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getParticipants(){return[...this.remotes.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}getRemoteStreams(){return[...this.remotes.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.connection}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async openSocket(e){const{credential:t}=this.options,i=new URL(t.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0,this.resolveJoin?.();return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.sessionId&&this.announceTracks();return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removeRemote(t.sender.participantId);return}if(i==="rtc.control"){if(String(t.action??"")==="sfu.tracks"&&t.sender){await this.handleTrackAnnouncement(t.sender,t.detail);return}this.options.onEvent("control",t);return}i==="rtc.chat"&&this.options.onEvent("chatupdate",t)}}async publish(){const e=[...this.localStream?.getAudioTracks()??[],...this.localStream?.getVideoTracks()??[]],t=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"});this.connection=t,t.addEventListener("track",r=>this.handleRemoteTrack(r)),t.addEventListener("connectionstatechange",()=>{this.options.onEvent("peerconnectionstate",{participant:this.selfIdentity(),state:t.connectionState})});const i=await this.sfuFetch("POST","/v1/rtc/sfu/sessions");if(this.sessionId=i.sessionId,e.length===0)return;const n=e.map(r=>t.addTransceiver(r,{direction:"sendonly",streams:this.localStream?[this.localStream]:[],...r.kind==="video"&&this.simulcastEnabled()?{sendEncodings:ie.map(c=>({...c}))}:{}})),s=await t.createOffer();await t.setLocalDescription(s);const o=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(i.sessionId)}/tracks`,{sessionDescription:t.localDescription,tracks:n.map(r=>({location:"local",mid:r.mid,trackName:this.trackName(r.sender.track)}))});o.sessionDescription&&await t.setRemoteDescription(o.sessionDescription),this.videoSender=t.getSenders().find(r=>r.track?.kind==="video")??null,await this.applyVideoQuality()}trackName(e){return`${this.options.credential.participantId}/${e.kind}`}announceTracks(){if(!this.sessionId)return;const e=[...(this.localStream?.getAudioTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!1})),...(this.localStream?.getVideoTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:this.simulcastEnabled()}))];e.length!==0&&this.sendControl("sfu.tracks",{sessionId:this.sessionId,tracks:e})}async handleTrackAnnouncement(e,t){const i=String(t?.sessionId??""),n=(t?.tracks??[]).map(r=>({trackName:String(r.trackName??""),simulcast:r.simulcast===!0})).filter(r=>!!r.trackName),s=n.map(r=>r.trackName);if(!i||s.length===0||e.participantId===this.options.credential.participantId)return;if(!this.connection||!this.sessionId){this.pendingAnnouncements.set(e.participantId,{sender:e,detail:t??{}});return}const o=this.remotes.get(e.participantId)??{identity:e,stream:new MediaStream,sessionId:i,trackNames:[],simulcastNames:new Set,mids:new Map};o.sessionId=i;for(const r of n)r.simulcast&&o.simulcastNames.add(r.trackName);this.remotes.set(e.participantId,o),this.negotiationQueue=this.negotiationQueue.then(async()=>{const r=s.filter(c=>!o.trackNames.includes(c));r.length!==0&&await this.subscribe(o,r)}).catch(r=>{this.options.onEvent("signalingerror",{message:r instanceof Error?r.message:"SFU subscribe failed"})}),await this.negotiationQueue}async subscribe(e,t){const i=this.connection;if(!i||!this.sessionId||t.length===0)return;const n=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks`,{tracks:t.map(s=>({location:"remote",sessionId:e.sessionId,trackName:s,...e.simulcastNames.has(s)?{simulcast:{preferredRid:"a",priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}:{}}))});for(const s of n.tracks??[])s.mid&&(e.mids.set(s.mid,String(s.trackName??"")),this.midToParticipant.set(s.mid,e.identity.participantId));if(e.trackNames=[...new Set([...e.trackNames,...t])],n.requiresImmediateRenegotiation&&n.sessionDescription){await i.setRemoteDescription(n.sessionDescription);const s=await i.createAnswer();await i.setLocalDescription(s),await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`,{sessionDescription:i.localDescription})}}handleRemoteTrack(e){const t=e.transceiver?.mid??null,i=t?this.midToParticipant.get(t):void 0,n=i?this.remotes.get(i):void 0;n&&(n.stream.getTracks().some(s=>s.id===e.track.id)||n.stream.addTrack(e.track),this.options.onEvent("remotestream",{participant:n.identity,stream:n.stream,track:e.track}))}removeRemote(e){const t=this.remotes.get(e);if(t){for(const i of t.mids.keys())this.midToParticipant.delete(i);this.remotes.delete(e),this.options.onEvent("participantleft",t.identity)}}async setRemoteQuality(e,t){const i=this.remotes.get(e);if(!i||!this.sessionId)return;const n=[...i.mids.entries()].find(([,s])=>i.simulcastNames.has(s))?.[0];n&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/update`,{tracks:[{mid:n,simulcast:{preferredRid:ne[t],priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}]})}async replaceVideoTrack(e){this.videoSender&&(await this.videoSender.replaceTrack(e),await this.applyVideoQuality())}simulcastEnabled(){return this.options.simulcast!==!1}async applyVideoQuality(){const e=this.videoSender;if(!e)return;const t=this.screenStream?.getVideoTracks()[0]?.id,i=!!(t&&e.track?.id===t),n=e.getParameters(),s=n.encodings?.length?n.encodings:[{}];if(s.length>1){const o=this.quality==="audio"?null:ne[this.quality];n.encodings=s.map(r=>{if(i)return{...r,active:r.rid==="a",...r.rid==="a"?{maxBitrate:z.maxBitrate,maxFramerate:z.maxFramerate}:{}};const c=ie.find(d=>d.rid===r.rid),l=o!=null&&String(r.rid??"")>=o;return{...r,...c??{},active:l}})}else{const o=this.quality==="audio"?V.audio:V[this.quality],r=i?z:o;n.encodings=s.map(c=>({...c,...r}))}n.degradationPreference=i?"maintain-resolution":"maintain-framerate",await e.setParameters(n)}async closePublishedTracks(){const e=this.connection;if(!e||!this.sessionId)return;const t=e.getTransceivers().filter(i=>i.sender.track&&i.mid).map(i=>({mid:i.mid}));t.length!==0&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/close`,{tracks:t,force:!0})}selfIdentity(){return{participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId??this.options.credential.participantId,displayName:this.options.credential.externalUserId??this.options.credential.participantId,platform:"web"}}async sfuFetch(e,t,i){const n=await fetch(`${this.apiBase}${t}`,{method:e,headers:{authorization:`Bearer ${this.options.credential.signalingToken}`,"content-type":"application/json"},body:i===void 0?void 0:JSON.stringify(i)});if(!n.ok)throw new Error(`SFU request failed with HTTP ${n.status}`);return n.json()}startHeartbeat(e){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}startSpeakerSampling(){this.stopSpeakerSampling(),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopSpeakerSampling(){this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=this.connection;if(!e||e.connectionState!=="connected")return;let t;try{t=await e.getStats()}catch{return}const i=[];for(const s of ee(t.values())){const o=s.mid?this.midToParticipant.get(s.mid):void 0;o&&i.push({participantId:o,level:s.level})}const n=H(i,this.activeSpeaker);n!==this.activeSpeaker&&(this.activeSpeaker=n,this.options.onEvent("activespeaker",{participantId:n}))}clearTimers(){this.stopHeartbeat(),this.stopSpeakerSampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).then(()=>this.announceTracks()).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??Pe(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function Pe(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function Ae(a,e={}){const t=e.document??globalThis.document,i=!!(t&&t.pictureInPictureEnabled);let n=null;function s(){return n||(n=t.createElement("video"),n.muted=!0,n.playsInline=!0,n.style.position="fixed",n.style.width="1px",n.style.height="1px",n.style.opacity="0",n.style.pointerEvents="none",n.addEventListener("leavepictureinpicture",()=>e.onLeave?.()),t.body.appendChild(n),n)}function o(r){const c=a.getRemoteStreams();return r?c.find(l=>l.participant.participantId===r)?.stream??null:c[0]?.stream??a.getLocalStream()}return{supported:i,async enter(r){if(!i)throw new Error("This browser does not support picture-in-picture");const c=o(r);if(!c)throw new Error("No video stream is available for picture-in-picture");const l=s();l.srcObject!==c&&(l.srcObject=c),await l.play().catch(()=>{}),await l.requestPictureInPicture()},async exit(){t.pictureInPictureElement&&typeof t.exitPictureInPicture=="function"&&await t.exitPictureInPicture()},dispose(){n?.remove(),n=null}}}function Le(a,e){const t=e.document??globalThis.document,i=e.fetchImplementation??globalThis.fetch.bind(globalThis),n=new URL(e.credential.signalingUrl,globalThis.location?.href).origin,s=e.mimeType??"video/webm",o=e.height??720,r=Math.round(o*16/9);let c=null,l=null,d=null,u=null,k=0,f=[],v=!1;async function R(y,w,p,I="application/json"){const A=await i(`${n}${w}`,{method:y,headers:{authorization:`Bearer ${e.credential.signalingToken}`,"content-type":I},body:p===void 0?void 0:I==="application/json"?JSON.stringify(p):p});if(!A.ok)throw new Error(`Recording request failed with HTTP ${A.status}`);return A.json()}return{get recording(){return v},async start(){if(v)return;u=(await R("POST","/v1/rtc/recordings")).recordingId;const w=t.createElement("canvas");w.width=r,w.height=o;const p=w.getContext("2d"),I=new Map,A=(T,P)=>{let C=I.get(T);return C||(C=t.createElement("video"),C.muted=!0,C.playsInline=!0,C.srcObject=P,C.play().catch(()=>{}),I.set(T,C)),C};l=setInterval(()=>{const T=a.getRemoteStreams(),P=a.getLocalStream(),C=[...T.map(q=>({key:q.participant.participantId,stream:q.stream,label:q.participant.displayName})),...P?[{key:"local",stream:P,label:"me"}]:[]];p.fillStyle="#101418",p.fillRect(0,0,r,o);const N=Math.max(1,Math.ceil(Math.sqrt(C.length))),Q=Math.max(1,Math.ceil(C.length/N)),j=r/N,g=o/Q;C.forEach((q,ke)=>{const we=A(q.key,q.stream),X=ke%N*j,Z=Math.floor(ke/N)*g;we.videoWidth>0&&p.drawImage(we,X+2,Z+2,j-4,g-4),p.fillStyle="rgba(0,0,0,.6)",p.fillRect(X+8,Z+g-30,Math.min(j-16,12+q.label.length*9),22),p.fillStyle="#fff",p.font="14px system-ui",p.fillText(q.label,X+14,Z+g-14)})},66);const D=w.captureStream(15);d=new AudioContext;const E=d.createMediaStreamDestination(),L=new Set,S=()=>{const T=[...a.getRemoteStreams().map(P=>P.stream),...a.getLocalStream()?[a.getLocalStream()]:[]];for(const P of T)for(const C of P.getAudioTracks())L.has(C.id)||(L.add(C.id),d.createMediaStreamSource(new MediaStream([C])).connect(E))};S();const M=setInterval(S,1e3),m=()=>clearInterval(M);for(const T of E.stream.getAudioTracks())D.addTrack(T);c=(e.mediaRecorderFactory??((T,P)=>new MediaRecorder(T,P)))(D,{mimeType:s}),f=[],c.addEventListener("dataavailable",T=>{T.data&&T.data.size>0&&f.push(T.data)}),c.addEventListener("stop",m),c.start(1e3),k=Date.now(),v=!0},async stop(){if(!v||!c||!u)throw new Error("The recorder is not running");v=!1;const y=c,w=new Promise(E=>y.addEventListener("stop",()=>E(),{once:!0}));y.stop(),await w,l&&clearInterval(l),l=null,await d?.close().catch(()=>{}),d=null;const p=new Blob(f,{type:s});f=[];const I=Math.max(1,Math.round((Date.now()-k)/1e3)),A=await R("PUT",`/v1/rtc/recordings/${encodeURIComponent(u)}/media`,p,s);await R("POST",`/v1/rtc/recordings/${encodeURIComponent(u)}/complete`,{durationSeconds:I});const D={recordingId:u,sizeBytes:A.sizeBytes,durationSeconds:I};return u=null,c=null,D},dispose(){l&&clearInterval(l),l=null;try{c?.stop()}catch{}c=null,d?.close().catch(()=>{}),d=null,v=!1}}}const ae=["none","blur","aurora","cybergrid","bokeh","sunset","image"],se=["none","snow","confetti","vignette"],_={aurora(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#050b1e"),n.addColorStop(1,"#0b2540"),a.fillStyle=n,a.fillRect(0,0,e,t);for(let s=0;s<3;s+=1){a.beginPath();for(let o=0;o<=e;o+=16){const r=t*.35+s*40+Math.sin(o/140+i/900+s)*60;o===0?a.moveTo(o,r):a.lineTo(o,r)}a.lineTo(e,0),a.lineTo(0,0),a.closePath(),a.fillStyle=`hsla(${140+s*40+Math.sin(i/1300)*20}, 80%, 55%, 0.14)`,a.fill()}},cybergrid(a,e,t,i){a.fillStyle="#0a0118",a.fillRect(0,0,e,t),a.strokeStyle="rgba(255,0,180,0.5)",a.lineWidth=1.5;const n=t*.45;for(let o=0;o<12;o+=1){const r=n+(o*34+i/18)%(t-n);a.beginPath(),a.moveTo(0,r),a.lineTo(e,r),a.stroke()}for(let o=-8;o<=8;o+=1)a.beginPath(),a.moveTo(e/2+o*24,n),a.lineTo(e/2+o*e*.12,t),a.stroke();const s=a.createLinearGradient(0,t*.1,0,n);s.addColorStop(0,"#ffd34d"),s.addColorStop(1,"#ff2d78"),a.fillStyle=s,a.beginPath(),a.arc(e/2,n,t*.22,Math.PI,0),a.fill()},bokeh(a,e,t,i){a.fillStyle="#14100c",a.fillRect(0,0,e,t);for(let n=0;n<26;n+=1){const s=(Math.sin(n*12.9898)*.5+.5)*e,o=(Math.sin(n*78.233)*.5+.5)*t+Math.sin(i/1500+n)*12,r=14+n%5*12;a.fillStyle=`hsla(${35+n%4*12}, 90%, 60%, ${.1+n%3*.05})`,a.beginPath(),a.arc(s,o,r,0,Math.PI*2),a.fill()}},sunset(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#2b1055"),n.addColorStop(.55,"#d1548a"),n.addColorStop(1,"#ffb36b"),a.fillStyle=n,a.fillRect(0,0,e,t),a.fillStyle="#ffdf8e",a.beginPath(),a.arc(e*.5,t*.62+Math.sin(i/4e3)*4,t*.13,0,Math.PI*2),a.fill(),a.fillStyle="rgba(20,8,40,0.85)",a.fillRect(0,t*.78,e,t*.22)}};function De(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.background??"none",l=e.overlay??"none",d=e.backgroundImage??null,u=e.segmenter??null,k=null,f=!1;const v=t.createElement("video");v.muted=!0,v.playsInline=!0,v.srcObject=new MediaStream([r]),v.play().catch(()=>{});const R=t.createElement("canvas");R.width=i,R.height=n;const y=R.getContext("2d"),w=t.createElement("canvas");w.width=i,w.height=n;const p=w.getContext("2d"),I=()=>Array.from({length:80},(m,h)=>({x:(Math.sin(h*12.9898)*.5+.5)*i,y:(Math.sin(h*78.233)*.5+.5)*n,speed:.6+h%5*.5,drift:Math.sin(h)*.6,size:2+h%4,hue:h*47%360}));let A=I();const D=()=>{if(s)return;const m=v.videoWidth,h=v.videoHeight;if(!m||!h)return;const T=Math.min(1,1280/Math.max(m,h)),P=Math.round(m*T),C=Math.round(h*T);P===i&&C===n||(i=P,n=C,R.width=i,R.height=n,w.width=i,w.height=n,A=I())};v.addEventListener?.("loadedmetadata",D),v.addEventListener?.("resize",D);function E(){if(k){p.clearRect(0,0,i,n),p.drawImage(k,0,0,i,n),p.globalCompositeOperation="source-in",p.drawImage(v,0,0,i,n),p.globalCompositeOperation="source-over",y.drawImage(w,0,0);return}const m=i*.42,h=m*9/16,T=i-m-24,P=n-h-24;y.save(),y.shadowColor="rgba(0,0,0,.5)",y.shadowBlur=24,y.beginPath(),y.roundRect(T,P,m,h,14),y.clip(),y.drawImage(v,T,P,m,h),y.restore()}function L(m){if(c==="none"?y.drawImage(v,0,0,i,n):c==="blur"?(y.filter="blur(14px)",y.drawImage(v,-16,-16,i+32,n+32),y.filter="none",k&&E()):c==="image"&&d?(y.drawImage(d,0,0,i,n),E()):((_[c]??_.aurora)(y,i,n,m),E()),l==="snow"||l==="confetti")for(const h of A)h.y+=h.speed*(l==="confetti"?1.6:1),h.x+=h.drift,h.y>n&&(h.y=-6,h.x=Math.random()*i),y.fillStyle=l==="snow"?"rgba(255,255,255,.85)":`hsl(${h.hue}, 90%, 60%)`,l==="confetti"?y.fillRect(h.x,h.y,h.size+2,h.size):(y.beginPath(),y.arc(h.x,h.y,h.size/2,0,Math.PI*2),y.fill());else if(l==="vignette"){const h=y.createRadialGradient(i/2,n/2,n*.4,i/2,n/2,n*.85);h.addColorStop(0,"rgba(0,0,0,0)"),h.addColorStop(1,"rgba(0,0,0,0.55)"),y.fillStyle=h,y.fillRect(0,0,i,n)}}const S=setInterval(()=>{L(Date.now()),u&&!f&&c!=="none"&&(f=!0,u.segment(v).then(m=>{k=m}).catch(()=>{k=null}).finally(()=>{f=!1}))},Math.round(1e3/o)),M=R.captureStream(o);return{track:M.getVideoTracks()[0],stream:M,setBackground(m,h){c=ae.includes(m)?m:"none",h!==void 0&&(d=h),c==="none"&&(k=null)},setOverlay(m){l=se.includes(m)?m:"none"},setSegmenter(m){u=m,m||(k=null)},dispose(){clearInterval(S),u?.close?.();for(const m of M.getTracks())m.stop();v.srcObject=null}}}function W(){return{blinkLeft:0,blinkRight:0,jawOpen:0,smile:0,browUp:0,pupilX:0,pupilY:0,headYaw:0,headPitch:0,headRoll:0,tracked:!1}}function O(a,e){const t=a.find(i=>i.categoryName===e);return t?Math.max(0,Math.min(1,t.score)):0}function re(a){if(!a||a.length<16)return{yaw:0,pitch:0,roll:0};a[0],a[4];const e=a[8],t=a[1],i=a[5],n=a[9],s=a[10],o=Math.atan2(e,s),r=Math.asin(Math.max(-1,Math.min(1,-n))),c=Math.atan2(t,i);return{yaw:o,pitch:r,roll:c}}function oe(a,e,t=1){if(!a||a.length===0)return W();const i=re(e),n=(O(a,"eyeLookOutRight")+O(a,"eyeLookInLeft"))/2,s=(O(a,"eyeLookOutLeft")+O(a,"eyeLookInRight"))/2,o=(O(a,"eyeLookUpLeft")+O(a,"eyeLookUpRight"))/2,r=(O(a,"eyeLookDownLeft")+O(a,"eyeLookDownRight"))/2,c=(O(a,"mouthSmileLeft")+O(a,"mouthSmileRight"))/2,l=(O(a,"mouthFrownLeft")+O(a,"mouthFrownRight"))/2,d=O(a,"eyeBlinkLeft"),u=O(a,"eyeBlinkRight"),k=Math.min(1,Math.abs(i.yaw)/.35)*Math.max(0,Math.min(1,t)),f=(d+u)/2;return{blinkLeft:d+(f-d)*k,blinkRight:u+(f-u)*k,jawOpen:O(a,"jawOpen"),smile:c-l,browUp:(O(a,"browInnerUp")+O(a,"browOuterUpLeft")+O(a,"browOuterUpRight"))/3,pupilX:n-s,pupilY:r-o,headYaw:i.yaw,headPitch:i.pitch,headRoll:i.roll,tracked:!0}}function $(a,e,t=.4){const i=(n,s)=>n+(s-n)*t;return{blinkLeft:i(a.blinkLeft,e.blinkLeft),blinkRight:i(a.blinkRight,e.blinkRight),jawOpen:i(a.jawOpen,e.jawOpen),smile:i(a.smile,e.smile),browUp:i(a.browUp,e.browUp),pupilX:i(a.pupilX,e.pupilX),pupilY:i(a.pupilY,e.pupilY),headYaw:i(a.headYaw,e.headYaw),headPitch:i(a.headPitch,e.headPitch),headRoll:i(a.headRoll,e.headRoll),tracked:e.tracked}}function ce(a,e,t,i,n){n||(a.fillStyle="#101822",a.fillRect(0,0,t,i));const s=n?n.x+e.headYaw*n.radius*.1:t/2+e.headYaw*t*.18,o=n?n.y+e.headPitch*n.radius*.1:i/2+e.headPitch*i*.18,r=n?n.radius:Math.min(t,i)*.28;a.save(),a.translate(s,o),a.rotate(e.headRoll*.6);const c=Math.max(-1.35,Math.min(1.35,e.headYaw)),l=Math.max(-.9,Math.min(.9,e.headPitch)),d=Math.sin(c),u=L=>Math.sin(L+c)*r,k=L=>Math.cos(L+c),f=Math.sin(l)*r*.22;a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.35,-r*.25,r*.98,r*.95,0,0,Math.PI*2),a.fill(),a.fillStyle=e.tracked?"#f2c9a0":"#5a6470",a.beginPath(),a.ellipse(0,0,r*(1-Math.abs(d)*.12),r*1.12,0,0,Math.PI*2),a.fill(),a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.28,-r*.72+f*.4,r*.92,r*.5,0,Math.PI,Math.PI*2),a.fill();const v=0;a.fillStyle="#e5b58a",a.beginPath();const R=u(v)*.9,y=r*.12+f;a.ellipse(R+d*r*.14,y,r*.11,r*.14,0,0,Math.PI*2),a.fill();for(const L of[-1,1]){const S=L*.42,M=k(S);if(M<=.12)continue;const m=u(S)*.82,h=Math.max(.2,M),T=L<0?e.blinkLeft:e.blinkRight,P=Math.max(.06,1-T),C=e.browUp*r*.14;a.strokeStyle="#4a2e18",a.lineWidth=r*.07,a.lineCap="round",a.beginPath(),a.moveTo(m-r*.2*h,-r*.42-C+f),a.quadraticCurveTo(m,-r*.52-C+f,m+r*.2*h,-r*.44-C+f),a.stroke();const N=-r*.18+f;a.fillStyle="#ffffff",a.beginPath(),a.ellipse(m,N,r*.16*h,r*.12*P,0,0,Math.PI*2),a.fill(),a.fillStyle="#22303c",a.beginPath(),a.ellipse(m+e.pupilX*r*.07*h,N+e.pupilY*r*.05,r*.06*h,r*.06*P,0,0,Math.PI*2),a.fill()}const w=Math.max(.25,k(0)),p=u(0)*.7,I=r*.55*w,A=r*(.04+e.jawOpen*.34),D=e.smile*r*.12,E=r*.42+f;a.fillStyle="#8c2f2f",a.beginPath(),a.moveTo(p-I/2,E-D),a.quadraticCurveTo(p,E+A,p+I/2,E-D),a.quadraticCurveTo(p,E-A*.25,p-I/2,E-D),a.closePath(),a.fill(),a.restore(),!e.tracked&&!n&&(a.fillStyle="#9fb0bd",a.font=`${Math.round(i*.045)}px system-ui`,a.textAlign="center",a.fillText("No face detected",t/2,i*.9),a.textAlign="left")}const J={mouthGain:1.7,blinkGain:1,eyeSync:1,smoothing:.4,avatarScale:1,avatarLift:0,lostHoldMs:400};function G(){return{render(a,e,t,i,n){ce(a,e,t,i,n??void 0)}}}const Y=a=>Math.max(0,Math.min(1,a));function Oe(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.tracker??null,l=e.renderer??G(),d=e.mode??"replace";const u={...J,...e.tuning},k=e.backdrop??"#101822",f=t.createElement("video");f.muted=!0,f.playsInline=!0,f.srcObject=new MediaStream([r]),f.play().catch(()=>{});const v=t.createElement("canvas");v.width=i,v.height=n;const R=v.getContext("2d"),y=()=>{if(s)return;const S=f.videoWidth,M=f.videoHeight;if(!S||!M)return;const m=Math.min(1,1280/Math.max(S,M)),h=Math.round(S*m),T=Math.round(M*m);(!(h===i||T===n)||h!==i||T!==n)&&(i=h,n=T,v.width=i,v.height=n)};f.addEventListener?.("loadedmetadata",y),f.addEventListener?.("resize",y);let w=W(),p=null,I=0;function A(S){if(S.length<=454)return;const M=S[10],m=S[152],h=S[234],T=S[454],P=(h.x+T.x)/2*i,C=(h.y+T.y)/2*n,N=Math.hypot((m.x-M.x)*i,(m.y-M.y)*n),Q=Math.max(.6,Math.cos(w.headPitch)),j={x:P,y:C,radius:Math.max(24,N/Q*.8)};p=p?{x:p.x+(j.x-p.x)*.4,y:p.y+(j.y-p.y)*.4,radius:p.radius+(j.radius-p.radius)*.15}:j}function D(S){if(c&&f.readyState>=2){let m=null;try{m=c.detect(f,S)}catch{}const h=oe(m?.categories??null,m?.matrix??null,u.eyeSync);h.jawOpen=Y(h.jawOpen*u.mouthGain),h.blinkLeft=Y(h.blinkLeft*u.blinkGain),h.blinkRight=Y(h.blinkRight*u.blinkGain),h.tracked?(I=S,w=$(w,h,u.smoothing),m?.landmarks&&A(m.landmarks)):w.tracked&&S-I>u.lostHoldMs&&(w=$(w,{...w,jawOpen:0,browUp:0,smile:w.smile*.5,blinkLeft:.1,blinkRight:.1},.05))}d==="overlay"&&f.readyState>=2?R.drawImage(f,0,0,i,n):(R.fillStyle=k,R.fillRect(0,0,i,n));const M=p?{x:p.x,y:p.y+u.avatarLift*p.radius,radius:p.radius*u.avatarScale}:null;l?.render(R,w,i,n,M)}const E=setInterval(()=>D(Date.now()),Math.round(1e3/o)),L=v.captureStream(o);return{track:L.getVideoTracks()[0],stream:L,getRig:()=>({...w}),setMode(S){d=S==="overlay"?"overlay":"replace"},setTracker(S){c?.close?.(),c=S},setRenderer(S){l?.dispose?.(),l=S??G()},setTuning(S){for(const M of Object.keys(J)){const m=S[M];typeof m=="number"&&Number.isFinite(m)&&(u[M]=m)}},getTuning:()=>({...u}),dispose(){clearInterval(E),c?.close?.(),l?.dispose?.();for(const S of L.getTracks())S.stop();f.srcObject=null}}}class le extends EventTarget{mode;options;client=null;peerConnection;state="initialized";latestMediaScore=null;latestQuality=null;statsTimer=null;statsRunning=!1;previousCounters=null;lastTelemetryAt=0;joinPromise=null;requestedQuality;effectiveQuality;poorQualitySamples=0;goodQualitySamples=0;constructor(e){if(super(),!e.credential?.signalingToken||!e.credential.signalingUrl||!e.credential.roomId||!e.credential.participantId)throw new Error("A valid room signaling credential is required");this.mode=e.mode,this.requestedQuality=e.mode==="voice"?"audio":e.quality??"auto",this.effectiveQuality=this.requestedQuality==="auto"?"medium":this.requestedQuality,this.peerConnection=e.peerConnection??null,this.options={...e,audio:e.audio??!0,video:e.mode==="voice"?!1:e.video??!0,receiveAudio:e.receiveAudio??!0,receiveVideo:e.mode==="voice"?!1:e.receiveVideo??!0,statsInterval:Math.max(1e3,e.statsInterval??5e3),telemetryInterval:Math.max(1e4,e.telemetryInterval??3e4),autoReportStats:e.autoReportStats??!0}}async initialize(){if(this.client)return this;try{const t=(this.options.transport==="auto"?this.mode==="meeting"?"sfu":"mesh":this.options.transport??"mesh")==="sfu"?Me:x;return this.client=new t({credential:{signalingToken:this.options.credential.signalingToken,signalingUrl:this.options.credential.signalingUrl,roomId:this.options.credential.roomId,participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId,iceServers:this.options.credential.iceServers},audio:this.options.audio,video:this.options.video,receiveAudio:this.options.receiveAudio,receiveVideo:this.options.receiveVideo,quality:this.effectiveQuality,preferredVideoCodec:this.options.preferredVideoCodec??"auto",iceTransportPolicy:this.options.iceTransportPolicy??"all",localStream:this.options.localStream,simulcast:this.options.simulcast??!0,onEvent:(i,n)=>this.handleNativeEvent(i,n)}),await this.client.initialize(),this.startStats(),this.emit("initialized",{mode:this.mode,sdkVersion:Se}),this.options.autoJoin&&await this.join(),this}catch(e){this.state="failed";const t=ve(e,"RTC initialization failed");throw this.emit("error",U(t)),t}}async join(){const e=this.requireClient();return this.joinPromise?this.joinPromise:(this.state="joining",this.emit("joining",{}),this.joinPromise=e.join().catch(t=>{this.state="failed";const i=ve(t,"RTC connection failed");throw this.emit("error",U(i)),i}).finally(()=>{this.joinPromise=null}),this.joinPromise)}async leave(){this.client&&(await this.joinPromise?.catch(()=>{}),await this.sampleAndReport("final").catch(()=>{}),await this.client.leave(),this.state="disconnected",this.stopStats())}async setMuted(e){this.requireClient().setMuted(e),this.emit("mediachange",{kind:"audio",enabled:!e})}async setCameraEnabled(e){if(this.mode==="voice"&&e)throw new Error("Camera is unavailable in voice mode");await this.requireClient().setCameraEnabled(e),this.emit("mediachange",{kind:"video",enabled:e})}async setVideoTrack(e){if(this.mode==="voice")throw new Error("Camera is unavailable in voice mode");await this.requireClient().setVideoTrack(e),this.emit("mediachange",{kind:"video",enabled:e.enabled,trackId:e.id})}async setScreenShareEnabled(e){await this.requireClient().setScreenShareEnabled(e),this.emit("mediachange",{kind:"screenshare",enabled:e})}async setDevice(e){await this.requireClient().setDevice(e),this.emit("devicechange",{deviceId:e.deviceId,kind:e.kind})}async setQuality(e){if(this.mode==="voice"&&e!=="audio")throw new Error("Voice calls only support audio quality");this.requestedQuality=e,this.poorQualitySamples=0,this.goodQualitySamples=0,await this.applyEffectiveQuality(e==="auto"?"medium":e,"manual")}async setPreferredVideoCodec(e){await this.requireClient().setPreferredVideoCodec(e),this.emit("codecchange",{preferredVideoCodec:e})}async getDevices(){return navigator.mediaDevices?.enumerateDevices?navigator.mediaDevices.enumerateDevices():[]}getParticipants(){return this.requireClient().getParticipants().map(fe)}getLocalStream(){return this.client?.getLocalStream()??null}getRemoteStreams(){return(this.client?.getRemoteStreams()??[]).map(e=>({participant:fe(e.participant),stream:e.stream}))}setPeerConnection(e){this.peerConnection=e,this.previousCounters=null}async sendChatMessage(e,t){const i=e.trim();if(!i)throw new Error("message is required");this.requireClient().sendChat(i,t)}async sendControlMessage(e,t={}){const i=e.trim();if(!i)throw new Error("action is required");this.requireClient().sendControl(i,t)}async createPoll(e,t,i={}){const n=t.map(s=>s.trim()).filter(Boolean);if(!e.trim()||n.length<2)throw new Error("A poll requires a question and at least two options");this.requireClient().sendControl("poll.create",{poll:{id:`poll-${crypto.randomUUID()}`,question:e.trim(),options:n,votes:{},...i}})}async votePoll(e,t){this.requireClient().sendControl("poll.vote",{pollId:e,optionIndex:t})}async requestStageAccess(){this.requireClient().sendControl("stage.request")}async cancelStageAccess(){this.requireClient().sendControl("stage.cancel")}async joinStage(){this.requireClient().sendControl("stage.join")}async leaveStage(){this.requireClient().sendControl("stage.leave")}async grantStageAccess(e){this.requireClient().sendControl("stage.grant",{participantIds:e})}async denyStageAccess(e){this.requireClient().sendControl("stage.deny",{participantIds:e})}async startRecording(e=!1){this.requireClient().sendControl("recording.start",{allowMultiple:e})}async stopRecording(e){this.requireClient().sendControl("recording.stop",{recordingId:e})}async pauseRecording(e){this.requireClient().sendControl("recording.pause",{recordingId:e})}async resumeRecording(e){this.requireClient().sendControl("recording.resume",{recordingId:e})}async runPreflight(e={}){const t=[],i=globalThis.isSecureContext===!0;i||t.push("secure_context_required");let n="unavailable";if(e.requestMedia!==!1&&navigator.mediaDevices?.getUserMedia)try{(await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})).getTracks().forEach(k=>k.stop()),n="granted"}catch{n="denied",t.push("media_permission_denied")}const s=navigator.mediaDevices?.enumerateDevices?await navigator.mediaDevices.enumerateDevices().catch(()=>[]):[],o=s.filter(u=>u.kind==="audioinput").length,r=s.filter(u=>u.kind==="videoinput").length;this.options.audio&&o===0&&t.push("microphone_unavailable"),this.options.video&&r===0&&t.push("camera_unavailable");const c=e.probeUrl??this.options.preflightProbeUrl;let l=null;if(c){const u=performance.now();try{const k=await fetch(c,{method:"HEAD",cache:"no-store",credentials:"omit"});l=Math.round(performance.now()-u),k.ok||t.push("network_probe_failed")}catch{t.push("network_probe_failed")}}const d={passed:t.length===0,sampledAt:new Date().toISOString(),secureContext:i,mediaPermission:n,microphoneCount:o,cameraCount:r,probeLatencyMs:l,effectiveConnectionType:ge()?.effectiveType??null,issues:t};return this.emit("preflight",d),this.options.autoReportStats&&await this.reportQuality({sampleType:"preflight",sampledAt:d.sampledAt,connectionState:d.passed?"ready":"failed",rttMs:d.probeLatencyMs,effectiveConnectionType:d.effectiveConnectionType}).catch(()=>{}),d}async collectQualitySample(e="runtime"){const t=this.options.statsProvider?await this.options.statsProvider():{},i=this.peerConnection?await Ue(this.peerConnection,this.previousCounters):null;i?.counters&&(this.previousCounters=i.counters);const s={...{sampleType:e,sampledAt:new Date().toISOString(),connectionState:this.peerConnection?.connectionState??this.state,mediaScore:this.latestMediaScore,networkScore:this.latestMediaScore==null?5:Math.max(0,Math.min(5,Math.round(this.latestMediaScore))),estimatedMos:4.5,rttMs:null,jitterMs:null,packetLossPct:null,packetLossCumulativePct:null,packetsLost:null,packetsReceived:null,inboundBitrateBps:null,outboundBitrateBps:null,availableOutgoingBitrateBps:null,framesPerSecond:null,frameWidth:null,frameHeight:null,inboundFrameWidth:null,inboundFrameHeight:null,outboundFrameWidth:null,outboundFrameHeight:null,framesDropped:null,freezeCount:null,freezeDurationMs:null,concealedSamples:null,codec:null,transportProtocol:null,candidateType:null,qualityLimitationReason:null,deviceMemoryGb:Be(),effectiveConnectionType:ge()?.effectiveType??null},...i?.sample??{},...t,sampleType:e,sampledAt:new Date().toISOString()},o=Qe(s);return s.networkScore=o.networkScore,s.estimatedMos=o.estimatedMos,this.latestQuality=s,s}async reportQuality(e){const t=this.options.credential.telemetryToken,i=this.options.telemetryEndpoint??this.options.credential.telemetryEndpoint;if(!t||!i)return;const n=await fetch(i,{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify(e),keepalive:e.sampleType==="final"});if(!n.ok)throw new Error(`RTC telemetry failed with HTTP ${n.status}`)}getStats(){const e=this.client,t=e?.getLocalStream();return{state:this.state,participantCount:e?e.getParticipants().length+1:0,audioEnabled:!!t?.getAudioTracks().some(i=>i.enabled),videoEnabled:!!t?.getVideoTracks().some(i=>i.enabled),screenShareEnabled:!1,latestMediaScore:this.latestMediaScore,latestQuality:this.latestQuality,sampledAt:new Date().toISOString()}}getNativeClient(){return this.client}destroy(){this.stopStats(),this.client&&this.client.leave(),this.client=null,this.joinPromise=null,this.peerConnection=null,this.state="disconnected"}handleNativeEvent(e,t){e==="connected"?(this.state="connected",this.peerConnection=this.client?.getPrimaryPeerConnection()??null):e==="disconnected"?this.state="disconnected":e==="peerconnectionstate"&&(this.peerConnection=this.client?.getPrimaryPeerConnection()??null),this.emit(e,t)}startStats(){this.stopStats(),this.statsTimer=setInterval(()=>{this.sampleAndReport("runtime")},this.options.statsInterval)}async sampleAndReport(e){if(!this.statsRunning){this.statsRunning=!0;try{const t=await this.collectQualitySample(e);await this.adaptQuality(t),this.emit("stats",{...this.getStats(),latestQuality:t});const i=Date.now(),n=e!=="runtime"||i-this.lastTelemetryAt>=this.options.telemetryInterval;this.options.autoReportStats&&n&&(await this.reportQuality(t),this.lastTelemetryAt=i)}catch(t){this.emit("telemetryerror",U(t))}finally{this.statsRunning=!1}}}stopStats(){this.statsTimer!==null&&clearInterval(this.statsTimer),this.statsTimer=null}async adaptQuality(e){if(this.requestedQuality!=="auto"||this.options.adaptiveQuality===!1||this.mode==="voice")return;if((this.client?.getParticipants().length??0)>1){this.poorQualitySamples=0,this.goodQualitySamples=0,this.effectiveQuality!=="high"&&await this.applyEffectiveQuality("high","network");return}const t=String(e.qualityLimitationReason??"").toLowerCase(),i=(e.packetLossPct??0)>=8||(e.rttMs??0)>=800||t==="cpu"||t==="bandwidth",n=i||(e.packetLossPct??0)>=3||(e.rttMs??0)>=350||e.availableOutgoingBitrateBps!=null&&e.availableOutgoingBitrateBps<35e4,s=(e.packetLossPct??100)<1&&(e.rttMs??1e3)<180&&!t&&(e.availableOutgoingBitrateBps==null||e.availableOutgoingBitrateBps>1e6);this.poorQualitySamples=n?this.poorQualitySamples+1:0,this.goodQualitySamples=s?this.goodQualitySamples+1:0;const o=["low","medium","high"],r=o.indexOf(this.effectiveQuality);i&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality("low","network")):this.poorQualitySamples>=2&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality(o[r-1],"network")):this.goodQualitySamples>=6&&r>=0&&r<o.length-1&&(this.goodQualitySamples=0,await this.applyEffectiveQuality(o[r+1],"network"))}async applyEffectiveQuality(e,t){if(this.effectiveQuality===e&&this.client)return;const i=this.effectiveQuality;this.effectiveQuality=e,this.client&&await this.client.setQuality(e),this.emit("qualitychange",{requestedQuality:this.requestedQuality,quality:e,previous:i,reason:t})}requireClient(){if(!this.client)throw new Error("Call initialize() before using the RTC client");return this.client}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}async function qe(a){return new le(a).initialize()}let F=null;function de(){if(F)return F;const a=globalThis,e=a.AudioContext??a.webkitAudioContext;return e?(F=new e,F):null}async function he(){const a=de();return a?(a.state==="suspended"&&await a.resume().catch(()=>{}),a.state==="running"):!1}class Ne{options;media=null;cadenceTimer=null;oscillators=new Set;constructor(e){this.options=e}unlock(){return he()}startIncoming(){this.start("incoming",this.options.ringtoneUrl)}startRingback(){this.start("ringback",this.options.ringbackUrl)}stop(){this.cadenceTimer&&clearInterval(this.cadenceTimer),this.cadenceTimer=null,this.media?.pause(),this.media&&(this.media.currentTime=0),this.media=null;for(const e of this.oscillators)try{e.stop()}catch{}this.oscillators.clear()}start(e,t){if(this.stop(),this.options.sounds===!1)return;if(t&&typeof Audio<"u"){this.media=new Audio(t),this.media.loop=!0,this.media.play().catch(()=>{});return}const i=()=>this.playBuiltInTone(e);i(),this.cadenceTimer=setInterval(i,e==="incoming"?3e3:3500)}playBuiltInTone(e){const t=de();if(!t||t.state!=="running")return;const i=e==="incoming"?1.25:1,n=e==="incoming"?[440,480]:[425],s=t.createGain(),o=t.currentTime;s.gain.setValueAtTime(1e-4,o),s.gain.exponentialRampToValueAtTime(.045,o+.025),s.gain.setValueAtTime(.045,o+i-.06),s.gain.exponentialRampToValueAtTime(1e-4,o+i),s.connect(t.destination);for(const r of n){const c=t.createOscillator();c.type="sine",c.frequency.value=r,c.connect(s),this.oscillators.add(c),c.onended=()=>this.oscillators.delete(c),c.start(o),c.stop(o+i+.02)}}}class ue extends EventTarget{options;socket=null;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;currentCall=null;outgoingCallId=null;mount=null;terminalTimer=null;tokenRefreshTimer=null;tokenRequest=null;currentToken;currentTokenExpiresAt;callAudio;mediaClients=new Map;activeCallIds=new Set;seenInvitationIds=new Set;startCallRequest=null;lastDisconnect=null;constructor(e){if(super(),!e.token&&!e.tokenProvider)throw new Error("A client token or tokenProvider is required");if(e.token&&!e.token.startsWith("rtcc_"))throw new Error("A valid RTC client token is required");if(!e.signalingUrl&&!e.callActionsUrl&&!e.apiBaseUrl&&!globalThis.location?.origin)throw new Error("signalingUrl or callActionsUrl is required outside a browser");this.options=e,this.currentToken=e.token??null,this.currentTokenExpiresAt=e.tokenExpiresAt,this.callAudio=new Ne(e),this.mount=me(e.mount),this.scheduleTokenRefresh()}connect(){if(!this.options.signalingUrl)throw new Error("signalingUrl is required to connect signaling");return this.stopped=!1,this.scheduleTokenRefresh(),this.openSocket("initial"),this}setMount(e){this.clearUi(),this.mount=me(e),this.currentCall&&this.renderIncoming(this.currentCall)}attachMediaClient(e,t){if(!e.trim())throw new Error("callId is required");return this.mediaClients.set(e,t),()=>{this.mediaClients.get(e)===t&&this.mediaClients.delete(e)}}async startCall(e){if(this.startCallRequest)return this.startCallRequest;if(this.activeCallIds.size>0)throw new Error("Another call is already active");const t=e.targets?.length?e.targets:e.target?[e.target]:[];if(t.length===0)throw new Error("startCall requires at least one target");const i={mode:e.mode,targets:t,metadata:e.metadata,idempotencyKey:e.idempotencyKey},n=(async()=>{await this.callAudio.unlock(),this.callAudio.startRingback();let s;try{s=await this.request("/v1/rtc/client/calls",{method:"POST",headers:e.idempotencyKey?{"idempotency-key":e.idempotencyKey}:void 0,body:JSON.stringify(i)})}catch(o){throw this.callAudio.stop(),o}return!s.credential&&s.callerCredential&&(s.credential=s.callerCredential),this.outgoingCallId=String(s.call?.id??"")||null,this.outgoingCallId&&this.activeCallIds.add(this.outgoingCallId),this.emit("outgoingcall",s),s})();this.startCallRequest=n;try{return await n}finally{this.startCallRequest===n&&(this.startCallRequest=null)}}async accept(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"accept");return this.activeCallIds.add(e),this.callAudio.stop(),this.dismiss(e),this.emit("callaccepted",t),t}async reject(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"reject");return this.releaseMedia(e),this.showTerminal(e,"Call declined"),this.emit("callrejected",t),t}async busy(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"busy");return this.releaseMedia(e),this.showTerminal(e,"Line busy"),t}async end(e){this.callAudio.stop();const t=await this.callAction(e,"end");return this.releaseMedia(e),this.showTerminal(e,"Call ended"),t}async cancel(e){this.callAudio.stop();const t=await this.callAction(e,"cancel");return this.releaseMedia(e),this.showTerminal(e,"Call cancelled"),t}handleIncomingCall(e){if(!e?.id||!e.caller?.id||!e.caller.name||e.mode!=="voice"&&e.mode!=="video")throw new Error("Incoming call payload is invalid");if(e.expiresAt&&Date.parse(e.expiresAt)<=Date.now()){this.emit("callexpired",{id:e.id,expiresAt:e.expiresAt});return}if(this.currentCall?.id===e.id){this.currentCall=e,this.emit("callrestored",e);return}if(this.seenInvitationIds.has(e.id)){this.emit("callrestored",e);return}this.seenInvitationIds.add(e.id),this.activeCallIds.add(e.id),this.currentCall=e,this.callAudio.startIncoming(),this.renderIncoming(e),this.emit("incomingcall",e)}handleCallUpdate(e){const t=e.id===this.currentCall?.id||e.id===this.outgoingCallId;t&&e.status==="accepted"?(this.activeCallIds.add(e.id),this.callAudio.stop(),this.dismiss(e.id)):t&&["rejected","busy","cancelled","ended","failed","missed"].includes(e.status)&&this.showTerminal(e.id,Ve(e.status)),["rejected","busy","cancelled","ended","failed","missed"].includes(e.status)&&this.releaseMedia(e.id),this.emit("callupdated",e)}disconnect(){this.stopped=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.reconnectTimer=null,this.heartbeatTimer=null,this.tokenRefreshTimer=null,this.socket?.close(1e3,"Client disconnected"),this.socket=null,this.callAudio.stop(),this.dismiss();for(const e of this.mediaClients.keys())this.releaseMedia(e);this.activeCallIds.clear(),this.seenInvitationIds.clear()}destroy(){this.disconnect(),this.mount=null}async openSocket(e){if(!this.options.signalingUrl||this.stopped||this.socket?.readyState===WebSocket.OPEN||this.socket?.readyState===WebSocket.CONNECTING)return;let t;try{t=await this.resolveToken(e,e==="reconnect")}catch(s){this.emit("tokenerror",U(s)),!this.stopped&&this.options.reconnect!==!1&&this.scheduleReconnect();return}if(this.stopped||this.socket)return;const i=new URL(this.options.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new WebSocket(i,["rtc-client",t]);this.socket=n,n.addEventListener("open",()=>{const s=this.reconnectAttempt>0,o=this.lastDisconnect;this.reconnectAttempt=0,this.heartbeatDeadline=Date.now()+45e3,this.emit("signalingconnected",{resumed:s}),o&&(this.reportSignalingDiagnostic({event:"recovered",code:o.code,reason:o.reason,wasClean:o.wasClean,outageMs:Date.now()-o.at}),this.lastDisconnect=null),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{if(n.readyState===WebSocket.OPEN){if(Date.now()>this.heartbeatDeadline){n.close(4e3,"Signaling heartbeat timed out");return}n.send("ping")}},2e4)}),n.addEventListener("message",s=>{this.heartbeatDeadline=Date.now()+45e3,this.handleSignal(String(s.data))}),n.addEventListener("close",s=>{if(this.socket!==n)return;this.socket=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;const o=!this.stopped&&this.options.reconnect!==!1,r={code:s.code,reason:s.reason,wasClean:s.wasClean,willReconnect:o,attempt:this.reconnectAttempt+1};o&&(this.lastDisconnect={code:s.code,reason:s.reason,wasClean:s.wasClean,at:Date.now()}),this.emit("signalingdisconnected",r),this.reportSignalingDiagnostic({event:o?"reconnecting":"disconnected",...r}),o&&this.scheduleReconnect()}),n.addEventListener("error",()=>{const s={message:"RTC signaling connection failed",reconnecting:!this.stopped&&this.options.reconnect!==!1,attempt:this.reconnectAttempt+1};this.emit("signalingerror",s),this.reportSignalingDiagnostic({event:"error",attempt:s.attempt})})}handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i==="call.incoming"&&t.call&&typeof t.call=="object"){this.handleIncomingCall(t.call);return}if(i==="call.updated"){const n=t.call;n?.id&&n.status&&this.handleCallUpdate({id:n.id,status:n.status});return}i==="presence"?this.emit("presence",{users:t.users??[]}):i!=="pong"&&this.emit(i||"signal",t)}async callAction(e,t){return this.request(`/v1/rtc/client/calls/${encodeURIComponent(e)}/${t}`,{method:"POST",body:"{}"})}async request(e,t,i=!1){const n=this.options.callActionsUrl??this.options.apiBaseUrl??this.options.signalingUrl??globalThis.location?.origin;if(!n)throw new Error("callActionsUrl is required for call actions");const s=new URL(n,globalThis.location?.href),o=await this.resolveToken("initial"),r=new Headers(t.headers);r.set("authorization",`Bearer ${o}`),r.set("content-type","application/json");const c=await fetch(new URL(e,s.origin),{...t,headers:r}),l=await c.json().catch(()=>({}));if(c.status===401&&this.options.tokenProvider&&!i)return await this.resolveToken("unauthorized",!0),this.request(e,t,!0);if(!c.ok){const d=l.error;throw new Error(typeof d=="string"?d:d?.message||`RTC API failed with HTTP ${c.status}`)}return l}renderIncoming(e){if(!this.mount)return;this.clearUi();const t=document.createElement("section");t.dataset.rtcIncomingCall=e.id,t.setAttribute("role","dialog"),t.setAttribute("aria-label",`Incoming ${e.mode} call from ${e.caller.name}`),Object.assign(t.style,{display:"grid",gap:"14px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const i=document.createElement("small");i.textContent=e.mode==="video"?"Incoming video call":"Incoming voice call",i.style.color="#5d7079";const n=document.createElement("strong");n.textContent=e.caller.name,n.style.fontSize="20px";const s=document.createElement("div");Object.assign(s.style,{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"});const o=pe("Decline","#fff","#a32920","#d9a09b"),r=pe("Answer","#fff","#08745f","#08745f");o.addEventListener("click",()=>{this.reject(e.id).catch(c=>this.emit("error",U(c)))}),r.addEventListener("click",()=>{this.accept(e.id).catch(c=>this.emit("error",U(c)))}),s.appendChild(o),s.appendChild(r),t.appendChild(i),t.appendChild(n),t.appendChild(s),this.mount.replaceChildren(t)}dismiss(e){e&&this.currentCall?.id!==e&&this.outgoingCallId!==e||(this.currentCall=null,(!e||this.outgoingCallId===e)&&(this.outgoingCallId=null),this.callAudio.stop(),this.clearUi())}showTerminal(e,t){if(this.callAudio.stop(),this.currentCall?.id===e&&(this.currentCall=null),this.outgoingCallId===e&&(this.outgoingCallId=null),!this.mount)return;this.clearUi();const i=document.createElement("section");i.dataset.rtcCallTerminal=e,i.setAttribute("role","status"),Object.assign(i.style,{display:"grid",placeItems:"center",gap:"10px",minHeight:"150px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const n=document.createElement("strong");n.textContent=t,n.style.fontSize="18px",i.appendChild(n),this.mount.replaceChildren(i);const s=Math.max(0,this.options.terminalStateDurationMs??1600);this.terminalTimer=setTimeout(()=>this.clearUi(),s)}clearUi(){this.terminalTimer&&clearTimeout(this.terminalTimer),this.terminalTimer=null,this.mount&&this.mount.replaceChildren()}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer);const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.openSocket("reconnect")},e)}async resolveToken(e,t=!1){if(!t&&this.currentToken&&!this.tokenNeedsRefresh())return this.currentToken;if(!this.options.tokenProvider){if(!this.currentToken)throw new Error("A valid RTC client token is required");return this.currentToken}return this.tokenRequest?this.tokenRequest:(this.tokenRequest=(async()=>{const i=await this.options.tokenProvider?.({reason:e,previousExpiresAt:this.currentTokenExpiresAt}),n=typeof i=="string"?{token:i}:i;if(!n?.token?.startsWith("rtcc_"))throw new Error("tokenProvider returned an invalid RTC client token");return this.currentToken=n.token,this.currentTokenExpiresAt=n.expiresAt,this.scheduleTokenRefresh(),this.emit("tokenrefreshed",{reason:e,expiresAt:n.expiresAt}),n.token})().finally(()=>{this.tokenRequest=null}),this.tokenRequest)}tokenNeedsRefresh(){if(!this.currentTokenExpiresAt)return!1;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();return Number.isFinite(e)?e<=this.refreshSkew(e):!0}refreshSkew(e){const t=Math.max(5e3,this.options.tokenRefreshSkewMs??3e5);return Math.min(t,Math.max(5e3,e*.2))}scheduleTokenRefresh(){if(this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.tokenRefreshTimer=null,this.stopped||!this.options.tokenProvider||!this.currentTokenExpiresAt)return;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();if(!Number.isFinite(e))return;const t=Math.max(0,e-this.refreshSkew(e));this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.resolveToken("expiring",!0).catch(i=>{this.emit("tokenerror",U(i)),this.stopped||(this.tokenRefreshTimer=setTimeout(()=>this.scheduleTokenRefresh(),3e4))})},Math.min(t,2147e6))}releaseMedia(e){this.activeCallIds.delete(e),this.seenInvitationIds.delete(e);const t=this.mediaClients.get(e);t&&(this.mediaClients.delete(e),t.leave().catch(()=>{}).finally(()=>t.destroy()))}reportSignalingDiagnostic(e){this.options.reportSignalingDiagnostics===!1||this.stopped||this.request("/v1/rtc/client/signaling-events",{method:"POST",body:JSON.stringify({...e,occurredAt:new Date().toISOString()})}).catch(t=>this.emit("diagnosticserror",U(t)))}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}function je(a){const e=new ue(a);return a.autoConnect!==!1&&a.signalingUrl&&e.connect(),e}function me(a){return a?typeof a=="string"?document.querySelector(a):a:null}function pe(a,e,t,i){const n=document.createElement("button");return n.type="button",n.textContent=a,Object.assign(n.style,{minHeight:"44px",border:`1px solid ${i}`,borderRadius:"6px",background:t,color:e,cursor:"pointer",font:"inherit",fontWeight:"700"}),n}function Ve(a){return{rejected:"Call declined",busy:"Line busy",cancelled:"Call cancelled",ended:"Call ended",failed:"Call failed",missed:"No answer"}[a]??"Call ended"}function fe(a){return{id:a.participantId,name:a.displayName,role:"participant"}}async function Ue(a,e){const t=await a.getStats(),i=new Map,n=new Map;let s=0,o=0,r=0,c=0,l=0,d=0,u=0,k=0,f=0,v=0,R=0,y=0,w=0,p=0,I=0,A=null,D=null,E=null,L=null,S=null,M=null;t.forEach(g=>{n.set(g.id,g),g.type==="codec"&&i.set(g.id,String(g.mimeType??""))}),t.forEach(g=>{if(g.type==="inbound-rtp"&&!g.isRemote&&(s+=Number(g.bytesReceived??0),r+=Math.max(0,Number(g.packetsLost??0)),c+=Math.max(0,Number(g.packetsReceived??0)),Number.isFinite(g.jitter)&&(l+=Number(g.jitter),d+=1),u=Math.max(u,Number(g.framesPerSecond??0)),k=Math.max(k,Number(g.frameWidth??0)),R=Math.max(R,Number(g.frameHeight??0)),y+=Number(g.framesDropped??0),w+=Number(g.freezeCount??0),p+=Number(g.totalFreezesDuration??0),I+=Number(g.concealedSamples??0),S||=i.get(String(g.codecId??""))??null),g.type==="outbound-rtp"&&!g.isRemote&&(f=Math.max(f,Number(g.frameWidth??0)),v=Math.max(v,Number(g.frameHeight??0)),o+=Number(g.bytesSent??0),M||=g.qualityLimitationReason?String(g.qualityLimitationReason):null,S||=i.get(String(g.codecId??""))??null),g.type==="candidate-pair"&&(g.selected||g.nominated)&&g.state==="succeeded"){A=Number.isFinite(g.currentRoundTripTime)?Number(g.currentRoundTripTime):A,D=Number.isFinite(g.availableOutgoingBitrate)?Number(g.availableOutgoingBitrate):D;const q=n.get(g.localCandidateId);E=q?.candidateType?String(q.candidateType):E,L=q?.protocol?String(q.protocol):L}});const m=Date.now(),h=e?Math.max(.001,(m-e.timestamp)/1e3):0,T=e?Math.max(0,Math.round((s-e.inboundBytes)*8/h)):null,P=e?Math.max(0,Math.round((o-e.outboundBytes)*8/h)):null,C=r+c,N=e?Math.max(0,r-e.packetsLost):r,Q=e?Math.max(0,c-e.packetsReceived):c,j=N+Q;return{counters:{timestamp:m,inboundBytes:s,outboundBytes:o,packetsLost:r,packetsReceived:c},sample:{rttMs:A===null?null:A*1e3,jitterMs:d?l/d*1e3:null,packetLossPct:j?N/j*100:null,packetLossCumulativePct:C?r/C*100:null,packetsLost:r,packetsReceived:c,inboundBitrateBps:T,outboundBitrateBps:P,availableOutgoingBitrateBps:D,framesPerSecond:u||null,frameWidth:k||null,frameHeight:R||null,inboundFrameWidth:k||null,inboundFrameHeight:R||null,outboundFrameWidth:f||null,outboundFrameHeight:v||null,framesDropped:y,freezeCount:w,freezeDurationMs:Math.round(p*1e3),concealedSamples:I,codec:S,transportProtocol:L,candidateType:E,qualityLimitationReason:M}}}function Qe(a){let e=a.mediaScore==null?5:Math.round(ye(Number(a.mediaScore),0,5));const t=K(a.packetLossPct)??0,i=K(a.rttMs)??0,n=K(a.jitterMs)??0,s=String(a.connectionState??"unknown").toLowerCase();["failed","closed","disconnected"].includes(s)?e=0:["reconnecting","checking"].includes(s)&&(e=Math.min(e,2)),t>=15?e=Math.min(e,0):t>=8?e=Math.min(e,1):t>=3?e=Math.min(e,2):t>=1&&(e=Math.min(e,3)),i>=1200?e=Math.min(e,0):i>=800?e=Math.min(e,1):i>=400?e=Math.min(e,2):i>=250&&(e=Math.min(e,3)),n>=150?e=Math.min(e,1):n>=80?e=Math.min(e,2):n>=40&&(e=Math.min(e,3));const o=Number(ye(4.5-t*.09-i*.0011-n*.006,1,4.5).toFixed(2));return{networkScore:e,estimatedMos:o}}function ge(){return navigator.connection??null}function Be(){const a=Number(navigator.deviceMemory);return Number.isFinite(a)?a:null}function K(a){const e=Number(a);return Number.isFinite(e)?e:null}function ye(a,e,t){return Math.min(t,Math.max(e,a))}function U(a){return a instanceof Error?{name:a.name,message:a.message}:{name:"ServiceError",message:String(a??"Service request failed")}}function ve(a,e){return a instanceof Error?a:new Error(e)}const Se="1.3.21";b.AVATAR_TUNING_DEFAULTS=J,b.BACKGROUND_PRESETS=ae,b.OVERLAY_EFFECTS=se,b.RtcClient=le,b.RtcIncomingClient=ue,b.SPEAKER_NOISE_FLOOR=.02,b.SPEAKER_TAKEOVER_RATIO=1.4,b.createAvatarPipeline=Oe,b.createCallRecorder=Le,b.createPictureInPictureController=Ae,b.createRtcClient=qe,b.createRtcIncomingClient=je,b.createVideoEffectsPipeline=De,b.drawAvatar=ce,b.headPoseFromMatrix=re,b.neutralRig=W,b.pickActiveSpeaker=H,b.rigFromBlendshapes=oe,b.scenePainters=_,b.smoothRig=$,b.stylizedAvatarRenderer=G,b.unlockRtcCallAudio=he,b.version=Se,Object.defineProperty(b,Symbol.toStringTag,{value:"Module"})}));
|