@streaming-cdn/rtc-web 1.3.15 → 1.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- (function(S,V){typeof exports=="object"&&typeof module<"u"?V(exports):typeof define=="function"&&define.amd?define(["exports"],V):(S=typeof globalThis<"u"?globalThis:S||self,V(S.StreamingCdnRtc={}))})(this,(function(S){"use strict";function U(r,e,t=.02,i=1.4){const n=r.filter(o=>o.level>=t);if(n.length===0)return null;const s=n.reduce((o,c)=>c.level>o.level?c:o),a=n.find(o=>o.participantId===e);return a?s.participantId===e?e:s.level>=a.level*i?s.participantId:e:s.participantId}function H(r){const e=[];for(const t of r){if(String(t.type??"")!=="inbound-rtp"||String(t.kind??t.mediaType??"")!=="audio")continue;const i=Number(t.audioLevel??NaN);Number.isFinite(i)&&e.push({mid:typeof t.mid=="string"?t.mid:null,level:i})}return e}const A={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4},low:{active:!0,maxBitrate:35e4,maxFramerate:15,scaleResolutionDownBy:4},medium:{active:!0,maxBitrate:11e5,maxFramerate:24,scaleResolutionDownBy:2},high:{active:!0,maxBitrate:28e5,maxFramerate:30,scaleResolutionDownBy:1}},me={warmupSamples:3,poorSamplesToStepDown:2,goodSamplesToStepUp:6,criticalLossPct:8,criticalRttMs:800,poorLossPct:3,poorRttMs:350,goodLossPct:1,goodRttMs:250,sustainRatio:.8};function pe(r){const e=String(r??"").trim().toLowerCase();return!e||e==="none"?"":e==="cpu"||e==="bandwidth"?e:"other"}function fe(r,e,t,i,n={}){const s={...me,...n.thresholds},a=["low","medium","high"],o=n.ceiling&&n.ceiling!=="audio"?n.ceiling:"high",c=a.indexOf(o),d=a.indexOf(r),l=(n.samplesSeen??s.warmupSamples)+1,h=l<=s.warmupSamples,v=pe(e.qualityLimitationReason),g=e.packetLossPct??0,T=e.rttMs??0,p=e.availableOutgoingBitrateBps,E=A[r].maxBitrate,k=g>=s.criticalLossPct||T>=s.criticalRttMs||v==="cpu",y=k||g>=s.poorLossPct||T>=s.poorRttMs||v==="bandwidth",w=p==null||p>=E*s.sustainRatio,I=g<s.goodLossPct&&T<s.goodRttMs&&!v&&w,P=y?t+1:0,R=I?i+1:0,f={quality:r,poorSamples:P,goodSamples:R,samplesSeen:l,changed:!1};return d>c?{quality:a[c],poorSamples:0,goodSamples:0,samplesSeen:l,changed:!0}:h?f:k&&d>0?{quality:a[d-1],poorSamples:0,goodSamples:0,samplesSeen:l,changed:!0}:P>=s.poorSamplesToStepDown&&d>0?{quality:a[d-1],poorSamples:0,goodSamples:0,samplesSeen:l,changed:!0}:R>=s.goodSamplesToStepUp&&d<c?{quality:a[d+1],poorSamples:0,goodSamples:0,samplesSeen:l,changed:!0}:f}function ge(r,e){const t=r instanceof Map?r:new Map(r);let i=0,n=0,s=null,a=null,o=null;for(const l of t.values()){const h=String(l.type??"");if(h==="outbound-rtp"&&String(l.kind??"")==="video"&&(i+=Number(l.packetsSent??0),s=l.qualityLimitationReason??s),h==="remote-inbound-rtp"&&String(l.kind??"")==="video"){n+=Number(l.packetsLost??0);const v=Number(l.roundTripTime??NaN);Number.isFinite(v)&&(a=v*1e3)}if(h==="candidate-pair"&&(l.nominated===!0||String(l.state??"")==="succeeded")){const v=Number(l.currentRoundTripTime??NaN);a==null&&Number.isFinite(v)&&(a=v*1e3);const g=Number(l.availableOutgoingBitrate??NaN);Number.isFinite(g)&&(o=g)}}const c={packetsSent:i,packetsLost:n,timestamp:Date.now()};let d=null;if(e){const l=i-e.packetsSent,h=n-e.packetsLost;l>0&&h>=0&&(d=h/(l+h)*100)}return{sample:{packetLossPct:d,rttMs:a,availableOutgoingBitrateBps:o,qualityLimitationReason:s},counters:c}}const ve={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4}},ye={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1};class j{options;socket=null;localStream=null;screenStream=null;peers=new Map;joinPromise=null;resolveJoin=null;rejectJoin=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;qualityTimer=null;speakerTimer=null;activeSpeaker=null;heartbeatDeadline=0;quality;preferredVideoCodec;signalQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.preferredVideoCodec=e.preferredVideoCodec??"auto"}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(!this.joined){if(this.joinPromise)return this.joinPromise;this.joinPromise=this.connect();try{await this.joinPromise}finally{this.joinPromise=null}}}async leave(){this.stopped=!0,this.clearConnectionTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null;for(const e of this.peers.values())e.connection.close();this.peers.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",a=this.localStream?.getTracks().find(o=>o.kind===s);if(this.localStream||(this.localStream=new MediaStream),a&&(this.localStream.removeTrack(a),a.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(o=>o.readyState==="live")||await this.replaceVideoTrack(n);return}for(const o of this.peers.values()){const c=o.connection.getSenders().find(d=>d.track?.kind===s);c?await c.replaceTrack(n):o.connection.addTrack(n,this.localStream)}await this.applyVideoQuality()}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";for(const t of this.peers.values())t.quality=j.clampToCeiling(t.quality,e),t.poorSamples=0,t.goodSamples=0;await this.applyVideoQuality()}static clampToCeiling(e,t){if(t==="audio")return e;const i=["low","medium","high"];return i.indexOf(e)>i.indexOf(t)?t:e}async setPreferredVideoCodec(e){this.preferredVideoCodec=e;for(const t of this.peers.values())this.applyCodecPreference(t.connection);await this.renegotiateAll()}getParticipants(){return[...this.peers.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getRemoteStreams(){return[...this.peers.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.peers.values().next().value?.connection??null}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async connect(){const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1)}async openSocket(e){const{credential:t}=this.options,i=new URL(t.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new Promise((c,d)=>{this.resolveJoin=c,this.rejectJoin=d}),s=new WebSocket(i,["rtc-client",t.signalingToken]),a=++this.socketGeneration;this.socket=s;const o=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.startQualitySampling(),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const d=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==a||await this.handleSignal(d)}).catch(l=>{const h=l instanceof Error?l.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:h}),this.joined||this.rejectJoin?.(new Error(h))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),this.stopQualitySampling(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const d=!this.stopped&&(this.joined||e);this.joined=!1,d?(this.resetPeers(),this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(o),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0;const n=Array.isArray(t.peers)?t.peers:[];for(const s of n)this.ensurePeer(s);this.resolveJoin?.();for(const s of n)this.shouldInitiateOffer(s)&&await this.createOffer(s);return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.ensurePeer(t.sender),this.shouldInitiateOffer(t.sender)&&await this.createOffer(t.sender);return}if(i==="rtc.offer"&&t.sender&&t.description){const n=this.ensurePeer(t.sender);n.offerTask&&await n.offerTask;const s=n.connection.signalingState!=="stable",a=!this.shouldInitiateOffer(t.sender);if(s&&!a)return;s&&await n.connection.setLocalDescription({type:"rollback"}),await n.connection.setRemoteDescription(t.description);const o=await n.connection.createAnswer();await n.connection.setLocalDescription(o),this.send({type:"rtc.answer",targetParticipantId:t.sender.participantId,negotiationId:t.negotiationId,description:n.connection.localDescription});return}if(i==="rtc.answer"&&t.sender&&t.description){const n=this.peers.get(t.sender.participantId);if(!n||n.connection.signalingState!=="have-local-offer"||t.negotiationId&&n.activeNegotiationId&&t.negotiationId!==n.activeNegotiationId)return;await n.connection.setRemoteDescription(t.description),n.activeNegotiationId=null;return}if(i==="rtc.ice"&&t.sender&&t.candidate){await this.ensurePeer(t.sender).connection.addIceCandidate(t.candidate).catch(()=>{});return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removePeer(t.sender.participantId);return}i==="rtc.chat"?this.options.onEvent("chatupdate",t):i==="rtc.control"&&this.options.onEvent("control",t)}}ensurePeer(e){const t=this.peers.get(e.participantId);if(t)return t;const i=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"}),n=new MediaStream,s={identity:e,connection:i,stream:n,makingOffer:!1,offerTask:null,activeNegotiationId:null,quality:j.startingQuality(this.quality),poorSamples:0,goodSamples:0,samplesSeen:0,counters:null};this.peers.set(e.participantId,s);for(const c of this.localStream?.getAudioTracks()??[])i.addTrack(c,this.localStream);const a=this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")?this.screenStream:this.localStream,o=a?.getVideoTracks()[0];return o&&a&&(s.videoSender=i.addTrack(o,a)),!this.localStream?.getAudioTracks().length&&this.options.receiveAudio&&i.addTransceiver("audio",{direction:"recvonly"}),!o&&this.options.receiveVideo&&(s.videoTransceiver=i.addTransceiver("video",{direction:"recvonly"}),s.videoSender=s.videoTransceiver.sender),this.applyCodecPreference(i),this.applyVideoQuality(i),i.addEventListener("icecandidate",c=>{c.candidate&&this.send({type:"rtc.ice",targetParticipantId:e.participantId,candidate:c.candidate.toJSON()})}),i.addEventListener("track",c=>{n.getTracks().some(d=>d.id===c.track.id)||n.addTrack(c.track),this.options.onEvent("remotestream",{participant:e,stream:n,track:c.track})}),i.addEventListener("connectionstatechange",()=>{["failed","closed"].includes(i.connectionState)&&this.removePeer(e.participantId),this.options.onEvent("peerconnectionstate",{participant:e,state:i.connectionState})}),s}async createOffer(e){const t=this.ensurePeer(e);if(t.offerTask)return t.offerTask;if(t.connection.signalingState!=="stable")return;const i=this.performOffer(t);t.offerTask=i;try{await i}finally{t.offerTask===i&&(t.offerTask=null)}}async performOffer(e){e.makingOffer=!0;try{const t=await e.connection.createOffer({offerToReceiveAudio:this.options.receiveAudio,offerToReceiveVideo:this.options.receiveVideo});if(e.connection.signalingState!=="stable")return;await e.connection.setLocalDescription(t);const i=W();e.activeNegotiationId=i,this.send({type:"rtc.offer",targetParticipantId:e.identity.participantId,negotiationId:i,description:e.connection.localDescription})}finally{e.makingOffer=!1}}shouldInitiateOffer(e){return this.options.credential.participantId.localeCompare(e.participantId)<0}async renegotiateAll(){for(const e of this.peers.values())await this.createOffer(e.identity)}async replaceVideoTrack(e){let t=!1;for(const i of this.peers.values()){const n=i.videoSender??i.connection.getSenders().find(a=>a.track?.kind==="video")??i.connection.getTransceivers().find(a=>a.receiver.track.kind==="video")?.sender,s=i.videoTransceiver??i.connection.getTransceivers().find(a=>a.sender===n);if(n){const a=e?this.options.receiveVideo?"sendrecv":"sendonly":this.options.receiveVideo?"recvonly":"inactive";s&&s.direction!==a&&(s.direction=a,t=!0),await n.replaceTrack(e),i.videoSender=n,s&&(i.videoTransceiver=s)}else e&&(i.videoSender=i.connection.addTrack(e,this.screenStream??this.localStream??new MediaStream([e])),t=!0)}t&&await this.renegotiateAll(),await this.applyVideoQuality()}async applyVideoQuality(e){const t=e?[...this.peers.values()].filter(i=>i.connection===e):[...this.peers.values()];for(const i of t)await this.applyPeerVideoQuality(i)}async applyPeerVideoQuality(e){const t=this.screenStream?.getVideoTracks()[0]?.id;for(const i of e.connection.getSenders().filter(n=>n.track?.kind==="video")){const n=!!(t&&i.track?.id===t),s=n?ye:this.quality==="audio"?ve.audio:A[e.quality],a=i.getParameters(),o=a.encodings?.length?a.encodings:[{}];a.encodings=o.map(c=>({...c,...s})),a.degradationPreference=n?"maintain-resolution":"maintain-framerate",await i.setParameters(a)}}startQualitySampling(){this.stopQualitySampling(),this.qualityTimer=setInterval(()=>{this.samplePeerQuality()},5e3),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopQualitySampling(){this.qualityTimer&&clearInterval(this.qualityTimer),this.qualityTimer=null,this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=[];for(const i of[...this.peers.values()])if(i.connection.connectionState==="connected")try{const n=await i.connection.getStats();for(const s of H(n.values()))e.push({participantId:i.identity.participantId,level:s.level})}catch{}const t=U(e,this.activeSpeaker);t!==this.activeSpeaker&&(this.activeSpeaker=t,this.options.onEvent("activespeaker",{participantId:t}))}async samplePeerQuality(){if(this.quality!=="audio")for(const e of[...this.peers.values()]){if(e.connection.connectionState!=="connected")continue;let t;try{t=await e.connection.getStats()}catch{continue}const{sample:i,counters:n}=ge(t,e.counters);e.counters=n;const s=fe(e.quality,i,e.poorSamples,e.goodSamples,{samplesSeen:e.samplesSeen,ceiling:this.quality});e.poorSamples=s.poorSamples,e.goodSamples=s.goodSamples,e.samplesSeen=s.samplesSeen,s.changed&&(e.quality=s.quality,await this.applyPeerVideoQuality(e).catch(()=>{}),this.options.onEvent("peerquality",{participantId:e.identity.participantId,quality:s.quality}))}}static startingQuality(e){return e==="low"?"low":"medium"}applyCodecPreference(e){if(this.preferredVideoCodec==="auto"||!globalThis.RTCRtpReceiver?.getCapabilities)return;const t=RTCRtpReceiver.getCapabilities("video")?.codecs??[],i=`video/${this.preferredVideoCodec}`,n=[...t.filter(s=>s.mimeType.toLowerCase()===i),...t.filter(s=>s.mimeType.toLowerCase()!==i)];for(const s of e.getTransceivers())s.receiver.track.kind==="video"&&typeof s.setCodecPreferences=="function"&&s.setCodecPreferences(n)}removePeer(e){const t=this.peers.get(e);t&&(t.connection.close(),this.peers.delete(e),this.options.onEvent("participantleft",t.identity))}resetPeers(){for(const e of this.peers.values())e.connection.close();this.peers.clear()}startHeartbeat(e){this.stopHeartbeat(),this.stopQualitySampling(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}clearConnectionTimers(){this.stopHeartbeat(),this.stopQualitySampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??W(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function W(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}const F={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1},K=[{rid:"a",maxBitrate:A.high.maxBitrate,maxFramerate:A.high.maxFramerate},{rid:"b",maxBitrate:A.medium.maxBitrate,maxFramerate:A.medium.maxFramerate,scaleResolutionDownBy:2},{rid:"c",maxBitrate:A.low.maxBitrate,maxFramerate:A.low.maxFramerate,scaleResolutionDownBy:4}],G={high:"a",medium:"b",low:"c"};class Se{options;apiBase;socket=null;connection=null;sessionId=null;localStream=null;screenStream=null;remotes=new Map;midToParticipant=new Map;pendingAnnouncements=new Map;videoSender=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;speakerTimer=null;activeSpeaker=null;quality;resolveJoin=null;rejectJoin=null;signalQueue=Promise.resolve();negotiationQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.apiBase=new URL(e.credential.signalingUrl,globalThis.location?.href).origin}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(this.joined)return;const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1),await this.publish(),this.startSpeakerSampling(),this.announceTracks();const t=[...this.pendingAnnouncements.values()];this.pendingAnnouncements.clear();for(const i of t)await this.handleTrackAnnouncement(i.sender,i.detail)}async leave(){this.stopped=!0,this.clearTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null,await this.closePublishedTracks().catch(()=>{}),this.connection?.close(),this.connection=null,this.sessionId=null,this.remotes.clear(),this.midToParticipant.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",a=this.localStream?.getTracks().find(c=>c.kind===s);if(this.localStream||(this.localStream=new MediaStream),a&&(this.localStream.removeTrack(a),a.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")||await this.replaceVideoTrack(n);return}const o=this.connection?.getSenders().find(c=>c.track?.kind==="audio");o&&await o.replaceTrack(n)}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";await this.applyVideoQuality()}async setPreferredVideoCodec(e){}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getParticipants(){return[...this.remotes.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}getRemoteStreams(){return[...this.remotes.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.connection}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async openSocket(e){const{credential:t}=this.options,i=new URL(t.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new Promise((c,d)=>{this.resolveJoin=c,this.rejectJoin=d}),s=new WebSocket(i,["rtc-client",t.signalingToken]),a=++this.socketGeneration;this.socket=s;const o=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const d=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==a||await this.handleSignal(d)}).catch(l=>{const h=l instanceof Error?l.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:h}),this.joined||this.rejectJoin?.(new Error(h))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const d=!this.stopped&&(this.joined||e);this.joined=!1,d?(this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(o),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0,this.resolveJoin?.();return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.sessionId&&this.announceTracks();return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removeRemote(t.sender.participantId);return}if(i==="rtc.control"){if(String(t.action??"")==="sfu.tracks"&&t.sender){await this.handleTrackAnnouncement(t.sender,t.detail);return}this.options.onEvent("control",t);return}i==="rtc.chat"&&this.options.onEvent("chatupdate",t)}}async publish(){const e=[...this.localStream?.getAudioTracks()??[],...this.localStream?.getVideoTracks()??[]],t=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"});this.connection=t,t.addEventListener("track",o=>this.handleRemoteTrack(o)),t.addEventListener("connectionstatechange",()=>{this.options.onEvent("peerconnectionstate",{participant:this.selfIdentity(),state:t.connectionState})});const i=await this.sfuFetch("POST","/v1/rtc/sfu/sessions");if(this.sessionId=i.sessionId,e.length===0)return;const n=e.map(o=>t.addTransceiver(o,{direction:"sendonly",streams:this.localStream?[this.localStream]:[],...o.kind==="video"?{sendEncodings:K.map(c=>({...c}))}:{}})),s=await t.createOffer();await t.setLocalDescription(s);const a=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(i.sessionId)}/tracks`,{sessionDescription:t.localDescription,tracks:n.map(o=>({location:"local",mid:o.mid,trackName:this.trackName(o.sender.track)}))});a.sessionDescription&&await t.setRemoteDescription(a.sessionDescription),this.videoSender=t.getSenders().find(o=>o.track?.kind==="video")??null,await this.applyVideoQuality()}trackName(e){return`${this.options.credential.participantId}/${e.kind}`}announceTracks(){if(!this.sessionId)return;const e=[...(this.localStream?.getAudioTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!1})),...(this.localStream?.getVideoTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!0}))];e.length!==0&&this.sendControl("sfu.tracks",{sessionId:this.sessionId,tracks:e})}async handleTrackAnnouncement(e,t){const i=String(t?.sessionId??""),n=(t?.tracks??[]).map(o=>({trackName:String(o.trackName??""),simulcast:o.simulcast===!0})).filter(o=>!!o.trackName),s=n.map(o=>o.trackName);if(!i||s.length===0||e.participantId===this.options.credential.participantId)return;if(!this.connection||!this.sessionId){this.pendingAnnouncements.set(e.participantId,{sender:e,detail:t??{}});return}const a=this.remotes.get(e.participantId)??{identity:e,stream:new MediaStream,sessionId:i,trackNames:[],simulcastNames:new Set,mids:new Map};a.sessionId=i;for(const o of n)o.simulcast&&a.simulcastNames.add(o.trackName);this.remotes.set(e.participantId,a),this.negotiationQueue=this.negotiationQueue.then(async()=>{const o=s.filter(c=>!a.trackNames.includes(c));o.length!==0&&await this.subscribe(a,o)}).catch(o=>{this.options.onEvent("signalingerror",{message:o instanceof Error?o.message:"SFU subscribe failed"})}),await this.negotiationQueue}async subscribe(e,t){const i=this.connection;if(!i||!this.sessionId||t.length===0)return;const n=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks`,{tracks:t.map(s=>({location:"remote",sessionId:e.sessionId,trackName:s,...e.simulcastNames.has(s)?{simulcast:{preferredRid:"a",priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}:{}}))});for(const s of n.tracks??[])s.mid&&(e.mids.set(s.mid,String(s.trackName??"")),this.midToParticipant.set(s.mid,e.identity.participantId));if(e.trackNames=[...new Set([...e.trackNames,...t])],n.requiresImmediateRenegotiation&&n.sessionDescription){await i.setRemoteDescription(n.sessionDescription);const s=await i.createAnswer();await i.setLocalDescription(s),await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`,{sessionDescription:i.localDescription})}}handleRemoteTrack(e){const t=e.transceiver?.mid??null,i=t?this.midToParticipant.get(t):void 0,n=i?this.remotes.get(i):void 0;n&&(n.stream.getTracks().some(s=>s.id===e.track.id)||n.stream.addTrack(e.track),this.options.onEvent("remotestream",{participant:n.identity,stream:n.stream,track:e.track}))}removeRemote(e){const t=this.remotes.get(e);if(t){for(const i of t.mids.keys())this.midToParticipant.delete(i);this.remotes.delete(e),this.options.onEvent("participantleft",t.identity)}}async setRemoteQuality(e,t){const i=this.remotes.get(e);if(!i||!this.sessionId)return;const n=[...i.mids.entries()].find(([,s])=>i.simulcastNames.has(s))?.[0];n&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/update`,{tracks:[{mid:n,simulcast:{preferredRid:G[t],priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}]})}async replaceVideoTrack(e){this.videoSender&&(await this.videoSender.replaceTrack(e),await this.applyVideoQuality())}async applyVideoQuality(){const e=this.videoSender;if(!e)return;const t=this.screenStream?.getVideoTracks()[0]?.id,i=!!(t&&e.track?.id===t),n=e.getParameters(),s=n.encodings?.length?n.encodings:[{}];if(s.length>1){const a=this.quality==="audio"?null:G[this.quality];n.encodings=s.map(o=>{if(i)return{...o,active:o.rid==="a",...o.rid==="a"?{maxBitrate:F.maxBitrate,maxFramerate:F.maxFramerate}:{}};const c=K.find(l=>l.rid===o.rid),d=a!=null&&String(o.rid??"")>=a;return{...o,...c??{},active:d}})}else{const a=this.quality==="audio"?A.audio:A[this.quality],o=i?F:a;n.encodings=s.map(c=>({...c,...o}))}n.degradationPreference=i?"maintain-resolution":"maintain-framerate",await e.setParameters(n)}async closePublishedTracks(){const e=this.connection;if(!e||!this.sessionId)return;const t=e.getTransceivers().filter(i=>i.sender.track&&i.mid).map(i=>({mid:i.mid}));t.length!==0&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/close`,{tracks:t,force:!0})}selfIdentity(){return{participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId??this.options.credential.participantId,displayName:this.options.credential.externalUserId??this.options.credential.participantId,platform:"web"}}async sfuFetch(e,t,i){const n=await fetch(`${this.apiBase}${t}`,{method:e,headers:{authorization:`Bearer ${this.options.credential.signalingToken}`,"content-type":"application/json"},body:i===void 0?void 0:JSON.stringify(i)});if(!n.ok)throw new Error(`SFU request failed with HTTP ${n.status}`);return n.json()}startHeartbeat(e){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}startSpeakerSampling(){this.stopSpeakerSampling(),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopSpeakerSampling(){this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=this.connection;if(!e||e.connectionState!=="connected")return;let t;try{t=await e.getStats()}catch{return}const i=[];for(const s of H(t.values())){const a=s.mid?this.midToParticipant.get(s.mid):void 0;a&&i.push({participantId:a,level:s.level})}const n=U(i,this.activeSpeaker);n!==this.activeSpeaker&&(this.activeSpeaker=n,this.options.onEvent("activespeaker",{participantId:n}))}clearTimers(){this.stopHeartbeat(),this.stopSpeakerSampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).then(()=>this.announceTracks()).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??ke(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function ke(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function Te(r,e={}){const t=e.document??globalThis.document,i=!!(t&&t.pictureInPictureEnabled);let n=null;function s(){return n||(n=t.createElement("video"),n.muted=!0,n.playsInline=!0,n.style.position="fixed",n.style.width="1px",n.style.height="1px",n.style.opacity="0",n.style.pointerEvents="none",n.addEventListener("leavepictureinpicture",()=>e.onLeave?.()),t.body.appendChild(n),n)}function a(o){const c=r.getRemoteStreams();return o?c.find(d=>d.participant.participantId===o)?.stream??null:c[0]?.stream??r.getLocalStream()}return{supported:i,async enter(o){if(!i)throw new Error("This browser does not support picture-in-picture");const c=a(o);if(!c)throw new Error("No video stream is available for picture-in-picture");const d=s();d.srcObject!==c&&(d.srcObject=c),await d.play().catch(()=>{}),await d.requestPictureInPicture()},async exit(){t.pictureInPictureElement&&typeof t.exitPictureInPicture=="function"&&await t.exitPictureInPicture()},dispose(){n?.remove(),n=null}}}function we(r,e){const t=e.document??globalThis.document,i=e.fetchImplementation??globalThis.fetch.bind(globalThis),n=new URL(e.credential.signalingUrl,globalThis.location?.href).origin,s=e.mimeType??"video/webm",a=e.height??720,o=Math.round(a*16/9);let c=null,d=null,l=null,h=null,v=0,g=[],T=!1;async function p(E,k,y,w="application/json"){const I=await i(`${n}${k}`,{method:E,headers:{authorization:`Bearer ${e.credential.signalingToken}`,"content-type":w},body:y===void 0?void 0:w==="application/json"?JSON.stringify(y):y});if(!I.ok)throw new Error(`Recording request failed with HTTP ${I.status}`);return I.json()}return{get recording(){return T},async start(){if(T)return;h=(await p("POST","/v1/rtc/recordings")).recordingId;const k=t.createElement("canvas");k.width=o,k.height=a;const y=k.getContext("2d"),w=new Map,I=(C,M)=>{let b=w.get(C);return b||(b=t.createElement("video"),b.muted=!0,b.playsInline=!0,b.srcObject=M,b.play().catch(()=>{}),w.set(C,b)),b};d=setInterval(()=>{const C=r.getRemoteStreams(),M=r.getLocalStream(),b=[...C.map(x=>({key:x.participant.participantId,stream:x.stream,label:x.participant.displayName})),...M?[{key:"local",stream:M,label:"me"}]:[]];y.fillStyle="#101418",y.fillRect(0,0,o,a);const O=Math.max(1,Math.ceil(Math.sqrt(b.length))),m=Math.max(1,Math.ceil(b.length/O)),q=o/O,B=a/m;b.forEach((x,he)=>{const ue=I(x.key,x.stream),z=he%O*q,J=Math.floor(he/O)*B;ue.videoWidth>0&&y.drawImage(ue,z+2,J+2,q-4,B-4),y.fillStyle="rgba(0,0,0,.6)",y.fillRect(z+8,J+B-30,Math.min(q-16,12+x.label.length*9),22),y.fillStyle="#fff",y.font="14px system-ui",y.fillText(x.label,z+14,J+B-14)})},66);const P=k.captureStream(15);l=new AudioContext;const R=l.createMediaStreamDestination(),f=new Set,u=()=>{const C=[...r.getRemoteStreams().map(M=>M.stream),...r.getLocalStream()?[r.getLocalStream()]:[]];for(const M of C)for(const b of M.getAudioTracks())f.has(b.id)||(f.add(b.id),l.createMediaStreamSource(new MediaStream([b])).connect(R))};u();const N=setInterval(u,1e3),L=()=>clearInterval(N);for(const C of R.stream.getAudioTracks())P.addTrack(C);c=(e.mediaRecorderFactory??((C,M)=>new MediaRecorder(C,M)))(P,{mimeType:s}),g=[],c.addEventListener("dataavailable",C=>{C.data&&C.data.size>0&&g.push(C.data)}),c.addEventListener("stop",L),c.start(1e3),v=Date.now(),T=!0},async stop(){if(!T||!c||!h)throw new Error("The recorder is not running");T=!1;const E=c,k=new Promise(R=>E.addEventListener("stop",()=>R(),{once:!0}));E.stop(),await k,d&&clearInterval(d),d=null,await l?.close().catch(()=>{}),l=null;const y=new Blob(g,{type:s});g=[];const w=Math.max(1,Math.round((Date.now()-v)/1e3)),I=await p("PUT",`/v1/rtc/recordings/${encodeURIComponent(h)}/media`,y,s);await p("POST",`/v1/rtc/recordings/${encodeURIComponent(h)}/complete`,{durationSeconds:w});const P={recordingId:h,sizeBytes:I.sizeBytes,durationSeconds:w};return h=null,c=null,P},dispose(){d&&clearInterval(d),d=null;try{c?.stop()}catch{}c=null,l?.close().catch(()=>{}),l=null,T=!1}}}const Y=["none","blur","aurora","cybergrid","bokeh","sunset","image"],X=["none","snow","confetti","vignette"],_={aurora(r,e,t,i){const n=r.createLinearGradient(0,0,0,t);n.addColorStop(0,"#050b1e"),n.addColorStop(1,"#0b2540"),r.fillStyle=n,r.fillRect(0,0,e,t);for(let s=0;s<3;s+=1){r.beginPath();for(let a=0;a<=e;a+=16){const o=t*.35+s*40+Math.sin(a/140+i/900+s)*60;a===0?r.moveTo(a,o):r.lineTo(a,o)}r.lineTo(e,0),r.lineTo(0,0),r.closePath(),r.fillStyle=`hsla(${140+s*40+Math.sin(i/1300)*20}, 80%, 55%, 0.14)`,r.fill()}},cybergrid(r,e,t,i){r.fillStyle="#0a0118",r.fillRect(0,0,e,t),r.strokeStyle="rgba(255,0,180,0.5)",r.lineWidth=1.5;const n=t*.45;for(let a=0;a<12;a+=1){const o=n+(a*34+i/18)%(t-n);r.beginPath(),r.moveTo(0,o),r.lineTo(e,o),r.stroke()}for(let a=-8;a<=8;a+=1)r.beginPath(),r.moveTo(e/2+a*24,n),r.lineTo(e/2+a*e*.12,t),r.stroke();const s=r.createLinearGradient(0,t*.1,0,n);s.addColorStop(0,"#ffd34d"),s.addColorStop(1,"#ff2d78"),r.fillStyle=s,r.beginPath(),r.arc(e/2,n,t*.22,Math.PI,0),r.fill()},bokeh(r,e,t,i){r.fillStyle="#14100c",r.fillRect(0,0,e,t);for(let n=0;n<26;n+=1){const s=(Math.sin(n*12.9898)*.5+.5)*e,a=(Math.sin(n*78.233)*.5+.5)*t+Math.sin(i/1500+n)*12,o=14+n%5*12;r.fillStyle=`hsla(${35+n%4*12}, 90%, 60%, ${.1+n%3*.05})`,r.beginPath(),r.arc(s,a,o,0,Math.PI*2),r.fill()}},sunset(r,e,t,i){const n=r.createLinearGradient(0,0,0,t);n.addColorStop(0,"#2b1055"),n.addColorStop(.55,"#d1548a"),n.addColorStop(1,"#ffb36b"),r.fillStyle=n,r.fillRect(0,0,e,t),r.fillStyle="#ffdf8e",r.beginPath(),r.arc(e*.5,t*.62+Math.sin(i/4e3)*4,t*.13,0,Math.PI*2),r.fill(),r.fillStyle="rgba(20,8,40,0.85)",r.fillRect(0,t*.78,e,t*.22)}};function be(r,e={}){const t=e.document??globalThis.document,i=e.width??1280,n=e.height??720,s=e.frameRate??24,a=r instanceof MediaStreamTrack?r:r.getVideoTracks()[0];if(!a)throw new Error("A video track is required");let o=e.background??"none",c=e.overlay??"none",d=e.backgroundImage??null,l=e.segmenter??null,h=null,v=!1;const g=t.createElement("video");g.muted=!0,g.playsInline=!0,g.srcObject=new MediaStream([a]),g.play().catch(()=>{});const T=t.createElement("canvas");T.width=i,T.height=n;const p=T.getContext("2d"),E=t.createElement("canvas");E.width=i,E.height=n;const k=E.getContext("2d"),y=Array.from({length:80},(f,u)=>({x:(Math.sin(u*12.9898)*.5+.5)*i,y:(Math.sin(u*78.233)*.5+.5)*n,speed:.6+u%5*.5,drift:Math.sin(u)*.6,size:2+u%4,hue:u*47%360}));function w(){if(h){k.clearRect(0,0,i,n),k.drawImage(h,0,0,i,n),k.globalCompositeOperation="source-in",k.drawImage(g,0,0,i,n),k.globalCompositeOperation="source-over",p.drawImage(E,0,0);return}const f=i*.42,u=f*9/16,N=i-f-24,L=n-u-24;p.save(),p.shadowColor="rgba(0,0,0,.5)",p.shadowBlur=24,p.beginPath(),p.roundRect(N,L,f,u,14),p.clip(),p.drawImage(g,N,L,f,u),p.restore()}function I(f){if(o==="none"?p.drawImage(g,0,0,i,n):o==="blur"?(p.filter="blur(14px)",p.drawImage(g,-16,-16,i+32,n+32),p.filter="none",h&&w()):o==="image"&&d?(p.drawImage(d,0,0,i,n),w()):((_[o]??_.aurora)(p,i,n,f),w()),c==="snow"||c==="confetti")for(const u of y)u.y+=u.speed*(c==="confetti"?1.6:1),u.x+=u.drift,u.y>n&&(u.y=-6,u.x=Math.random()*i),p.fillStyle=c==="snow"?"rgba(255,255,255,.85)":`hsl(${u.hue}, 90%, 60%)`,c==="confetti"?p.fillRect(u.x,u.y,u.size+2,u.size):(p.beginPath(),p.arc(u.x,u.y,u.size/2,0,Math.PI*2),p.fill());else if(c==="vignette"){const u=p.createRadialGradient(i/2,n/2,n*.4,i/2,n/2,n*.85);u.addColorStop(0,"rgba(0,0,0,0)"),u.addColorStop(1,"rgba(0,0,0,0.55)"),p.fillStyle=u,p.fillRect(0,0,i,n)}}const P=setInterval(()=>{I(Date.now()),l&&!v&&o!=="none"&&(v=!0,l.segment(g).then(f=>{h=f}).catch(()=>{h=null}).finally(()=>{v=!1}))},Math.round(1e3/s)),R=T.captureStream(s);return{track:R.getVideoTracks()[0],stream:R,setBackground(f,u){o=Y.includes(f)?f:"none",u!==void 0&&(d=u),o==="none"&&(h=null)},setOverlay(f){c=X.includes(f)?f:"none"},setSegmenter(f){l=f,f||(h=null)},dispose(){clearInterval(P),l?.close?.();for(const f of R.getTracks())f.stop();g.srcObject=null}}}class Z extends EventTarget{mode;options;client=null;peerConnection;state="initialized";latestMediaScore=null;latestQuality=null;statsTimer=null;statsRunning=!1;previousCounters=null;lastTelemetryAt=0;joinPromise=null;requestedQuality;effectiveQuality;poorQualitySamples=0;goodQualitySamples=0;constructor(e){if(super(),!e.credential?.signalingToken||!e.credential.signalingUrl||!e.credential.roomId||!e.credential.participantId)throw new Error("A valid room signaling credential is required");this.mode=e.mode,this.requestedQuality=e.mode==="voice"?"audio":e.quality??"auto",this.effectiveQuality=this.requestedQuality==="auto"?"medium":this.requestedQuality,this.peerConnection=e.peerConnection??null,this.options={...e,audio:e.audio??!0,video:e.mode==="voice"?!1:e.video??!0,receiveAudio:e.receiveAudio??!0,receiveVideo:e.mode==="voice"?!1:e.receiveVideo??!0,statsInterval:Math.max(1e3,e.statsInterval??5e3),telemetryInterval:Math.max(1e4,e.telemetryInterval??3e4),autoReportStats:e.autoReportStats??!0}}async initialize(){if(this.client)return this;try{const t=(this.options.transport==="auto"?this.mode==="meeting"?"sfu":"mesh":this.options.transport??"mesh")==="sfu"?Se:j;return this.client=new t({credential:{signalingToken:this.options.credential.signalingToken,signalingUrl:this.options.credential.signalingUrl,roomId:this.options.credential.roomId,participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId,iceServers:this.options.credential.iceServers},audio:this.options.audio,video:this.options.video,receiveAudio:this.options.receiveAudio,receiveVideo:this.options.receiveVideo,quality:this.effectiveQuality,preferredVideoCodec:this.options.preferredVideoCodec??"auto",iceTransportPolicy:this.options.iceTransportPolicy??"all",localStream:this.options.localStream,onEvent:(i,n)=>this.handleNativeEvent(i,n)}),await this.client.initialize(),this.startStats(),this.emit("initialized",{mode:this.mode,sdkVersion:le}),this.options.autoJoin&&await this.join(),this}catch(e){this.state="failed";const t=ce(e,"RTC initialization failed");throw this.emit("error",D(t)),t}}async join(){const e=this.requireClient();return this.joinPromise?this.joinPromise:(this.state="joining",this.emit("joining",{}),this.joinPromise=e.join().catch(t=>{this.state="failed";const i=ce(t,"RTC connection failed");throw this.emit("error",D(i)),i}).finally(()=>{this.joinPromise=null}),this.joinPromise)}async leave(){this.client&&(await this.joinPromise?.catch(()=>{}),await this.sampleAndReport("final").catch(()=>{}),await this.client.leave(),this.state="disconnected",this.stopStats())}async setMuted(e){this.requireClient().setMuted(e),this.emit("mediachange",{kind:"audio",enabled:!e})}async setCameraEnabled(e){if(this.mode==="voice"&&e)throw new Error("Camera is unavailable in voice mode");await this.requireClient().setCameraEnabled(e),this.emit("mediachange",{kind:"video",enabled:e})}async setVideoTrack(e){if(this.mode==="voice")throw new Error("Camera is unavailable in voice mode");await this.requireClient().setVideoTrack(e),this.emit("mediachange",{kind:"video",enabled:e.enabled,trackId:e.id})}async setScreenShareEnabled(e){await this.requireClient().setScreenShareEnabled(e),this.emit("mediachange",{kind:"screenshare",enabled:e})}async setDevice(e){await this.requireClient().setDevice(e),this.emit("devicechange",{deviceId:e.deviceId,kind:e.kind})}async setQuality(e){if(this.mode==="voice"&&e!=="audio")throw new Error("Voice calls only support audio quality");this.requestedQuality=e,this.poorQualitySamples=0,this.goodQualitySamples=0,await this.applyEffectiveQuality(e==="auto"?"medium":e,"manual")}async setPreferredVideoCodec(e){await this.requireClient().setPreferredVideoCodec(e),this.emit("codecchange",{preferredVideoCodec:e})}async getDevices(){return navigator.mediaDevices?.enumerateDevices?navigator.mediaDevices.enumerateDevices():[]}getParticipants(){return this.requireClient().getParticipants().map(ae)}getLocalStream(){return this.client?.getLocalStream()??null}getRemoteStreams(){return(this.client?.getRemoteStreams()??[]).map(e=>({participant:ae(e.participant),stream:e.stream}))}setPeerConnection(e){this.peerConnection=e,this.previousCounters=null}async sendChatMessage(e,t){const i=e.trim();if(!i)throw new Error("message is required");this.requireClient().sendChat(i,t)}async sendControlMessage(e,t={}){const i=e.trim();if(!i)throw new Error("action is required");this.requireClient().sendControl(i,t)}async createPoll(e,t,i={}){const n=t.map(s=>s.trim()).filter(Boolean);if(!e.trim()||n.length<2)throw new Error("A poll requires a question and at least two options");this.requireClient().sendControl("poll.create",{poll:{id:`poll-${crypto.randomUUID()}`,question:e.trim(),options:n,votes:{},...i}})}async votePoll(e,t){this.requireClient().sendControl("poll.vote",{pollId:e,optionIndex:t})}async requestStageAccess(){this.requireClient().sendControl("stage.request")}async cancelStageAccess(){this.requireClient().sendControl("stage.cancel")}async joinStage(){this.requireClient().sendControl("stage.join")}async leaveStage(){this.requireClient().sendControl("stage.leave")}async grantStageAccess(e){this.requireClient().sendControl("stage.grant",{participantIds:e})}async denyStageAccess(e){this.requireClient().sendControl("stage.deny",{participantIds:e})}async startRecording(e=!1){this.requireClient().sendControl("recording.start",{allowMultiple:e})}async stopRecording(e){this.requireClient().sendControl("recording.stop",{recordingId:e})}async pauseRecording(e){this.requireClient().sendControl("recording.pause",{recordingId:e})}async resumeRecording(e){this.requireClient().sendControl("recording.resume",{recordingId:e})}async runPreflight(e={}){const t=[],i=globalThis.isSecureContext===!0;i||t.push("secure_context_required");let n="unavailable";if(e.requestMedia!==!1&&navigator.mediaDevices?.getUserMedia)try{(await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})).getTracks().forEach(v=>v.stop()),n="granted"}catch{n="denied",t.push("media_permission_denied")}const s=navigator.mediaDevices?.enumerateDevices?await navigator.mediaDevices.enumerateDevices().catch(()=>[]):[],a=s.filter(h=>h.kind==="audioinput").length,o=s.filter(h=>h.kind==="videoinput").length;this.options.audio&&a===0&&t.push("microphone_unavailable"),this.options.video&&o===0&&t.push("camera_unavailable");const c=e.probeUrl??this.options.preflightProbeUrl;let d=null;if(c){const h=performance.now();try{const v=await fetch(c,{method:"HEAD",cache:"no-store",credentials:"omit"});d=Math.round(performance.now()-h),v.ok||t.push("network_probe_failed")}catch{t.push("network_probe_failed")}}const l={passed:t.length===0,sampledAt:new Date().toISOString(),secureContext:i,mediaPermission:n,microphoneCount:a,cameraCount:o,probeLatencyMs:d,effectiveConnectionType:re()?.effectiveType??null,issues:t};return this.emit("preflight",l),this.options.autoReportStats&&await this.reportQuality({sampleType:"preflight",sampledAt:l.sampledAt,connectionState:l.passed?"ready":"failed",rttMs:l.probeLatencyMs,effectiveConnectionType:l.effectiveConnectionType}).catch(()=>{}),l}async collectQualitySample(e="runtime"){const t=this.options.statsProvider?await this.options.statsProvider():{},i=this.peerConnection?await Pe(this.peerConnection,this.previousCounters):null;i?.counters&&(this.previousCounters=i.counters);const s={...{sampleType:e,sampledAt:new Date().toISOString(),connectionState:this.peerConnection?.connectionState??this.state,mediaScore:this.latestMediaScore,networkScore:this.latestMediaScore==null?5:Math.max(0,Math.min(5,Math.round(this.latestMediaScore))),estimatedMos:4.5,rttMs:null,jitterMs:null,packetLossPct:null,packetLossCumulativePct:null,packetsLost:null,packetsReceived:null,inboundBitrateBps:null,outboundBitrateBps:null,availableOutgoingBitrateBps:null,framesPerSecond:null,frameWidth:null,frameHeight:null,framesDropped:null,freezeCount:null,freezeDurationMs:null,concealedSamples:null,codec:null,transportProtocol:null,candidateType:null,qualityLimitationReason:null,deviceMemoryGb:Ae(),effectiveConnectionType:re()?.effectiveType??null},...i?.sample??{},...t,sampleType:e,sampledAt:new Date().toISOString()},a=Me(s);return s.networkScore=a.networkScore,s.estimatedMos=a.estimatedMos,this.latestQuality=s,s}async reportQuality(e){const t=this.options.credential.telemetryToken,i=this.options.telemetryEndpoint??this.options.credential.telemetryEndpoint;if(!t||!i)return;const n=await fetch(i,{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify(e),keepalive:e.sampleType==="final"});if(!n.ok)throw new Error(`RTC telemetry failed with HTTP ${n.status}`)}getStats(){const e=this.client,t=e?.getLocalStream();return{state:this.state,participantCount:e?e.getParticipants().length+1:0,audioEnabled:!!t?.getAudioTracks().some(i=>i.enabled),videoEnabled:!!t?.getVideoTracks().some(i=>i.enabled),screenShareEnabled:!1,latestMediaScore:this.latestMediaScore,latestQuality:this.latestQuality,sampledAt:new Date().toISOString()}}getNativeClient(){return this.client}destroy(){this.stopStats(),this.client&&this.client.leave(),this.client=null,this.joinPromise=null,this.peerConnection=null,this.state="disconnected"}handleNativeEvent(e,t){e==="connected"?(this.state="connected",this.peerConnection=this.client?.getPrimaryPeerConnection()??null):e==="disconnected"?this.state="disconnected":e==="peerconnectionstate"&&(this.peerConnection=this.client?.getPrimaryPeerConnection()??null),this.emit(e,t)}startStats(){this.stopStats(),this.statsTimer=setInterval(()=>{this.sampleAndReport("runtime")},this.options.statsInterval)}async sampleAndReport(e){if(!this.statsRunning){this.statsRunning=!0;try{const t=await this.collectQualitySample(e);await this.adaptQuality(t),this.emit("stats",{...this.getStats(),latestQuality:t});const i=Date.now(),n=e!=="runtime"||i-this.lastTelemetryAt>=this.options.telemetryInterval;this.options.autoReportStats&&n&&(await this.reportQuality(t),this.lastTelemetryAt=i)}catch(t){this.emit("telemetryerror",D(t))}finally{this.statsRunning=!1}}}stopStats(){this.statsTimer!==null&&clearInterval(this.statsTimer),this.statsTimer=null}async adaptQuality(e){if(this.requestedQuality!=="auto"||this.options.adaptiveQuality===!1||this.mode==="voice")return;if((this.client?.getParticipants().length??0)>1){this.poorQualitySamples=0,this.goodQualitySamples=0,this.effectiveQuality!=="high"&&await this.applyEffectiveQuality("high","network");return}const t=String(e.qualityLimitationReason??"").toLowerCase(),i=(e.packetLossPct??0)>=8||(e.rttMs??0)>=800||t==="cpu"||t==="bandwidth",n=i||(e.packetLossPct??0)>=3||(e.rttMs??0)>=350||e.availableOutgoingBitrateBps!=null&&e.availableOutgoingBitrateBps<35e4,s=(e.packetLossPct??100)<1&&(e.rttMs??1e3)<180&&!t&&(e.availableOutgoingBitrateBps==null||e.availableOutgoingBitrateBps>1e6);this.poorQualitySamples=n?this.poorQualitySamples+1:0,this.goodQualitySamples=s?this.goodQualitySamples+1:0;const a=["low","medium","high"],o=a.indexOf(this.effectiveQuality);i&&o>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality("low","network")):this.poorQualitySamples>=2&&o>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality(a[o-1],"network")):this.goodQualitySamples>=6&&o>=0&&o<a.length-1&&(this.goodQualitySamples=0,await this.applyEffectiveQuality(a[o+1],"network"))}async applyEffectiveQuality(e,t){if(this.effectiveQuality===e&&this.client)return;const i=this.effectiveQuality;this.effectiveQuality=e,this.client&&await this.client.setQuality(e),this.emit("qualitychange",{requestedQuality:this.requestedQuality,quality:e,previous:i,reason:t})}requireClient(){if(!this.client)throw new Error("Call initialize() before using the RTC client");return this.client}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}async function Ce(r){return new Z(r).initialize()}let Q=null;function ee(){if(Q)return Q;const r=globalThis,e=r.AudioContext??r.webkitAudioContext;return e?(Q=new e,Q):null}async function te(){const r=ee();return r?(r.state==="suspended"&&await r.resume().catch(()=>{}),r.state==="running"):!1}class Ee{options;media=null;cadenceTimer=null;oscillators=new Set;constructor(e){this.options=e}unlock(){return te()}startIncoming(){this.start("incoming",this.options.ringtoneUrl)}startRingback(){this.start("ringback",this.options.ringbackUrl)}stop(){this.cadenceTimer&&clearInterval(this.cadenceTimer),this.cadenceTimer=null,this.media?.pause(),this.media&&(this.media.currentTime=0),this.media=null;for(const e of this.oscillators)try{e.stop()}catch{}this.oscillators.clear()}start(e,t){if(this.stop(),this.options.sounds===!1)return;if(t&&typeof Audio<"u"){this.media=new Audio(t),this.media.loop=!0,this.media.play().catch(()=>{});return}const i=()=>this.playBuiltInTone(e);i(),this.cadenceTimer=setInterval(i,e==="incoming"?3e3:3500)}playBuiltInTone(e){const t=ee();if(!t||t.state!=="running")return;const i=e==="incoming"?1.25:1,n=e==="incoming"?[440,480]:[425],s=t.createGain(),a=t.currentTime;s.gain.setValueAtTime(1e-4,a),s.gain.exponentialRampToValueAtTime(.045,a+.025),s.gain.setValueAtTime(.045,a+i-.06),s.gain.exponentialRampToValueAtTime(1e-4,a+i),s.connect(t.destination);for(const o of n){const c=t.createOscillator();c.type="sine",c.frequency.value=o,c.connect(s),this.oscillators.add(c),c.onended=()=>this.oscillators.delete(c),c.start(a),c.stop(a+i+.02)}}}class ie extends EventTarget{options;socket=null;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;currentCall=null;outgoingCallId=null;mount=null;terminalTimer=null;tokenRefreshTimer=null;tokenRequest=null;currentToken;currentTokenExpiresAt;callAudio;mediaClients=new Map;activeCallIds=new Set;seenInvitationIds=new Set;startCallRequest=null;lastDisconnect=null;constructor(e){if(super(),!e.token&&!e.tokenProvider)throw new Error("A client token or tokenProvider is required");if(e.token&&!e.token.startsWith("rtcc_"))throw new Error("A valid RTC client token is required");if(!e.signalingUrl&&!e.apiBaseUrl&&!globalThis.location?.origin)throw new Error("signalingUrl or apiBaseUrl is required outside a browser");this.options=e,this.currentToken=e.token??null,this.currentTokenExpiresAt=e.tokenExpiresAt,this.callAudio=new Ee(e),this.mount=ne(e.mount),this.scheduleTokenRefresh()}connect(){if(!this.options.signalingUrl)throw new Error("signalingUrl is required to connect signaling");return this.stopped=!1,this.scheduleTokenRefresh(),this.openSocket("initial"),this}setMount(e){this.clearUi(),this.mount=ne(e),this.currentCall&&this.renderIncoming(this.currentCall)}attachMediaClient(e,t){if(!e.trim())throw new Error("callId is required");return this.mediaClients.set(e,t),()=>{this.mediaClients.get(e)===t&&this.mediaClients.delete(e)}}async startCall(e){if(this.startCallRequest)return this.startCallRequest;if(this.activeCallIds.size>0)throw new Error("Another call is already active");const t=e.targets?.length?e.targets:e.target?[e.target]:[];if(t.length===0)throw new Error("startCall requires at least one target");const i={mode:e.mode,targets:t,metadata:e.metadata,idempotencyKey:e.idempotencyKey},n=(async()=>{await this.callAudio.unlock(),this.callAudio.startRingback();let s;try{s=await this.request("/v1/rtc/client/calls",{method:"POST",headers:e.idempotencyKey?{"idempotency-key":e.idempotencyKey}:void 0,body:JSON.stringify(i)})}catch(a){throw this.callAudio.stop(),a}return!s.credential&&s.callerCredential&&(s.credential=s.callerCredential),this.outgoingCallId=String(s.call?.id??"")||null,this.outgoingCallId&&this.activeCallIds.add(this.outgoingCallId),this.emit("outgoingcall",s),s})();this.startCallRequest=n;try{return await n}finally{this.startCallRequest===n&&(this.startCallRequest=null)}}async accept(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"accept");return this.activeCallIds.add(e),this.callAudio.stop(),this.dismiss(e),this.emit("callaccepted",t),t}async reject(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"reject");return this.releaseMedia(e),this.showTerminal(e,"Call declined"),this.emit("callrejected",t),t}async busy(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"busy");return this.releaseMedia(e),this.showTerminal(e,"Line busy"),t}async end(e){this.callAudio.stop();const t=await this.callAction(e,"end");return this.releaseMedia(e),this.showTerminal(e,"Call ended"),t}async cancel(e){this.callAudio.stop();const t=await this.callAction(e,"cancel");return this.releaseMedia(e),this.showTerminal(e,"Call cancelled"),t}handleIncomingCall(e){if(!e?.id||!e.caller?.id||!e.caller.name||e.mode!=="voice"&&e.mode!=="video")throw new Error("Incoming call payload is invalid");if(e.expiresAt&&Date.parse(e.expiresAt)<=Date.now()){this.emit("callexpired",{id:e.id,expiresAt:e.expiresAt});return}if(this.currentCall?.id===e.id){this.currentCall=e,this.emit("callrestored",e);return}if(this.seenInvitationIds.has(e.id)){this.emit("callrestored",e);return}this.seenInvitationIds.add(e.id),this.activeCallIds.add(e.id),this.currentCall=e,this.callAudio.startIncoming(),this.renderIncoming(e),this.emit("incomingcall",e)}handleCallUpdate(e){const t=e.id===this.currentCall?.id||e.id===this.outgoingCallId;t&&e.status==="accepted"?(this.activeCallIds.add(e.id),this.callAudio.stop(),this.dismiss(e.id)):t&&["rejected","busy","cancelled","ended","failed","missed"].includes(e.status)&&this.showTerminal(e.id,Re(e.status)),["rejected","busy","cancelled","ended","failed","missed"].includes(e.status)&&this.releaseMedia(e.id),this.emit("callupdated",e)}disconnect(){this.stopped=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.reconnectTimer=null,this.heartbeatTimer=null,this.tokenRefreshTimer=null,this.socket?.close(1e3,"Client disconnected"),this.socket=null,this.callAudio.stop(),this.dismiss();for(const e of this.mediaClients.keys())this.releaseMedia(e);this.activeCallIds.clear(),this.seenInvitationIds.clear()}destroy(){this.disconnect(),this.mount=null}async openSocket(e){if(!this.options.signalingUrl||this.stopped||this.socket?.readyState===WebSocket.OPEN||this.socket?.readyState===WebSocket.CONNECTING)return;let t;try{t=await this.resolveToken(e,e==="reconnect")}catch(s){this.emit("tokenerror",D(s)),!this.stopped&&this.options.reconnect!==!1&&this.scheduleReconnect();return}if(this.stopped||this.socket)return;const i=new URL(this.options.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new WebSocket(i,["rtc-client",t]);this.socket=n,n.addEventListener("open",()=>{const s=this.reconnectAttempt>0,a=this.lastDisconnect;this.reconnectAttempt=0,this.heartbeatDeadline=Date.now()+45e3,this.emit("signalingconnected",{resumed:s}),a&&(this.reportSignalingDiagnostic({event:"recovered",code:a.code,reason:a.reason,wasClean:a.wasClean,outageMs:Date.now()-a.at}),this.lastDisconnect=null),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{if(n.readyState===WebSocket.OPEN){if(Date.now()>this.heartbeatDeadline){n.close(4e3,"Signaling heartbeat timed out");return}n.send("ping")}},2e4)}),n.addEventListener("message",s=>{this.heartbeatDeadline=Date.now()+45e3,this.handleSignal(String(s.data))}),n.addEventListener("close",s=>{if(this.socket!==n)return;this.socket=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;const a=!this.stopped&&this.options.reconnect!==!1,o={code:s.code,reason:s.reason,wasClean:s.wasClean,willReconnect:a,attempt:this.reconnectAttempt+1};a&&(this.lastDisconnect={code:s.code,reason:s.reason,wasClean:s.wasClean,at:Date.now()}),this.emit("signalingdisconnected",o),this.reportSignalingDiagnostic({event:a?"reconnecting":"disconnected",...o}),a&&this.scheduleReconnect()}),n.addEventListener("error",()=>{const s={message:"RTC signaling connection failed",reconnecting:!this.stopped&&this.options.reconnect!==!1,attempt:this.reconnectAttempt+1};this.emit("signalingerror",s),this.reportSignalingDiagnostic({event:"error",attempt:s.attempt})})}handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i==="call.incoming"&&t.call&&typeof t.call=="object"){this.handleIncomingCall(t.call);return}if(i==="call.updated"){const n=t.call;n?.id&&n.status&&this.handleCallUpdate({id:n.id,status:n.status});return}i==="presence"?this.emit("presence",{users:t.users??[]}):i!=="pong"&&this.emit(i||"signal",t)}async callAction(e,t){return this.request(`/v1/rtc/client/calls/${encodeURIComponent(e)}/${t}`,{method:"POST",body:"{}"})}async request(e,t,i=!1){const n=this.options.apiBaseUrl??this.options.signalingUrl??globalThis.location?.origin;if(!n)throw new Error("apiBaseUrl is required for call actions");const s=new URL(n,globalThis.location?.href),a=await this.resolveToken("initial"),o=new Headers(t.headers);o.set("authorization",`Bearer ${a}`),o.set("content-type","application/json");const c=await fetch(new URL(e,s.origin),{...t,headers:o}),d=await c.json().catch(()=>({}));if(c.status===401&&this.options.tokenProvider&&!i)return await this.resolveToken("unauthorized",!0),this.request(e,t,!0);if(!c.ok){const l=d.error;throw new Error(typeof l=="string"?l:l?.message||`RTC API failed with HTTP ${c.status}`)}return d}renderIncoming(e){if(!this.mount)return;this.clearUi();const t=document.createElement("section");t.dataset.rtcIncomingCall=e.id,t.setAttribute("role","dialog"),t.setAttribute("aria-label",`Incoming ${e.mode} call from ${e.caller.name}`),Object.assign(t.style,{display:"grid",gap:"14px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const i=document.createElement("small");i.textContent=e.mode==="video"?"Incoming video call":"Incoming voice call",i.style.color="#5d7079";const n=document.createElement("strong");n.textContent=e.caller.name,n.style.fontSize="20px";const s=document.createElement("div");Object.assign(s.style,{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"});const a=se("Decline","#fff","#a32920","#d9a09b"),o=se("Answer","#fff","#08745f","#08745f");a.addEventListener("click",()=>{this.reject(e.id).catch(c=>this.emit("error",D(c)))}),o.addEventListener("click",()=>{this.accept(e.id).catch(c=>this.emit("error",D(c)))}),s.appendChild(a),s.appendChild(o),t.appendChild(i),t.appendChild(n),t.appendChild(s),this.mount.replaceChildren(t)}dismiss(e){e&&this.currentCall?.id!==e&&this.outgoingCallId!==e||(this.currentCall=null,(!e||this.outgoingCallId===e)&&(this.outgoingCallId=null),this.callAudio.stop(),this.clearUi())}showTerminal(e,t){if(this.callAudio.stop(),this.currentCall?.id===e&&(this.currentCall=null),this.outgoingCallId===e&&(this.outgoingCallId=null),!this.mount)return;this.clearUi();const i=document.createElement("section");i.dataset.rtcCallTerminal=e,i.setAttribute("role","status"),Object.assign(i.style,{display:"grid",placeItems:"center",gap:"10px",minHeight:"150px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const n=document.createElement("strong");n.textContent=t,n.style.fontSize="18px",i.appendChild(n),this.mount.replaceChildren(i);const s=Math.max(0,this.options.terminalStateDurationMs??1600);this.terminalTimer=setTimeout(()=>this.clearUi(),s)}clearUi(){this.terminalTimer&&clearTimeout(this.terminalTimer),this.terminalTimer=null,this.mount&&this.mount.replaceChildren()}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer);const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.openSocket("reconnect")},e)}async resolveToken(e,t=!1){if(!t&&this.currentToken&&!this.tokenNeedsRefresh())return this.currentToken;if(!this.options.tokenProvider){if(!this.currentToken)throw new Error("A valid RTC client token is required");return this.currentToken}return this.tokenRequest?this.tokenRequest:(this.tokenRequest=(async()=>{const i=await this.options.tokenProvider?.({reason:e,previousExpiresAt:this.currentTokenExpiresAt}),n=typeof i=="string"?{token:i}:i;if(!n?.token?.startsWith("rtcc_"))throw new Error("tokenProvider returned an invalid RTC client token");return this.currentToken=n.token,this.currentTokenExpiresAt=n.expiresAt,this.scheduleTokenRefresh(),this.emit("tokenrefreshed",{reason:e,expiresAt:n.expiresAt}),n.token})().finally(()=>{this.tokenRequest=null}),this.tokenRequest)}tokenNeedsRefresh(){if(!this.currentTokenExpiresAt)return!1;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();return Number.isFinite(e)?e<=this.refreshSkew(e):!0}refreshSkew(e){const t=Math.max(5e3,this.options.tokenRefreshSkewMs??3e5);return Math.min(t,Math.max(5e3,e*.2))}scheduleTokenRefresh(){if(this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.tokenRefreshTimer=null,this.stopped||!this.options.tokenProvider||!this.currentTokenExpiresAt)return;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();if(!Number.isFinite(e))return;const t=Math.max(0,e-this.refreshSkew(e));this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.resolveToken("expiring",!0).catch(i=>{this.emit("tokenerror",D(i)),this.stopped||(this.tokenRefreshTimer=setTimeout(()=>this.scheduleTokenRefresh(),3e4))})},Math.min(t,2147e6))}releaseMedia(e){this.activeCallIds.delete(e),this.seenInvitationIds.delete(e);const t=this.mediaClients.get(e);t&&(this.mediaClients.delete(e),t.leave().catch(()=>{}).finally(()=>t.destroy()))}reportSignalingDiagnostic(e){this.options.reportSignalingDiagnostics===!1||this.stopped||this.request("/v1/rtc/client/signaling-events",{method:"POST",body:JSON.stringify({...e,occurredAt:new Date().toISOString()})}).catch(t=>this.emit("diagnosticserror",D(t)))}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}function Ie(r){const e=new ie(r);return r.autoConnect!==!1&&r.signalingUrl&&e.connect(),e}function ne(r){return r?typeof r=="string"?document.querySelector(r):r:null}function se(r,e,t,i){const n=document.createElement("button");return n.type="button",n.textContent=r,Object.assign(n.style,{minHeight:"44px",border:`1px solid ${i}`,borderRadius:"6px",background:t,color:e,cursor:"pointer",font:"inherit",fontWeight:"700"}),n}function Re(r){return{rejected:"Call declined",busy:"Line busy",cancelled:"Call cancelled",ended:"Call ended",failed:"Call failed",missed:"No answer"}[r]??"Call ended"}function ae(r){return{id:r.participantId,name:r.displayName,role:"participant"}}async function Pe(r,e){const t=await r.getStats(),i=new Map,n=new Map;let s=0,a=0,o=0,c=0,d=0,l=0,h=0,v=0,g=0,T=0,p=0,E=0,k=0,y=null,w=null,I=null,P=null,R=null,f=null;t.forEach(m=>{n.set(m.id,m),m.type==="codec"&&i.set(m.id,String(m.mimeType??""))}),t.forEach(m=>{if(m.type==="inbound-rtp"&&!m.isRemote&&(s+=Number(m.bytesReceived??0),o+=Math.max(0,Number(m.packetsLost??0)),c+=Math.max(0,Number(m.packetsReceived??0)),Number.isFinite(m.jitter)&&(d+=Number(m.jitter),l+=1),h=Math.max(h,Number(m.framesPerSecond??0)),v=Math.max(v,Number(m.frameWidth??0)),g=Math.max(g,Number(m.frameHeight??0)),T+=Number(m.framesDropped??0),p+=Number(m.freezeCount??0),E+=Number(m.totalFreezesDuration??0),k+=Number(m.concealedSamples??0),R||=i.get(String(m.codecId??""))??null),m.type==="outbound-rtp"&&!m.isRemote&&(a+=Number(m.bytesSent??0),f||=m.qualityLimitationReason?String(m.qualityLimitationReason):null,R||=i.get(String(m.codecId??""))??null),m.type==="candidate-pair"&&(m.selected||m.nominated)&&m.state==="succeeded"){y=Number.isFinite(m.currentRoundTripTime)?Number(m.currentRoundTripTime):y,w=Number.isFinite(m.availableOutgoingBitrate)?Number(m.availableOutgoingBitrate):w;const q=n.get(m.localCandidateId);I=q?.candidateType?String(q.candidateType):I,P=q?.protocol?String(q.protocol):P}});const u=Date.now(),N=e?Math.max(.001,(u-e.timestamp)/1e3):0,L=e?Math.max(0,Math.round((s-e.inboundBytes)*8/N)):null,de=e?Math.max(0,Math.round((a-e.outboundBytes)*8/N)):null,C=o+c,M=e?Math.max(0,o-e.packetsLost):o,b=e?Math.max(0,c-e.packetsReceived):c,O=M+b;return{counters:{timestamp:u,inboundBytes:s,outboundBytes:a,packetsLost:o,packetsReceived:c},sample:{rttMs:y===null?null:y*1e3,jitterMs:l?d/l*1e3:null,packetLossPct:O?M/O*100:null,packetLossCumulativePct:C?o/C*100:null,packetsLost:o,packetsReceived:c,inboundBitrateBps:L,outboundBitrateBps:de,availableOutgoingBitrateBps:w,framesPerSecond:h||null,frameWidth:v||null,frameHeight:g||null,framesDropped:T,freezeCount:p,freezeDurationMs:Math.round(E*1e3),concealedSamples:k,codec:R,transportProtocol:P,candidateType:I,qualityLimitationReason:f}}}function Me(r){let e=r.mediaScore==null?5:Math.round(oe(Number(r.mediaScore),0,5));const t=$(r.packetLossPct)??0,i=$(r.rttMs)??0,n=$(r.jitterMs)??0,s=String(r.connectionState??"unknown").toLowerCase();["failed","closed","disconnected"].includes(s)?e=0:["reconnecting","checking"].includes(s)&&(e=Math.min(e,2)),t>=15?e=Math.min(e,0):t>=8?e=Math.min(e,1):t>=3?e=Math.min(e,2):t>=1&&(e=Math.min(e,3)),i>=1200?e=Math.min(e,0):i>=800?e=Math.min(e,1):i>=400?e=Math.min(e,2):i>=250&&(e=Math.min(e,3)),n>=150?e=Math.min(e,1):n>=80?e=Math.min(e,2):n>=40&&(e=Math.min(e,3));const a=Number(oe(4.5-t*.09-i*.0011-n*.006,1,4.5).toFixed(2));return{networkScore:e,estimatedMos:a}}function re(){return navigator.connection??null}function Ae(){const r=Number(navigator.deviceMemory);return Number.isFinite(r)?r:null}function $(r){const e=Number(r);return Number.isFinite(e)?e:null}function oe(r,e,t){return Math.min(t,Math.max(e,r))}function D(r){return r instanceof Error?{name:r.name,message:r.message}:{name:"ServiceError",message:String(r??"Service request failed")}}function ce(r,e){return r instanceof Error?r:new Error(e)}const le="1.3.15";S.BACKGROUND_PRESETS=Y,S.OVERLAY_EFFECTS=X,S.RtcClient=Z,S.RtcIncomingClient=ie,S.SPEAKER_NOISE_FLOOR=.02,S.SPEAKER_TAKEOVER_RATIO=1.4,S.createCallRecorder=we,S.createPictureInPictureController=Te,S.createRtcClient=Ce,S.createRtcIncomingClient=Ie,S.createVideoEffectsPipeline=be,S.pickActiveSpeaker=U,S.scenePainters=_,S.unlockRtcCallAudio=te,S.version=le,Object.defineProperty(S,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(w,Q){typeof exports=="object"&&typeof module<"u"?Q(exports):typeof define=="function"&&define.amd?define(["exports"],Q):(w=typeof globalThis<"u"?globalThis:w||self,Q(w.StreamingCdnRtc={}))})(this,(function(w){"use strict";function _(a,e,t=.02,i=1.4){const n=a.filter(r=>r.level>=t);if(n.length===0)return null;const s=n.reduce((r,c)=>c.level>r.level?c:r),o=n.find(r=>r.participantId===e);return o?s.participantId===e?e:s.level>=o.level*i?s.participantId:e:s.participantId}function ee(a){const e=[];for(const t of a){if(String(t.type??"")!=="inbound-rtp"||String(t.kind??t.mediaType??"")!=="audio")continue;const i=Number(t.audioLevel??NaN);Number.isFinite(i)&&e.push({mid:typeof t.mid=="string"?t.mid:null,level:i})}return e}const q={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4},low:{active:!0,maxBitrate:35e4,maxFramerate:15,scaleResolutionDownBy:4},medium:{active:!0,maxBitrate:11e5,maxFramerate:24,scaleResolutionDownBy:2},high:{active:!0,maxBitrate:28e5,maxFramerate:30,scaleResolutionDownBy:1}},Te={warmupSamples:3,poorSamplesToStepDown:2,goodSamplesToStepUp:6,criticalLossPct:8,criticalRttMs:800,poorLossPct:3,poorRttMs:350,goodLossPct:1,goodRttMs:250,sustainRatio:.8};function be(a){const e=String(a??"").trim().toLowerCase();return!e||e==="none"?"":e==="cpu"||e==="bandwidth"?e:"other"}function Ce(a,e,t,i,n={}){const s={...Te,...n.thresholds},o=["low","medium","high"],r=n.ceiling&&n.ceiling!=="audio"?n.ceiling:"high",c=o.indexOf(r),d=o.indexOf(a),l=(n.samplesSeen??s.warmupSamples)+1,u=l<=s.warmupSamples,y=be(e.qualityLimitationReason),f=e.packetLossPct??0,T=e.rttMs??0,m=e.availableOutgoingBitrateBps,S=q[a].maxBitrate,b=f>=s.criticalLossPct||T>=s.criticalRttMs||y==="cpu",k=b||f>=s.poorLossPct||T>=s.poorRttMs||y==="bandwidth",C=m==null||m>=S*s.sustainRatio,M=f<s.goodLossPct&&T<s.goodRttMs&&!y&&C,E=k?t+1:0,g=M?i+1:0,p={quality:a,poorSamples:E,goodSamples:g,samplesSeen:l,changed:!1};return d>c?{quality:o[c],poorSamples:0,goodSamples:0,samplesSeen:l,changed:!0}:u?p:b&&d>0?{quality:o[d-1],poorSamples:0,goodSamples:0,samplesSeen:l,changed:!0}:E>=s.poorSamplesToStepDown&&d>0?{quality:o[d-1],poorSamples:0,goodSamples:0,samplesSeen:l,changed:!0}:g>=s.goodSamplesToStepUp&&d<c?{quality:o[d+1],poorSamples:0,goodSamples:0,samplesSeen:l,changed:!0}:p}function Re(a,e){const t=a instanceof Map?a:new Map(a);let i=0,n=0,s=null,o=null,r=null;for(const l of t.values()){const u=String(l.type??"");if(u==="outbound-rtp"&&String(l.kind??"")==="video"&&(i+=Number(l.packetsSent??0),s=l.qualityLimitationReason??s),u==="remote-inbound-rtp"&&String(l.kind??"")==="video"){n+=Number(l.packetsLost??0);const y=Number(l.roundTripTime??NaN);Number.isFinite(y)&&(o=y*1e3)}if(u==="candidate-pair"&&(l.nominated===!0||String(l.state??"")==="succeeded")){const y=Number(l.currentRoundTripTime??NaN);o==null&&Number.isFinite(y)&&(o=y*1e3);const f=Number(l.availableOutgoingBitrate??NaN);Number.isFinite(f)&&(r=f)}}const c={packetsSent:i,packetsLost:n,timestamp:Date.now()};let d=null;if(e){const l=i-e.packetsSent,u=n-e.packetsLost;l>0&&u>=0&&(d=u/(l+u)*100)}return{sample:{packetLossPct:d,rttMs:o,availableOutgoingBitrateBps:r,qualityLimitationReason:s},counters:c}}const Ie={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4}},Ee={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1};class B{options;socket=null;localStream=null;screenStream=null;peers=new Map;joinPromise=null;resolveJoin=null;rejectJoin=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;qualityTimer=null;speakerTimer=null;activeSpeaker=null;heartbeatDeadline=0;quality;preferredVideoCodec;signalQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.preferredVideoCodec=e.preferredVideoCodec??"auto"}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(!this.joined){if(this.joinPromise)return this.joinPromise;this.joinPromise=this.connect();try{await this.joinPromise}finally{this.joinPromise=null}}}async leave(){this.stopped=!0,this.clearConnectionTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null;for(const e of this.peers.values())e.connection.close();this.peers.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(r=>r.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(r=>r.readyState==="live")||await this.replaceVideoTrack(n);return}for(const r of this.peers.values()){const c=r.connection.getSenders().find(d=>d.track?.kind===s);c?await c.replaceTrack(n):r.connection.addTrack(n,this.localStream)}await this.applyVideoQuality()}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";for(const t of this.peers.values())t.quality=B.clampToCeiling(t.quality,e),t.poorSamples=0,t.goodSamples=0;await this.applyVideoQuality()}static clampToCeiling(e,t){if(t==="audio")return e;const i=["low","medium","high"];return i.indexOf(e)>i.indexOf(t)?t:e}async setPreferredVideoCodec(e){this.preferredVideoCodec=e;for(const t of this.peers.values())this.applyCodecPreference(t.connection);await this.renegotiateAll()}getParticipants(){return[...this.peers.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getRemoteStreams(){return[...this.peers.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.peers.values().next().value?.connection??null}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async connect(){const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1)}async openSocket(e){const{credential:t}=this.options,i=new URL(t.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new Promise((c,d)=>{this.resolveJoin=c,this.rejectJoin=d}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.startQualitySampling(),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const d=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(d)}).catch(l=>{const u=l instanceof Error?l.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),this.stopQualitySampling(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const d=!this.stopped&&(this.joined||e);this.joined=!1,d?(this.resetPeers(),this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0;const n=Array.isArray(t.peers)?t.peers:[];for(const s of n)this.ensurePeer(s);this.resolveJoin?.();for(const s of n)this.shouldInitiateOffer(s)&&await this.createOffer(s);return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.ensurePeer(t.sender),this.shouldInitiateOffer(t.sender)&&await this.createOffer(t.sender);return}if(i==="rtc.offer"&&t.sender&&t.description){const n=this.ensurePeer(t.sender);n.offerTask&&await n.offerTask;const s=n.connection.signalingState!=="stable",o=!this.shouldInitiateOffer(t.sender);if(s&&!o)return;s&&await n.connection.setLocalDescription({type:"rollback"}),await n.connection.setRemoteDescription(t.description);const r=await n.connection.createAnswer();await n.connection.setLocalDescription(r),this.send({type:"rtc.answer",targetParticipantId:t.sender.participantId,negotiationId:t.negotiationId,description:n.connection.localDescription});return}if(i==="rtc.answer"&&t.sender&&t.description){const n=this.peers.get(t.sender.participantId);if(!n||n.connection.signalingState!=="have-local-offer"||t.negotiationId&&n.activeNegotiationId&&t.negotiationId!==n.activeNegotiationId)return;await n.connection.setRemoteDescription(t.description),n.activeNegotiationId=null;return}if(i==="rtc.ice"&&t.sender&&t.candidate){await this.ensurePeer(t.sender).connection.addIceCandidate(t.candidate).catch(()=>{});return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removePeer(t.sender.participantId);return}i==="rtc.chat"?this.options.onEvent("chatupdate",t):i==="rtc.control"&&this.options.onEvent("control",t)}}ensurePeer(e){const t=this.peers.get(e.participantId);if(t)return t;const i=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"}),n=new MediaStream,s={identity:e,connection:i,stream:n,makingOffer:!1,offerTask:null,activeNegotiationId:null,quality:B.startingQuality(this.quality),poorSamples:0,goodSamples:0,samplesSeen:0,counters:null};this.peers.set(e.participantId,s);for(const c of this.localStream?.getAudioTracks()??[])i.addTrack(c,this.localStream);const o=this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")?this.screenStream:this.localStream,r=o?.getVideoTracks()[0];return r&&o&&(s.videoSender=i.addTrack(r,o)),!this.localStream?.getAudioTracks().length&&this.options.receiveAudio&&i.addTransceiver("audio",{direction:"recvonly"}),!r&&this.options.receiveVideo&&(s.videoTransceiver=i.addTransceiver("video",{direction:"recvonly"}),s.videoSender=s.videoTransceiver.sender),this.applyCodecPreference(i),this.applyVideoQuality(i),i.addEventListener("icecandidate",c=>{c.candidate&&this.send({type:"rtc.ice",targetParticipantId:e.participantId,candidate:c.candidate.toJSON()})}),i.addEventListener("track",c=>{n.getTracks().some(d=>d.id===c.track.id)||n.addTrack(c.track),this.options.onEvent("remotestream",{participant:e,stream:n,track:c.track})}),i.addEventListener("connectionstatechange",()=>{["failed","closed"].includes(i.connectionState)&&this.removePeer(e.participantId),this.options.onEvent("peerconnectionstate",{participant:e,state:i.connectionState})}),s}async createOffer(e){const t=this.ensurePeer(e);if(t.offerTask)return t.offerTask;if(t.connection.signalingState!=="stable")return;const i=this.performOffer(t);t.offerTask=i;try{await i}finally{t.offerTask===i&&(t.offerTask=null)}}async performOffer(e){e.makingOffer=!0;try{const t=await e.connection.createOffer({offerToReceiveAudio:this.options.receiveAudio,offerToReceiveVideo:this.options.receiveVideo});if(e.connection.signalingState!=="stable")return;await e.connection.setLocalDescription(t);const i=te();e.activeNegotiationId=i,this.send({type:"rtc.offer",targetParticipantId:e.identity.participantId,negotiationId:i,description:e.connection.localDescription})}finally{e.makingOffer=!1}}shouldInitiateOffer(e){return this.options.credential.participantId.localeCompare(e.participantId)<0}async renegotiateAll(){for(const e of this.peers.values())await this.createOffer(e.identity)}async replaceVideoTrack(e){let t=!1;for(const i of this.peers.values()){const n=i.videoSender??i.connection.getSenders().find(o=>o.track?.kind==="video")??i.connection.getTransceivers().find(o=>o.receiver.track.kind==="video")?.sender,s=i.videoTransceiver??i.connection.getTransceivers().find(o=>o.sender===n);if(n){const o=e?this.options.receiveVideo?"sendrecv":"sendonly":this.options.receiveVideo?"recvonly":"inactive";s&&s.direction!==o&&(s.direction=o,t=!0),await n.replaceTrack(e),i.videoSender=n,s&&(i.videoTransceiver=s)}else e&&(i.videoSender=i.connection.addTrack(e,this.screenStream??this.localStream??new MediaStream([e])),t=!0)}t&&await this.renegotiateAll(),await this.applyVideoQuality()}async applyVideoQuality(e){const t=e?[...this.peers.values()].filter(i=>i.connection===e):[...this.peers.values()];for(const i of t)await this.applyPeerVideoQuality(i)}async applyPeerVideoQuality(e){const t=this.screenStream?.getVideoTracks()[0]?.id;for(const i of e.connection.getSenders().filter(n=>n.track?.kind==="video")){const n=!!(t&&i.track?.id===t),s=n?Ee:this.quality==="audio"?Ie.audio:q[e.quality],o=i.getParameters(),r=o.encodings?.length?o.encodings:[{}];o.encodings=r.map(c=>({...c,...s})),o.degradationPreference=n?"maintain-resolution":"maintain-framerate",await i.setParameters(o)}}startQualitySampling(){this.stopQualitySampling(),this.qualityTimer=setInterval(()=>{this.samplePeerQuality()},5e3),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopQualitySampling(){this.qualityTimer&&clearInterval(this.qualityTimer),this.qualityTimer=null,this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=[];for(const i of[...this.peers.values()])if(i.connection.connectionState==="connected")try{const n=await i.connection.getStats();for(const s of ee(n.values()))e.push({participantId:i.identity.participantId,level:s.level})}catch{}const t=_(e,this.activeSpeaker);t!==this.activeSpeaker&&(this.activeSpeaker=t,this.options.onEvent("activespeaker",{participantId:t}))}async samplePeerQuality(){if(this.quality!=="audio")for(const e of[...this.peers.values()]){if(e.connection.connectionState!=="connected")continue;let t;try{t=await e.connection.getStats()}catch{continue}const{sample:i,counters:n}=Re(t,e.counters);e.counters=n;const s=Ce(e.quality,i,e.poorSamples,e.goodSamples,{samplesSeen:e.samplesSeen,ceiling:this.quality});e.poorSamples=s.poorSamples,e.goodSamples=s.goodSamples,e.samplesSeen=s.samplesSeen,s.changed&&(e.quality=s.quality,await this.applyPeerVideoQuality(e).catch(()=>{}),this.options.onEvent("peerquality",{participantId:e.identity.participantId,quality:s.quality}))}}static startingQuality(e){return e==="low"?"low":"medium"}applyCodecPreference(e){if(this.preferredVideoCodec==="auto"||!globalThis.RTCRtpReceiver?.getCapabilities)return;const t=RTCRtpReceiver.getCapabilities("video")?.codecs??[],i=`video/${this.preferredVideoCodec}`,n=[...t.filter(s=>s.mimeType.toLowerCase()===i),...t.filter(s=>s.mimeType.toLowerCase()!==i)];for(const s of e.getTransceivers())s.receiver.track.kind==="video"&&typeof s.setCodecPreferences=="function"&&s.setCodecPreferences(n)}removePeer(e){const t=this.peers.get(e);t&&(t.connection.close(),this.peers.delete(e),this.options.onEvent("participantleft",t.identity))}resetPeers(){for(const e of this.peers.values())e.connection.close();this.peers.clear()}startHeartbeat(e){this.stopHeartbeat(),this.stopQualitySampling(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}clearConnectionTimers(){this.stopHeartbeat(),this.stopQualitySampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??te(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function te(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}const $={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1},ie=[{rid:"a",maxBitrate:q.high.maxBitrate,maxFramerate:q.high.maxFramerate},{rid:"b",maxBitrate:q.medium.maxBitrate,maxFramerate:q.medium.maxFramerate,scaleResolutionDownBy:2},{rid:"c",maxBitrate:q.low.maxBitrate,maxFramerate:q.low.maxFramerate,scaleResolutionDownBy:4}],ne={high:"a",medium:"b",low:"c"};class Pe{options;apiBase;socket=null;connection=null;sessionId=null;localStream=null;screenStream=null;remotes=new Map;midToParticipant=new Map;pendingAnnouncements=new Map;videoSender=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;speakerTimer=null;activeSpeaker=null;quality;resolveJoin=null;rejectJoin=null;signalQueue=Promise.resolve();negotiationQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.apiBase=new URL(e.credential.signalingUrl,globalThis.location?.href).origin}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(this.joined)return;const{credential:e}=this.options;if(!e.signalingToken||!e.signalingUrl||!e.roomId||!e.participantId)throw new Error("A room signaling credential is required");this.stopped=!1,!this.localStream&&(this.options.audio||this.options.video)&&(this.localStream=await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})),this.localStream&&this.options.onEvent("localstream",{stream:this.localStream}),await this.openSocket(!1),await this.publish(),this.startSpeakerSampling(),this.announceTracks();const t=[...this.pendingAnnouncements.values()];this.pendingAnnouncements.clear();for(const i of t)await this.handleTrackAnnouncement(i.sender,i.detail)}async leave(){this.stopped=!0,this.clearTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null,await this.closePublishedTracks().catch(()=>{}),this.connection?.close(),this.connection=null,this.sessionId=null,this.remotes.clear(),this.midToParticipant.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(c=>c.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")||await this.replaceVideoTrack(n);return}const r=this.connection?.getSenders().find(c=>c.track?.kind==="audio");r&&await r.replaceTrack(n)}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";await this.applyVideoQuality()}async setPreferredVideoCodec(e){}async setVideoTrack(e){if(e.kind!=="video")throw new Error("A video track is required");this.localStream||(this.localStream=new MediaStream);const t=this.localStream.getVideoTracks()[0];t?.id!==e.id&&(t&&this.localStream.removeTrack(t),this.localStream.addTrack(e)),await this.replaceVideoTrack(e),this.options.onEvent("localstream",{stream:this.localStream,track:e})}getParticipants(){return[...this.remotes.values()].map(e=>e.identity)}getLocalStream(){return this.localStream}getRemoteStreams(){return[...this.remotes.values()].map(e=>({participant:e.identity,stream:e.stream}))}getPrimaryPeerConnection(){return this.connection}sendChat(e,t){this.send({type:"rtc.chat",message:e,participantIds:t??[]})}sendControl(e,t={}){this.send({type:"rtc.control",action:e,detail:t})}async openSocket(e){const{credential:t}=this.options,i=new URL(t.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new Promise((c,d)=>{this.resolveJoin=c,this.rejectJoin=d}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const d=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(d)}).catch(l=>{const u=l instanceof Error?l.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const d=!this.stopped&&(this.joined||e);this.joined=!1,d?(this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0,this.resolveJoin?.();return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.sessionId&&this.announceTracks();return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removeRemote(t.sender.participantId);return}if(i==="rtc.control"){if(String(t.action??"")==="sfu.tracks"&&t.sender){await this.handleTrackAnnouncement(t.sender,t.detail);return}this.options.onEvent("control",t);return}i==="rtc.chat"&&this.options.onEvent("chatupdate",t)}}async publish(){const e=[...this.localStream?.getAudioTracks()??[],...this.localStream?.getVideoTracks()??[]],t=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"});this.connection=t,t.addEventListener("track",r=>this.handleRemoteTrack(r)),t.addEventListener("connectionstatechange",()=>{this.options.onEvent("peerconnectionstate",{participant:this.selfIdentity(),state:t.connectionState})});const i=await this.sfuFetch("POST","/v1/rtc/sfu/sessions");if(this.sessionId=i.sessionId,e.length===0)return;const n=e.map(r=>t.addTransceiver(r,{direction:"sendonly",streams:this.localStream?[this.localStream]:[],...r.kind==="video"?{sendEncodings:ie.map(c=>({...c}))}:{}})),s=await t.createOffer();await t.setLocalDescription(s);const o=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(i.sessionId)}/tracks`,{sessionDescription:t.localDescription,tracks:n.map(r=>({location:"local",mid:r.mid,trackName:this.trackName(r.sender.track)}))});o.sessionDescription&&await t.setRemoteDescription(o.sessionDescription),this.videoSender=t.getSenders().find(r=>r.track?.kind==="video")??null,await this.applyVideoQuality()}trackName(e){return`${this.options.credential.participantId}/${e.kind}`}announceTracks(){if(!this.sessionId)return;const e=[...(this.localStream?.getAudioTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!1})),...(this.localStream?.getVideoTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!0}))];e.length!==0&&this.sendControl("sfu.tracks",{sessionId:this.sessionId,tracks:e})}async handleTrackAnnouncement(e,t){const i=String(t?.sessionId??""),n=(t?.tracks??[]).map(r=>({trackName:String(r.trackName??""),simulcast:r.simulcast===!0})).filter(r=>!!r.trackName),s=n.map(r=>r.trackName);if(!i||s.length===0||e.participantId===this.options.credential.participantId)return;if(!this.connection||!this.sessionId){this.pendingAnnouncements.set(e.participantId,{sender:e,detail:t??{}});return}const o=this.remotes.get(e.participantId)??{identity:e,stream:new MediaStream,sessionId:i,trackNames:[],simulcastNames:new Set,mids:new Map};o.sessionId=i;for(const r of n)r.simulcast&&o.simulcastNames.add(r.trackName);this.remotes.set(e.participantId,o),this.negotiationQueue=this.negotiationQueue.then(async()=>{const r=s.filter(c=>!o.trackNames.includes(c));r.length!==0&&await this.subscribe(o,r)}).catch(r=>{this.options.onEvent("signalingerror",{message:r instanceof Error?r.message:"SFU subscribe failed"})}),await this.negotiationQueue}async subscribe(e,t){const i=this.connection;if(!i||!this.sessionId||t.length===0)return;const n=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks`,{tracks:t.map(s=>({location:"remote",sessionId:e.sessionId,trackName:s,...e.simulcastNames.has(s)?{simulcast:{preferredRid:"a",priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}:{}}))});for(const s of n.tracks??[])s.mid&&(e.mids.set(s.mid,String(s.trackName??"")),this.midToParticipant.set(s.mid,e.identity.participantId));if(e.trackNames=[...new Set([...e.trackNames,...t])],n.requiresImmediateRenegotiation&&n.sessionDescription){await i.setRemoteDescription(n.sessionDescription);const s=await i.createAnswer();await i.setLocalDescription(s),await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`,{sessionDescription:i.localDescription})}}handleRemoteTrack(e){const t=e.transceiver?.mid??null,i=t?this.midToParticipant.get(t):void 0,n=i?this.remotes.get(i):void 0;n&&(n.stream.getTracks().some(s=>s.id===e.track.id)||n.stream.addTrack(e.track),this.options.onEvent("remotestream",{participant:n.identity,stream:n.stream,track:e.track}))}removeRemote(e){const t=this.remotes.get(e);if(t){for(const i of t.mids.keys())this.midToParticipant.delete(i);this.remotes.delete(e),this.options.onEvent("participantleft",t.identity)}}async setRemoteQuality(e,t){const i=this.remotes.get(e);if(!i||!this.sessionId)return;const n=[...i.mids.entries()].find(([,s])=>i.simulcastNames.has(s))?.[0];n&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/update`,{tracks:[{mid:n,simulcast:{preferredRid:ne[t],priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}]})}async replaceVideoTrack(e){this.videoSender&&(await this.videoSender.replaceTrack(e),await this.applyVideoQuality())}async applyVideoQuality(){const e=this.videoSender;if(!e)return;const t=this.screenStream?.getVideoTracks()[0]?.id,i=!!(t&&e.track?.id===t),n=e.getParameters(),s=n.encodings?.length?n.encodings:[{}];if(s.length>1){const o=this.quality==="audio"?null:ne[this.quality];n.encodings=s.map(r=>{if(i)return{...r,active:r.rid==="a",...r.rid==="a"?{maxBitrate:$.maxBitrate,maxFramerate:$.maxFramerate}:{}};const c=ie.find(l=>l.rid===r.rid),d=o!=null&&String(r.rid??"")>=o;return{...r,...c??{},active:d}})}else{const o=this.quality==="audio"?q.audio:q[this.quality],r=i?$:o;n.encodings=s.map(c=>({...c,...r}))}n.degradationPreference=i?"maintain-resolution":"maintain-framerate",await e.setParameters(n)}async closePublishedTracks(){const e=this.connection;if(!e||!this.sessionId)return;const t=e.getTransceivers().filter(i=>i.sender.track&&i.mid).map(i=>({mid:i.mid}));t.length!==0&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/close`,{tracks:t,force:!0})}selfIdentity(){return{participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId??this.options.credential.participantId,displayName:this.options.credential.externalUserId??this.options.credential.participantId,platform:"web"}}async sfuFetch(e,t,i){const n=await fetch(`${this.apiBase}${t}`,{method:e,headers:{authorization:`Bearer ${this.options.credential.signalingToken}`,"content-type":"application/json"},body:i===void 0?void 0:JSON.stringify(i)});if(!n.ok)throw new Error(`SFU request failed with HTTP ${n.status}`);return n.json()}startHeartbeat(e){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}startSpeakerSampling(){this.stopSpeakerSampling(),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopSpeakerSampling(){this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=this.connection;if(!e||e.connectionState!=="connected")return;let t;try{t=await e.getStats()}catch{return}const i=[];for(const s of ee(t.values())){const o=s.mid?this.midToParticipant.get(s.mid):void 0;o&&i.push({participantId:o,level:s.level})}const n=_(i,this.activeSpeaker);n!==this.activeSpeaker&&(this.activeSpeaker=n,this.options.onEvent("activespeaker",{participantId:n}))}clearTimers(){this.stopHeartbeat(),this.stopSpeakerSampling(),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null}scheduleReconnect(){if(this.reconnectTimer||this.stopped)return;const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.openSocket(!0).then(()=>this.announceTracks()).catch(t=>{this.options.onEvent("signalingerror",{message:t instanceof Error?t.message:"RTC signaling connection failed"}),this.scheduleReconnect()})},e)}send(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??Me(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function Me(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function Ae(a,e={}){const t=e.document??globalThis.document,i=!!(t&&t.pictureInPictureEnabled);let n=null;function s(){return n||(n=t.createElement("video"),n.muted=!0,n.playsInline=!0,n.style.position="fixed",n.style.width="1px",n.style.height="1px",n.style.opacity="0",n.style.pointerEvents="none",n.addEventListener("leavepictureinpicture",()=>e.onLeave?.()),t.body.appendChild(n),n)}function o(r){const c=a.getRemoteStreams();return r?c.find(d=>d.participant.participantId===r)?.stream??null:c[0]?.stream??a.getLocalStream()}return{supported:i,async enter(r){if(!i)throw new Error("This browser does not support picture-in-picture");const c=o(r);if(!c)throw new Error("No video stream is available for picture-in-picture");const d=s();d.srcObject!==c&&(d.srcObject=c),await d.play().catch(()=>{}),await d.requestPictureInPicture()},async exit(){t.pictureInPictureElement&&typeof t.exitPictureInPicture=="function"&&await t.exitPictureInPicture()},dispose(){n?.remove(),n=null}}}function Le(a,e){const t=e.document??globalThis.document,i=e.fetchImplementation??globalThis.fetch.bind(globalThis),n=new URL(e.credential.signalingUrl,globalThis.location?.href).origin,s=e.mimeType??"video/webm",o=e.height??720,r=Math.round(o*16/9);let c=null,d=null,l=null,u=null,y=0,f=[],T=!1;async function m(S,b,k,C="application/json"){const M=await i(`${n}${b}`,{method:S,headers:{authorization:`Bearer ${e.credential.signalingToken}`,"content-type":C},body:k===void 0?void 0:C==="application/json"?JSON.stringify(k):k});if(!M.ok)throw new Error(`Recording request failed with HTTP ${M.status}`);return M.json()}return{get recording(){return T},async start(){if(T)return;u=(await m("POST","/v1/rtc/recordings")).recordingId;const b=t.createElement("canvas");b.width=r,b.height=o;const k=b.getContext("2d"),C=new Map,M=(P,L)=>{let R=C.get(P);return R||(R=t.createElement("video"),R.muted=!0,R.playsInline=!0,R.srcObject=L,R.play().catch(()=>{}),C.set(P,R)),R};d=setInterval(()=>{const P=a.getRemoteStreams(),L=a.getLocalStream(),R=[...P.map(U=>({key:U.participant.participantId,stream:U.stream,label:U.participant.displayName})),...L?[{key:"local",stream:L,label:"me"}]:[]];k.fillStyle="#101418",k.fillRect(0,0,r,o);const O=Math.max(1,Math.ceil(Math.sqrt(R.length))),v=Math.max(1,Math.ceil(R.length/O)),V=r/O,F=o/v;R.forEach((U,ke)=>{const we=M(U.key,U.stream),X=ke%O*V,Z=Math.floor(ke/O)*F;we.videoWidth>0&&k.drawImage(we,X+2,Z+2,V-4,F-4),k.fillStyle="rgba(0,0,0,.6)",k.fillRect(X+8,Z+F-30,Math.min(V-16,12+U.label.length*9),22),k.fillStyle="#fff",k.font="14px system-ui",k.fillText(U.label,X+14,Z+F-14)})},66);const E=b.captureStream(15);l=new AudioContext;const g=l.createMediaStreamDestination(),p=new Set,h=()=>{const P=[...a.getRemoteStreams().map(L=>L.stream),...a.getLocalStream()?[a.getLocalStream()]:[]];for(const L of P)for(const R of L.getAudioTracks())p.has(R.id)||(p.add(R.id),l.createMediaStreamSource(new MediaStream([R])).connect(g))};h();const I=setInterval(h,1e3),D=()=>clearInterval(I);for(const P of g.stream.getAudioTracks())E.addTrack(P);c=(e.mediaRecorderFactory??((P,L)=>new MediaRecorder(P,L)))(E,{mimeType:s}),f=[],c.addEventListener("dataavailable",P=>{P.data&&P.data.size>0&&f.push(P.data)}),c.addEventListener("stop",D),c.start(1e3),y=Date.now(),T=!0},async stop(){if(!T||!c||!u)throw new Error("The recorder is not running");T=!1;const S=c,b=new Promise(g=>S.addEventListener("stop",()=>g(),{once:!0}));S.stop(),await b,d&&clearInterval(d),d=null,await l?.close().catch(()=>{}),l=null;const k=new Blob(f,{type:s});f=[];const C=Math.max(1,Math.round((Date.now()-y)/1e3)),M=await m("PUT",`/v1/rtc/recordings/${encodeURIComponent(u)}/media`,k,s);await m("POST",`/v1/rtc/recordings/${encodeURIComponent(u)}/complete`,{durationSeconds:C});const E={recordingId:u,sizeBytes:M.sizeBytes,durationSeconds:C};return u=null,c=null,E},dispose(){d&&clearInterval(d),d=null;try{c?.stop()}catch{}c=null,l?.close().catch(()=>{}),l=null,T=!1}}}const ae=["none","blur","aurora","cybergrid","bokeh","sunset","image"],se=["none","snow","confetti","vignette"],z={aurora(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#050b1e"),n.addColorStop(1,"#0b2540"),a.fillStyle=n,a.fillRect(0,0,e,t);for(let s=0;s<3;s+=1){a.beginPath();for(let o=0;o<=e;o+=16){const r=t*.35+s*40+Math.sin(o/140+i/900+s)*60;o===0?a.moveTo(o,r):a.lineTo(o,r)}a.lineTo(e,0),a.lineTo(0,0),a.closePath(),a.fillStyle=`hsla(${140+s*40+Math.sin(i/1300)*20}, 80%, 55%, 0.14)`,a.fill()}},cybergrid(a,e,t,i){a.fillStyle="#0a0118",a.fillRect(0,0,e,t),a.strokeStyle="rgba(255,0,180,0.5)",a.lineWidth=1.5;const n=t*.45;for(let o=0;o<12;o+=1){const r=n+(o*34+i/18)%(t-n);a.beginPath(),a.moveTo(0,r),a.lineTo(e,r),a.stroke()}for(let o=-8;o<=8;o+=1)a.beginPath(),a.moveTo(e/2+o*24,n),a.lineTo(e/2+o*e*.12,t),a.stroke();const s=a.createLinearGradient(0,t*.1,0,n);s.addColorStop(0,"#ffd34d"),s.addColorStop(1,"#ff2d78"),a.fillStyle=s,a.beginPath(),a.arc(e/2,n,t*.22,Math.PI,0),a.fill()},bokeh(a,e,t,i){a.fillStyle="#14100c",a.fillRect(0,0,e,t);for(let n=0;n<26;n+=1){const s=(Math.sin(n*12.9898)*.5+.5)*e,o=(Math.sin(n*78.233)*.5+.5)*t+Math.sin(i/1500+n)*12,r=14+n%5*12;a.fillStyle=`hsla(${35+n%4*12}, 90%, 60%, ${.1+n%3*.05})`,a.beginPath(),a.arc(s,o,r,0,Math.PI*2),a.fill()}},sunset(a,e,t,i){const n=a.createLinearGradient(0,0,0,t);n.addColorStop(0,"#2b1055"),n.addColorStop(.55,"#d1548a"),n.addColorStop(1,"#ffb36b"),a.fillStyle=n,a.fillRect(0,0,e,t),a.fillStyle="#ffdf8e",a.beginPath(),a.arc(e*.5,t*.62+Math.sin(i/4e3)*4,t*.13,0,Math.PI*2),a.fill(),a.fillStyle="rgba(20,8,40,0.85)",a.fillRect(0,t*.78,e,t*.22)}};function De(a,e={}){const t=e.document??globalThis.document,i=e.width??1280,n=e.height??720,s=e.frameRate??24,o=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!o)throw new Error("A video track is required");let r=e.background??"none",c=e.overlay??"none",d=e.backgroundImage??null,l=e.segmenter??null,u=null,y=!1;const f=t.createElement("video");f.muted=!0,f.playsInline=!0,f.srcObject=new MediaStream([o]),f.play().catch(()=>{});const T=t.createElement("canvas");T.width=i,T.height=n;const m=T.getContext("2d"),S=t.createElement("canvas");S.width=i,S.height=n;const b=S.getContext("2d"),k=Array.from({length:80},(p,h)=>({x:(Math.sin(h*12.9898)*.5+.5)*i,y:(Math.sin(h*78.233)*.5+.5)*n,speed:.6+h%5*.5,drift:Math.sin(h)*.6,size:2+h%4,hue:h*47%360}));function C(){if(u){b.clearRect(0,0,i,n),b.drawImage(u,0,0,i,n),b.globalCompositeOperation="source-in",b.drawImage(f,0,0,i,n),b.globalCompositeOperation="source-over",m.drawImage(S,0,0);return}const p=i*.42,h=p*9/16,I=i-p-24,D=n-h-24;m.save(),m.shadowColor="rgba(0,0,0,.5)",m.shadowBlur=24,m.beginPath(),m.roundRect(I,D,p,h,14),m.clip(),m.drawImage(f,I,D,p,h),m.restore()}function M(p){if(r==="none"?m.drawImage(f,0,0,i,n):r==="blur"?(m.filter="blur(14px)",m.drawImage(f,-16,-16,i+32,n+32),m.filter="none",u&&C()):r==="image"&&d?(m.drawImage(d,0,0,i,n),C()):((z[r]??z.aurora)(m,i,n,p),C()),c==="snow"||c==="confetti")for(const h of k)h.y+=h.speed*(c==="confetti"?1.6:1),h.x+=h.drift,h.y>n&&(h.y=-6,h.x=Math.random()*i),m.fillStyle=c==="snow"?"rgba(255,255,255,.85)":`hsl(${h.hue}, 90%, 60%)`,c==="confetti"?m.fillRect(h.x,h.y,h.size+2,h.size):(m.beginPath(),m.arc(h.x,h.y,h.size/2,0,Math.PI*2),m.fill());else if(c==="vignette"){const h=m.createRadialGradient(i/2,n/2,n*.4,i/2,n/2,n*.85);h.addColorStop(0,"rgba(0,0,0,0)"),h.addColorStop(1,"rgba(0,0,0,0.55)"),m.fillStyle=h,m.fillRect(0,0,i,n)}}const E=setInterval(()=>{M(Date.now()),l&&!y&&r!=="none"&&(y=!0,l.segment(f).then(p=>{u=p}).catch(()=>{u=null}).finally(()=>{y=!1}))},Math.round(1e3/s)),g=T.captureStream(s);return{track:g.getVideoTracks()[0],stream:g,setBackground(p,h){r=ae.includes(p)?p:"none",h!==void 0&&(d=h),r==="none"&&(u=null)},setOverlay(p){c=se.includes(p)?p:"none"},setSegmenter(p){l=p,p||(u=null)},dispose(){clearInterval(E),l?.close?.();for(const p of g.getTracks())p.stop();f.srcObject=null}}}function J(){return{blinkLeft:0,blinkRight:0,jawOpen:0,smile:0,browUp:0,pupilX:0,pupilY:0,headYaw:0,headPitch:0,headRoll:0,tracked:!1}}function A(a,e){const t=a.find(i=>i.categoryName===e);return t?Math.max(0,Math.min(1,t.score)):0}function re(a){if(!a||a.length<16)return{yaw:0,pitch:0,roll:0};a[0],a[4];const e=a[8],t=a[1],i=a[5],n=a[9],s=a[10],o=Math.atan2(e,s),r=Math.asin(Math.max(-1,Math.min(1,-n))),c=Math.atan2(t,i);return{yaw:o,pitch:r,roll:c}}function oe(a,e,t=1){if(!a||a.length===0)return J();const i=re(e),n=(A(a,"eyeLookOutRight")+A(a,"eyeLookInLeft"))/2,s=(A(a,"eyeLookOutLeft")+A(a,"eyeLookInRight"))/2,o=(A(a,"eyeLookUpLeft")+A(a,"eyeLookUpRight"))/2,r=(A(a,"eyeLookDownLeft")+A(a,"eyeLookDownRight"))/2,c=(A(a,"mouthSmileLeft")+A(a,"mouthSmileRight"))/2,d=(A(a,"mouthFrownLeft")+A(a,"mouthFrownRight"))/2,l=A(a,"eyeBlinkLeft"),u=A(a,"eyeBlinkRight"),y=Math.min(1,Math.abs(i.yaw)/.35)*Math.max(0,Math.min(1,t)),f=(l+u)/2;return{blinkLeft:l+(f-l)*y,blinkRight:u+(f-u)*y,jawOpen:A(a,"jawOpen"),smile:c-d,browUp:(A(a,"browInnerUp")+A(a,"browOuterUpLeft")+A(a,"browOuterUpRight"))/3,pupilX:n-s,pupilY:r-o,headYaw:i.yaw,headPitch:i.pitch,headRoll:i.roll,tracked:!0}}function H(a,e,t=.4){const i=(n,s)=>n+(s-n)*t;return{blinkLeft:i(a.blinkLeft,e.blinkLeft),blinkRight:i(a.blinkRight,e.blinkRight),jawOpen:i(a.jawOpen,e.jawOpen),smile:i(a.smile,e.smile),browUp:i(a.browUp,e.browUp),pupilX:i(a.pupilX,e.pupilX),pupilY:i(a.pupilY,e.pupilY),headYaw:i(a.headYaw,e.headYaw),headPitch:i(a.headPitch,e.headPitch),headRoll:i(a.headRoll,e.headRoll),tracked:e.tracked}}function ce(a,e,t,i,n){n||(a.fillStyle="#101822",a.fillRect(0,0,t,i));const s=n?n.x+e.headYaw*n.radius*.1:t/2+e.headYaw*t*.18,o=n?n.y+e.headPitch*n.radius*.1:i/2+e.headPitch*i*.18,r=n?n.radius:Math.min(t,i)*.28;a.save(),a.translate(s,o),a.rotate(e.headRoll*.6);const c=Math.max(-1.35,Math.min(1.35,e.headYaw)),d=Math.max(-.9,Math.min(.9,e.headPitch)),l=Math.sin(c),u=p=>Math.sin(p+c)*r,y=p=>Math.cos(p+c),f=Math.sin(d)*r*.22;a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-l*r*.35,-r*.25,r*.98,r*.95,0,0,Math.PI*2),a.fill(),a.fillStyle=e.tracked?"#f2c9a0":"#5a6470",a.beginPath(),a.ellipse(0,0,r*(1-Math.abs(l)*.12),r*1.12,0,0,Math.PI*2),a.fill(),a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-l*r*.28,-r*.72+f*.4,r*.92,r*.5,0,Math.PI,Math.PI*2),a.fill();const T=0;a.fillStyle="#e5b58a",a.beginPath();const m=u(T)*.9,S=r*.12+f;a.ellipse(m+l*r*.14,S,r*.11,r*.14,0,0,Math.PI*2),a.fill();for(const p of[-1,1]){const h=p*.42,I=y(h);if(I<=.12)continue;const D=u(h)*.82,N=Math.max(.2,I),P=p<0?e.blinkLeft:e.blinkRight,L=Math.max(.06,1-P),R=e.browUp*r*.14;a.strokeStyle="#4a2e18",a.lineWidth=r*.07,a.lineCap="round",a.beginPath(),a.moveTo(D-r*.2*N,-r*.42-R+f),a.quadraticCurveTo(D,-r*.52-R+f,D+r*.2*N,-r*.44-R+f),a.stroke();const O=-r*.18+f;a.fillStyle="#ffffff",a.beginPath(),a.ellipse(D,O,r*.16*N,r*.12*L,0,0,Math.PI*2),a.fill(),a.fillStyle="#22303c",a.beginPath(),a.ellipse(D+e.pupilX*r*.07*N,O+e.pupilY*r*.05,r*.06*N,r*.06*L,0,0,Math.PI*2),a.fill()}const b=Math.max(.25,y(0)),k=u(0)*.7,C=r*.55*b,M=r*(.04+e.jawOpen*.34),E=e.smile*r*.12,g=r*.42+f;a.fillStyle="#8c2f2f",a.beginPath(),a.moveTo(k-C/2,g-E),a.quadraticCurveTo(k,g+M,k+C/2,g-E),a.quadraticCurveTo(k,g-M*.25,k-C/2,g-E),a.closePath(),a.fill(),a.restore(),!e.tracked&&!n&&(a.fillStyle="#9fb0bd",a.font=`${Math.round(i*.045)}px system-ui`,a.textAlign="center",a.fillText("No face detected",t/2,i*.9),a.textAlign="left")}const W={mouthGain:1.7,blinkGain:1,eyeSync:1,smoothing:.4,avatarScale:1,avatarLift:0,lostHoldMs:400};function G(){return{render(a,e,t,i,n){ce(a,e,t,i,n??void 0)}}}const Y=a=>Math.max(0,Math.min(1,a));function Oe(a,e={}){const t=e.document??globalThis.document,i=e.width??1280,n=e.height??720,s=e.frameRate??24,o=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!o)throw new Error("A video track is required");let r=e.tracker??null,c=e.renderer??G(),d=e.mode??"replace";const l={...W,...e.tuning},u=e.backdrop??"#101822",y=t.createElement("video");y.muted=!0,y.playsInline=!0,y.srcObject=new MediaStream([o]),y.play().catch(()=>{});const f=t.createElement("canvas");f.width=i,f.height=n;const T=f.getContext("2d");let m=J(),S=null,b=0;function k(g){if(g.length<=454)return;const p=g[10],h=g[152],I=g[234],D=g[454],N=(I.x+D.x)/2*i,P=(I.y+D.y)/2*n,L=Math.hypot((h.x-p.x)*i,(h.y-p.y)*n),R=Math.max(.6,Math.cos(m.headPitch)),O={x:N,y:P,radius:Math.max(24,L/R*.8)};S=S?{x:S.x+(O.x-S.x)*.4,y:S.y+(O.y-S.y)*.4,radius:S.radius+(O.radius-S.radius)*.15}:O}function C(g){if(r&&y.readyState>=2){let h=null;try{h=r.detect(y,g)}catch{}const I=oe(h?.categories??null,h?.matrix??null,l.eyeSync);I.jawOpen=Y(I.jawOpen*l.mouthGain),I.blinkLeft=Y(I.blinkLeft*l.blinkGain),I.blinkRight=Y(I.blinkRight*l.blinkGain),I.tracked?(b=g,m=H(m,I,l.smoothing),h?.landmarks&&k(h.landmarks)):m.tracked&&g-b>l.lostHoldMs&&(m=H(m,{...m,jawOpen:0,browUp:0,smile:m.smile*.5,blinkLeft:.1,blinkRight:.1},.05))}d==="overlay"&&y.readyState>=2?T.drawImage(y,0,0,i,n):(T.fillStyle=u,T.fillRect(0,0,i,n));const p=d==="overlay"&&S?{x:S.x,y:S.y+l.avatarLift*S.radius,radius:S.radius*l.avatarScale}:null;c?.render(T,m,i,n,p)}const M=setInterval(()=>C(Date.now()),Math.round(1e3/s)),E=f.captureStream(s);return{track:E.getVideoTracks()[0],stream:E,getRig:()=>({...m}),setMode(g){d=g==="overlay"?"overlay":"replace"},setTracker(g){r?.close?.(),r=g},setRenderer(g){c?.dispose?.(),c=g??G()},setTuning(g){for(const p of Object.keys(W)){const h=g[p];typeof h=="number"&&Number.isFinite(h)&&(l[p]=h)}},getTuning:()=>({...l}),dispose(){clearInterval(M),r?.close?.(),c?.dispose?.();for(const g of E.getTracks())g.stop();y.srcObject=null}}}class le extends EventTarget{mode;options;client=null;peerConnection;state="initialized";latestMediaScore=null;latestQuality=null;statsTimer=null;statsRunning=!1;previousCounters=null;lastTelemetryAt=0;joinPromise=null;requestedQuality;effectiveQuality;poorQualitySamples=0;goodQualitySamples=0;constructor(e){if(super(),!e.credential?.signalingToken||!e.credential.signalingUrl||!e.credential.roomId||!e.credential.participantId)throw new Error("A valid room signaling credential is required");this.mode=e.mode,this.requestedQuality=e.mode==="voice"?"audio":e.quality??"auto",this.effectiveQuality=this.requestedQuality==="auto"?"medium":this.requestedQuality,this.peerConnection=e.peerConnection??null,this.options={...e,audio:e.audio??!0,video:e.mode==="voice"?!1:e.video??!0,receiveAudio:e.receiveAudio??!0,receiveVideo:e.mode==="voice"?!1:e.receiveVideo??!0,statsInterval:Math.max(1e3,e.statsInterval??5e3),telemetryInterval:Math.max(1e4,e.telemetryInterval??3e4),autoReportStats:e.autoReportStats??!0}}async initialize(){if(this.client)return this;try{const t=(this.options.transport==="auto"?this.mode==="meeting"?"sfu":"mesh":this.options.transport??"mesh")==="sfu"?Pe:B;return this.client=new t({credential:{signalingToken:this.options.credential.signalingToken,signalingUrl:this.options.credential.signalingUrl,roomId:this.options.credential.roomId,participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId,iceServers:this.options.credential.iceServers},audio:this.options.audio,video:this.options.video,receiveAudio:this.options.receiveAudio,receiveVideo:this.options.receiveVideo,quality:this.effectiveQuality,preferredVideoCodec:this.options.preferredVideoCodec??"auto",iceTransportPolicy:this.options.iceTransportPolicy??"all",localStream:this.options.localStream,onEvent:(i,n)=>this.handleNativeEvent(i,n)}),await this.client.initialize(),this.startStats(),this.emit("initialized",{mode:this.mode,sdkVersion:Se}),this.options.autoJoin&&await this.join(),this}catch(e){this.state="failed";const t=ve(e,"RTC initialization failed");throw this.emit("error",j(t)),t}}async join(){const e=this.requireClient();return this.joinPromise?this.joinPromise:(this.state="joining",this.emit("joining",{}),this.joinPromise=e.join().catch(t=>{this.state="failed";const i=ve(t,"RTC connection failed");throw this.emit("error",j(i)),i}).finally(()=>{this.joinPromise=null}),this.joinPromise)}async leave(){this.client&&(await this.joinPromise?.catch(()=>{}),await this.sampleAndReport("final").catch(()=>{}),await this.client.leave(),this.state="disconnected",this.stopStats())}async setMuted(e){this.requireClient().setMuted(e),this.emit("mediachange",{kind:"audio",enabled:!e})}async setCameraEnabled(e){if(this.mode==="voice"&&e)throw new Error("Camera is unavailable in voice mode");await this.requireClient().setCameraEnabled(e),this.emit("mediachange",{kind:"video",enabled:e})}async setVideoTrack(e){if(this.mode==="voice")throw new Error("Camera is unavailable in voice mode");await this.requireClient().setVideoTrack(e),this.emit("mediachange",{kind:"video",enabled:e.enabled,trackId:e.id})}async setScreenShareEnabled(e){await this.requireClient().setScreenShareEnabled(e),this.emit("mediachange",{kind:"screenshare",enabled:e})}async setDevice(e){await this.requireClient().setDevice(e),this.emit("devicechange",{deviceId:e.deviceId,kind:e.kind})}async setQuality(e){if(this.mode==="voice"&&e!=="audio")throw new Error("Voice calls only support audio quality");this.requestedQuality=e,this.poorQualitySamples=0,this.goodQualitySamples=0,await this.applyEffectiveQuality(e==="auto"?"medium":e,"manual")}async setPreferredVideoCodec(e){await this.requireClient().setPreferredVideoCodec(e),this.emit("codecchange",{preferredVideoCodec:e})}async getDevices(){return navigator.mediaDevices?.enumerateDevices?navigator.mediaDevices.enumerateDevices():[]}getParticipants(){return this.requireClient().getParticipants().map(fe)}getLocalStream(){return this.client?.getLocalStream()??null}getRemoteStreams(){return(this.client?.getRemoteStreams()??[]).map(e=>({participant:fe(e.participant),stream:e.stream}))}setPeerConnection(e){this.peerConnection=e,this.previousCounters=null}async sendChatMessage(e,t){const i=e.trim();if(!i)throw new Error("message is required");this.requireClient().sendChat(i,t)}async sendControlMessage(e,t={}){const i=e.trim();if(!i)throw new Error("action is required");this.requireClient().sendControl(i,t)}async createPoll(e,t,i={}){const n=t.map(s=>s.trim()).filter(Boolean);if(!e.trim()||n.length<2)throw new Error("A poll requires a question and at least two options");this.requireClient().sendControl("poll.create",{poll:{id:`poll-${crypto.randomUUID()}`,question:e.trim(),options:n,votes:{},...i}})}async votePoll(e,t){this.requireClient().sendControl("poll.vote",{pollId:e,optionIndex:t})}async requestStageAccess(){this.requireClient().sendControl("stage.request")}async cancelStageAccess(){this.requireClient().sendControl("stage.cancel")}async joinStage(){this.requireClient().sendControl("stage.join")}async leaveStage(){this.requireClient().sendControl("stage.leave")}async grantStageAccess(e){this.requireClient().sendControl("stage.grant",{participantIds:e})}async denyStageAccess(e){this.requireClient().sendControl("stage.deny",{participantIds:e})}async startRecording(e=!1){this.requireClient().sendControl("recording.start",{allowMultiple:e})}async stopRecording(e){this.requireClient().sendControl("recording.stop",{recordingId:e})}async pauseRecording(e){this.requireClient().sendControl("recording.pause",{recordingId:e})}async resumeRecording(e){this.requireClient().sendControl("recording.resume",{recordingId:e})}async runPreflight(e={}){const t=[],i=globalThis.isSecureContext===!0;i||t.push("secure_context_required");let n="unavailable";if(e.requestMedia!==!1&&navigator.mediaDevices?.getUserMedia)try{(await navigator.mediaDevices.getUserMedia({audio:this.options.audio,video:this.options.video})).getTracks().forEach(y=>y.stop()),n="granted"}catch{n="denied",t.push("media_permission_denied")}const s=navigator.mediaDevices?.enumerateDevices?await navigator.mediaDevices.enumerateDevices().catch(()=>[]):[],o=s.filter(u=>u.kind==="audioinput").length,r=s.filter(u=>u.kind==="videoinput").length;this.options.audio&&o===0&&t.push("microphone_unavailable"),this.options.video&&r===0&&t.push("camera_unavailable");const c=e.probeUrl??this.options.preflightProbeUrl;let d=null;if(c){const u=performance.now();try{const y=await fetch(c,{method:"HEAD",cache:"no-store",credentials:"omit"});d=Math.round(performance.now()-u),y.ok||t.push("network_probe_failed")}catch{t.push("network_probe_failed")}}const l={passed:t.length===0,sampledAt:new Date().toISOString(),secureContext:i,mediaPermission:n,microphoneCount:o,cameraCount:r,probeLatencyMs:d,effectiveConnectionType:ge()?.effectiveType??null,issues:t};return this.emit("preflight",l),this.options.autoReportStats&&await this.reportQuality({sampleType:"preflight",sampledAt:l.sampledAt,connectionState:l.passed?"ready":"failed",rttMs:l.probeLatencyMs,effectiveConnectionType:l.effectiveConnectionType}).catch(()=>{}),l}async collectQualitySample(e="runtime"){const t=this.options.statsProvider?await this.options.statsProvider():{},i=this.peerConnection?await Ue(this.peerConnection,this.previousCounters):null;i?.counters&&(this.previousCounters=i.counters);const s={...{sampleType:e,sampledAt:new Date().toISOString(),connectionState:this.peerConnection?.connectionState??this.state,mediaScore:this.latestMediaScore,networkScore:this.latestMediaScore==null?5:Math.max(0,Math.min(5,Math.round(this.latestMediaScore))),estimatedMos:4.5,rttMs:null,jitterMs:null,packetLossPct:null,packetLossCumulativePct:null,packetsLost:null,packetsReceived:null,inboundBitrateBps:null,outboundBitrateBps:null,availableOutgoingBitrateBps:null,framesPerSecond:null,frameWidth:null,frameHeight:null,framesDropped:null,freezeCount:null,freezeDurationMs:null,concealedSamples:null,codec:null,transportProtocol:null,candidateType:null,qualityLimitationReason:null,deviceMemoryGb:Be(),effectiveConnectionType:ge()?.effectiveType??null},...i?.sample??{},...t,sampleType:e,sampledAt:new Date().toISOString()},o=Qe(s);return s.networkScore=o.networkScore,s.estimatedMos=o.estimatedMos,this.latestQuality=s,s}async reportQuality(e){const t=this.options.credential.telemetryToken,i=this.options.telemetryEndpoint??this.options.credential.telemetryEndpoint;if(!t||!i)return;const n=await fetch(i,{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify(e),keepalive:e.sampleType==="final"});if(!n.ok)throw new Error(`RTC telemetry failed with HTTP ${n.status}`)}getStats(){const e=this.client,t=e?.getLocalStream();return{state:this.state,participantCount:e?e.getParticipants().length+1:0,audioEnabled:!!t?.getAudioTracks().some(i=>i.enabled),videoEnabled:!!t?.getVideoTracks().some(i=>i.enabled),screenShareEnabled:!1,latestMediaScore:this.latestMediaScore,latestQuality:this.latestQuality,sampledAt:new Date().toISOString()}}getNativeClient(){return this.client}destroy(){this.stopStats(),this.client&&this.client.leave(),this.client=null,this.joinPromise=null,this.peerConnection=null,this.state="disconnected"}handleNativeEvent(e,t){e==="connected"?(this.state="connected",this.peerConnection=this.client?.getPrimaryPeerConnection()??null):e==="disconnected"?this.state="disconnected":e==="peerconnectionstate"&&(this.peerConnection=this.client?.getPrimaryPeerConnection()??null),this.emit(e,t)}startStats(){this.stopStats(),this.statsTimer=setInterval(()=>{this.sampleAndReport("runtime")},this.options.statsInterval)}async sampleAndReport(e){if(!this.statsRunning){this.statsRunning=!0;try{const t=await this.collectQualitySample(e);await this.adaptQuality(t),this.emit("stats",{...this.getStats(),latestQuality:t});const i=Date.now(),n=e!=="runtime"||i-this.lastTelemetryAt>=this.options.telemetryInterval;this.options.autoReportStats&&n&&(await this.reportQuality(t),this.lastTelemetryAt=i)}catch(t){this.emit("telemetryerror",j(t))}finally{this.statsRunning=!1}}}stopStats(){this.statsTimer!==null&&clearInterval(this.statsTimer),this.statsTimer=null}async adaptQuality(e){if(this.requestedQuality!=="auto"||this.options.adaptiveQuality===!1||this.mode==="voice")return;if((this.client?.getParticipants().length??0)>1){this.poorQualitySamples=0,this.goodQualitySamples=0,this.effectiveQuality!=="high"&&await this.applyEffectiveQuality("high","network");return}const t=String(e.qualityLimitationReason??"").toLowerCase(),i=(e.packetLossPct??0)>=8||(e.rttMs??0)>=800||t==="cpu"||t==="bandwidth",n=i||(e.packetLossPct??0)>=3||(e.rttMs??0)>=350||e.availableOutgoingBitrateBps!=null&&e.availableOutgoingBitrateBps<35e4,s=(e.packetLossPct??100)<1&&(e.rttMs??1e3)<180&&!t&&(e.availableOutgoingBitrateBps==null||e.availableOutgoingBitrateBps>1e6);this.poorQualitySamples=n?this.poorQualitySamples+1:0,this.goodQualitySamples=s?this.goodQualitySamples+1:0;const o=["low","medium","high"],r=o.indexOf(this.effectiveQuality);i&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality("low","network")):this.poorQualitySamples>=2&&r>0?(this.poorQualitySamples=0,await this.applyEffectiveQuality(o[r-1],"network")):this.goodQualitySamples>=6&&r>=0&&r<o.length-1&&(this.goodQualitySamples=0,await this.applyEffectiveQuality(o[r+1],"network"))}async applyEffectiveQuality(e,t){if(this.effectiveQuality===e&&this.client)return;const i=this.effectiveQuality;this.effectiveQuality=e,this.client&&await this.client.setQuality(e),this.emit("qualitychange",{requestedQuality:this.requestedQuality,quality:e,previous:i,reason:t})}requireClient(){if(!this.client)throw new Error("Call initialize() before using the RTC client");return this.client}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}async function qe(a){return new le(a).initialize()}let x=null;function de(){if(x)return x;const a=globalThis,e=a.AudioContext??a.webkitAudioContext;return e?(x=new e,x):null}async function he(){const a=de();return a?(a.state==="suspended"&&await a.resume().catch(()=>{}),a.state==="running"):!1}class Ne{options;media=null;cadenceTimer=null;oscillators=new Set;constructor(e){this.options=e}unlock(){return he()}startIncoming(){this.start("incoming",this.options.ringtoneUrl)}startRingback(){this.start("ringback",this.options.ringbackUrl)}stop(){this.cadenceTimer&&clearInterval(this.cadenceTimer),this.cadenceTimer=null,this.media?.pause(),this.media&&(this.media.currentTime=0),this.media=null;for(const e of this.oscillators)try{e.stop()}catch{}this.oscillators.clear()}start(e,t){if(this.stop(),this.options.sounds===!1)return;if(t&&typeof Audio<"u"){this.media=new Audio(t),this.media.loop=!0,this.media.play().catch(()=>{});return}const i=()=>this.playBuiltInTone(e);i(),this.cadenceTimer=setInterval(i,e==="incoming"?3e3:3500)}playBuiltInTone(e){const t=de();if(!t||t.state!=="running")return;const i=e==="incoming"?1.25:1,n=e==="incoming"?[440,480]:[425],s=t.createGain(),o=t.currentTime;s.gain.setValueAtTime(1e-4,o),s.gain.exponentialRampToValueAtTime(.045,o+.025),s.gain.setValueAtTime(.045,o+i-.06),s.gain.exponentialRampToValueAtTime(1e-4,o+i),s.connect(t.destination);for(const r of n){const c=t.createOscillator();c.type="sine",c.frequency.value=r,c.connect(s),this.oscillators.add(c),c.onended=()=>this.oscillators.delete(c),c.start(o),c.stop(o+i+.02)}}}class ue extends EventTarget{options;socket=null;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;heartbeatDeadline=0;currentCall=null;outgoingCallId=null;mount=null;terminalTimer=null;tokenRefreshTimer=null;tokenRequest=null;currentToken;currentTokenExpiresAt;callAudio;mediaClients=new Map;activeCallIds=new Set;seenInvitationIds=new Set;startCallRequest=null;lastDisconnect=null;constructor(e){if(super(),!e.token&&!e.tokenProvider)throw new Error("A client token or tokenProvider is required");if(e.token&&!e.token.startsWith("rtcc_"))throw new Error("A valid RTC client token is required");if(!e.signalingUrl&&!e.apiBaseUrl&&!globalThis.location?.origin)throw new Error("signalingUrl or apiBaseUrl is required outside a browser");this.options=e,this.currentToken=e.token??null,this.currentTokenExpiresAt=e.tokenExpiresAt,this.callAudio=new Ne(e),this.mount=me(e.mount),this.scheduleTokenRefresh()}connect(){if(!this.options.signalingUrl)throw new Error("signalingUrl is required to connect signaling");return this.stopped=!1,this.scheduleTokenRefresh(),this.openSocket("initial"),this}setMount(e){this.clearUi(),this.mount=me(e),this.currentCall&&this.renderIncoming(this.currentCall)}attachMediaClient(e,t){if(!e.trim())throw new Error("callId is required");return this.mediaClients.set(e,t),()=>{this.mediaClients.get(e)===t&&this.mediaClients.delete(e)}}async startCall(e){if(this.startCallRequest)return this.startCallRequest;if(this.activeCallIds.size>0)throw new Error("Another call is already active");const t=e.targets?.length?e.targets:e.target?[e.target]:[];if(t.length===0)throw new Error("startCall requires at least one target");const i={mode:e.mode,targets:t,metadata:e.metadata,idempotencyKey:e.idempotencyKey},n=(async()=>{await this.callAudio.unlock(),this.callAudio.startRingback();let s;try{s=await this.request("/v1/rtc/client/calls",{method:"POST",headers:e.idempotencyKey?{"idempotency-key":e.idempotencyKey}:void 0,body:JSON.stringify(i)})}catch(o){throw this.callAudio.stop(),o}return!s.credential&&s.callerCredential&&(s.credential=s.callerCredential),this.outgoingCallId=String(s.call?.id??"")||null,this.outgoingCallId&&this.activeCallIds.add(this.outgoingCallId),this.emit("outgoingcall",s),s})();this.startCallRequest=n;try{return await n}finally{this.startCallRequest===n&&(this.startCallRequest=null)}}async accept(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"accept");return this.activeCallIds.add(e),this.callAudio.stop(),this.dismiss(e),this.emit("callaccepted",t),t}async reject(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"reject");return this.releaseMedia(e),this.showTerminal(e,"Call declined"),this.emit("callrejected",t),t}async busy(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");const t=await this.callAction(e,"busy");return this.releaseMedia(e),this.showTerminal(e,"Line busy"),t}async end(e){this.callAudio.stop();const t=await this.callAction(e,"end");return this.releaseMedia(e),this.showTerminal(e,"Call ended"),t}async cancel(e){this.callAudio.stop();const t=await this.callAction(e,"cancel");return this.releaseMedia(e),this.showTerminal(e,"Call cancelled"),t}handleIncomingCall(e){if(!e?.id||!e.caller?.id||!e.caller.name||e.mode!=="voice"&&e.mode!=="video")throw new Error("Incoming call payload is invalid");if(e.expiresAt&&Date.parse(e.expiresAt)<=Date.now()){this.emit("callexpired",{id:e.id,expiresAt:e.expiresAt});return}if(this.currentCall?.id===e.id){this.currentCall=e,this.emit("callrestored",e);return}if(this.seenInvitationIds.has(e.id)){this.emit("callrestored",e);return}this.seenInvitationIds.add(e.id),this.activeCallIds.add(e.id),this.currentCall=e,this.callAudio.startIncoming(),this.renderIncoming(e),this.emit("incomingcall",e)}handleCallUpdate(e){const t=e.id===this.currentCall?.id||e.id===this.outgoingCallId;t&&e.status==="accepted"?(this.activeCallIds.add(e.id),this.callAudio.stop(),this.dismiss(e.id)):t&&["rejected","busy","cancelled","ended","failed","missed"].includes(e.status)&&this.showTerminal(e.id,Ve(e.status)),["rejected","busy","cancelled","ended","failed","missed"].includes(e.status)&&this.releaseMedia(e.id),this.emit("callupdated",e)}disconnect(){this.stopped=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.reconnectTimer=null,this.heartbeatTimer=null,this.tokenRefreshTimer=null,this.socket?.close(1e3,"Client disconnected"),this.socket=null,this.callAudio.stop(),this.dismiss();for(const e of this.mediaClients.keys())this.releaseMedia(e);this.activeCallIds.clear(),this.seenInvitationIds.clear()}destroy(){this.disconnect(),this.mount=null}async openSocket(e){if(!this.options.signalingUrl||this.stopped||this.socket?.readyState===WebSocket.OPEN||this.socket?.readyState===WebSocket.CONNECTING)return;let t;try{t=await this.resolveToken(e,e==="reconnect")}catch(s){this.emit("tokenerror",j(s)),!this.stopped&&this.options.reconnect!==!1&&this.scheduleReconnect();return}if(this.stopped||this.socket)return;const i=new URL(this.options.signalingUrl,globalThis.location?.href);i.protocol=i.protocol==="https:"?"wss:":"ws:";const n=new WebSocket(i,["rtc-client",t]);this.socket=n,n.addEventListener("open",()=>{const s=this.reconnectAttempt>0,o=this.lastDisconnect;this.reconnectAttempt=0,this.heartbeatDeadline=Date.now()+45e3,this.emit("signalingconnected",{resumed:s}),o&&(this.reportSignalingDiagnostic({event:"recovered",code:o.code,reason:o.reason,wasClean:o.wasClean,outageMs:Date.now()-o.at}),this.lastDisconnect=null),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{if(n.readyState===WebSocket.OPEN){if(Date.now()>this.heartbeatDeadline){n.close(4e3,"Signaling heartbeat timed out");return}n.send("ping")}},2e4)}),n.addEventListener("message",s=>{this.heartbeatDeadline=Date.now()+45e3,this.handleSignal(String(s.data))}),n.addEventListener("close",s=>{if(this.socket!==n)return;this.socket=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;const o=!this.stopped&&this.options.reconnect!==!1,r={code:s.code,reason:s.reason,wasClean:s.wasClean,willReconnect:o,attempt:this.reconnectAttempt+1};o&&(this.lastDisconnect={code:s.code,reason:s.reason,wasClean:s.wasClean,at:Date.now()}),this.emit("signalingdisconnected",r),this.reportSignalingDiagnostic({event:o?"reconnecting":"disconnected",...r}),o&&this.scheduleReconnect()}),n.addEventListener("error",()=>{const s={message:"RTC signaling connection failed",reconnecting:!this.stopped&&this.options.reconnect!==!1,attempt:this.reconnectAttempt+1};this.emit("signalingerror",s),this.reportSignalingDiagnostic({event:"error",attempt:s.attempt})})}handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i==="call.incoming"&&t.call&&typeof t.call=="object"){this.handleIncomingCall(t.call);return}if(i==="call.updated"){const n=t.call;n?.id&&n.status&&this.handleCallUpdate({id:n.id,status:n.status});return}i==="presence"?this.emit("presence",{users:t.users??[]}):i!=="pong"&&this.emit(i||"signal",t)}async callAction(e,t){return this.request(`/v1/rtc/client/calls/${encodeURIComponent(e)}/${t}`,{method:"POST",body:"{}"})}async request(e,t,i=!1){const n=this.options.apiBaseUrl??this.options.signalingUrl??globalThis.location?.origin;if(!n)throw new Error("apiBaseUrl is required for call actions");const s=new URL(n,globalThis.location?.href),o=await this.resolveToken("initial"),r=new Headers(t.headers);r.set("authorization",`Bearer ${o}`),r.set("content-type","application/json");const c=await fetch(new URL(e,s.origin),{...t,headers:r}),d=await c.json().catch(()=>({}));if(c.status===401&&this.options.tokenProvider&&!i)return await this.resolveToken("unauthorized",!0),this.request(e,t,!0);if(!c.ok){const l=d.error;throw new Error(typeof l=="string"?l:l?.message||`RTC API failed with HTTP ${c.status}`)}return d}renderIncoming(e){if(!this.mount)return;this.clearUi();const t=document.createElement("section");t.dataset.rtcIncomingCall=e.id,t.setAttribute("role","dialog"),t.setAttribute("aria-label",`Incoming ${e.mode} call from ${e.caller.name}`),Object.assign(t.style,{display:"grid",gap:"14px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const i=document.createElement("small");i.textContent=e.mode==="video"?"Incoming video call":"Incoming voice call",i.style.color="#5d7079";const n=document.createElement("strong");n.textContent=e.caller.name,n.style.fontSize="20px";const s=document.createElement("div");Object.assign(s.style,{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"});const o=pe("Decline","#fff","#a32920","#d9a09b"),r=pe("Answer","#fff","#08745f","#08745f");o.addEventListener("click",()=>{this.reject(e.id).catch(c=>this.emit("error",j(c)))}),r.addEventListener("click",()=>{this.accept(e.id).catch(c=>this.emit("error",j(c)))}),s.appendChild(o),s.appendChild(r),t.appendChild(i),t.appendChild(n),t.appendChild(s),this.mount.replaceChildren(t)}dismiss(e){e&&this.currentCall?.id!==e&&this.outgoingCallId!==e||(this.currentCall=null,(!e||this.outgoingCallId===e)&&(this.outgoingCallId=null),this.callAudio.stop(),this.clearUi())}showTerminal(e,t){if(this.callAudio.stop(),this.currentCall?.id===e&&(this.currentCall=null),this.outgoingCallId===e&&(this.outgoingCallId=null),!this.mount)return;this.clearUi();const i=document.createElement("section");i.dataset.rtcCallTerminal=e,i.setAttribute("role","status"),Object.assign(i.style,{display:"grid",placeItems:"center",gap:"10px",minHeight:"150px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const n=document.createElement("strong");n.textContent=t,n.style.fontSize="18px",i.appendChild(n),this.mount.replaceChildren(i);const s=Math.max(0,this.options.terminalStateDurationMs??1600);this.terminalTimer=setTimeout(()=>this.clearUi(),s)}clearUi(){this.terminalTimer&&clearTimeout(this.terminalTimer),this.terminalTimer=null,this.mount&&this.mount.replaceChildren()}scheduleReconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer);const e=Math.min(3e4,1e3*2**Math.min(this.reconnectAttempt,5))+Math.round(Math.random()*300);this.reconnectAttempt+=1,this.reconnectTimer=setTimeout(()=>{this.openSocket("reconnect")},e)}async resolveToken(e,t=!1){if(!t&&this.currentToken&&!this.tokenNeedsRefresh())return this.currentToken;if(!this.options.tokenProvider){if(!this.currentToken)throw new Error("A valid RTC client token is required");return this.currentToken}return this.tokenRequest?this.tokenRequest:(this.tokenRequest=(async()=>{const i=await this.options.tokenProvider?.({reason:e,previousExpiresAt:this.currentTokenExpiresAt}),n=typeof i=="string"?{token:i}:i;if(!n?.token?.startsWith("rtcc_"))throw new Error("tokenProvider returned an invalid RTC client token");return this.currentToken=n.token,this.currentTokenExpiresAt=n.expiresAt,this.scheduleTokenRefresh(),this.emit("tokenrefreshed",{reason:e,expiresAt:n.expiresAt}),n.token})().finally(()=>{this.tokenRequest=null}),this.tokenRequest)}tokenNeedsRefresh(){if(!this.currentTokenExpiresAt)return!1;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();return Number.isFinite(e)?e<=this.refreshSkew(e):!0}refreshSkew(e){const t=Math.max(5e3,this.options.tokenRefreshSkewMs??3e5);return Math.min(t,Math.max(5e3,e*.2))}scheduleTokenRefresh(){if(this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.tokenRefreshTimer=null,this.stopped||!this.options.tokenProvider||!this.currentTokenExpiresAt)return;const e=Date.parse(this.currentTokenExpiresAt)-Date.now();if(!Number.isFinite(e))return;const t=Math.max(0,e-this.refreshSkew(e));this.tokenRefreshTimer=setTimeout(()=>{this.tokenRefreshTimer=null,this.resolveToken("expiring",!0).catch(i=>{this.emit("tokenerror",j(i)),this.stopped||(this.tokenRefreshTimer=setTimeout(()=>this.scheduleTokenRefresh(),3e4))})},Math.min(t,2147e6))}releaseMedia(e){this.activeCallIds.delete(e),this.seenInvitationIds.delete(e);const t=this.mediaClients.get(e);t&&(this.mediaClients.delete(e),t.leave().catch(()=>{}).finally(()=>t.destroy()))}reportSignalingDiagnostic(e){this.options.reportSignalingDiagnostics===!1||this.stopped||this.request("/v1/rtc/client/signaling-events",{method:"POST",body:JSON.stringify({...e,occurredAt:new Date().toISOString()})}).catch(t=>this.emit("diagnosticserror",j(t)))}emit(e,t){const i={type:e,detail:t,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent(e,{detail:i})),this.options.onEvent?.(i)}}function je(a){const e=new ue(a);return a.autoConnect!==!1&&a.signalingUrl&&e.connect(),e}function me(a){return a?typeof a=="string"?document.querySelector(a):a:null}function pe(a,e,t,i){const n=document.createElement("button");return n.type="button",n.textContent=a,Object.assign(n.style,{minHeight:"44px",border:`1px solid ${i}`,borderRadius:"6px",background:t,color:e,cursor:"pointer",font:"inherit",fontWeight:"700"}),n}function Ve(a){return{rejected:"Call declined",busy:"Line busy",cancelled:"Call cancelled",ended:"Call ended",failed:"Call failed",missed:"No answer"}[a]??"Call ended"}function fe(a){return{id:a.participantId,name:a.displayName,role:"participant"}}async function Ue(a,e){const t=await a.getStats(),i=new Map,n=new Map;let s=0,o=0,r=0,c=0,d=0,l=0,u=0,y=0,f=0,T=0,m=0,S=0,b=0,k=null,C=null,M=null,E=null,g=null,p=null;t.forEach(v=>{n.set(v.id,v),v.type==="codec"&&i.set(v.id,String(v.mimeType??""))}),t.forEach(v=>{if(v.type==="inbound-rtp"&&!v.isRemote&&(s+=Number(v.bytesReceived??0),r+=Math.max(0,Number(v.packetsLost??0)),c+=Math.max(0,Number(v.packetsReceived??0)),Number.isFinite(v.jitter)&&(d+=Number(v.jitter),l+=1),u=Math.max(u,Number(v.framesPerSecond??0)),y=Math.max(y,Number(v.frameWidth??0)),f=Math.max(f,Number(v.frameHeight??0)),T+=Number(v.framesDropped??0),m+=Number(v.freezeCount??0),S+=Number(v.totalFreezesDuration??0),b+=Number(v.concealedSamples??0),g||=i.get(String(v.codecId??""))??null),v.type==="outbound-rtp"&&!v.isRemote&&(o+=Number(v.bytesSent??0),p||=v.qualityLimitationReason?String(v.qualityLimitationReason):null,g||=i.get(String(v.codecId??""))??null),v.type==="candidate-pair"&&(v.selected||v.nominated)&&v.state==="succeeded"){k=Number.isFinite(v.currentRoundTripTime)?Number(v.currentRoundTripTime):k,C=Number.isFinite(v.availableOutgoingBitrate)?Number(v.availableOutgoingBitrate):C;const V=n.get(v.localCandidateId);M=V?.candidateType?String(V.candidateType):M,E=V?.protocol?String(V.protocol):E}});const h=Date.now(),I=e?Math.max(.001,(h-e.timestamp)/1e3):0,D=e?Math.max(0,Math.round((s-e.inboundBytes)*8/I)):null,N=e?Math.max(0,Math.round((o-e.outboundBytes)*8/I)):null,P=r+c,L=e?Math.max(0,r-e.packetsLost):r,R=e?Math.max(0,c-e.packetsReceived):c,O=L+R;return{counters:{timestamp:h,inboundBytes:s,outboundBytes:o,packetsLost:r,packetsReceived:c},sample:{rttMs:k===null?null:k*1e3,jitterMs:l?d/l*1e3:null,packetLossPct:O?L/O*100:null,packetLossCumulativePct:P?r/P*100:null,packetsLost:r,packetsReceived:c,inboundBitrateBps:D,outboundBitrateBps:N,availableOutgoingBitrateBps:C,framesPerSecond:u||null,frameWidth:y||null,frameHeight:f||null,framesDropped:T,freezeCount:m,freezeDurationMs:Math.round(S*1e3),concealedSamples:b,codec:g,transportProtocol:E,candidateType:M,qualityLimitationReason:p}}}function Qe(a){let e=a.mediaScore==null?5:Math.round(ye(Number(a.mediaScore),0,5));const t=K(a.packetLossPct)??0,i=K(a.rttMs)??0,n=K(a.jitterMs)??0,s=String(a.connectionState??"unknown").toLowerCase();["failed","closed","disconnected"].includes(s)?e=0:["reconnecting","checking"].includes(s)&&(e=Math.min(e,2)),t>=15?e=Math.min(e,0):t>=8?e=Math.min(e,1):t>=3?e=Math.min(e,2):t>=1&&(e=Math.min(e,3)),i>=1200?e=Math.min(e,0):i>=800?e=Math.min(e,1):i>=400?e=Math.min(e,2):i>=250&&(e=Math.min(e,3)),n>=150?e=Math.min(e,1):n>=80?e=Math.min(e,2):n>=40&&(e=Math.min(e,3));const o=Number(ye(4.5-t*.09-i*.0011-n*.006,1,4.5).toFixed(2));return{networkScore:e,estimatedMos:o}}function ge(){return navigator.connection??null}function Be(){const a=Number(navigator.deviceMemory);return Number.isFinite(a)?a:null}function K(a){const e=Number(a);return Number.isFinite(e)?e:null}function ye(a,e,t){return Math.min(t,Math.max(e,a))}function j(a){return a instanceof Error?{name:a.name,message:a.message}:{name:"ServiceError",message:String(a??"Service request failed")}}function ve(a,e){return a instanceof Error?a:new Error(e)}const Se="1.3.16";w.AVATAR_TUNING_DEFAULTS=W,w.BACKGROUND_PRESETS=ae,w.OVERLAY_EFFECTS=se,w.RtcClient=le,w.RtcIncomingClient=ue,w.SPEAKER_NOISE_FLOOR=.02,w.SPEAKER_TAKEOVER_RATIO=1.4,w.createAvatarPipeline=Oe,w.createCallRecorder=Le,w.createPictureInPictureController=Ae,w.createRtcClient=qe,w.createRtcIncomingClient=je,w.createVideoEffectsPipeline=De,w.drawAvatar=ce,w.headPoseFromMatrix=re,w.neutralRig=J,w.pickActiveSpeaker=_,w.rigFromBlendshapes=oe,w.scenePainters=z,w.smoothRig=H,w.stylizedAvatarRenderer=G,w.unlockRtcCallAudio=he,w.version=Se,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streaming-cdn/rtc-web",
3
- "version": "1.3.15",
3
+ "version": "1.3.16",
4
4
  "type": "module",
5
5
  "main": "dist/rtc-web.umd.js",
6
6
  "module": "dist/rtc-web.es.js",