@streaming-cdn/rtc-web 1.3.16 → 1.3.18
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 +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/native-room.d.ts +3 -0
- package/dist/rtc-web.es.js +299 -272
- package/dist/rtc-web.umd.js +1 -1
- package/dist/sfu-room.d.ts +1 -0
- package/package.json +1 -1
package/dist/rtc-web.es.js
CHANGED
|
@@ -14,7 +14,7 @@ function ce(a) {
|
|
|
14
14
|
}
|
|
15
15
|
return e;
|
|
16
16
|
}
|
|
17
|
-
const
|
|
17
|
+
const N = {
|
|
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 },
|
|
@@ -36,41 +36,41 @@ function ue(a) {
|
|
|
36
36
|
return !e || e === "none" ? "" : e === "cpu" || e === "bandwidth" ? e : "other";
|
|
37
37
|
}
|
|
38
38
|
function me(a, e, t, i, n = {}) {
|
|
39
|
-
const s = { ...he, ...n.thresholds }, o = ["low", "medium", "high"], r = n.ceiling && n.ceiling !== "audio" ? n.ceiling : "high", c = o.indexOf(r),
|
|
40
|
-
return
|
|
39
|
+
const s = { ...he, ...n.thresholds }, o = ["low", "medium", "high"], r = n.ceiling && n.ceiling !== "audio" ? n.ceiling : "high", c = o.indexOf(r), l = o.indexOf(a), d = (n.samplesSeen ?? s.warmupSamples) + 1, u = d <= s.warmupSamples, S = ue(e.qualityLimitationReason), f = e.packetLossPct ?? 0, v = e.rttMs ?? 0, I = e.availableOutgoingBitrateBps, y = N[a].maxBitrate, k = f >= s.criticalLossPct || v >= s.criticalRttMs || S === "cpu", p = k || f >= s.poorLossPct || v >= s.poorRttMs || S === "bandwidth", C = I == null || I >= y * s.sustainRatio, P = f < s.goodLossPct && v < s.goodRttMs && !S && C, L = p ? t + 1 : 0, E = P ? i + 1 : 0, A = { quality: a, poorSamples: L, goodSamples: E, samplesSeen: d, changed: !1 };
|
|
40
|
+
return l > c ? { quality: o[c], poorSamples: 0, goodSamples: 0, samplesSeen: d, changed: !0 } : u ? A : k && l > 0 ? { quality: o[l - 1], poorSamples: 0, goodSamples: 0, samplesSeen: d, changed: !0 } : L >= 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 } : A;
|
|
41
41
|
}
|
|
42
42
|
function pe(a, e) {
|
|
43
43
|
const t = a instanceof Map ? a : new Map(a);
|
|
44
44
|
let i = 0, n = 0, s = null, o = null, r = null;
|
|
45
|
-
for (const
|
|
46
|
-
const u = String(
|
|
47
|
-
if (u === "outbound-rtp" && String(
|
|
48
|
-
n += Number(
|
|
49
|
-
const
|
|
50
|
-
Number.isFinite(
|
|
45
|
+
for (const d of t.values()) {
|
|
46
|
+
const u = String(d.type ?? "");
|
|
47
|
+
if (u === "outbound-rtp" && String(d.kind ?? "") === "video" && (i += Number(d.packetsSent ?? 0), s = d.qualityLimitationReason ?? s), u === "remote-inbound-rtp" && String(d.kind ?? "") === "video") {
|
|
48
|
+
n += Number(d.packetsLost ?? 0);
|
|
49
|
+
const S = Number(d.roundTripTime ?? NaN);
|
|
50
|
+
Number.isFinite(S) && (o = S * 1e3);
|
|
51
51
|
}
|
|
52
|
-
if (u === "candidate-pair" && (
|
|
53
|
-
const
|
|
54
|
-
o == null && Number.isFinite(
|
|
55
|
-
const f = Number(
|
|
52
|
+
if (u === "candidate-pair" && (d.nominated === !0 || String(d.state ?? "") === "succeeded")) {
|
|
53
|
+
const S = Number(d.currentRoundTripTime ?? NaN);
|
|
54
|
+
o == null && Number.isFinite(S) && (o = S * 1e3);
|
|
55
|
+
const f = Number(d.availableOutgoingBitrate ?? NaN);
|
|
56
56
|
Number.isFinite(f) && (r = f);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
const c = { packetsSent: i, packetsLost: n, timestamp: Date.now() };
|
|
60
|
-
let
|
|
60
|
+
let l = null;
|
|
61
61
|
if (e) {
|
|
62
|
-
const
|
|
63
|
-
|
|
62
|
+
const d = i - e.packetsSent, u = n - e.packetsLost;
|
|
63
|
+
d > 0 && u >= 0 && (l = u / (d + u) * 100);
|
|
64
64
|
}
|
|
65
65
|
return {
|
|
66
|
-
sample: { packetLossPct:
|
|
66
|
+
sample: { packetLossPct: l, rttMs: o, availableOutgoingBitrateBps: r, qualityLimitationReason: s },
|
|
67
67
|
counters: c
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
const fe = {
|
|
71
71
|
audio: { active: !1, maxBitrate: 0, maxFramerate: 0, scaleResolutionDownBy: 4 }
|
|
72
72
|
}, ge = { active: !0, maxBitrate: 4e6, maxFramerate: 30, scaleResolutionDownBy: 1 };
|
|
73
|
-
class
|
|
73
|
+
class Q {
|
|
74
74
|
options;
|
|
75
75
|
socket = null;
|
|
76
76
|
localStream = null;
|
|
@@ -159,7 +159,7 @@ class x {
|
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
for (const r of this.peers.values()) {
|
|
162
|
-
const c = r.connection.getSenders().find((
|
|
162
|
+
const c = r.connection.getSenders().find((l) => l.track?.kind === s);
|
|
163
163
|
c ? await c.replaceTrack(n) : r.connection.addTrack(n, this.localStream);
|
|
164
164
|
}
|
|
165
165
|
await this.applyVideoQuality();
|
|
@@ -168,7 +168,7 @@ class x {
|
|
|
168
168
|
this.quality = e;
|
|
169
169
|
for (const t of this.localStream?.getVideoTracks() ?? []) t.enabled = e !== "audio";
|
|
170
170
|
for (const t of this.peers.values())
|
|
171
|
-
t.quality =
|
|
171
|
+
t.quality = Q.clampToCeiling(t.quality, e), t.poorSamples = 0, t.goodSamples = 0;
|
|
172
172
|
await this.applyVideoQuality();
|
|
173
173
|
}
|
|
174
174
|
static clampToCeiling(e, t) {
|
|
@@ -214,26 +214,26 @@ class x {
|
|
|
214
214
|
async openSocket(e) {
|
|
215
215
|
const { credential: t } = this.options, i = new URL(t.signalingUrl, globalThis.location?.href);
|
|
216
216
|
i.protocol = i.protocol === "https:" ? "wss:" : "ws:";
|
|
217
|
-
const n = new Promise((c,
|
|
218
|
-
this.resolveJoin = c, this.rejectJoin =
|
|
217
|
+
const n = new Promise((c, l) => {
|
|
218
|
+
this.resolveJoin = c, this.rejectJoin = l;
|
|
219
219
|
}), s = new WebSocket(i, ["rtc-client", t.signalingToken]), o = ++this.socketGeneration;
|
|
220
220
|
this.socket = s;
|
|
221
221
|
const r = setTimeout(() => this.rejectJoin?.(new Error("RTC signaling timed out")), 15e3);
|
|
222
222
|
s.addEventListener("open", () => {
|
|
223
223
|
this.heartbeatDeadline = Date.now() + 45e3, this.startHeartbeat(s), this.startQualitySampling(), this.send({ type: "rtc.room.join" });
|
|
224
224
|
}), s.addEventListener("message", (c) => {
|
|
225
|
-
const
|
|
225
|
+
const l = String(c.data);
|
|
226
226
|
this.signalQueue = this.signalQueue.then(async () => {
|
|
227
|
-
this.socket !== s || this.socketGeneration !== o || await this.handleSignal(
|
|
228
|
-
}).catch((
|
|
229
|
-
const u =
|
|
227
|
+
this.socket !== s || this.socketGeneration !== o || await this.handleSignal(l);
|
|
228
|
+
}).catch((d) => {
|
|
229
|
+
const u = d instanceof Error ? d.message : "RTC signaling processing failed";
|
|
230
230
|
this.options.onEvent("signalingerror", { message: u }), this.joined || this.rejectJoin?.(new Error(u));
|
|
231
231
|
});
|
|
232
232
|
}), s.addEventListener("error", () => this.rejectJoin?.(new Error("RTC signaling connection failed"))), s.addEventListener("close", (c) => {
|
|
233
233
|
if (this.socket !== s) return;
|
|
234
234
|
this.socket = null, this.stopHeartbeat(), this.stopQualitySampling(), !this.joined && !e && this.rejectJoin?.(new Error(c.reason || "RTC signaling connection closed"));
|
|
235
|
-
const
|
|
236
|
-
this.joined = !1,
|
|
235
|
+
const l = !this.stopped && (this.joined || e);
|
|
236
|
+
this.joined = !1, l ? (this.resetPeers(), this.options.onEvent("reconnecting", { reason: c.reason, code: c.code, attempt: this.reconnectAttempt + 1 }), this.scheduleReconnect()) : this.stopped || this.options.onEvent("disconnected", { reason: c.reason, code: c.code });
|
|
237
237
|
});
|
|
238
238
|
try {
|
|
239
239
|
await n, this.joined = !0, this.options.onEvent("connected", { participantId: t.participantId, resumed: e });
|
|
@@ -313,7 +313,7 @@ class x {
|
|
|
313
313
|
makingOffer: !1,
|
|
314
314
|
offerTask: null,
|
|
315
315
|
activeNegotiationId: null,
|
|
316
|
-
quality:
|
|
316
|
+
quality: Q.startingQuality(this.quality),
|
|
317
317
|
poorSamples: 0,
|
|
318
318
|
goodSamples: 0,
|
|
319
319
|
samplesSeen: 0,
|
|
@@ -325,7 +325,7 @@ class x {
|
|
|
325
325
|
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) => {
|
|
326
326
|
c.candidate && this.send({ type: "rtc.ice", targetParticipantId: e.participantId, candidate: c.candidate.toJSON() });
|
|
327
327
|
}), i.addEventListener("track", (c) => {
|
|
328
|
-
n.getTracks().some((
|
|
328
|
+
n.getTracks().some((l) => l.id === c.track.id) || n.addTrack(c.track), this.options.onEvent("remotestream", { participant: e, stream: n, track: c.track });
|
|
329
329
|
}), i.addEventListener("connectionstatechange", () => {
|
|
330
330
|
["failed", "closed"].includes(i.connectionState) && this.removePeer(e.participantId), this.options.onEvent("peerconnectionstate", { participant: e, state: i.connectionState });
|
|
331
331
|
}), s;
|
|
@@ -351,7 +351,7 @@ class x {
|
|
|
351
351
|
});
|
|
352
352
|
if (e.connection.signalingState !== "stable") return;
|
|
353
353
|
await e.connection.setLocalDescription(t);
|
|
354
|
-
const i =
|
|
354
|
+
const i = _();
|
|
355
355
|
e.activeNegotiationId = i, this.send({
|
|
356
356
|
type: "rtc.offer",
|
|
357
357
|
targetParticipantId: e.identity.participantId,
|
|
@@ -391,7 +391,7 @@ class x {
|
|
|
391
391
|
async applyPeerVideoQuality(e) {
|
|
392
392
|
const t = this.screenStream?.getVideoTracks()[0]?.id;
|
|
393
393
|
for (const i of e.connection.getSenders().filter((n) => n.track?.kind === "video")) {
|
|
394
|
-
const n = !!(t && i.track?.id === t), s = n ? ge : this.quality === "audio" ? fe.audio :
|
|
394
|
+
const n = !!(t && i.track?.id === t), s = n ? ge : this.quality === "audio" ? fe.audio : N[e.quality], o = i.getParameters(), r = o.encodings?.length ? o.encodings : [{}];
|
|
395
395
|
o.encodings = r.map((c) => ({ ...c, ...s })), o.degradationPreference = n ? "maintain-resolution" : "maintain-framerate", await i.setParameters(o);
|
|
396
396
|
}
|
|
397
397
|
}
|
|
@@ -487,7 +487,7 @@ class x {
|
|
|
487
487
|
}
|
|
488
488
|
send(e) {
|
|
489
489
|
this.socket?.readyState === WebSocket.OPEN && this.socket.send(JSON.stringify({
|
|
490
|
-
clientMessageId: e.clientMessageId ??
|
|
490
|
+
clientMessageId: e.clientMessageId ?? _(),
|
|
491
491
|
...e
|
|
492
492
|
}));
|
|
493
493
|
}
|
|
@@ -495,13 +495,13 @@ class x {
|
|
|
495
495
|
for (const t of e?.getTracks() ?? []) t.stop();
|
|
496
496
|
}
|
|
497
497
|
}
|
|
498
|
-
function
|
|
498
|
+
function _() {
|
|
499
499
|
return globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
500
500
|
}
|
|
501
|
-
const
|
|
502
|
-
{ rid: "a", maxBitrate:
|
|
503
|
-
{ rid: "b", maxBitrate:
|
|
504
|
-
{ rid: "c", maxBitrate:
|
|
501
|
+
const z = { active: !0, maxBitrate: 4e6, maxFramerate: 30, scaleResolutionDownBy: 1 }, Y = [
|
|
502
|
+
{ rid: "a", maxBitrate: N.high.maxBitrate, maxFramerate: N.high.maxFramerate },
|
|
503
|
+
{ rid: "b", maxBitrate: N.medium.maxBitrate, maxFramerate: N.medium.maxFramerate, scaleResolutionDownBy: 2 },
|
|
504
|
+
{ rid: "c", maxBitrate: N.low.maxBitrate, maxFramerate: N.low.maxFramerate, scaleResolutionDownBy: 4 }
|
|
505
505
|
], G = { high: "a", medium: "b", low: "c" };
|
|
506
506
|
class ye {
|
|
507
507
|
options;
|
|
@@ -638,26 +638,26 @@ class ye {
|
|
|
638
638
|
async openSocket(e) {
|
|
639
639
|
const { credential: t } = this.options, i = new URL(t.signalingUrl, globalThis.location?.href);
|
|
640
640
|
i.protocol = i.protocol === "https:" ? "wss:" : "ws:";
|
|
641
|
-
const n = new Promise((c,
|
|
642
|
-
this.resolveJoin = c, this.rejectJoin =
|
|
641
|
+
const n = new Promise((c, l) => {
|
|
642
|
+
this.resolveJoin = c, this.rejectJoin = l;
|
|
643
643
|
}), s = new WebSocket(i, ["rtc-client", t.signalingToken]), o = ++this.socketGeneration;
|
|
644
644
|
this.socket = s;
|
|
645
645
|
const r = setTimeout(() => this.rejectJoin?.(new Error("RTC signaling timed out")), 15e3);
|
|
646
646
|
s.addEventListener("open", () => {
|
|
647
647
|
this.heartbeatDeadline = Date.now() + 45e3, this.startHeartbeat(s), this.send({ type: "rtc.room.join" });
|
|
648
648
|
}), s.addEventListener("message", (c) => {
|
|
649
|
-
const
|
|
649
|
+
const l = String(c.data);
|
|
650
650
|
this.signalQueue = this.signalQueue.then(async () => {
|
|
651
|
-
this.socket !== s || this.socketGeneration !== o || await this.handleSignal(
|
|
652
|
-
}).catch((
|
|
653
|
-
const u =
|
|
651
|
+
this.socket !== s || this.socketGeneration !== o || await this.handleSignal(l);
|
|
652
|
+
}).catch((d) => {
|
|
653
|
+
const u = d instanceof Error ? d.message : "RTC signaling processing failed";
|
|
654
654
|
this.options.onEvent("signalingerror", { message: u }), this.joined || this.rejectJoin?.(new Error(u));
|
|
655
655
|
});
|
|
656
656
|
}), s.addEventListener("error", () => this.rejectJoin?.(new Error("RTC signaling connection failed"))), s.addEventListener("close", (c) => {
|
|
657
657
|
if (this.socket !== s) return;
|
|
658
658
|
this.socket = null, this.stopHeartbeat(), !this.joined && !e && this.rejectJoin?.(new Error(c.reason || "RTC signaling connection closed"));
|
|
659
|
-
const
|
|
660
|
-
this.joined = !1,
|
|
659
|
+
const l = !this.stopped && (this.joined || e);
|
|
660
|
+
this.joined = !1, l ? (this.options.onEvent("reconnecting", { reason: c.reason, code: c.code, attempt: this.reconnectAttempt + 1 }), this.scheduleReconnect()) : this.stopped || this.options.onEvent("disconnected", { reason: c.reason, code: c.code });
|
|
661
661
|
});
|
|
662
662
|
try {
|
|
663
663
|
await n, this.joined = !0, this.options.onEvent("connected", { participantId: t.participantId, resumed: e });
|
|
@@ -718,7 +718,10 @@ class ye {
|
|
|
718
718
|
const n = e.map((r) => t.addTransceiver(r, {
|
|
719
719
|
direction: "sendonly",
|
|
720
720
|
streams: this.localStream ? [this.localStream] : [],
|
|
721
|
-
|
|
721
|
+
// A single encoding when simulcast is off: constrained publishers
|
|
722
|
+
// (phone CPU, cellular uplink) should not pay for three encoders. The
|
|
723
|
+
// quality ladder then adapts that one encoding instead of gating layers.
|
|
724
|
+
...r.kind === "video" && this.simulcastEnabled() ? { sendEncodings: Y.map((c) => ({ ...c })) } : {}
|
|
722
725
|
})), s = await t.createOffer();
|
|
723
726
|
await t.setLocalDescription(s);
|
|
724
727
|
const o = await this.sfuFetch(
|
|
@@ -742,7 +745,7 @@ class ye {
|
|
|
742
745
|
if (!this.sessionId) return;
|
|
743
746
|
const e = [
|
|
744
747
|
...(this.localStream?.getAudioTracks() ?? []).map((t) => ({ trackName: this.trackName(t), kind: t.kind, simulcast: !1 })),
|
|
745
|
-
...(this.localStream?.getVideoTracks() ?? []).map((t) => ({ trackName: this.trackName(t), kind: t.kind, simulcast:
|
|
748
|
+
...(this.localStream?.getVideoTracks() ?? []).map((t) => ({ trackName: this.trackName(t), kind: t.kind, simulcast: this.simulcastEnabled() }))
|
|
746
749
|
];
|
|
747
750
|
e.length !== 0 && this.sendControl("sfu.tracks", { sessionId: this.sessionId, tracks: e });
|
|
748
751
|
}
|
|
@@ -825,6 +828,9 @@ class ye {
|
|
|
825
828
|
async replaceVideoTrack(e) {
|
|
826
829
|
this.videoSender && (await this.videoSender.replaceTrack(e), await this.applyVideoQuality());
|
|
827
830
|
}
|
|
831
|
+
simulcastEnabled() {
|
|
832
|
+
return this.options.simulcast !== !1;
|
|
833
|
+
}
|
|
828
834
|
async applyVideoQuality() {
|
|
829
835
|
const e = this.videoSender;
|
|
830
836
|
if (!e) return;
|
|
@@ -833,12 +839,12 @@ class ye {
|
|
|
833
839
|
const o = this.quality === "audio" ? null : G[this.quality];
|
|
834
840
|
n.encodings = s.map((r) => {
|
|
835
841
|
if (i)
|
|
836
|
-
return { ...r, active: r.rid === "a", ...r.rid === "a" ? { maxBitrate:
|
|
837
|
-
const c = Y.find((
|
|
838
|
-
return { ...r, ...c ?? {}, active:
|
|
842
|
+
return { ...r, active: r.rid === "a", ...r.rid === "a" ? { maxBitrate: z.maxBitrate, maxFramerate: z.maxFramerate } : {} };
|
|
843
|
+
const c = Y.find((d) => d.rid === r.rid), l = o != null && String(r.rid ?? "") >= o;
|
|
844
|
+
return { ...r, ...c ?? {}, active: l };
|
|
839
845
|
});
|
|
840
846
|
} else {
|
|
841
|
-
const o = this.quality === "audio" ?
|
|
847
|
+
const o = this.quality === "audio" ? N.audio : N[this.quality], r = i ? z : o;
|
|
842
848
|
n.encodings = s.map((c) => ({ ...c, ...r }));
|
|
843
849
|
}
|
|
844
850
|
n.degradationPreference = i ? "maintain-resolution" : "maintain-framerate", await e.setParameters(n);
|
|
@@ -945,7 +951,7 @@ function Ne(a, e = {}) {
|
|
|
945
951
|
}
|
|
946
952
|
function o(r) {
|
|
947
953
|
const c = a.getRemoteStreams();
|
|
948
|
-
return r ? c.find((
|
|
954
|
+
return r ? c.find((l) => l.participant.participantId === r)?.stream ?? null : c[0]?.stream ?? a.getLocalStream();
|
|
949
955
|
}
|
|
950
956
|
return {
|
|
951
957
|
supported: i,
|
|
@@ -953,9 +959,9 @@ function Ne(a, e = {}) {
|
|
|
953
959
|
if (!i) throw new Error("This browser does not support picture-in-picture");
|
|
954
960
|
const c = o(r);
|
|
955
961
|
if (!c) throw new Error("No video stream is available for picture-in-picture");
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
}), await
|
|
962
|
+
const l = s();
|
|
963
|
+
l.srcObject !== c && (l.srcObject = c), await l.play().catch(() => {
|
|
964
|
+
}), await l.requestPictureInPicture();
|
|
959
965
|
},
|
|
960
966
|
async exit() {
|
|
961
967
|
t.pictureInPictureElement && typeof t.exitPictureInPicture == "function" && await t.exitPictureInPicture();
|
|
@@ -967,89 +973,89 @@ function Ne(a, e = {}) {
|
|
|
967
973
|
}
|
|
968
974
|
function je(a, e) {
|
|
969
975
|
const t = e.document ?? globalThis.document, i = e.fetchImplementation ?? globalThis.fetch.bind(globalThis), n = new URL(e.credential.signalingUrl, globalThis.location?.href).origin, s = e.mimeType ?? "video/webm", o = e.height ?? 720, r = Math.round(o * 16 / 9);
|
|
970
|
-
let c = null,
|
|
971
|
-
async function
|
|
972
|
-
const
|
|
973
|
-
method:
|
|
976
|
+
let c = null, l = null, d = null, u = null, S = 0, f = [], v = !1;
|
|
977
|
+
async function I(y, k, p, C = "application/json") {
|
|
978
|
+
const P = await i(`${n}${k}`, {
|
|
979
|
+
method: y,
|
|
974
980
|
headers: {
|
|
975
981
|
authorization: `Bearer ${e.credential.signalingToken}`,
|
|
976
|
-
"content-type":
|
|
982
|
+
"content-type": C
|
|
977
983
|
},
|
|
978
|
-
body:
|
|
984
|
+
body: p === void 0 ? void 0 : C === "application/json" ? JSON.stringify(p) : p
|
|
979
985
|
});
|
|
980
|
-
if (!
|
|
981
|
-
return
|
|
986
|
+
if (!P.ok) throw new Error(`Recording request failed with HTTP ${P.status}`);
|
|
987
|
+
return P.json();
|
|
982
988
|
}
|
|
983
989
|
return {
|
|
984
990
|
get recording() {
|
|
985
|
-
return
|
|
991
|
+
return v;
|
|
986
992
|
},
|
|
987
993
|
async start() {
|
|
988
|
-
if (
|
|
989
|
-
u = (await
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
const
|
|
993
|
-
let
|
|
994
|
-
return
|
|
995
|
-
}),
|
|
994
|
+
if (v) return;
|
|
995
|
+
u = (await I("POST", "/v1/rtc/recordings")).recordingId;
|
|
996
|
+
const k = t.createElement("canvas");
|
|
997
|
+
k.width = r, k.height = o;
|
|
998
|
+
const p = k.getContext("2d"), C = /* @__PURE__ */ new Map(), P = (T, R) => {
|
|
999
|
+
let b = C.get(T);
|
|
1000
|
+
return b || (b = t.createElement("video"), b.muted = !0, b.playsInline = !0, b.srcObject = R, b.play().catch(() => {
|
|
1001
|
+
}), C.set(T, b)), b;
|
|
996
1002
|
};
|
|
997
|
-
|
|
998
|
-
const
|
|
999
|
-
...
|
|
1000
|
-
...
|
|
1003
|
+
l = setInterval(() => {
|
|
1004
|
+
const T = a.getRemoteStreams(), R = a.getLocalStream(), b = [
|
|
1005
|
+
...T.map((j) => ({ key: j.participant.participantId, stream: j.stream, label: j.participant.displayName })),
|
|
1006
|
+
...R ? [{ key: "local", stream: R, label: "me" }] : []
|
|
1001
1007
|
];
|
|
1002
|
-
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1008
|
+
p.fillStyle = "#101418", p.fillRect(0, 0, r, o);
|
|
1009
|
+
const O = Math.max(1, Math.ceil(Math.sqrt(b.length))), g = Math.max(1, Math.ceil(b.length / O)), q = r / O, x = o / g;
|
|
1010
|
+
b.forEach((j, J) => {
|
|
1011
|
+
const W = P(j.key, j.stream), B = J % O * q, F = Math.floor(J / O) * x;
|
|
1012
|
+
W.videoWidth > 0 && p.drawImage(W, B + 2, F + 2, q - 4, x - 4), p.fillStyle = "rgba(0,0,0,.6)", p.fillRect(B + 8, F + x - 30, Math.min(q - 16, 12 + j.label.length * 9), 22), p.fillStyle = "#fff", p.font = "14px system-ui", p.fillText(j.label, B + 14, F + x - 14);
|
|
1007
1013
|
});
|
|
1008
1014
|
}, 66);
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
const
|
|
1012
|
-
const
|
|
1013
|
-
...a.getRemoteStreams().map((
|
|
1015
|
+
const L = k.captureStream(15);
|
|
1016
|
+
d = new AudioContext();
|
|
1017
|
+
const E = d.createMediaStreamDestination(), A = /* @__PURE__ */ new Set(), w = () => {
|
|
1018
|
+
const T = [
|
|
1019
|
+
...a.getRemoteStreams().map((R) => R.stream),
|
|
1014
1020
|
...a.getLocalStream() ? [a.getLocalStream()] : []
|
|
1015
1021
|
];
|
|
1016
|
-
for (const
|
|
1017
|
-
for (const
|
|
1018
|
-
|
|
1022
|
+
for (const R of T)
|
|
1023
|
+
for (const b of R.getAudioTracks())
|
|
1024
|
+
A.has(b.id) || (A.add(b.id), d.createMediaStreamSource(new MediaStream([b])).connect(E));
|
|
1019
1025
|
};
|
|
1020
|
-
|
|
1021
|
-
const
|
|
1022
|
-
for (const
|
|
1023
|
-
c = (e.mediaRecorderFactory ?? ((
|
|
1024
|
-
|
|
1025
|
-
}), c.addEventListener("stop",
|
|
1026
|
+
w();
|
|
1027
|
+
const M = setInterval(w, 1e3), m = () => clearInterval(M);
|
|
1028
|
+
for (const T of E.stream.getAudioTracks()) L.addTrack(T);
|
|
1029
|
+
c = (e.mediaRecorderFactory ?? ((T, R) => new MediaRecorder(T, R)))(L, { mimeType: s }), f = [], c.addEventListener("dataavailable", (T) => {
|
|
1030
|
+
T.data && T.data.size > 0 && f.push(T.data);
|
|
1031
|
+
}), c.addEventListener("stop", m), c.start(1e3), S = Date.now(), v = !0;
|
|
1026
1032
|
},
|
|
1027
1033
|
async stop() {
|
|
1028
|
-
if (!
|
|
1029
|
-
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
}),
|
|
1033
|
-
const
|
|
1034
|
+
if (!v || !c || !u) throw new Error("The recorder is not running");
|
|
1035
|
+
v = !1;
|
|
1036
|
+
const y = c, k = new Promise((E) => y.addEventListener("stop", () => E(), { once: !0 }));
|
|
1037
|
+
y.stop(), await k, l && clearInterval(l), l = null, await d?.close().catch(() => {
|
|
1038
|
+
}), d = null;
|
|
1039
|
+
const p = new Blob(f, { type: s });
|
|
1034
1040
|
f = [];
|
|
1035
|
-
const
|
|
1041
|
+
const C = Math.max(1, Math.round((Date.now() - S) / 1e3)), P = await I(
|
|
1036
1042
|
"PUT",
|
|
1037
1043
|
`/v1/rtc/recordings/${encodeURIComponent(u)}/media`,
|
|
1038
|
-
|
|
1044
|
+
p,
|
|
1039
1045
|
s
|
|
1040
1046
|
);
|
|
1041
|
-
await
|
|
1042
|
-
const
|
|
1043
|
-
return u = null, c = null,
|
|
1047
|
+
await I("POST", `/v1/rtc/recordings/${encodeURIComponent(u)}/complete`, { durationSeconds: C });
|
|
1048
|
+
const L = { recordingId: u, sizeBytes: P.sizeBytes, durationSeconds: C };
|
|
1049
|
+
return u = null, c = null, L;
|
|
1044
1050
|
},
|
|
1045
1051
|
dispose() {
|
|
1046
|
-
|
|
1052
|
+
l && clearInterval(l), l = null;
|
|
1047
1053
|
try {
|
|
1048
1054
|
c?.stop();
|
|
1049
1055
|
} catch {
|
|
1050
1056
|
}
|
|
1051
|
-
c = null,
|
|
1052
|
-
}),
|
|
1057
|
+
c = null, d?.close().catch(() => {
|
|
1058
|
+
}), d = null, v = !1;
|
|
1053
1059
|
}
|
|
1054
1060
|
};
|
|
1055
1061
|
}
|
|
@@ -1091,17 +1097,19 @@ const Se = ["none", "blur", "aurora", "cybergrid", "bokeh", "sunset", "image"],
|
|
|
1091
1097
|
}
|
|
1092
1098
|
};
|
|
1093
1099
|
function Ve(a, e = {}) {
|
|
1094
|
-
const t = e.document ?? globalThis.document
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1100
|
+
const t = e.document ?? globalThis.document;
|
|
1101
|
+
let i = e.width ?? 1280, n = e.height ?? 720;
|
|
1102
|
+
const s = !!(e.width || e.height), o = e.frameRate ?? 24, r = a instanceof MediaStreamTrack ? a : a.getVideoTracks()[0];
|
|
1103
|
+
if (!r) throw new Error("A video track is required");
|
|
1104
|
+
let c = e.background ?? "none", l = e.overlay ?? "none", d = e.backgroundImage ?? null, u = e.segmenter ?? null, S = null, f = !1;
|
|
1105
|
+
const v = t.createElement("video");
|
|
1106
|
+
v.muted = !0, v.playsInline = !0, v.srcObject = new MediaStream([r]), v.play().catch(() => {
|
|
1099
1107
|
});
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
const
|
|
1108
|
+
const I = t.createElement("canvas");
|
|
1109
|
+
I.width = i, I.height = n;
|
|
1110
|
+
const y = I.getContext("2d"), k = t.createElement("canvas");
|
|
1111
|
+
k.width = i, k.height = n;
|
|
1112
|
+
const p = k.getContext("2d"), C = () => Array.from({ length: 80 }, (m, h) => ({
|
|
1105
1113
|
x: (Math.sin(h * 12.9898) * 0.5 + 0.5) * i,
|
|
1106
1114
|
y: (Math.sin(h * 78.233) * 0.5 + 0.5) * n,
|
|
1107
1115
|
speed: 0.6 + h % 5 * 0.5,
|
|
@@ -1109,48 +1117,57 @@ function Ve(a, e = {}) {
|
|
|
1109
1117
|
size: 2 + h % 4,
|
|
1110
1118
|
hue: h * 47 % 360
|
|
1111
1119
|
}));
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1120
|
+
let P = C();
|
|
1121
|
+
const L = () => {
|
|
1122
|
+
if (s) return;
|
|
1123
|
+
const m = v.videoWidth, h = v.videoHeight;
|
|
1124
|
+
if (!m || !h) return;
|
|
1125
|
+
const T = Math.min(1, 1280 / Math.max(m, h)), R = Math.round(m * T), b = Math.round(h * T);
|
|
1126
|
+
R === i && b === n || (i = R, n = b, I.width = i, I.height = n, k.width = i, k.height = n, P = C());
|
|
1127
|
+
};
|
|
1128
|
+
v.addEventListener?.("loadedmetadata", L), v.addEventListener?.("resize", L);
|
|
1129
|
+
function E() {
|
|
1130
|
+
if (S) {
|
|
1131
|
+
p.clearRect(0, 0, i, n), p.drawImage(S, 0, 0, i, n), p.globalCompositeOperation = "source-in", p.drawImage(v, 0, 0, i, n), p.globalCompositeOperation = "source-over", y.drawImage(k, 0, 0);
|
|
1115
1132
|
return;
|
|
1116
1133
|
}
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
function
|
|
1121
|
-
if (
|
|
1122
|
-
for (const h of
|
|
1123
|
-
h.y += h.speed * (
|
|
1124
|
-
else if (
|
|
1125
|
-
const h =
|
|
1126
|
-
h.addColorStop(0, "rgba(0,0,0,0)"), h.addColorStop(1, "rgba(0,0,0,0.55)"),
|
|
1134
|
+
const m = i * 0.42, h = m * 9 / 16, T = i - m - 24, R = n - h - 24;
|
|
1135
|
+
y.save(), y.shadowColor = "rgba(0,0,0,.5)", y.shadowBlur = 24, y.beginPath(), y.roundRect(T, R, m, h, 14), y.clip(), y.drawImage(v, T, R, m, h), y.restore();
|
|
1136
|
+
}
|
|
1137
|
+
function A(m) {
|
|
1138
|
+
if (c === "none" ? y.drawImage(v, 0, 0, i, n) : c === "blur" ? (y.filter = "blur(14px)", y.drawImage(v, -16, -16, i + 32, n + 32), y.filter = "none", S && E()) : c === "image" && d ? (y.drawImage(d, 0, 0, i, n), E()) : ((K[c] ?? K.aurora)(y, i, n, m), E()), l === "snow" || l === "confetti")
|
|
1139
|
+
for (const h of P)
|
|
1140
|
+
h.y += h.speed * (l === "confetti" ? 1.6 : 1), h.x += h.drift, h.y > n && (h.y = -6, h.x = Math.random() * i), y.fillStyle = l === "snow" ? "rgba(255,255,255,.85)" : `hsl(${h.hue}, 90%, 60%)`, l === "confetti" ? y.fillRect(h.x, h.y, h.size + 2, h.size) : (y.beginPath(), y.arc(h.x, h.y, h.size / 2, 0, Math.PI * 2), y.fill());
|
|
1141
|
+
else if (l === "vignette") {
|
|
1142
|
+
const h = y.createRadialGradient(i / 2, n / 2, n * 0.4, i / 2, n / 2, n * 0.85);
|
|
1143
|
+
h.addColorStop(0, "rgba(0,0,0,0)"), h.addColorStop(1, "rgba(0,0,0,0.55)"), y.fillStyle = h, y.fillRect(0, 0, i, n);
|
|
1127
1144
|
}
|
|
1128
1145
|
}
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1146
|
+
const w = setInterval(() => {
|
|
1147
|
+
A(Date.now()), u && !f && c !== "none" && (f = !0, u.segment(v).then((m) => {
|
|
1148
|
+
S = m;
|
|
1132
1149
|
}).catch(() => {
|
|
1133
|
-
|
|
1150
|
+
S = null;
|
|
1134
1151
|
}).finally(() => {
|
|
1135
|
-
|
|
1152
|
+
f = !1;
|
|
1136
1153
|
}));
|
|
1137
|
-
}, Math.round(1e3 /
|
|
1154
|
+
}, Math.round(1e3 / o)), M = I.captureStream(o);
|
|
1138
1155
|
return {
|
|
1139
|
-
track:
|
|
1140
|
-
stream:
|
|
1141
|
-
setBackground(
|
|
1142
|
-
|
|
1156
|
+
track: M.getVideoTracks()[0],
|
|
1157
|
+
stream: M,
|
|
1158
|
+
setBackground(m, h) {
|
|
1159
|
+
c = Se.includes(m) ? m : "none", h !== void 0 && (d = h), c === "none" && (S = null);
|
|
1143
1160
|
},
|
|
1144
|
-
setOverlay(
|
|
1145
|
-
|
|
1161
|
+
setOverlay(m) {
|
|
1162
|
+
l = ke.includes(m) ? m : "none";
|
|
1146
1163
|
},
|
|
1147
|
-
setSegmenter(
|
|
1148
|
-
|
|
1164
|
+
setSegmenter(m) {
|
|
1165
|
+
u = m, m || (S = null);
|
|
1149
1166
|
},
|
|
1150
1167
|
dispose() {
|
|
1151
|
-
clearInterval(
|
|
1152
|
-
for (const
|
|
1153
|
-
|
|
1168
|
+
clearInterval(w), u?.close?.();
|
|
1169
|
+
for (const m of M.getTracks()) m.stop();
|
|
1170
|
+
v.srcObject = null;
|
|
1154
1171
|
}
|
|
1155
1172
|
};
|
|
1156
1173
|
}
|
|
@@ -1169,7 +1186,7 @@ function le() {
|
|
|
1169
1186
|
tracked: !1
|
|
1170
1187
|
};
|
|
1171
1188
|
}
|
|
1172
|
-
function
|
|
1189
|
+
function D(a, e) {
|
|
1173
1190
|
const t = a.find((i) => i.categoryName === e);
|
|
1174
1191
|
return t ? Math.max(0, Math.min(1, t.score)) : 0;
|
|
1175
1192
|
}
|
|
@@ -1181,13 +1198,13 @@ function we(a) {
|
|
|
1181
1198
|
}
|
|
1182
1199
|
function Te(a, e, t = 1) {
|
|
1183
1200
|
if (!a || a.length === 0) return le();
|
|
1184
|
-
const i = we(e), n = (
|
|
1201
|
+
const i = we(e), n = (D(a, "eyeLookOutRight") + D(a, "eyeLookInLeft")) / 2, s = (D(a, "eyeLookOutLeft") + D(a, "eyeLookInRight")) / 2, o = (D(a, "eyeLookUpLeft") + D(a, "eyeLookUpRight")) / 2, r = (D(a, "eyeLookDownLeft") + D(a, "eyeLookDownRight")) / 2, c = (D(a, "mouthSmileLeft") + D(a, "mouthSmileRight")) / 2, l = (D(a, "mouthFrownLeft") + D(a, "mouthFrownRight")) / 2, d = D(a, "eyeBlinkLeft"), u = D(a, "eyeBlinkRight"), S = Math.min(1, Math.abs(i.yaw) / 0.35) * Math.max(0, Math.min(1, t)), f = (d + u) / 2;
|
|
1185
1202
|
return {
|
|
1186
|
-
blinkLeft:
|
|
1187
|
-
blinkRight: u + (f - u) *
|
|
1188
|
-
jawOpen:
|
|
1189
|
-
smile: c -
|
|
1190
|
-
browUp: (
|
|
1203
|
+
blinkLeft: d + (f - d) * S,
|
|
1204
|
+
blinkRight: u + (f - u) * S,
|
|
1205
|
+
jawOpen: D(a, "jawOpen"),
|
|
1206
|
+
smile: c - l,
|
|
1207
|
+
browUp: (D(a, "browInnerUp") + D(a, "browOuterUpLeft") + D(a, "browOuterUpRight")) / 3,
|
|
1191
1208
|
pupilX: n - s,
|
|
1192
1209
|
pupilY: r - o,
|
|
1193
1210
|
headYaw: i.yaw,
|
|
@@ -1216,30 +1233,30 @@ function be(a, e, t, i, n) {
|
|
|
1216
1233
|
n || (a.fillStyle = "#101822", a.fillRect(0, 0, t, i));
|
|
1217
1234
|
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;
|
|
1218
1235
|
a.save(), a.translate(s, o), a.rotate(e.headRoll * 0.6);
|
|
1219
|
-
const c = Math.max(-1.35, Math.min(1.35, e.headYaw)),
|
|
1220
|
-
a.fillStyle = "#3a2a1c", a.beginPath(), a.ellipse(-
|
|
1221
|
-
const
|
|
1236
|
+
const c = Math.max(-1.35, Math.min(1.35, e.headYaw)), l = Math.max(-0.9, Math.min(0.9, e.headPitch)), d = Math.sin(c), u = (A) => Math.sin(A + c) * r, S = (A) => Math.cos(A + c), f = Math.sin(l) * r * 0.22;
|
|
1237
|
+
a.fillStyle = "#3a2a1c", a.beginPath(), a.ellipse(-d * r * 0.35, -r * 0.25, r * 0.98, r * 0.95, 0, 0, Math.PI * 2), a.fill(), a.fillStyle = e.tracked ? "#f2c9a0" : "#5a6470", a.beginPath(), a.ellipse(0, 0, r * (1 - Math.abs(d) * 0.12), r * 1.12, 0, 0, Math.PI * 2), a.fill(), a.fillStyle = "#3a2a1c", a.beginPath(), a.ellipse(-d * r * 0.28, -r * 0.72 + f * 0.4, r * 0.92, r * 0.5, 0, Math.PI, Math.PI * 2), a.fill();
|
|
1238
|
+
const v = 0;
|
|
1222
1239
|
a.fillStyle = "#e5b58a", a.beginPath();
|
|
1223
|
-
const
|
|
1224
|
-
a.ellipse(
|
|
1225
|
-
for (const
|
|
1226
|
-
const
|
|
1227
|
-
if (
|
|
1228
|
-
const
|
|
1229
|
-
a.strokeStyle = "#4a2e18", a.lineWidth = r * 0.07, a.lineCap = "round", a.beginPath(), a.moveTo(
|
|
1230
|
-
const
|
|
1231
|
-
a.fillStyle = "#ffffff", a.beginPath(), a.ellipse(
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
r * 0.06 *
|
|
1235
|
-
r * 0.06 *
|
|
1240
|
+
const I = u(v) * 0.9, y = r * 0.12 + f;
|
|
1241
|
+
a.ellipse(I + d * r * 0.14, y, r * 0.11, r * 0.14, 0, 0, Math.PI * 2), a.fill();
|
|
1242
|
+
for (const A of [-1, 1]) {
|
|
1243
|
+
const w = A * 0.42, M = S(w);
|
|
1244
|
+
if (M <= 0.12) continue;
|
|
1245
|
+
const m = u(w) * 0.82, h = Math.max(0.2, M), T = A < 0 ? e.blinkLeft : e.blinkRight, R = Math.max(0.06, 1 - T), b = e.browUp * r * 0.14;
|
|
1246
|
+
a.strokeStyle = "#4a2e18", a.lineWidth = r * 0.07, a.lineCap = "round", a.beginPath(), a.moveTo(m - r * 0.2 * h, -r * 0.42 - b + f), a.quadraticCurveTo(m, -r * 0.52 - b + f, m + r * 0.2 * h, -r * 0.44 - b + f), a.stroke();
|
|
1247
|
+
const O = -r * 0.18 + f;
|
|
1248
|
+
a.fillStyle = "#ffffff", a.beginPath(), a.ellipse(m, O, r * 0.16 * h, r * 0.12 * R, 0, 0, Math.PI * 2), a.fill(), a.fillStyle = "#22303c", a.beginPath(), a.ellipse(
|
|
1249
|
+
m + e.pupilX * r * 0.07 * h,
|
|
1250
|
+
O + e.pupilY * r * 0.05,
|
|
1251
|
+
r * 0.06 * h,
|
|
1252
|
+
r * 0.06 * R,
|
|
1236
1253
|
0,
|
|
1237
1254
|
0,
|
|
1238
1255
|
Math.PI * 2
|
|
1239
1256
|
), a.fill();
|
|
1240
1257
|
}
|
|
1241
|
-
const
|
|
1242
|
-
a.fillStyle = "#8c2f2f", a.beginPath(), a.moveTo(
|
|
1258
|
+
const k = Math.max(0.25, S(0)), p = u(0) * 0.7, C = r * 0.55 * k, P = r * (0.04 + e.jawOpen * 0.34), L = e.smile * r * 0.12, E = r * 0.42 + f;
|
|
1259
|
+
a.fillStyle = "#8c2f2f", a.beginPath(), a.moveTo(p - C / 2, E - L), a.quadraticCurveTo(p, E + P, p + C / 2, E - L), a.quadraticCurveTo(p, E - P * 0.25, p - C / 2, E - 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");
|
|
1243
1260
|
}
|
|
1244
1261
|
const Z = {
|
|
1245
1262
|
mouthGain: 1.7,
|
|
@@ -1257,62 +1274,71 @@ function ee() {
|
|
|
1257
1274
|
}
|
|
1258
1275
|
};
|
|
1259
1276
|
}
|
|
1260
|
-
const
|
|
1261
|
-
function
|
|
1262
|
-
const t = e.document ?? globalThis.document
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1277
|
+
const $ = (a) => Math.max(0, Math.min(1, a));
|
|
1278
|
+
function xe(a, e = {}) {
|
|
1279
|
+
const t = e.document ?? globalThis.document;
|
|
1280
|
+
let i = e.width ?? 1280, n = e.height ?? 720;
|
|
1281
|
+
const s = !!(e.width || e.height), o = e.frameRate ?? 24, r = a instanceof MediaStreamTrack ? a : a.getVideoTracks()[0];
|
|
1282
|
+
if (!r) throw new Error("A video track is required");
|
|
1283
|
+
let c = e.tracker ?? null, l = e.renderer ?? ee(), d = e.mode ?? "replace";
|
|
1284
|
+
const u = { ...Z, ...e.tuning }, S = e.backdrop ?? "#101822", f = t.createElement("video");
|
|
1285
|
+
f.muted = !0, f.playsInline = !0, f.srcObject = new MediaStream([r]), f.play().catch(() => {
|
|
1267
1286
|
});
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
if (
|
|
1274
|
-
const
|
|
1275
|
-
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1287
|
+
const v = t.createElement("canvas");
|
|
1288
|
+
v.width = i, v.height = n;
|
|
1289
|
+
const I = v.getContext("2d"), y = () => {
|
|
1290
|
+
if (s) return;
|
|
1291
|
+
const w = f.videoWidth, M = f.videoHeight;
|
|
1292
|
+
if (!w || !M) return;
|
|
1293
|
+
const m = Math.min(1, 1280 / Math.max(w, M)), h = Math.round(w * m), T = Math.round(M * m);
|
|
1294
|
+
(!(h === i || T === n) || h !== i || T !== n) && (i = h, n = T, v.width = i, v.height = n);
|
|
1295
|
+
};
|
|
1296
|
+
f.addEventListener?.("loadedmetadata", y), f.addEventListener?.("resize", y);
|
|
1297
|
+
let k = le(), p = null, C = 0;
|
|
1298
|
+
function P(w) {
|
|
1299
|
+
if (w.length <= 454) return;
|
|
1300
|
+
const M = w[10], m = w[152], h = w[234], T = w[454], R = (h.x + T.x) / 2 * i, b = (h.y + T.y) / 2 * n, O = Math.hypot((m.x - M.x) * i, (m.y - M.y) * n), g = Math.max(0.6, Math.cos(k.headPitch)), q = { x: R, y: b, radius: Math.max(24, O / g * 0.8) };
|
|
1301
|
+
p = p ? { x: p.x + (q.x - p.x) * 0.4, y: p.y + (q.y - p.y) * 0.4, radius: p.radius + (q.radius - p.radius) * 0.15 } : q;
|
|
1302
|
+
}
|
|
1303
|
+
function L(w) {
|
|
1304
|
+
if (c && f.readyState >= 2) {
|
|
1305
|
+
let m = null;
|
|
1280
1306
|
try {
|
|
1281
|
-
|
|
1307
|
+
m = c.detect(f, w);
|
|
1282
1308
|
} catch {
|
|
1283
1309
|
}
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1310
|
+
const h = Te(m?.categories ?? null, m?.matrix ?? null, u.eyeSync);
|
|
1311
|
+
h.jawOpen = $(h.jawOpen * u.mouthGain), h.blinkLeft = $(h.blinkLeft * u.blinkGain), h.blinkRight = $(h.blinkRight * u.blinkGain), h.tracked ? (C = w, k = X(k, h, u.smoothing), m?.landmarks && P(m.landmarks)) : k.tracked && w - C > u.lostHoldMs && (k = X(k, { ...k, jawOpen: 0, browUp: 0, smile: k.smile * 0.5, blinkLeft: 0.1, blinkRight: 0.1 }, 0.05));
|
|
1286
1312
|
}
|
|
1287
|
-
d === "overlay" &&
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1313
|
+
d === "overlay" && f.readyState >= 2 ? I.drawImage(f, 0, 0, i, n) : (I.fillStyle = S, I.fillRect(0, 0, i, n));
|
|
1314
|
+
const M = d === "overlay" && p ? { x: p.x, y: p.y + u.avatarLift * p.radius, radius: p.radius * u.avatarScale } : null;
|
|
1315
|
+
l?.render(I, k, i, n, M);
|
|
1290
1316
|
}
|
|
1291
|
-
const
|
|
1317
|
+
const E = setInterval(() => L(Date.now()), Math.round(1e3 / o)), A = v.captureStream(o);
|
|
1292
1318
|
return {
|
|
1293
|
-
track:
|
|
1294
|
-
stream:
|
|
1295
|
-
getRig: () => ({ ...
|
|
1296
|
-
setMode(
|
|
1297
|
-
d =
|
|
1319
|
+
track: A.getVideoTracks()[0],
|
|
1320
|
+
stream: A,
|
|
1321
|
+
getRig: () => ({ ...k }),
|
|
1322
|
+
setMode(w) {
|
|
1323
|
+
d = w === "overlay" ? "overlay" : "replace";
|
|
1298
1324
|
},
|
|
1299
|
-
setTracker(
|
|
1300
|
-
|
|
1325
|
+
setTracker(w) {
|
|
1326
|
+
c?.close?.(), c = w;
|
|
1301
1327
|
},
|
|
1302
|
-
setRenderer(
|
|
1303
|
-
|
|
1328
|
+
setRenderer(w) {
|
|
1329
|
+
l?.dispose?.(), l = w ?? ee();
|
|
1304
1330
|
},
|
|
1305
|
-
setTuning(
|
|
1306
|
-
for (const
|
|
1307
|
-
const
|
|
1308
|
-
typeof
|
|
1331
|
+
setTuning(w) {
|
|
1332
|
+
for (const M of Object.keys(Z)) {
|
|
1333
|
+
const m = w[M];
|
|
1334
|
+
typeof m == "number" && Number.isFinite(m) && (u[M] = m);
|
|
1309
1335
|
}
|
|
1310
1336
|
},
|
|
1311
|
-
getTuning: () => ({ ...
|
|
1337
|
+
getTuning: () => ({ ...u }),
|
|
1312
1338
|
dispose() {
|
|
1313
|
-
clearInterval(
|
|
1314
|
-
for (const
|
|
1315
|
-
|
|
1339
|
+
clearInterval(E), c?.close?.(), l?.dispose?.();
|
|
1340
|
+
for (const w of A.getTracks()) w.stop();
|
|
1341
|
+
f.srcObject = null;
|
|
1316
1342
|
}
|
|
1317
1343
|
};
|
|
1318
1344
|
}
|
|
@@ -1350,7 +1376,7 @@ class Ce extends EventTarget {
|
|
|
1350
1376
|
async initialize() {
|
|
1351
1377
|
if (this.client) return this;
|
|
1352
1378
|
try {
|
|
1353
|
-
const t = (this.options.transport === "auto" ? this.mode === "meeting" ? "sfu" : "mesh" : this.options.transport ?? "mesh") === "sfu" ? ye :
|
|
1379
|
+
const t = (this.options.transport === "auto" ? this.mode === "meeting" ? "sfu" : "mesh" : this.options.transport ?? "mesh") === "sfu" ? ye : Q;
|
|
1354
1380
|
return this.client = new t({
|
|
1355
1381
|
credential: {
|
|
1356
1382
|
signalingToken: this.options.credential.signalingToken,
|
|
@@ -1368,6 +1394,7 @@ class Ce extends EventTarget {
|
|
|
1368
1394
|
preferredVideoCodec: this.options.preferredVideoCodec ?? "auto",
|
|
1369
1395
|
iceTransportPolicy: this.options.iceTransportPolicy ?? "all",
|
|
1370
1396
|
localStream: this.options.localStream,
|
|
1397
|
+
simulcast: this.options.simulcast ?? !0,
|
|
1371
1398
|
onEvent: (i, n) => this.handleNativeEvent(i, n)
|
|
1372
1399
|
}), await this.client.initialize(), this.startStats(), this.emit("initialized", { mode: this.mode, sdkVersion: De }), this.options.autoJoin && await this.join(), this;
|
|
1373
1400
|
} catch (e) {
|
|
@@ -1495,42 +1522,42 @@ class Ce extends EventTarget {
|
|
|
1495
1522
|
let n = "unavailable";
|
|
1496
1523
|
if (e.requestMedia !== !1 && navigator.mediaDevices?.getUserMedia)
|
|
1497
1524
|
try {
|
|
1498
|
-
(await navigator.mediaDevices.getUserMedia({ audio: this.options.audio, video: this.options.video })).getTracks().forEach((
|
|
1525
|
+
(await navigator.mediaDevices.getUserMedia({ audio: this.options.audio, video: this.options.video })).getTracks().forEach((S) => S.stop()), n = "granted";
|
|
1499
1526
|
} catch {
|
|
1500
1527
|
n = "denied", t.push("media_permission_denied");
|
|
1501
1528
|
}
|
|
1502
1529
|
const s = navigator.mediaDevices?.enumerateDevices ? await navigator.mediaDevices.enumerateDevices().catch(() => []) : [], o = s.filter((u) => u.kind === "audioinput").length, r = s.filter((u) => u.kind === "videoinput").length;
|
|
1503
1530
|
this.options.audio && o === 0 && t.push("microphone_unavailable"), this.options.video && r === 0 && t.push("camera_unavailable");
|
|
1504
1531
|
const c = e.probeUrl ?? this.options.preflightProbeUrl;
|
|
1505
|
-
let
|
|
1532
|
+
let l = null;
|
|
1506
1533
|
if (c) {
|
|
1507
1534
|
const u = performance.now();
|
|
1508
1535
|
try {
|
|
1509
|
-
const
|
|
1510
|
-
|
|
1536
|
+
const S = await fetch(c, { method: "HEAD", cache: "no-store", credentials: "omit" });
|
|
1537
|
+
l = Math.round(performance.now() - u), S.ok || t.push("network_probe_failed");
|
|
1511
1538
|
} catch {
|
|
1512
1539
|
t.push("network_probe_failed");
|
|
1513
1540
|
}
|
|
1514
1541
|
}
|
|
1515
|
-
const
|
|
1542
|
+
const d = {
|
|
1516
1543
|
passed: t.length === 0,
|
|
1517
1544
|
sampledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1518
1545
|
secureContext: i,
|
|
1519
1546
|
mediaPermission: n,
|
|
1520
1547
|
microphoneCount: o,
|
|
1521
1548
|
cameraCount: r,
|
|
1522
|
-
probeLatencyMs:
|
|
1549
|
+
probeLatencyMs: l,
|
|
1523
1550
|
effectiveConnectionType: ae()?.effectiveType ?? null,
|
|
1524
1551
|
issues: t
|
|
1525
1552
|
};
|
|
1526
|
-
return this.emit("preflight",
|
|
1553
|
+
return this.emit("preflight", d), this.options.autoReportStats && await this.reportQuality({
|
|
1527
1554
|
sampleType: "preflight",
|
|
1528
|
-
sampledAt:
|
|
1529
|
-
connectionState:
|
|
1530
|
-
rttMs:
|
|
1531
|
-
effectiveConnectionType:
|
|
1555
|
+
sampledAt: d.sampledAt,
|
|
1556
|
+
connectionState: d.passed ? "ready" : "failed",
|
|
1557
|
+
rttMs: d.probeLatencyMs,
|
|
1558
|
+
effectiveConnectionType: d.effectiveConnectionType
|
|
1532
1559
|
}).catch(() => {
|
|
1533
|
-
}),
|
|
1560
|
+
}), d;
|
|
1534
1561
|
}
|
|
1535
1562
|
async collectQualitySample(e = "runtime") {
|
|
1536
1563
|
const t = this.options.statsProvider ? await this.options.statsProvider() : {}, i = this.peerConnection ? await Pe(this.peerConnection, this.previousCounters) : null;
|
|
@@ -1649,14 +1676,14 @@ class Ce extends EventTarget {
|
|
|
1649
1676
|
this.dispatchEvent(new CustomEvent(e, { detail: i })), this.options.onEvent?.(i);
|
|
1650
1677
|
}
|
|
1651
1678
|
}
|
|
1652
|
-
async function
|
|
1679
|
+
async function Ue(a) {
|
|
1653
1680
|
return new Ce(a).initialize();
|
|
1654
1681
|
}
|
|
1655
|
-
let
|
|
1682
|
+
let U = null;
|
|
1656
1683
|
function de() {
|
|
1657
|
-
if (
|
|
1684
|
+
if (U) return U;
|
|
1658
1685
|
const a = globalThis, e = a.AudioContext ?? a.webkitAudioContext;
|
|
1659
|
-
return e ? (
|
|
1686
|
+
return e ? (U = new e(), U) : null;
|
|
1660
1687
|
}
|
|
1661
1688
|
async function Re() {
|
|
1662
1689
|
const a = de();
|
|
@@ -1912,14 +1939,14 @@ class Ee extends EventTarget {
|
|
|
1912
1939
|
if (!n) throw new Error("apiBaseUrl is required for call actions");
|
|
1913
1940
|
const s = new URL(n, globalThis.location?.href), o = await this.resolveToken("initial"), r = new Headers(t.headers);
|
|
1914
1941
|
r.set("authorization", `Bearer ${o}`), r.set("content-type", "application/json");
|
|
1915
|
-
const c = await fetch(new URL(e, s.origin), { ...t, headers: r }),
|
|
1942
|
+
const c = await fetch(new URL(e, s.origin), { ...t, headers: r }), l = await c.json().catch(() => ({}));
|
|
1916
1943
|
if (c.status === 401 && this.options.tokenProvider && !i)
|
|
1917
1944
|
return await this.resolveToken("unauthorized", !0), this.request(e, t, !0);
|
|
1918
1945
|
if (!c.ok) {
|
|
1919
|
-
const
|
|
1920
|
-
throw new Error(typeof
|
|
1946
|
+
const d = l.error;
|
|
1947
|
+
throw new Error(typeof d == "string" ? d : d?.message || `RTC API failed with HTTP ${c.status}`);
|
|
1921
1948
|
}
|
|
1922
|
-
return
|
|
1949
|
+
return l;
|
|
1923
1950
|
}
|
|
1924
1951
|
renderIncoming(e) {
|
|
1925
1952
|
if (!this.mount) return;
|
|
@@ -2035,7 +2062,7 @@ class Ee extends EventTarget {
|
|
|
2035
2062
|
this.dispatchEvent(new CustomEvent(e, { detail: i })), this.options.onEvent?.(i);
|
|
2036
2063
|
}
|
|
2037
2064
|
}
|
|
2038
|
-
function
|
|
2065
|
+
function Qe(a) {
|
|
2039
2066
|
const e = new Ee(a);
|
|
2040
2067
|
return a.autoConnect !== !1 && a.signalingUrl && e.connect(), e;
|
|
2041
2068
|
}
|
|
@@ -2074,46 +2101,46 @@ function ne(a) {
|
|
|
2074
2101
|
}
|
|
2075
2102
|
async function Pe(a, e) {
|
|
2076
2103
|
const t = await a.getStats(), i = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
|
|
2077
|
-
let s = 0, o = 0, r = 0, c = 0,
|
|
2078
|
-
t.forEach((
|
|
2079
|
-
n.set(
|
|
2080
|
-
}), t.forEach((
|
|
2081
|
-
if (
|
|
2082
|
-
|
|
2083
|
-
const
|
|
2084
|
-
|
|
2104
|
+
let s = 0, o = 0, r = 0, c = 0, l = 0, d = 0, u = 0, S = 0, f = 0, v = 0, I = 0, y = 0, k = 0, p = null, C = null, P = null, L = null, E = null, A = null;
|
|
2105
|
+
t.forEach((g) => {
|
|
2106
|
+
n.set(g.id, g), g.type === "codec" && i.set(g.id, String(g.mimeType ?? ""));
|
|
2107
|
+
}), t.forEach((g) => {
|
|
2108
|
+
if (g.type === "inbound-rtp" && !g.isRemote && (s += Number(g.bytesReceived ?? 0), r += Math.max(0, Number(g.packetsLost ?? 0)), c += Math.max(0, Number(g.packetsReceived ?? 0)), Number.isFinite(g.jitter) && (l += Number(g.jitter), d += 1), u = Math.max(u, Number(g.framesPerSecond ?? 0)), S = Math.max(S, Number(g.frameWidth ?? 0)), f = Math.max(f, Number(g.frameHeight ?? 0)), v += Number(g.framesDropped ?? 0), I += Number(g.freezeCount ?? 0), y += Number(g.totalFreezesDuration ?? 0), k += Number(g.concealedSamples ?? 0), E ||= i.get(String(g.codecId ?? "")) ?? null), g.type === "outbound-rtp" && !g.isRemote && (o += Number(g.bytesSent ?? 0), A ||= g.qualityLimitationReason ? String(g.qualityLimitationReason) : null, E ||= i.get(String(g.codecId ?? "")) ?? null), g.type === "candidate-pair" && (g.selected || g.nominated) && g.state === "succeeded") {
|
|
2109
|
+
p = Number.isFinite(g.currentRoundTripTime) ? Number(g.currentRoundTripTime) : p, C = Number.isFinite(g.availableOutgoingBitrate) ? Number(g.availableOutgoingBitrate) : C;
|
|
2110
|
+
const q = n.get(g.localCandidateId);
|
|
2111
|
+
P = q?.candidateType ? String(q.candidateType) : P, L = q?.protocol ? String(q.protocol) : L;
|
|
2085
2112
|
}
|
|
2086
2113
|
});
|
|
2087
|
-
const
|
|
2114
|
+
const w = Date.now(), M = e ? Math.max(1e-3, (w - e.timestamp) / 1e3) : 0, m = e ? Math.max(0, Math.round((s - e.inboundBytes) * 8 / M)) : null, h = e ? Math.max(0, Math.round((o - e.outboundBytes) * 8 / M)) : null, T = r + c, R = e ? Math.max(0, r - e.packetsLost) : r, b = e ? Math.max(0, c - e.packetsReceived) : c, O = R + b;
|
|
2088
2115
|
return {
|
|
2089
|
-
counters: { timestamp:
|
|
2116
|
+
counters: { timestamp: w, inboundBytes: s, outboundBytes: o, packetsLost: r, packetsReceived: c },
|
|
2090
2117
|
sample: {
|
|
2091
|
-
rttMs:
|
|
2092
|
-
jitterMs:
|
|
2093
|
-
packetLossPct:
|
|
2094
|
-
packetLossCumulativePct:
|
|
2118
|
+
rttMs: p === null ? null : p * 1e3,
|
|
2119
|
+
jitterMs: d ? l / d * 1e3 : null,
|
|
2120
|
+
packetLossPct: O ? R / O * 100 : null,
|
|
2121
|
+
packetLossCumulativePct: T ? r / T * 100 : null,
|
|
2095
2122
|
packetsLost: r,
|
|
2096
2123
|
packetsReceived: c,
|
|
2097
|
-
inboundBitrateBps:
|
|
2098
|
-
outboundBitrateBps:
|
|
2099
|
-
availableOutgoingBitrateBps:
|
|
2124
|
+
inboundBitrateBps: m,
|
|
2125
|
+
outboundBitrateBps: h,
|
|
2126
|
+
availableOutgoingBitrateBps: C,
|
|
2100
2127
|
framesPerSecond: u || null,
|
|
2101
|
-
frameWidth:
|
|
2128
|
+
frameWidth: S || null,
|
|
2102
2129
|
frameHeight: f || null,
|
|
2103
|
-
framesDropped:
|
|
2104
|
-
freezeCount:
|
|
2105
|
-
freezeDurationMs: Math.round(
|
|
2106
|
-
concealedSamples:
|
|
2107
|
-
codec:
|
|
2108
|
-
transportProtocol:
|
|
2109
|
-
candidateType:
|
|
2110
|
-
qualityLimitationReason:
|
|
2130
|
+
framesDropped: v,
|
|
2131
|
+
freezeCount: I,
|
|
2132
|
+
freezeDurationMs: Math.round(y * 1e3),
|
|
2133
|
+
concealedSamples: k,
|
|
2134
|
+
codec: E,
|
|
2135
|
+
transportProtocol: L,
|
|
2136
|
+
candidateType: P,
|
|
2137
|
+
qualityLimitationReason: A
|
|
2111
2138
|
}
|
|
2112
2139
|
};
|
|
2113
2140
|
}
|
|
2114
2141
|
function Ae(a) {
|
|
2115
2142
|
let e = a.mediaScore == null ? 5 : Math.round(se(Number(a.mediaScore), 0, 5));
|
|
2116
|
-
const t =
|
|
2143
|
+
const t = H(a.packetLossPct) ?? 0, i = H(a.rttMs) ?? 0, n = H(a.jitterMs) ?? 0, s = String(a.connectionState ?? "unknown").toLowerCase();
|
|
2117
2144
|
["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));
|
|
2118
2145
|
const o = Number(se(4.5 - t * 0.09 - i * 11e-4 - n * 6e-3, 1, 4.5).toFixed(2));
|
|
2119
2146
|
return { networkScore: e, estimatedMos: o };
|
|
@@ -2125,7 +2152,7 @@ function Le() {
|
|
|
2125
2152
|
const a = Number(navigator.deviceMemory);
|
|
2126
2153
|
return Number.isFinite(a) ? a : null;
|
|
2127
2154
|
}
|
|
2128
|
-
function
|
|
2155
|
+
function H(a) {
|
|
2129
2156
|
const e = Number(a);
|
|
2130
2157
|
return Number.isFinite(e) ? e : null;
|
|
2131
2158
|
}
|
|
@@ -2138,7 +2165,7 @@ function V(a) {
|
|
|
2138
2165
|
function re(a, e) {
|
|
2139
2166
|
return a instanceof Error ? a : new Error(e);
|
|
2140
2167
|
}
|
|
2141
|
-
const De = "1.3.
|
|
2168
|
+
const De = "1.3.18";
|
|
2142
2169
|
export {
|
|
2143
2170
|
Z as AVATAR_TUNING_DEFAULTS,
|
|
2144
2171
|
Se as BACKGROUND_PRESETS,
|
|
@@ -2147,11 +2174,11 @@ export {
|
|
|
2147
2174
|
Ee as RtcIncomingClient,
|
|
2148
2175
|
qe as SPEAKER_NOISE_FLOOR,
|
|
2149
2176
|
Oe as SPEAKER_TAKEOVER_RATIO,
|
|
2150
|
-
|
|
2177
|
+
xe as createAvatarPipeline,
|
|
2151
2178
|
je as createCallRecorder,
|
|
2152
2179
|
Ne as createPictureInPictureController,
|
|
2153
|
-
|
|
2154
|
-
|
|
2180
|
+
Ue as createRtcClient,
|
|
2181
|
+
Qe as createRtcIncomingClient,
|
|
2155
2182
|
Ve as createVideoEffectsPipeline,
|
|
2156
2183
|
be as drawAvatar,
|
|
2157
2184
|
we as headPoseFromMatrix,
|