callskit
Version:
A toolkit for building call experience using Cloudflare Realtime
1,373 lines (1,363 loc) • 50.8 kB
JavaScript
import { setLogLevel, PartyTracks, getMic, getCamera, devices$, getScreenshare } from 'partytracks/client';
import { Observable, Subject, BehaviorSubject, interval, of, filter as filter$1, switchMap } from 'rxjs';
import { takeUntil, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators';
import invariant from 'tiny-invariant';
import PartySocket from 'partysocket';
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/utils/events-handler.ts
var _callbacksMap, _globalCallbacks;
var EventsHandler = class {
constructor() {
__privateAdd(this, _callbacksMap);
__privateAdd(this, _globalCallbacks);
__privateSet(this, _callbacksMap, /* @__PURE__ */ new Map());
__privateSet(this, _globalCallbacks, /* @__PURE__ */ new Set());
}
emit(event, ...args) {
var _a;
(_a = __privateGet(this, _callbacksMap).get(event)) == null ? void 0 : _a.forEach((callback) => callback(...args));
__privateGet(this, _globalCallbacks).forEach((callback) => callback(event, ...args));
}
addEventListener(event, callback) {
if (!__privateGet(this, _callbacksMap).has(event)) {
__privateGet(this, _callbacksMap).set(event, /* @__PURE__ */ new Set());
}
__privateGet(this, _callbacksMap).get(event).add(callback);
}
removeEventListener(event, callback) {
var _a;
(_a = __privateGet(this, _callbacksMap).get(event)) == null ? void 0 : _a.delete(callback);
}
subscribe(event, callback) {
this.addEventListener(event, callback);
return () => this.removeEventListener(event, callback);
}
subscribeAll(callback) {
__privateGet(this, _globalCallbacks).add(callback);
return () => {
__privateGet(this, _globalCallbacks).delete(callback);
};
}
unsubscribeAll(callback) {
__privateGet(this, _globalCallbacks).delete(callback);
}
once(event, callback) {
const wrapper = (...args) => {
this.removeEventListener(event, wrapper);
callback(...args);
};
this.addEventListener(event, wrapper);
return () => {
this.removeEventListener(event, wrapper);
};
}
clearAll() {
__privateGet(this, _callbacksMap).clear();
__privateGet(this, _globalCallbacks).clear();
}
};
_callbacksMap = new WeakMap();
_globalCallbacks = new WeakMap();
// src/utils/logger.ts
var _Logger = class _Logger {
constructor(options = {}) {
var _a, _b, _c;
this.level = (_a = options.level) != null ? _a : "info";
this.prefix = (_b = options.prefix) != null ? _b : "";
this.enabled = (_c = options.enabled) != null ? _c : true;
}
shouldLog(level) {
return this.enabled && _Logger.levels[level] >= _Logger.levels[this.level];
}
formatMessage(level, args) {
const prefix = this.prefix ? `[${this.prefix}]` : "";
return [prefix, ...args];
}
debug(...args) {
if (this.shouldLog("debug")) {
console.debug(...this.formatMessage("debug", args));
}
}
info(...args) {
if (this.shouldLog("info")) {
console.info(...this.formatMessage("info", args));
}
}
warn(...args) {
if (this.shouldLog("warn")) {
console.warn(...this.formatMessage("warn", args));
}
}
error(...args) {
if (this.shouldLog("error")) {
console.error(...this.formatMessage("error", args));
}
}
setLevel(level) {
this.level = level;
}
enable() {
this.enabled = true;
}
disable() {
this.enabled = false;
}
};
_Logger.levels = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
var Logger = _Logger;
var contextStack = [];
function runWithContext(context, fn) {
contextStack.push(context);
try {
return fn();
} finally {
contextStack.pop();
}
}
function getCurrentCallContext() {
const ctx = contextStack[contextStack.length - 1];
invariant(ctx, "No active CallContext");
return ctx;
}
// src/lib/call-chat/call-chat.ts
var _ctx, _CallChat_instances, sendMessage_fn;
var CallChat = class extends EventsHandler {
constructor() {
super();
__privateAdd(this, _CallChat_instances);
this.messages = [];
__privateAdd(this, _ctx);
__privateSet(this, _ctx, getCurrentCallContext());
}
sendTextMessage(message) {
__privateMethod(this, _CallChat_instances, sendMessage_fn).call(this, { type: "text", message: message.trim() });
}
addMessage(message) {
const parsedMessage = __spreadProps(__spreadValues({}, message), {
created_at: new Date(message.created_at)
});
this.messages = [...this.messages, parsedMessage];
this.emit("message", parsedMessage);
}
addMessagesInBulk(messages) {
const parsed = messages.map((message) => __spreadProps(__spreadValues({}, message), {
created_at: new Date(message.created_at)
}));
this.messages = [...this.messages, ...parsed];
this.emit("loaded", parsed);
}
};
_ctx = new WeakMap();
_CallChat_instances = new WeakSet();
sendMessage_fn = function(message) {
__privateGet(this, _ctx).socket.sendAction({ action: "chat/message", message });
};
new Observable(
(subscriber) => {
const canvas = document.createElement("canvas");
canvas.height = 720;
canvas.width = 1280;
const ctx = canvas.getContext("2d");
invariant(ctx);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
const interval2 = setInterval(() => {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}, 1e3);
subscriber.next(canvas.captureStream().getVideoTracks()[0]);
return () => {
clearInterval(interval2);
};
}
);
function getInaudibleTrack() {
const audioContext = new AudioContext();
audioContext.resume();
const oscillator = audioContext.createOscillator();
oscillator.type = "triangle";
oscillator.frequency.setValueAtTime(20, audioContext.currentTime);
const gainNode = audioContext.createGain();
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
oscillator.connect(gainNode);
const destination = audioContext.createMediaStreamDestination();
gainNode.connect(destination);
oscillator.start();
const track = destination.stream.getAudioTracks()[0];
return { track, audioContext };
}
new Observable(
(subscriber) => {
const { audioContext, track } = getInaudibleTrack();
subscriber.next(track);
return () => {
track.stop();
audioContext.close();
};
}
);
function createTrackId(metadata) {
return `${metadata.sessionId}/${metadata.trackName}`;
}
function parseTrackId(trackId) {
const [sessionId, trackName] = trackId.split("/");
return { sessionId, trackName };
}
// src/lib/call-participant/call-participant.ts
var _ctx2, _micEnabled$, _micTrackId$, _micEnabled, _micTrack, _cameraEnabled$, _cameraTrackId$, _cameraEnabled, _cameraTrack, _screenshareEnabled$, _screenshareVideoTrackId$, _screenshareAudioTrackId$, _screenshareEnabled, _screenshareVideoTrack, _screenshareAudioTrack;
var _CallParticipant = class _CallParticipant extends EventsHandler {
constructor(options) {
super();
__privateAdd(this, _ctx2);
__privateAdd(this, _micEnabled$);
__privateAdd(this, _micTrackId$);
__privateAdd(this, _micEnabled, false);
__privateAdd(this, _micTrack);
__privateAdd(this, _cameraEnabled$);
__privateAdd(this, _cameraTrackId$);
__privateAdd(this, _cameraEnabled, false);
__privateAdd(this, _cameraTrack);
__privateAdd(this, _screenshareEnabled$, new BehaviorSubject(false));
__privateAdd(this, _screenshareVideoTrackId$);
__privateAdd(this, _screenshareAudioTrackId$);
__privateAdd(this, _screenshareEnabled, false);
__privateAdd(this, _screenshareVideoTrack);
__privateAdd(this, _screenshareAudioTrack);
this.id = options.id;
this.name = options.name;
__privateSet(this, _ctx2, getCurrentCallContext());
__privateSet(this, _micEnabled$, new BehaviorSubject(
options.micEnabled || false
));
__privateSet(this, _micTrackId$, new BehaviorSubject(
options.micEnabled ? options.micTrackId : void 0
));
__privateSet(this, _cameraEnabled$, new BehaviorSubject(
options.cameraEnabled || false
));
__privateSet(this, _cameraTrackId$, new BehaviorSubject(
options.cameraEnabled ? options.cameraTrackId : void 0
));
__privateSet(this, _screenshareEnabled, options.screenshareEnabled || false);
__privateSet(this, _screenshareVideoTrackId$, new BehaviorSubject(
options.screenshareEnabled ? options.screenshareVideoTrackId : void 0
));
__privateSet(this, _screenshareAudioTrackId$, new BehaviorSubject(
options.screenshareEnabled ? options.screenshareAudioTrackId : void 0
));
__privateGet(this, _micEnabled$).subscribe((enabled) => {
if (!enabled) {
__privateSet(this, _micEnabled, false);
this.emit("micUpdate", { micEnabled: false });
__privateGet(this, _ctx2).call.participants.joined.emit("micUpdate", this);
} else if (enabled && __privateGet(this, _micTrack)) {
__privateSet(this, _micEnabled, true);
this.emit("micUpdate", {
micEnabled: true,
micTrack: __privateGet(this, _micTrack)
});
__privateGet(this, _ctx2).call.participants.joined.emit("micUpdate", this);
__privateGet(this, _ctx2).call.participants.emit("micUpdate", this);
}
});
const micMetadata$ = __privateGet(this, _micTrackId$).pipe(
filter$1((id) => typeof id === "string"),
switchMap((id) => {
const { sessionId, trackName } = parseTrackId(id);
return of({
sessionId,
trackName,
location: "remote"
});
})
);
const micTrack$ = __privateGet(this, _ctx2).partyTracks.pull(micMetadata$);
micTrack$.subscribe((track) => {
__privateSet(this, _micEnabled, true);
__privateSet(this, _micTrack, track);
this.emit("micUpdate", { micEnabled: true, micTrack: track });
__privateGet(this, _ctx2).call.participants.joined.emit("micUpdate", this);
__privateGet(this, _ctx2).call.participants.emit("micUpdate", this);
});
__privateGet(this, _cameraEnabled$).subscribe((enabled) => {
if (!enabled) {
__privateSet(this, _cameraEnabled, false);
this.emit("cameraUpdate", { cameraEnabled: false });
__privateGet(this, _ctx2).call.participants.joined.emit("cameraUpdate", this);
__privateGet(this, _ctx2).call.participants.emit("cameraUpdate", this);
} else if (enabled && __privateGet(this, _cameraTrack)) {
__privateSet(this, _cameraEnabled, true);
this.emit("cameraUpdate", {
cameraEnabled: true,
cameraTrack: __privateGet(this, _cameraTrack)
});
__privateGet(this, _ctx2).call.participants.joined.emit("cameraUpdate", this);
__privateGet(this, _ctx2).call.participants.emit("cameraUpdate", this);
}
});
const cameraMetadata$ = __privateGet(this, _cameraTrackId$).pipe(
filter$1((id) => typeof id === "string"),
switchMap((id) => {
const { sessionId, trackName } = parseTrackId(id);
return of({
sessionId,
trackName,
location: "remote"
});
})
);
const cameraTrack$ = __privateGet(this, _ctx2).partyTracks.pull(cameraMetadata$, {
simulcast: { preferredRid$: __privateGet(this, _ctx2).cameraRid$ }
});
cameraTrack$.subscribe((track) => {
__privateSet(this, _cameraEnabled, true);
__privateSet(this, _cameraTrack, track);
this.emit("cameraUpdate", { cameraEnabled: true, cameraTrack: track });
__privateGet(this, _ctx2).call.participants.joined.emit("cameraUpdate", this);
__privateGet(this, _ctx2).call.participants.emit("cameraUpdate", this);
});
const screenshareVideoMetadata$ = __privateGet(this, _screenshareVideoTrackId$).pipe(
filter$1((id) => typeof id === "string"),
switchMap((id) => {
const { sessionId, trackName } = parseTrackId(id);
return of({
sessionId,
trackName,
location: "remote"
});
})
);
const screenshareVideoTrack$ = __privateGet(this, _ctx2).partyTracks.pull(
screenshareVideoMetadata$
);
screenshareVideoTrack$.subscribe((track) => {
__privateSet(this, _screenshareVideoTrack, track);
this.emit("screenshareUpdate", {
screenshareEnabled: true,
screenshareVideoTrack: track,
screenshareAudioTrack: __privateGet(this, _screenshareAudioTrack)
});
__privateGet(this, _ctx2).call.participants.joined.emit("screenshareUpdate", this);
__privateGet(this, _ctx2).call.participants.emit("screenshareUpdate", this);
});
const screenshareAudioMetadata$ = __privateGet(this, _screenshareAudioTrackId$).pipe(
filter$1((id) => typeof id === "string"),
switchMap((id) => {
const { sessionId, trackName } = parseTrackId(id);
return of({
sessionId,
trackName,
location: "remote"
});
})
);
const screenshareAudioTrack$ = __privateGet(this, _ctx2).partyTracks.pull(
screenshareAudioMetadata$
);
screenshareAudioTrack$.subscribe((track) => {
__privateSet(this, _screenshareAudioTrack, track);
this.emit("screenshareUpdate", {
screenshareEnabled: true,
screenshareVideoTrack: __privateGet(this, _screenshareVideoTrack),
screenshareAudioTrack: track
});
__privateGet(this, _ctx2).call.participants.joined.emit("screenshareUpdate", this);
__privateGet(this, _ctx2).call.participants.emit("screenshareUpdate", this);
});
}
updateMicState(updates) {
__privateGet(this, _ctx2).logger.debug("\u{1F399}\uFE0F participant mic state updated", updates);
if (updates.micEnabled && updates.micTrackId) {
__privateGet(this, _micTrackId$).next(updates.micTrackId);
__privateGet(this, _micEnabled$).next(true);
} else {
__privateGet(this, _micEnabled$).next(false);
}
}
updateCameraState(updates) {
__privateGet(this, _ctx2).logger.debug("\u{1F3A5} participant camera state updated", updates);
if (updates.cameraEnabled && updates.cameraTrackId) {
__privateGet(this, _cameraTrackId$).next(updates.cameraTrackId);
__privateGet(this, _cameraEnabled$).next(true);
} else {
__privateGet(this, _cameraEnabled$).next(false);
}
}
updateScreenshareState(updates) {
__privateGet(this, _ctx2).logger.debug("\u{1F5A5}\uFE0F participant screenshare state updated", updates);
if (updates.screenshareEnabled) {
if (updates.screenshareVideoTrackId) {
__privateGet(this, _screenshareVideoTrackId$).next(updates.screenshareVideoTrackId);
}
if (updates.screenshareAudioTrackId) {
__privateGet(this, _screenshareAudioTrackId$).next(updates.screenshareAudioTrackId);
}
__privateGet(this, _screenshareEnabled$).next(true);
__privateSet(this, _screenshareEnabled, true);
} else {
__privateGet(this, _screenshareEnabled$).next(false);
__privateGet(this, _screenshareVideoTrackId$).next(void 0);
__privateGet(this, _screenshareAudioTrackId$).next(void 0);
__privateSet(this, _screenshareEnabled, false);
this.emit("screenshareUpdate", { screenshareEnabled: false });
__privateGet(this, _ctx2).call.participants.joined.emit("screenshareUpdate", this);
__privateGet(this, _ctx2).call.participants.emit("screenshareUpdate", this);
}
}
get micEnabled() {
return __privateGet(this, _micEnabled);
}
get micTrack() {
return __privateGet(this, _micEnabled) ? __privateGet(this, _micTrack) : void 0;
}
get cameraEnabled() {
return __privateGet(this, _cameraEnabled);
}
get cameraTrack() {
return __privateGet(this, _cameraEnabled) ? __privateGet(this, _cameraTrack) : void 0;
}
get screenshareEnabled() {
return __privateGet(this, _screenshareEnabled);
}
get screenshareTracks() {
if (!this.screenshareEnabled) {
return void 0;
}
return {
video: __privateGet(this, _screenshareVideoTrack),
audio: __privateGet(this, _screenshareAudioTrack)
};
}
toString() {
return `CallParticipant::${this.id}::${this.name}`;
}
static fromJSON(obj) {
return new _CallParticipant(obj);
}
};
_ctx2 = new WeakMap();
_micEnabled$ = new WeakMap();
_micTrackId$ = new WeakMap();
_micEnabled = new WeakMap();
_micTrack = new WeakMap();
_cameraEnabled$ = new WeakMap();
_cameraTrackId$ = new WeakMap();
_cameraEnabled = new WeakMap();
_cameraTrack = new WeakMap();
_screenshareEnabled$ = new WeakMap();
_screenshareVideoTrackId$ = new WeakMap();
_screenshareAudioTrackId$ = new WeakMap();
_screenshareEnabled = new WeakMap();
_screenshareVideoTrack = new WeakMap();
_screenshareAudioTrack = new WeakMap();
var CallParticipant = _CallParticipant;
// src/lib/call-participant-map/call-participant-map.ts
var _participants;
var CallParticipantMap = class extends EventsHandler {
constructor() {
super();
__privateAdd(this, _participants, /* @__PURE__ */ new Map());
}
get(id) {
return __privateGet(this, _participants).get(id);
}
set(id, participant) {
const isUpdate = __privateGet(this, _participants).has(id);
__privateGet(this, _participants).set(id, participant);
this.emit(isUpdate ? "updated" : "added", participant);
return this;
}
delete(id) {
const removed = __privateGet(this, _participants).get(id);
const didDelete = __privateGet(this, _participants).delete(id);
if (didDelete && removed) {
this.emit("removed", removed);
}
return didDelete;
}
has(id) {
return __privateGet(this, _participants).has(id);
}
values() {
return __privateGet(this, _participants).values();
}
keys() {
return __privateGet(this, _participants).keys();
}
entries() {
return __privateGet(this, _participants).entries();
}
forEach(callbackfn, thisArg) {
return __privateGet(this, _participants).forEach(callbackfn, thisArg);
}
get size() {
return __privateGet(this, _participants).size;
}
clear() {
__privateGet(this, _participants).clear();
this.emit("cleared");
}
toArray() {
return Array.from(__privateGet(this, _participants).values());
}
[Symbol.iterator]() {
return __privateGet(this, _participants)[Symbol.iterator]();
}
get [Symbol.toStringTag]() {
return "CallParticipantMap";
}
};
_participants = new WeakMap();
// src/lib/participants-controller/participants-controller.ts
var ParticipantsController = class extends EventsHandler {
constructor() {
super();
this.joined = new CallParticipantMap();
}
addParticipant(participant) {
this.joined.set(participant.id, participant);
}
removeParticipantById(participantId) {
this.joined.delete(participantId);
}
};
var _ctx3, _mic, _camera, _screenshare, _micEnabled2, _micTrackId, _micTrack2, _cameraEnabled2, _cameraTrackId, _cameraTrack2, _screenshareEnabled2, _screenshareVideoTrackId, _screenshareVideoTrack2, _screenshareAudioTrackId, _screenshareAudioTrack2, _micDevice, _cameraDevice, _devices;
var CallSelf = class extends EventsHandler {
constructor(options) {
var _a, _b;
super();
this.id = crypto.randomUUID();
__privateAdd(this, _ctx3);
__privateAdd(this, _mic);
__privateAdd(this, _camera);
__privateAdd(this, _screenshare);
__privateAdd(this, _micEnabled2, false);
__privateAdd(this, _micTrackId);
__privateAdd(this, _micTrack2);
__privateAdd(this, _cameraEnabled2, false);
__privateAdd(this, _cameraTrackId);
__privateAdd(this, _cameraTrack2);
__privateAdd(this, _screenshareEnabled2, false);
__privateAdd(this, _screenshareVideoTrackId);
__privateAdd(this, _screenshareVideoTrack2);
__privateAdd(this, _screenshareAudioTrackId);
__privateAdd(this, _screenshareAudioTrack2);
__privateAdd(this, _micDevice);
__privateAdd(this, _cameraDevice);
__privateAdd(this, _devices);
this.name = options.name;
__privateSet(this, _ctx3, getCurrentCallContext());
__privateSet(this, _mic, getMic());
__privateSet(this, _camera, getCamera());
__privateGet(this, _mic).isBroadcasting$.subscribe((enabled) => {
__privateSet(this, _micEnabled2, enabled);
});
__privateGet(this, _mic).broadcastTrack$.subscribe((track) => {
__privateSet(this, _micTrack2, track);
});
__privateGet(this, _camera).isBroadcasting$.subscribe((enabled) => {
__privateSet(this, _cameraEnabled2, enabled);
});
__privateGet(this, _camera).broadcastTrack$.subscribe((track) => {
__privateSet(this, _cameraTrack2, track);
});
__privateGet(this, _mic).activeDevice$.subscribe((device) => {
__privateSet(this, _micDevice, device);
this.emit("activeDeviceUpdate", "mic", device);
});
__privateGet(this, _camera).activeDevice$.subscribe((device) => {
__privateSet(this, _cameraDevice, device);
this.emit("activeDeviceUpdate", "camera", device);
});
devices$.subscribe((devices) => {
__privateSet(this, _devices, devices);
this.emit("devicesUpdate", devices);
});
const micMetadata$ = __privateGet(this, _ctx3).partyTracks.push(__privateGet(this, _mic).broadcastTrack$, {
sendEncodings$: of([
{ networkPriority: "high" }
])
});
micMetadata$.subscribe((metadata) => {
if (__privateGet(this, _micEnabled2) && __privateGet(this, _micTrack2)) {
const micTrackId = createTrackId(metadata);
__privateSet(this, _micTrackId, micTrackId);
__privateGet(this, _ctx3).socket.sendAction({
action: "self/mic-update",
updates: {
micEnabled: true,
micTrackId
}
});
this.emit("micUpdate", {
micEnabled: true,
micTrack: __privateGet(this, _micTrack2)
});
} else if (!__privateGet(this, _micEnabled2)) {
__privateGet(this, _ctx3).socket.sendAction({
action: "self/mic-update",
updates: { micEnabled: false }
});
this.emit("micUpdate", { micEnabled: false });
}
});
const cameraMetadata$ = __privateGet(this, _ctx3).partyTracks.push(
__privateGet(this, _camera).broadcastTrack$,
{
sendEncodings$: __privateGet(this, _ctx3).cameraEncodings$
}
);
cameraMetadata$.subscribe((metadata) => {
if (__privateGet(this, _cameraEnabled2) && __privateGet(this, _cameraTrack2)) {
const cameraTrackId = createTrackId(metadata);
__privateSet(this, _cameraTrackId, cameraTrackId);
__privateGet(this, _ctx3).socket.sendAction({
action: "self/camera-update",
updates: {
cameraEnabled: true,
cameraTrackId
}
});
this.emit("cameraUpdate", {
cameraEnabled: true,
cameraTrack: __privateGet(this, _cameraTrack2)
});
} else if (!__privateGet(this, _cameraEnabled2)) {
__privateSet(this, _cameraTrackId, void 0);
__privateGet(this, _ctx3).socket.sendAction({
action: "self/camera-update",
updates: { cameraEnabled: false }
});
this.emit("cameraUpdate", { cameraEnabled: false });
}
});
if ((_a = options.defaults) == null ? void 0 : _a.audio) {
this.startMic();
}
if ((_b = options.defaults) == null ? void 0 : _b.video) {
this.startCamera();
}
__privateSet(this, _screenshare, getScreenshare({ audio: true }));
__privateGet(this, _screenshare).video.isBroadcasting$.subscribe((enabled) => {
__privateSet(this, _screenshareEnabled2, enabled);
});
__privateGet(this, _screenshare).video.broadcastTrack$.subscribe((track) => {
__privateSet(this, _screenshareVideoTrack2, track);
});
__privateGet(this, _screenshare).audio.broadcastTrack$.subscribe((track) => {
__privateSet(this, _screenshareAudioTrack2, track);
});
const screenshareVideoMetadata$ = __privateGet(this, _ctx3).partyTracks.push(
__privateGet(this, _screenshare).video.broadcastTrack$
);
const screenshareAudioMetadata$ = __privateGet(this, _ctx3).partyTracks.push(
__privateGet(this, _screenshare).audio.broadcastTrack$
);
screenshareVideoMetadata$.subscribe((metadata) => {
if (!__privateGet(this, _screenshareEnabled2)) {
__privateGet(this, _ctx3).socket.sendAction({
action: "self/screenshare-update",
updates: {
screenshareEnabled: false
}
});
this.emit("screenshareUpdate", { screenshareEnabled: false });
} else {
__privateSet(this, _screenshareVideoTrackId, createTrackId(metadata));
__privateGet(this, _ctx3).socket.sendAction({
action: "self/screenshare-update",
updates: {
screenshareEnabled: true,
screenshareVideoTrackId: __privateGet(this, _screenshareVideoTrackId)
}
});
this.emit("screenshareUpdate", {
screenshareEnabled: true,
screenshareVideoTrack: __privateGet(this, _screenshareVideoTrack2)
});
}
});
screenshareAudioMetadata$.subscribe((metadata) => {
if (!__privateGet(this, _screenshareEnabled2)) {
__privateGet(this, _ctx3).socket.sendAction({
action: "self/screenshare-update",
updates: {
screenshareEnabled: false
}
});
this.emit("screenshareUpdate", { screenshareEnabled: false });
} else {
__privateSet(this, _screenshareAudioTrackId, createTrackId(metadata));
__privateGet(this, _ctx3).socket.sendAction({
action: "self/screenshare-update",
updates: {
screenshareEnabled: true,
screenshareAudioTrackId: __privateGet(this, _screenshareAudioTrackId)
}
});
this.emit("screenshareUpdate", {
screenshareEnabled: true,
screenshareAudioTrack: __privateGet(this, _screenshareAudioTrack2)
});
}
});
}
startMic() {
__privateGet(this, _mic).startBroadcasting();
}
stopMic() {
__privateGet(this, _mic).stopBroadcasting();
}
startCamera() {
__privateGet(this, _camera).startBroadcasting();
}
stopCamera() {
__privateGet(this, _camera).stopBroadcasting();
}
startScreenshare() {
__privateGet(this, _screenshare).startBroadcasting();
}
stopScreenshare() {
__privateGet(this, _screenshare).stopBroadcasting();
}
setCameraDevice(deviceId) {
return __async(this, null, function* () {
const devices = this.devices;
const device = devices == null ? void 0 : devices.find((d) => d.deviceId === deviceId);
if (!device) {
throw new Error(`Camera device not found: ${deviceId}`);
}
__privateGet(this, _camera).setPreferredDevice(device);
});
}
setMicDevice(deviceId) {
return __async(this, null, function* () {
const devices = this.devices;
const device = devices == null ? void 0 : devices.find((d) => d.deviceId === deviceId);
if (!device) {
throw new Error(`Microphone device not found: ${deviceId}`);
}
__privateGet(this, _mic).setPreferredDevice(device);
});
}
get micEnabled() {
return __privateGet(this, _micEnabled2);
}
get micTrack() {
return __privateGet(this, _micEnabled2) ? __privateGet(this, _micTrack2) : void 0;
}
get cameraEnabled() {
return __privateGet(this, _cameraEnabled2);
}
get cameraTrack() {
return __privateGet(this, _cameraEnabled2) ? __privateGet(this, _cameraTrack2) : void 0;
}
get screenshareEnabled() {
return __privateGet(this, _screenshareEnabled2);
}
get screenshareTracks() {
if (!this.screenshareEnabled) {
return void 0;
}
return {
video: __privateGet(this, _screenshareVideoTrack2),
audio: __privateGet(this, _screenshareAudioTrack2)
};
}
get devices() {
return __privateGet(this, _devices);
}
get activeDevices() {
var _a, _b, _c, _d;
return {
mic: (_b = __privateGet(this, _micDevice)) != null ? _b : (_a = this.devices) == null ? void 0 : _a.find(
(d) => d.deviceId === "default" && d.kind === "audioinput"
),
camera: (_d = __privateGet(this, _cameraDevice)) != null ? _d : (_c = this.devices) == null ? void 0 : _c.find(
(d) => d.deviceId === "default" && d.kind === "videoinput"
)
};
}
setName(name) {
const oldName = this.name;
this.name = name;
this.emit("nameChange", this.name, oldName);
}
toJSON() {
return {
id: this.id,
name: this.name,
micEnabled: this.micEnabled,
micTrackId: __privateGet(this, _micTrackId),
cameraEnabled: this.cameraEnabled,
cameraTrackId: __privateGet(this, _cameraTrackId),
screenshareEnabled: this.screenshareEnabled,
screenshareVideoTrackId: __privateGet(this, _screenshareVideoTrackId),
screenshareAudioTrackId: __privateGet(this, _screenshareAudioTrackId)
};
}
toString() {
return `CallSelf::${this.id}::${this.name}`;
}
};
_ctx3 = new WeakMap();
_mic = new WeakMap();
_camera = new WeakMap();
_screenshare = new WeakMap();
_micEnabled2 = new WeakMap();
_micTrackId = new WeakMap();
_micTrack2 = new WeakMap();
_cameraEnabled2 = new WeakMap();
_cameraTrackId = new WeakMap();
_cameraTrack2 = new WeakMap();
_screenshareEnabled2 = new WeakMap();
_screenshareVideoTrackId = new WeakMap();
_screenshareVideoTrack2 = new WeakMap();
_screenshareAudioTrackId = new WeakMap();
_screenshareAudioTrack2 = new WeakMap();
_micDevice = new WeakMap();
_cameraDevice = new WeakMap();
_devices = new WeakMap();
var _logger;
var CallSocket = class extends PartySocket {
constructor(options) {
super(options);
__privateAdd(this, _logger);
__privateSet(this, _logger, options.logger);
}
sendAction(action) {
__privateGet(this, _logger).debug("CallSocket:sendAction", action);
return this.send(JSON.stringify(action));
}
};
_logger = new WeakMap();
// src/lib/call-self/call-self-data.ts
var cameraEncodings = [
{
rid: "a",
maxBitrate: 13e5,
maxFramerate: 30,
active: true
},
{
rid: "b",
scaleResolutionDownBy: 2,
maxBitrate: 5e5,
maxFramerate: 24,
active: true
}
];
var DEFAULT_CONFIG = {
monitoringInterval: 2e3,
// 2 seconds
adaptiveQualityEnabled: true,
qualityThresholds: {
excellent: { minBandwidth: 2e3, maxRtt: 100, maxPacketLoss: 1 },
good: { minBandwidth: 1e3, maxRtt: 200, maxPacketLoss: 3 },
fair: { minBandwidth: 500, maxRtt: 400, maxPacketLoss: 8 }
},
qualityChangeDebounce: 3e3
// 3 seconds
};
var _logger2, _config, _destroy$, _peerConnection, _statsHistory, _maxHistorySize, _lastQualityChange, _NetworkQualityMonitor_instances, startMonitoring_fn, collectNetworkStats_fn, parseWebRTCStats_fn, estimateBandwidthFromRTT_fn, calculateNetworkQuality_fn, addToHistory_fn, setupAdaptiveQuality_fn, getRecommendedCameraQuality_fn;
var NetworkQualityMonitor = class {
constructor(logger, config) {
__privateAdd(this, _NetworkQualityMonitor_instances);
__privateAdd(this, _logger2);
__privateAdd(this, _config);
__privateAdd(this, _destroy$, new Subject());
__privateAdd(this, _peerConnection, null);
// Observables
this.networkStats$ = new BehaviorSubject(null);
this.networkQuality$ = new BehaviorSubject("good");
this.recommendedCameraQuality$ = new BehaviorSubject("a");
// Internal state
__privateAdd(this, _statsHistory, []);
__privateAdd(this, _maxHistorySize, 10);
__privateAdd(this, _lastQualityChange, 0);
__privateSet(this, _logger2, logger);
__privateSet(this, _config, __spreadValues(__spreadValues({}, DEFAULT_CONFIG), config));
__privateMethod(this, _NetworkQualityMonitor_instances, startMonitoring_fn).call(this);
__privateMethod(this, _NetworkQualityMonitor_instances, setupAdaptiveQuality_fn).call(this);
}
setPeerConnection(peerConnection) {
__privateSet(this, _peerConnection, peerConnection);
__privateGet(this, _logger2).debug("NetworkQualityMonitor: PeerConnection set");
}
getAverageStats(windowSize = 5) {
if (__privateGet(this, _statsHistory).length === 0) {
return null;
}
const recentStats = __privateGet(this, _statsHistory).slice(-windowSize);
const count = recentStats.length;
return {
rtt: recentStats.reduce((sum, stat) => sum + stat.rtt, 0) / count,
packetLoss: recentStats.reduce((sum, stat) => sum + stat.packetLoss, 0) / count,
jitter: recentStats.reduce((sum, stat) => sum + stat.jitter, 0) / count,
bandwidth: recentStats.reduce((sum, stat) => sum + stat.bandwidth, 0) / count
};
}
getCurrentQuality() {
return this.networkQuality$.value;
}
getRecommendedCameraQuality() {
return this.recommendedCameraQuality$.value;
}
destroy() {
__privateGet(this, _destroy$).next();
__privateGet(this, _destroy$).complete();
this.networkStats$.complete();
this.networkQuality$.complete();
this.recommendedCameraQuality$.complete();
}
};
_logger2 = new WeakMap();
_config = new WeakMap();
_destroy$ = new WeakMap();
_peerConnection = new WeakMap();
_statsHistory = new WeakMap();
_maxHistorySize = new WeakMap();
_lastQualityChange = new WeakMap();
_NetworkQualityMonitor_instances = new WeakSet();
startMonitoring_fn = function() {
interval(__privateGet(this, _config).monitoringInterval).pipe(takeUntil(__privateGet(this, _destroy$))).subscribe(() => {
__privateMethod(this, _NetworkQualityMonitor_instances, collectNetworkStats_fn).call(this);
});
};
collectNetworkStats_fn = function() {
return __async(this, null, function* () {
if (!__privateGet(this, _peerConnection)) {
return;
}
try {
const stats = yield __privateGet(this, _peerConnection).getStats();
const networkStats = __privateMethod(this, _NetworkQualityMonitor_instances, parseWebRTCStats_fn).call(this, stats);
if (networkStats) {
__privateMethod(this, _NetworkQualityMonitor_instances, addToHistory_fn).call(this, networkStats);
this.networkStats$.next(networkStats);
this.networkQuality$.next(networkStats.quality);
__privateGet(this, _logger2).debug("NetworkQualityMonitor: Stats collected", {
quality: networkStats.quality,
rtt: networkStats.rtt,
bandwidth: networkStats.bandwidth,
packetLoss: networkStats.packetLoss
});
}
} catch (error) {
__privateGet(this, _logger2).warn("NetworkQualityMonitor: Failed to collect stats", error);
}
});
};
parseWebRTCStats_fn = function(stats) {
let rtt = 0;
let packetLoss = 0;
let jitter = 0;
let bandwidth = 0;
let hasValidStats = false;
for (const [, stat] of stats) {
if (stat.type === "candidate-pair" && stat.state === "succeeded") {
if (typeof stat.currentRoundTripTime === "number") {
rtt = stat.currentRoundTripTime * 1e3;
hasValidStats = true;
}
}
if (stat.type === "inbound-rtp" && stat.mediaType === "video") {
if (typeof stat.packetsLost === "number" && typeof stat.packetsReceived === "number") {
const totalPackets = stat.packetsLost + stat.packetsReceived;
if (totalPackets > 0) {
packetLoss = stat.packetsLost / totalPackets * 100;
}
}
if (typeof stat.jitter === "number") {
jitter = stat.jitter * 1e3;
}
hasValidStats = true;
}
if (stat.type === "transport") {
if (typeof stat.selectedCandidatePairChanges === "number") {
const bytesReceived = stat.bytesReceived || 0;
const bytesSent = stat.bytesSent || 0;
const totalBytes = bytesReceived + bytesSent;
if (totalBytes > 0) {
bandwidth = Math.min(totalBytes * 8 / (__privateGet(this, _config).monitoringInterval / 1e3) / 1e3, 1e4);
}
}
}
}
if (!hasValidStats) {
return null;
}
if (bandwidth === 0) {
bandwidth = __privateMethod(this, _NetworkQualityMonitor_instances, estimateBandwidthFromRTT_fn).call(this, rtt, packetLoss);
}
const quality = __privateMethod(this, _NetworkQualityMonitor_instances, calculateNetworkQuality_fn).call(this, rtt, packetLoss, bandwidth);
return {
rtt,
packetLoss,
jitter,
bandwidth,
quality,
timestamp: Date.now()
};
};
estimateBandwidthFromRTT_fn = function(rtt, packetLoss) {
if (rtt < 50 && packetLoss < 1) {
return 3e3;
} else if (rtt < 100 && packetLoss < 2) {
return 2e3;
} else if (rtt < 200 && packetLoss < 5) {
return 1e3;
} else {
return 500;
}
};
calculateNetworkQuality_fn = function(rtt, packetLoss, bandwidth) {
const { excellent, good, fair } = __privateGet(this, _config).qualityThresholds;
if (bandwidth >= excellent.minBandwidth && rtt <= excellent.maxRtt && packetLoss <= excellent.maxPacketLoss) {
return "excellent";
}
if (bandwidth >= good.minBandwidth && rtt <= good.maxRtt && packetLoss <= good.maxPacketLoss) {
return "good";
}
if (bandwidth >= fair.minBandwidth && rtt <= fair.maxRtt && packetLoss <= fair.maxPacketLoss) {
return "fair";
}
return "poor";
};
addToHistory_fn = function(stats) {
__privateGet(this, _statsHistory).push(stats);
if (__privateGet(this, _statsHistory).length > __privateGet(this, _maxHistorySize)) {
__privateGet(this, _statsHistory).shift();
}
};
setupAdaptiveQuality_fn = function() {
if (!__privateGet(this, _config).adaptiveQualityEnabled) {
return;
}
this.networkQuality$.pipe(
takeUntil(__privateGet(this, _destroy$)),
debounceTime(__privateGet(this, _config).qualityChangeDebounce),
distinctUntilChanged(),
filter(() => {
const now = Date.now();
const timeSinceLastChange = now - __privateGet(this, _lastQualityChange);
return timeSinceLastChange >= __privateGet(this, _config).qualityChangeDebounce;
})
).subscribe((quality) => {
const recommendedQuality = __privateMethod(this, _NetworkQualityMonitor_instances, getRecommendedCameraQuality_fn).call(this, quality);
if (recommendedQuality !== this.recommendedCameraQuality$.value) {
__privateSet(this, _lastQualityChange, Date.now());
this.recommendedCameraQuality$.next(recommendedQuality);
__privateGet(this, _logger2).info("NetworkQualityMonitor: Quality change recommended", {
networkQuality: quality,
recommendedCameraQuality: recommendedQuality
});
}
});
};
getRecommendedCameraQuality_fn = function(networkQuality) {
switch (networkQuality) {
case "excellent":
case "good":
return "a";
// High quality
case "fair":
case "poor":
return "b";
// Lower quality
default:
return "a";
}
};
// src/lib/call-client/call-client.ts
setLogLevel("debug");
var _ctx4, _logger3, _autoJoin, _networkQualityMonitor, _adaptiveQualityEnabled, _destroy$2, _CallClient_instances, setupAdaptiveQuality_fn2, onMessage_fn;
var CallClient = class extends EventsHandler {
constructor(options) {
var _a, _b, _c, _d;
super();
__privateAdd(this, _CallClient_instances);
this.state = void 0;
__privateAdd(this, _ctx4);
__privateAdd(this, _logger3);
__privateAdd(this, _autoJoin);
__privateAdd(this, _networkQualityMonitor);
__privateAdd(this, _adaptiveQualityEnabled, true);
__privateAdd(this, _destroy$2, new Subject());
this.room = options.room;
__privateSet(this, _autoJoin, options.autoJoin || false);
__privateSet(this, _logger3, new Logger({
level: (_a = options.logLevel) != null ? _a : "warn"
// prefix: 'callskit',
}));
const socket = new CallSocket({
room: this.room,
host: options.socketBaseUrl,
logger: __privateGet(this, _logger3)
});
socket.addEventListener("message", __privateMethod(this, _CallClient_instances, onMessage_fn).bind(this));
if (options.onError) {
const onError = options.onError;
socket.addEventListener("error", (e) => {
onError(new Error("Error while connecting to server"));
});
}
const partyTracks = new PartyTracks({
prefix: options.apiBaseUrl + "/partytracks",
headers: options.apiHeaders
});
let unsubSession = partyTracks.session$.subscribe(
() => {
this.emit("mediaConnected");
unsubSession.unsubscribe();
unsubSession = void 0;
}
);
__privateSet(this, _networkQualityMonitor, new NetworkQualityMonitor(
__privateGet(this, _logger3),
(_b = options.config) == null ? void 0 : _b.networkQuality
));
__privateSet(this, _ctx4, {
socket,
partyTracks,
call: this,
logger: __privateGet(this, _logger3),
cameraRid$: new BehaviorSubject(
(_d = (_c = options.config) == null ? void 0 : _c.preferredCameraQuality) != null ? _d : "a"
),
cameraEncodings$: new BehaviorSubject(
cameraEncodings
),
networkQualityMonitor: __privateGet(this, _networkQualityMonitor),
onError: options.onError
});
this.self = runWithContext(
__privateGet(this, _ctx4),
() => new CallSelf({ name: options.displayName, defaults: options.defaults })
);
this.participants = runWithContext(
__privateGet(this, _ctx4),
() => new ParticipantsController()
);
this.chat = runWithContext(__privateGet(this, _ctx4), () => new CallChat());
__privateMethod(this, _CallClient_instances, setupAdaptiveQuality_fn2).call(this);
}
join() {
if (this.state !== "connected") return;
__privateGet(this, _ctx4).socket.sendAction({ action: "join", self: this.self.toJSON() });
}
leave() {
__privateGet(this, _destroy$2).next();
__privateGet(this, _destroy$2).complete();
__privateGet(this, _networkQualityMonitor).destroy();
__privateGet(this, _ctx4).socket.close();
this.state = "left";
this.emit("left");
}
get cameraTrackQuality() {
return __privateGet(this, _ctx4).cameraRid$.value;
}
/**
* Set the remote camera track quality.
* - `a` - high quality
* - `b` - lower quality
*/
setRemoteCameraTrackQuality(quality) {
const qualities = ["a", "b"];
if (qualities.includes(quality)) {
const oldQuality = __privateGet(this, _ctx4).cameraRid$.value;
__privateGet(this, _ctx4).cameraRid$.next(quality);
this.emit("cameraQualityChanged", quality, oldQuality);
__privateGet(this, _logger3).info("Camera quality changed manually", { from: oldQuality, to: quality });
}
}
/**
* Enable or disable adaptive camera quality based on network conditions
*/
setAdaptiveQualityEnabled(enabled) {
__privateSet(this, _adaptiveQualityEnabled, enabled);
__privateGet(this, _logger3).info("Adaptive quality", enabled ? "enabled" : "disabled");
}
/**
* Get current network quality information
*/
getNetworkQuality() {
return {
quality: __privateGet(this, _networkQualityMonitor).getCurrentQuality(),
stats: __privateGet(this, _networkQualityMonitor).networkStats$.value,
recommendedCameraQuality: __privateGet(this, _networkQualityMonitor).getRecommendedCameraQuality()
};
}
};
_ctx4 = new WeakMap();
_logger3 = new WeakMap();
_autoJoin = new WeakMap();
_networkQualityMonitor = new WeakMap();
_adaptiveQualityEnabled = new WeakMap();
_destroy$2 = new WeakMap();
_CallClient_instances = new WeakSet();
/**
* Setup adaptive quality switching based on network conditions
*/
setupAdaptiveQuality_fn2 = function() {
__privateGet(this, _networkQualityMonitor).recommendedCameraQuality$.pipe(takeUntil(__privateGet(this, _destroy$2))).subscribe((recommendedQuality) => {
if (!__privateGet(this, _adaptiveQualityEnabled)) {
return;
}
const currentQuality = __privateGet(this, _ctx4).cameraRid$.value;
if (recommendedQuality !== currentQuality) {
__privateGet(this, _logger3).info("Adaptive quality: Switching camera quality", {
from: currentQuality,
to: recommendedQuality,
networkQuality: __privateGet(this, _networkQualityMonitor).getCurrentQuality()
});
this.setRemoteCameraTrackQuality(recommendedQuality);
this.emit("adaptiveQualityChanged", recommendedQuality, currentQuality);
}
});
const partyTracks = __privateGet(this, _ctx4).partyTracks;
if (partyTracks.session$) {
partyTracks.session$.pipe(takeUntil(__privateGet(this, _destroy$2))).subscribe((session) => {
if (session == null ? void 0 : session.peerConnection) {
__privateGet(this, _networkQualityMonitor).setPeerConnection(session.peerConnection);
__privateGet(this, _logger3).debug("Network quality monitor: PeerConnection attached");
}
});
}
};
onMessage_fn = function(event) {
const ev = JSON.parse(event.data);
__privateGet(this, _logger3).debug("\u{1F4E9} CallSocket:Event", ev);
switch (ev.event) {
case "connected":
this.state = "connected";
this.emit("connected");
if (__privateGet(this, _autoJoin)) {
this.join();
}
break;
case "room/init": {
if (this.state !== "connected") return;
const user_objects = ev.participants;
runWithContext(
__privateGet(this, _ctx4),
() => user_objects.map((obj) => CallParticipant.fromJSON(obj))
).forEach((participant) => {
this.participants.addParticipant(participant);
});
this.started_at = new Date(ev.started_at);
this.chat.addMessagesInBulk(ev.chatMessages);
this.state = "joined";
this.emit("joined");
break;
}
case "participant/joined": {
const participant = runWithContext(
__privateGet(this, _ctx4),
() => CallParticipant.fromJSON(ev.participant)
);
this.participants.addParticipant(participant);
break;
}
case "participant/left": {
this.participants.removeParticipantById(ev.participantId);
break;
}
case "participant/mic-update": {
const _a = ev.data, { participantId } = _a, updates = __objRest(_a, ["participantId"]);
const participant = this.participants.joined.get(participantId);
participant == null ? void 0 : participant.updateMicState(updates);
break;
}
case "participant/camera-update": {
const _b = ev.data, { participantId } = _b, updates = __objRest(_b, ["participantId"]);
const participant = this.participants.joined.get(participantId);
participant == null ? void 0 : participant.updateCameraState(updates);
break;
}
case "participant/screenshare-update": {
const _c = ev.data, { participantId } = _c, updates = __objRest(_c, ["participantId"]);
const participant = this.participants.joined.get(participantId);
participant == null ? void 0 : participant.updateScreenshareState(updates);
break;
}
case "chat/new-message": {
this.chat.addMessage(ev.message);
break;
}
default:
__privateGet(this, _logger3).info("Received unknown event:", ev);
break;
}
};
// src/index.ts
function createCallClient(options) {
return __async(this, null, function* () {
return new Promise((resolve, reject) => {
const client = new CallClient(options);
let socketConnected = false, mediaConnected = false;
const checkConnection = () => {
if (sock