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