@streaming-cdn/rtc-web 1.3.28 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- (function(k,F){typeof exports=="object"&&typeof module<"u"?F(exports):typeof define=="function"&&define.amd?define(["exports"],F):(k=typeof globalThis<"u"?globalThis:k||self,F(k.StreamingCdnRtc={}))})(this,(function(k){"use strict";function J(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 ce(a){const e=[];for(const t of a){if(String(t.type??"")!=="inbound-rtp"||String(t.kind??t.mediaType??"")!=="audio")continue;const i=Number(t.audioLevel??NaN);Number.isFinite(i)&&e.push({mid:typeof t.mid=="string"?t.mid:null,level:i})}return e}const V={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4},low:{active:!0,maxBitrate:35e4,maxFramerate:15,scaleResolutionDownBy:4},medium:{active:!0,maxBitrate:11e5,maxFramerate:24,scaleResolutionDownBy:2},high:{active:!0,maxBitrate:28e5,maxFramerate:30,scaleResolutionDownBy:1}},je={warmupSamples:3,poorSamplesToStepDown:2,goodSamplesToStepUp:6,criticalLossPct:8,criticalRttMs:800,poorLossPct:3,poorRttMs:350,goodLossPct:1,goodRttMs:250,sustainRatio:.8,bandwidthSustainRatio:.7};function Qe(a){const e=String(a??"").trim().toLowerCase();return!e||e==="none"?"":e==="cpu"||e==="bandwidth"?e:"other"}function _e(a,e,t,i,n={}){const s={...je,...n.thresholds},o=["low","medium","high"],r=n.ceiling&&n.ceiling!=="audio"?n.ceiling:"high",c=o.indexOf(r),l=o.indexOf(a),d=(n.samplesSeen??s.warmupSamples)+1,u=d<=s.warmupSamples,v=Qe(e.qualityLimitationReason),p=e.packetLossPct??0,S=e.rttMs??0,b=e.availableOutgoingBitrateBps,y=V[a].maxBitrate,T=p>=s.criticalLossPct||S>=s.criticalRttMs||v==="cpu",f=v==="bandwidth"&&(b==null||b<y*s.bandwidthSustainRatio),M=T||p>=s.poorLossPct||S>=s.poorRttMs||f,I=l<o.length-1?V[o[l+1]].maxBitrate:null,D=v==="bandwidth"&&b!=null&&I!=null&&b>=I*s.bandwidthSustainRatio,P=b==null||b>=y*s.sustainRatio,L=p<s.goodLossPct&&S<s.goodRttMs&&(!v||D)&&P,w=M?t+1:0,R=L?i+1:0,m={quality:a,poorSamples:w,goodSamples:R,samplesSeen:d,changed:!1};return l>c?{quality:o[c],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:u?m:T&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:w>=s.poorSamplesToStepDown&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:R>=s.goodSamplesToStepUp&&l<c?{quality:o[l+1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:m}function Fe(a,e){const t=a instanceof Map?a:new Map(a);let i=0,n=0,s=null,o=null,r=null;for(const d of t.values()){const u=String(d.type??"");if(u==="outbound-rtp"&&String(d.kind??"")==="video"&&(i+=Number(d.packetsSent??0),s=d.qualityLimitationReason??s),u==="remote-inbound-rtp"&&String(d.kind??"")==="video"){n+=Number(d.packetsLost??0);const v=Number(d.roundTripTime??NaN);Number.isFinite(v)&&(o=v*1e3)}if(u==="candidate-pair"&&(d.nominated===!0||String(d.state??"")==="succeeded")){const v=Number(d.currentRoundTripTime??NaN);o==null&&Number.isFinite(v)&&(o=v*1e3);const p=Number(d.availableOutgoingBitrate??NaN);Number.isFinite(p)&&(r=p)}}const c={packetsSent:i,packetsLost:n,timestamp:Date.now()};let l=null;if(e){const d=i-e.packetsSent,u=n-e.packetsLost;d>0&&u>=0&&(l=u/(d+u)*100)}return{sample:{packetLossPct:l,rttMs:o,availableOutgoingBitrateBps:r,qualityLimitationReason:s},counters:c}}function G(a,e){const t=new URL(String(a),e);return t.protocol==="https:"?t.protocol="wss:":t.protocol==="http:"&&(t.protocol="ws:"),t}function Y(a,e){const t=new URL(String(a),e);return t.protocol==="wss:"?t.protocol="https:":t.protocol==="ws:"&&(t.protocol="http:"),t}const ze={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4}},He={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1};class z{options;socket=null;localStream=null;screenStream=null;peers=new Map;joinPromise=null;resolveJoin=null;rejectJoin=null;joined=!1;stopped=!1;reconnectAttempt=0;reconnectTimer=null;heartbeatTimer=null;qualityTimer=null;speakerTimer=null;activeSpeaker=null;heartbeatDeadline=0;quality;preferredVideoCodec;signalQueue=Promise.resolve();socketGeneration=0;constructor(e){this.options=e,this.localStream=e.localStream??null,this.quality=e.quality??"high",this.preferredVideoCodec=e.preferredVideoCodec??"auto"}async initialize(){if(!globalThis.RTCPeerConnection||!globalThis.WebSocket)throw new Error("This browser does not support WebRTC")}async join(){if(!this.joined){if(this.joinPromise)return this.joinPromise;this.joinPromise=this.connect();try{await this.joinPromise}finally{this.joinPromise=null}}}async leave(){this.stopped=!0,this.clearConnectionTimers(),this.send({type:"rtc.leave"}),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null;for(const e of this.peers.values())e.connection.close();this.peers.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(r=>r.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(r=>r.readyState==="live")||await this.replaceVideoTrack(n);return}for(const r of this.peers.values()){const c=r.connection.getSenders().find(l=>l.track?.kind===s);c?await c.replaceTrack(n):r.connection.addTrack(n,this.localStream)}await this.applyVideoQuality()}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";for(const t of this.peers.values())t.quality=z.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=G(t.signalingUrl,globalThis.location?.href),n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.startQualitySampling(),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),this.stopQualitySampling(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.resetPeers(),this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0;const n=Array.isArray(t.peers)?t.peers:[];for(const s of n)this.ensurePeer(s);this.resolveJoin?.();for(const s of n)this.shouldInitiateOffer(s)&&await this.createOffer(s);return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.ensurePeer(t.sender),this.shouldInitiateOffer(t.sender)&&await this.createOffer(t.sender);return}if(i==="rtc.offer"&&t.sender&&t.description){const n=this.ensurePeer(t.sender);n.offerTask&&await n.offerTask;const s=n.connection.signalingState!=="stable",o=!this.shouldInitiateOffer(t.sender);if(s&&!o)return;s&&await n.connection.setLocalDescription({type:"rollback"}),await n.connection.setRemoteDescription(t.description);const r=await n.connection.createAnswer();await n.connection.setLocalDescription(r),this.send({type:"rtc.answer",targetParticipantId:t.sender.participantId,negotiationId:t.negotiationId,description:n.connection.localDescription});return}if(i==="rtc.answer"&&t.sender&&t.description){const n=this.peers.get(t.sender.participantId);if(!n||n.connection.signalingState!=="have-local-offer"||t.negotiationId&&n.activeNegotiationId&&t.negotiationId!==n.activeNegotiationId)return;await n.connection.setRemoteDescription(t.description),n.activeNegotiationId=null;return}if(i==="rtc.ice"&&t.sender&&t.candidate){await this.ensurePeer(t.sender).connection.addIceCandidate(t.candidate).catch(()=>{});return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removePeer(t.sender.participantId);return}i==="rtc.chat"?this.options.onEvent("chatupdate",t):i==="rtc.control"&&this.options.onEvent("control",t)}}ensurePeer(e){const t=this.peers.get(e.participantId);if(t)return t;const i=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"}),n=new MediaStream,s={identity:e,connection:i,stream:n,makingOffer:!1,offerTask:null,activeNegotiationId:null,quality:z.startingQuality(this.quality),poorSamples:0,goodSamples:0,samplesSeen:0,counters:null};this.peers.set(e.participantId,s);for(const c of this.localStream?.getAudioTracks()??[])i.addTrack(c,this.localStream);const o=this.screenStream?.getVideoTracks().some(c=>c.readyState==="live")?this.screenStream:this.localStream,r=o?.getVideoTracks()[0];return r&&o&&(s.videoSender=i.addTrack(r,o)),!this.localStream?.getAudioTracks().length&&this.options.receiveAudio&&i.addTransceiver("audio",{direction:"recvonly"}),!r&&this.options.receiveVideo&&(s.videoTransceiver=i.addTransceiver("video",{direction:"recvonly"}),s.videoSender=s.videoTransceiver.sender),this.applyCodecPreference(i),this.applyVideoQuality(i),i.addEventListener("icecandidate",c=>{c.candidate&&this.send({type:"rtc.ice",targetParticipantId:e.participantId,candidate:c.candidate.toJSON()})}),i.addEventListener("track",c=>{n.getTracks().some(l=>l.id===c.track.id)||n.addTrack(c.track),this.options.onEvent("remotestream",{participant:e,stream:n,track:c.track})}),i.addEventListener("connectionstatechange",()=>{["failed","closed"].includes(i.connectionState)&&this.removePeer(e.participantId),this.options.onEvent("peerconnectionstate",{participant:e,state:i.connectionState})}),s}async createOffer(e){const t=this.ensurePeer(e);if(t.offerTask)return t.offerTask;if(t.connection.signalingState!=="stable")return;const i=this.performOffer(t);t.offerTask=i;try{await i}finally{t.offerTask===i&&(t.offerTask=null)}}async performOffer(e){e.makingOffer=!0;try{const t=await e.connection.createOffer({offerToReceiveAudio:this.options.receiveAudio,offerToReceiveVideo:this.options.receiveVideo});if(e.connection.signalingState!=="stable")return;await e.connection.setLocalDescription(t);const i=le();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?He:this.quality==="audio"?ze.audio:V[e.quality],o=i.getParameters(),r=o.encodings?.length?o.encodings:[{}];o.encodings=r.map(c=>({...c,...s})),o.degradationPreference=n?"maintain-resolution":"maintain-framerate",await i.setParameters(o)}}startQualitySampling(){this.stopQualitySampling(),this.qualityTimer=setInterval(()=>{this.samplePeerQuality()},5e3),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopQualitySampling(){this.qualityTimer&&clearInterval(this.qualityTimer),this.qualityTimer=null,this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=[];for(const i of[...this.peers.values()])if(i.connection.connectionState==="connected")try{const n=await i.connection.getStats();for(const s of ce(n.values()))e.push({participantId:i.identity.participantId,level:s.level})}catch{}const t=J(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}=Fe(t,e.counters);e.counters=n;const s=_e(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??le(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function le(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}const K={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1},de=[{rid:"a",maxBitrate:V.high.maxBitrate,maxFramerate:V.high.maxFramerate},{rid:"b",maxBitrate:V.medium.maxBitrate,maxFramerate:V.medium.maxFramerate,scaleResolutionDownBy:2},{rid:"c",maxBitrate:V.low.maxBitrate,maxFramerate:V.low.maxFramerate,scaleResolutionDownBy:4}],he={high:"a",medium:"b",low:"c"};class $e{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=Y(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=G(t.signalingUrl,globalThis.location?.href),n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0,this.resolveJoin?.();return}if(i==="rtc.room.join"&&t.sender){this.options.onEvent("participantjoined",t.sender),this.sessionId&&this.announceTracks();return}if((i==="rtc.peer.left"||i==="rtc.leave")&&t.sender){this.removeRemote(t.sender.participantId);return}if(i==="rtc.control"){if(String(t.action??"")==="sfu.tracks"&&t.sender){await this.handleTrackAnnouncement(t.sender,t.detail);return}this.options.onEvent("control",t);return}i==="rtc.chat"&&this.options.onEvent("chatupdate",t)}}async publish(){const e=[...this.localStream?.getAudioTracks()??[],...this.localStream?.getVideoTracks()??[]],t=new RTCPeerConnection({iceServers:this.options.credential.iceServers??[],iceTransportPolicy:this.options.iceTransportPolicy??"all"});this.connection=t,t.addEventListener("track",r=>this.handleRemoteTrack(r)),t.addEventListener("connectionstatechange",()=>{this.options.onEvent("peerconnectionstate",{participant:this.selfIdentity(),state:t.connectionState})});const i=await this.sfuFetch("POST","/v1/rtc/sfu/sessions");if(this.sessionId=i.sessionId,e.length===0)return;const n=e.map(r=>t.addTransceiver(r,{direction:"sendonly",streams:this.localStream?[this.localStream]:[],...r.kind==="video"&&this.simulcastEnabled()?{sendEncodings:de.map(c=>({...c}))}:{}})),s=await t.createOffer();await t.setLocalDescription(s);const o=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(i.sessionId)}/tracks`,{sessionDescription:t.localDescription,tracks:n.map(r=>({location:"local",mid:r.mid,trackName:this.trackName(r.sender.track)}))});o.sessionDescription&&await t.setRemoteDescription(o.sessionDescription),this.videoSender=t.getSenders().find(r=>r.track?.kind==="video")??null,await this.applyVideoQuality()}trackName(e){return`${this.options.credential.participantId}/${e.kind}`}announceTracks(){if(!this.sessionId)return;const e=[...(this.localStream?.getAudioTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!1})),...(this.localStream?.getVideoTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:this.simulcastEnabled()}))];e.length!==0&&this.sendControl("sfu.tracks",{sessionId:this.sessionId,tracks:e})}async handleTrackAnnouncement(e,t){const i=String(t?.sessionId??""),n=(t?.tracks??[]).map(r=>({trackName:String(r.trackName??""),simulcast:r.simulcast===!0})).filter(r=>!!r.trackName),s=n.map(r=>r.trackName);if(!i||s.length===0||e.participantId===this.options.credential.participantId)return;if(!this.connection||!this.sessionId){this.pendingAnnouncements.set(e.participantId,{sender:e,detail:t??{}});return}const o=this.remotes.get(e.participantId)??{identity:e,stream:new MediaStream,sessionId:i,trackNames:[],simulcastNames:new Set,mids:new Map};o.sessionId=i;for(const r of n)r.simulcast&&o.simulcastNames.add(r.trackName);this.remotes.set(e.participantId,o),this.negotiationQueue=this.negotiationQueue.then(async()=>{const r=s.filter(c=>!o.trackNames.includes(c));r.length!==0&&await this.subscribe(o,r)}).catch(r=>{this.options.onEvent("signalingerror",{message:r instanceof Error?r.message:"SFU subscribe failed"})}),await this.negotiationQueue}async subscribe(e,t){const i=this.connection;if(!i||!this.sessionId||t.length===0)return;const n=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks`,{tracks:t.map(s=>({location:"remote",sessionId:e.sessionId,trackName:s,...e.simulcastNames.has(s)?{simulcast:{preferredRid:"a",priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}:{}}))});for(const s of n.tracks??[])s.mid&&(e.mids.set(s.mid,String(s.trackName??"")),this.midToParticipant.set(s.mid,e.identity.participantId));if(e.trackNames=[...new Set([...e.trackNames,...t])],n.requiresImmediateRenegotiation&&n.sessionDescription){await i.setRemoteDescription(n.sessionDescription);const s=await i.createAnswer();await i.setLocalDescription(s),await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`,{sessionDescription:i.localDescription})}}handleRemoteTrack(e){const t=e.transceiver?.mid??null,i=t?this.midToParticipant.get(t):void 0,n=i?this.remotes.get(i):void 0;n&&(n.stream.getTracks().some(s=>s.id===e.track.id)||n.stream.addTrack(e.track),this.options.onEvent("remotestream",{participant:n.identity,stream:n.stream,track:e.track}))}removeRemote(e){const t=this.remotes.get(e);if(t){for(const i of t.mids.keys())this.midToParticipant.delete(i);this.remotes.delete(e),this.options.onEvent("participantleft",t.identity)}}async setRemoteQuality(e,t){const i=this.remotes.get(e);if(!i||!this.sessionId)return;const n=[...i.mids.entries()].find(([,s])=>i.simulcastNames.has(s))?.[0];n&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/update`,{tracks:[{mid:n,simulcast:{preferredRid:he[t],priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}]})}async replaceVideoTrack(e){this.videoSender&&(await this.videoSender.replaceTrack(e),await this.applyVideoQuality())}simulcastEnabled(){return this.options.simulcast!==!1}async applyVideoQuality(){const e=this.videoSender;if(!e)return;const t=this.screenStream?.getVideoTracks()[0]?.id,i=!!(t&&e.track?.id===t),n=e.getParameters(),s=n.encodings?.length?n.encodings:[{}];if(s.length>1){const o=this.quality==="audio"?null:he[this.quality];n.encodings=s.map(r=>{if(i)return{...r,active:r.rid==="a",...r.rid==="a"?{maxBitrate:K.maxBitrate,maxFramerate:K.maxFramerate}:{}};const c=de.find(d=>d.rid===r.rid),l=o!=null&&String(r.rid??"")>=o;return{...r,...c??{},active:l}})}else{const o=this.quality==="audio"?V.audio:V[this.quality],r=i?K: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 ce(t.values())){const o=s.mid?this.midToParticipant.get(s.mid):void 0;o&&i.push({participantId:o,level:s.level})}const n=J(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??We(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function We(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function Je(a,e={}){const t=e.document??globalThis.document,i=!!(t&&t.pictureInPictureEnabled);let n=null;function s(){return n||(n=t.createElement("video"),n.muted=!0,n.playsInline=!0,n.style.position="fixed",n.style.width="1px",n.style.height="1px",n.style.opacity="0",n.style.pointerEvents="none",n.addEventListener("leavepictureinpicture",()=>e.onLeave?.()),t.body.appendChild(n),n)}function o(r){const c=a.getRemoteStreams();return r?c.find(l=>l.participant.participantId===r)?.stream??null:c[0]?.stream??a.getLocalStream()}return{supported:i,async enter(r){if(!i)throw new Error("This browser does not support picture-in-picture");const c=o(r);if(!c)throw new Error("No video stream is available for picture-in-picture");const l=s();l.srcObject!==c&&(l.srcObject=c),await l.play().catch(()=>{}),await l.requestPictureInPicture()},async exit(){t.pictureInPictureElement&&typeof t.exitPictureInPicture=="function"&&await t.exitPictureInPicture()},dispose(){n?.remove(),n=null}}}function Ge(a,e){const t=e.document??globalThis.document,i=e.fetchImplementation??globalThis.fetch.bind(globalThis),n=Y(e.credential.signalingUrl,globalThis.location?.href).origin,s=e.mimeType??"video/webm",o=e.height??720,r=Math.round(o*16/9);let c=null,l=null,d=null,u=null,v=0,p=[],S=!1;async function b(y,T,f,M="application/json"){const I=await i(`${n}${T}`,{method:y,headers:{authorization:`Bearer ${e.credential.signalingToken}`,"content-type":M},body:f===void 0?void 0:M==="application/json"?JSON.stringify(f):f});if(!I.ok)throw new Error(`Recording request failed with HTTP ${I.status}`);return I.json()}return{get recording(){return S},async start(){if(S)return;u=(await b("POST","/v1/rtc/recordings")).recordingId;const T=t.createElement("canvas");T.width=r,T.height=o;const f=T.getContext("2d"),M=new Map,I=(C,A)=>{let E=M.get(C);return E||(E=t.createElement("video"),E.muted=!0,E.playsInline=!0,E.srcObject=A,E.play().catch(()=>{}),M.set(C,E)),E};l=setInterval(()=>{const C=a.getRemoteStreams(),A=a.getLocalStream(),E=[...C.map(U=>({key:U.participant.participantId,stream:U.stream,label:U.participant.displayName})),...A?[{key:"local",stream:A,label:"me"}]:[]];f.fillStyle="#101418",f.fillRect(0,0,r,o);const x=Math.max(1,Math.ceil(Math.sqrt(E.length))),_=Math.max(1,Math.ceil(E.length/x)),j=r/x,g=o/_;E.forEach((U,Ve)=>{const Be=I(U.key,U.stream),re=Ve%x*j,oe=Math.floor(Ve/x)*g;Be.videoWidth>0&&f.drawImage(Be,re+2,oe+2,j-4,g-4),f.fillStyle="rgba(0,0,0,.6)",f.fillRect(re+8,oe+g-30,Math.min(j-16,12+U.label.length*9),22),f.fillStyle="#fff",f.font="14px system-ui",f.fillText(U.label,re+14,oe+g-14)})},66);const D=T.captureStream(15);d=new AudioContext;const P=d.createMediaStreamDestination(),L=new Set,w=()=>{const C=[...a.getRemoteStreams().map(A=>A.stream),...a.getLocalStream()?[a.getLocalStream()]:[]];for(const A of C)for(const E of A.getAudioTracks())L.has(E.id)||(L.add(E.id),d.createMediaStreamSource(new MediaStream([E])).connect(P))};w();const R=setInterval(w,1e3),m=()=>clearInterval(R);for(const C of P.stream.getAudioTracks())D.addTrack(C);c=(e.mediaRecorderFactory??((C,A)=>new MediaRecorder(C,A)))(D,{mimeType:s}),p=[],c.addEventListener("dataavailable",C=>{C.data&&C.data.size>0&&p.push(C.data)}),c.addEventListener("stop",m),c.start(1e3),v=Date.now(),S=!0},async stop(){if(!S||!c||!u)throw new Error("The recorder is not running");S=!1;const y=c,T=new Promise(P=>y.addEventListener("stop",()=>P(),{once:!0}));y.stop(),await T,l&&clearInterval(l),l=null,await d?.close().catch(()=>{}),d=null;const f=new Blob(p,{type:s});p=[];const M=Math.max(1,Math.round((Date.now()-v)/1e3)),I=await b("PUT",`/v1/rtc/recordings/${encodeURIComponent(u)}/media`,f,s);await b("POST",`/v1/rtc/recordings/${encodeURIComponent(u)}/complete`,{durationSeconds:M});const D={recordingId:u,sizeBytes:I.sizeBytes,durationSeconds:M};return u=null,c=null,D},dispose(){l&&clearInterval(l),l=null;try{c?.stop()}catch{}c=null,d?.close().catch(()=>{}),d=null,S=!1}}}const ue=["none","blur","aurora","cybergrid","bokeh","sunset","image"],me=["none","snow","confetti","vignette"],X={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 Ye(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.background??"none",l=e.overlay??"none",d=e.backgroundImage??null,u=e.segmenter??null,v=null,p=!1;const S=t.createElement("video");S.muted=!0,S.playsInline=!0,S.srcObject=new MediaStream([r]),S.play().catch(()=>{});const b=t.createElement("canvas");b.width=i,b.height=n;const y=b.getContext("2d"),T=t.createElement("canvas");T.width=i,T.height=n;const f=T.getContext("2d"),M=()=>Array.from({length:80},(m,h)=>({x:(Math.sin(h*12.9898)*.5+.5)*i,y:(Math.sin(h*78.233)*.5+.5)*n,speed:.6+h%5*.5,drift:Math.sin(h)*.6,size:2+h%4,hue:h*47%360}));let I=M();const D=()=>{if(s)return;const m=S.videoWidth,h=S.videoHeight;if(!m||!h)return;const C=Math.min(1,1280/Math.max(m,h)),A=Math.round(m*C),E=Math.round(h*C);A===i&&E===n||(i=A,n=E,b.width=i,b.height=n,T.width=i,T.height=n,I=M())};S.addEventListener?.("loadedmetadata",D),S.addEventListener?.("resize",D);function P(){if(v){f.clearRect(0,0,i,n),f.drawImage(v,0,0,i,n),f.globalCompositeOperation="source-in",f.drawImage(S,0,0,i,n),f.globalCompositeOperation="source-over",y.drawImage(T,0,0);return}const m=i*.42,h=m*9/16,C=i-m-24,A=n-h-24;y.save(),y.shadowColor="rgba(0,0,0,.5)",y.shadowBlur=24,y.beginPath(),y.roundRect(C,A,m,h,14),y.clip(),y.drawImage(S,C,A,m,h),y.restore()}function L(m){if(c==="none"?y.drawImage(S,0,0,i,n):c==="blur"?(y.filter="blur(14px)",y.drawImage(S,-16,-16,i+32,n+32),y.filter="none",v&&P()):c==="image"&&d?(y.drawImage(d,0,0,i,n),P()):((X[c]??X.aurora)(y,i,n,m),P()),l==="snow"||l==="confetti")for(const h of I)h.y+=h.speed*(l==="confetti"?1.6:1),h.x+=h.drift,h.y>n&&(h.y=-6,h.x=Math.random()*i),y.fillStyle=l==="snow"?"rgba(255,255,255,.85)":`hsl(${h.hue}, 90%, 60%)`,l==="confetti"?y.fillRect(h.x,h.y,h.size+2,h.size):(y.beginPath(),y.arc(h.x,h.y,h.size/2,0,Math.PI*2),y.fill());else if(l==="vignette"){const h=y.createRadialGradient(i/2,n/2,n*.4,i/2,n/2,n*.85);h.addColorStop(0,"rgba(0,0,0,0)"),h.addColorStop(1,"rgba(0,0,0,0.55)"),y.fillStyle=h,y.fillRect(0,0,i,n)}}const w=setInterval(()=>{L(Date.now()),u&&!p&&c!=="none"&&(p=!0,u.segment(S).then(m=>{v=m}).catch(()=>{v=null}).finally(()=>{p=!1}))},Math.round(1e3/o)),R=b.captureStream(o);return{track:R.getVideoTracks()[0],stream:R,setBackground(m,h){c=ue.includes(m)?m:"none",h!==void 0&&(d=h),c==="none"&&(v=null)},setOverlay(m){l=me.includes(m)?m:"none"},setSegmenter(m){u=m,m||(v=null)},dispose(){clearInterval(w),u?.close?.();for(const m of R.getTracks())m.stop();S.srcObject=null}}}function Z(){return{blinkLeft:0,blinkRight:0,jawOpen:0,smile:0,browUp:0,pupilX:0,pupilY:0,headYaw:0,headPitch:0,headRoll:0,tracked:!1}}function O(a,e){const t=a.find(i=>i.categoryName===e);return t?Math.max(0,Math.min(1,t.score)):0}function pe(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 fe(a,e,t=1){if(!a||a.length===0)return Z();const i=pe(e),n=(O(a,"eyeLookOutRight")+O(a,"eyeLookInLeft"))/2,s=(O(a,"eyeLookOutLeft")+O(a,"eyeLookInRight"))/2,o=(O(a,"eyeLookUpLeft")+O(a,"eyeLookUpRight"))/2,r=(O(a,"eyeLookDownLeft")+O(a,"eyeLookDownRight"))/2,c=(O(a,"mouthSmileLeft")+O(a,"mouthSmileRight"))/2,l=(O(a,"mouthFrownLeft")+O(a,"mouthFrownRight"))/2,d=O(a,"eyeBlinkLeft"),u=O(a,"eyeBlinkRight"),v=Math.min(1,Math.abs(i.yaw)/.35)*Math.max(0,Math.min(1,t)),p=(d+u)/2;return{blinkLeft:d+(p-d)*v,blinkRight:u+(p-u)*v,jawOpen:O(a,"jawOpen"),smile:c-l,browUp:(O(a,"browInnerUp")+O(a,"browOuterUpLeft")+O(a,"browOuterUpRight"))/3,pupilX:n-s,pupilY:r-o,headYaw:i.yaw,headPitch:i.pitch,headRoll:i.roll,tracked:!0}}function ee(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 ge(a,e,t,i,n){n||(a.fillStyle="#101822",a.fillRect(0,0,t,i));const s=n?n.x+e.headYaw*n.radius*.1:t/2+e.headYaw*t*.18,o=n?n.y+e.headPitch*n.radius*.1:i/2+e.headPitch*i*.18,r=n?n.radius:Math.min(t,i)*.28;a.save(),a.translate(s,o),a.rotate(e.headRoll*.6);const c=Math.max(-1.35,Math.min(1.35,e.headYaw)),l=Math.max(-.9,Math.min(.9,e.headPitch)),d=Math.sin(c),u=L=>Math.sin(L+c)*r,v=L=>Math.cos(L+c),p=Math.sin(l)*r*.22;a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.35,-r*.25,r*.98,r*.95,0,0,Math.PI*2),a.fill(),a.fillStyle=e.tracked?"#f2c9a0":"#5a6470",a.beginPath(),a.ellipse(0,0,r*(1-Math.abs(d)*.12),r*1.12,0,0,Math.PI*2),a.fill(),a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.28,-r*.72+p*.4,r*.92,r*.5,0,Math.PI,Math.PI*2),a.fill();const S=0;a.fillStyle="#e5b58a",a.beginPath();const b=u(S)*.9,y=r*.12+p;a.ellipse(b+d*r*.14,y,r*.11,r*.14,0,0,Math.PI*2),a.fill();for(const L of[-1,1]){const w=L*.42,R=v(w);if(R<=.12)continue;const m=u(w)*.82,h=Math.max(.2,R),C=L<0?e.blinkLeft:e.blinkRight,A=Math.max(.06,1-C),E=e.browUp*r*.14;a.strokeStyle="#4a2e18",a.lineWidth=r*.07,a.lineCap="round",a.beginPath(),a.moveTo(m-r*.2*h,-r*.42-E+p),a.quadraticCurveTo(m,-r*.52-E+p,m+r*.2*h,-r*.44-E+p),a.stroke();const x=-r*.18+p;a.fillStyle="#ffffff",a.beginPath(),a.ellipse(m,x,r*.16*h,r*.12*A,0,0,Math.PI*2),a.fill(),a.fillStyle="#22303c",a.beginPath(),a.ellipse(m+e.pupilX*r*.07*h,x+e.pupilY*r*.05,r*.06*h,r*.06*A,0,0,Math.PI*2),a.fill()}const T=Math.max(.25,v(0)),f=u(0)*.7,M=r*.55*T,I=r*(.04+e.jawOpen*.34),D=e.smile*r*.12,P=r*.42+p;a.fillStyle="#8c2f2f",a.beginPath(),a.moveTo(f-M/2,P-D),a.quadraticCurveTo(f,P+I,f+M/2,P-D),a.quadraticCurveTo(f,P-I*.25,f-M/2,P-D),a.closePath(),a.fill(),a.restore(),!e.tracked&&!n&&(a.fillStyle="#9fb0bd",a.font=`${Math.round(i*.045)}px system-ui`,a.textAlign="center",a.fillText("No face detected",t/2,i*.9),a.textAlign="left")}const te={mouthGain:1.7,blinkGain:1,eyeSync:1,smoothing:.4,avatarScale:1,avatarLift:0,lostHoldMs:400};function ie(){return{render(a,e,t,i,n){ge(a,e,t,i,n??void 0)}}}const ne=a=>Math.max(0,Math.min(1,a));function Ke(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.tracker??null,l=e.renderer??ie(),d=e.mode??"replace";const u={...te,...e.tuning},v=e.backdrop??"#101822",p=t.createElement("video");p.muted=!0,p.playsInline=!0,p.srcObject=new MediaStream([r]),p.play().catch(()=>{});const S=t.createElement("canvas");S.width=i,S.height=n;const b=S.getContext("2d"),y=()=>{if(s)return;const w=p.videoWidth,R=p.videoHeight;if(!w||!R)return;const m=Math.min(1,1280/Math.max(w,R)),h=Math.round(w*m),C=Math.round(R*m);(!(h===i||C===n)||h!==i||C!==n)&&(i=h,n=C,S.width=i,S.height=n)};p.addEventListener?.("loadedmetadata",y),p.addEventListener?.("resize",y);let T=Z(),f=null,M=0;function I(w){if(w.length<=454)return;const R=w[10],m=w[152],h=w[234],C=w[454],A=(h.x+C.x)/2*i,E=(h.y+C.y)/2*n,x=Math.hypot((m.x-R.x)*i,(m.y-R.y)*n),_=Math.max(.6,Math.cos(T.headPitch)),j={x:A,y:E,radius:Math.max(24,x/_*.8)};f=f?{x:f.x+(j.x-f.x)*.4,y:f.y+(j.y-f.y)*.4,radius:f.radius+(j.radius-f.radius)*.15}:j}function D(w){if(c&&p.readyState>=2){let m=null;try{m=c.detect(p,w)}catch{}const h=fe(m?.categories??null,m?.matrix??null,u.eyeSync);h.jawOpen=ne(h.jawOpen*u.mouthGain),h.blinkLeft=ne(h.blinkLeft*u.blinkGain),h.blinkRight=ne(h.blinkRight*u.blinkGain),h.tracked?(M=w,T=ee(T,h,u.smoothing),m?.landmarks&&I(m.landmarks)):T.tracked&&w-M>u.lostHoldMs&&(T=ee(T,{...T,jawOpen:0,browUp:0,smile:T.smile*.5,blinkLeft:.1,blinkRight:.1},.05))}d==="overlay"&&p.readyState>=2?b.drawImage(p,0,0,i,n):(b.fillStyle=v,b.fillRect(0,0,i,n));const R=f?{x:f.x,y:f.y+u.avatarLift*f.radius,radius:f.radius*u.avatarScale}:null;l?.render(b,T,i,n,R)}const P=setInterval(()=>D(Date.now()),Math.round(1e3/o)),L=S.captureStream(o);return{track:L.getVideoTracks()[0],stream:L,getRig:()=>({...T}),setMode(w){d=w==="overlay"?"overlay":"replace"},setTracker(w){c?.close?.(),c=w},setRenderer(w){l?.dispose?.(),l=w??ie()},setTuning(w){for(const R of Object.keys(te)){const m=w[R];typeof m=="number"&&Number.isFinite(m)&&(u[R]=m)}},getTuning:()=>({...u}),dispose(){clearInterval(P),c?.close?.(),l?.dispose?.();for(const w of L.getTracks())w.stop();p.srcObject=null}}}class B extends Error{constructor(e,t){super(t),this.code=e,this.name="ZipError"}code}const ye=101010256,ve=33639248,Se=67324752,ae=4294967295;function we(a){if(a.byteLength<4)return!1;const e=new DataView(a).getUint32(0,!0);return e===Se||e===ve||e===ye}function Xe(a){const e=Math.max(0,a.byteLength-22-65535);for(let t=a.byteLength-22;t>=e;t-=1)if(a.getUint32(t,!0)===ye)return t;throw new B("zip_unreadable","The archive index could not be found.")}function ke(a){const e=new DataView(a),t=Xe(e),i=e.getUint16(t+10,!0);let n=e.getUint32(t+16,!0);if(n===ae)throw new B("zip_unsupported","ZIP64 archives are not supported.");const s=new TextDecoder,o=[];for(let r=0;r<i;r+=1){if(n+46>a.byteLength||e.getUint32(n,!0)!==ve)throw new B("zip_unreadable","The archive index is malformed.");const c=e.getUint16(n+8,!0),l=e.getUint16(n+10,!0),d=e.getUint32(n+20,!0),u=e.getUint32(n+24,!0),v=e.getUint16(n+28,!0),p=e.getUint16(n+30,!0),S=e.getUint16(n+32,!0),b=e.getUint32(n+42,!0),y=s.decode(new Uint8Array(a,n+46,v));o.push({path:y,bytes:u,compressedBytes:d,compressionMethod:l,encrypted:(c&1)!==0,localHeaderOffset:b}),n+=46+v+p+S}return o}async function Ze(a,e){if(e.encrypted)throw new B("zip_encrypted","The archive is password protected.");if(e.bytes===ae||e.compressedBytes===ae)throw new B("zip_unsupported","ZIP64 archives are not supported.");const t=new DataView(a),i=e.localHeaderOffset;if(i+30>a.byteLength||t.getUint32(i,!0)!==Se)throw new B("zip_unreadable","An archive entry header is malformed.");const n=t.getUint16(i+26,!0),s=t.getUint16(i+28,!0),o=i+30+n+s;if(o+e.compressedBytes>a.byteLength)throw new B("zip_unreadable","An archive entry runs past the end of the file.");const r=a.slice(o,o+e.compressedBytes);if(e.compressionMethod===0)return r;if(e.compressionMethod!==8)throw new B("zip_unsupported",`Archive compression method ${e.compressionMethod} is not supported.`);if(typeof DecompressionStream>"u")throw new B("zip_unsupported","This browser cannot expand compressed archives.");const c=new Blob([r]).stream().pipeThrough(new DecompressionStream("deflate-raw"));return new Response(c).arrayBuffer()}async function et(a){const e=typeof File<"u"&&a instanceof File?await a.arrayBuffer():a;if(!we(e))return[];try{return be(ke(e)).map(t=>t.path)}catch{return[]}}const Te=64*1024*1024,tt="streaming-cdn-avatars",H="models",it=1179937895,nt=1313821514;class N extends Error{constructor(e,t,i=[]){super(t),this.code=e,this.name="AvatarModelError",this.choices=i}code;choices}function be(a){return a.filter(e=>/\.vrm$/i.test(e.path)&&!e.path.endsWith("/")&&!e.path.split("/").some(t=>t==="__MACOSX"||t.startsWith("._")))}function q(a){const e=typeof a=="string"?a.trim():"";return e.length>0?e:null}function Ce(a){const e=new DataView(a);if(a.byteLength<20||e.getUint32(0,!0)!==it)throw new N("model_not_glb","The file is not a binary glTF (.vrm / .glb) model.");let t=12,i=null;for(;t+8<=a.byteLength;){const r=e.getUint32(t,!0),c=e.getUint32(t+4,!0),l=t+8;if(l+r>a.byteLength)break;if(c===nt){try{const d=new TextDecoder().decode(new Uint8Array(a,l,r)).replace(/[\s\u0000]+$/,"");i=JSON.parse(d)}catch{throw new N("model_unreadable","The model's glTF descriptor could not be read.")}break}t=l+r+(4-r%4)%4}const n=i?.extensions??{},s=n.VRM?.meta,o=n.VRMC_vrm?.meta;if(!s&&!o)return null;if(o){const r=Array.isArray(o.authors)?o.authors.filter(c=>typeof c=="string"):[];return{title:q(o.name),author:r.length?r.join(", "):null,allowedUser:q(o.avatarPermission),commercialUse:q(o.commercialUsage),licenseName:q(o.licenseUrl)?"VRM 1.0":null,licenseUrl:q(o.licenseUrl)??q(o.otherLicenseUrl)}}return{title:q(s?.title),author:q(s?.author),allowedUser:q(s?.allowedUserName),commercialUse:q(s?.commercialUssageName),licenseName:q(s?.licenseName),licenseUrl:q(s?.otherLicenseUrl)}}function at(){return new Promise((a,e)=>{if(typeof indexedDB>"u"){e(new N("storage_unavailable","This browser cannot store imported avatar models."));return}const t=indexedDB.open(tt,1);t.onupgradeneeded=()=>{t.result.objectStoreNames.contains(H)||t.result.createObjectStore(H,{keyPath:"id"})},t.onsuccess=()=>a(t.result),t.onerror=()=>e(new N("storage_unavailable","Imported avatar storage could not be opened."))})}async function $(a,e){const t=await at();try{return await new Promise((i,n)=>{const s=e(t.transaction(H,a).objectStore(H));s.onsuccess=()=>i(s.result),s.onerror=()=>n(new N("storage_unavailable","The imported avatar could not be stored."))})}finally{t.close()}}const Ee=a=>({id:a.id,name:a.name,bytes:a.bytes,importedAt:a.importedAt,license:a.license});async function st(a,e={}){const t=e.maxBytes??Te,i=typeof File<"u"&&a instanceof File;let n=i?a.name:null,s=i?await a.arrayBuffer():a;if(we(s)){const l=await rt(s,e.entryPath,t);s=l.buffer,n=l.path.split("/").pop()??n}if(s.byteLength>t)throw new N("model_too_large","The model is larger than the "+Math.round(t/1024/1024)+" MB limit.");const o=Ce(s);if(!o)throw new N("model_not_vrm","The file is a glTF model but carries no VRM character data.");const r=o.title||(n?n.replace(/\.(vrm|glb)$/i,""):null),c={id:"avatar_"+Date.now().toString(36)+"_"+Math.random().toString(36).slice(2,8),name:e.name?.trim()||r||"Imported avatar",bytes:s.byteLength,importedAt:new Date().toISOString(),license:o,data:s};return await $("readwrite",l=>l.put(c)),Ee(c)}async function rt(a,e,t){let i;try{i=be(ke(a))}catch(s){throw s instanceof B?new N(Re(s.code),s.message):new N("archive_unreadable","The archive could not be read.")}if(i.length===0)throw new N("archive_no_model","The archive contains no .vrm character.");const n=e?i.find(s=>s.path===e):i.length===1?i[0]:null;if(!n)throw new N("archive_many_models","The archive contains several characters; choose one.",i.map(s=>s.path));if(n.bytes>t)throw new N("model_too_large","The character inside the archive is larger than the "+Math.round(t/1024/1024)+" MB limit.");try{return{buffer:await Ze(a,n),path:n.path}}catch(s){throw s instanceof B?new N(Re(s.code),s.message):new N("archive_unreadable","The character could not be expanded from the archive.")}}function Re(a){return a==="zip_encrypted"?"archive_encrypted":a==="zip_unsupported"?"archive_unsupported":"archive_unreadable"}async function ot(){return(await $("readonly",e=>e.getAll())??[]).map(Ee).sort((e,t)=>t.importedAt.localeCompare(e.importedAt))}async function ct(a){return(await $("readonly",t=>t.get(a)))?.data??null}async function lt(a){await $("readwrite",e=>e.delete(a))}class Me 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"?$e:z;return this.client=new t({credential:{signalingToken:this.options.credential.signalingToken,signalingUrl:this.options.credential.signalingUrl,roomId:this.options.credential.roomId,participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId,iceServers:this.options.credential.iceServers},audio:this.options.audio,video:this.options.video,receiveAudio:this.options.receiveAudio,receiveVideo:this.options.receiveVideo,quality:this.effectiveQuality,preferredVideoCodec:this.options.preferredVideoCodec??"auto",iceTransportPolicy:this.options.iceTransportPolicy??"all",localStream:this.options.localStream,simulcast:this.options.simulcast??!0,onEvent:(i,n)=>this.handleNativeEvent(i,n)}),await this.client.initialize(),this.startStats(),this.emit("initialized",{mode:this.mode,sdkVersion:xe}),this.options.autoJoin&&await this.join(),this}catch(e){this.state="failed";const t=qe(e,"RTC initialization failed");throw this.emit("error",Q(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=qe(t,"RTC connection failed");throw this.emit("error",Q(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(Oe)}getLocalStream(){return this.client?.getLocalStream()??null}getRemoteStreams(){return(this.client?.getRemoteStreams()??[]).map(e=>({participant:Oe(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(()=>[]):[],o=s.filter(u=>u.kind==="audioinput").length,r=s.filter(u=>u.kind==="videoinput").length;this.options.audio&&o===0&&t.push("microphone_unavailable"),this.options.video&&r===0&&t.push("camera_unavailable");const c=e.probeUrl??this.options.preflightProbeUrl;let l=null;if(c){const u=performance.now();try{const v=await fetch(c,{method:"HEAD",cache:"no-store",credentials:"omit"});l=Math.round(performance.now()-u),v.ok||t.push("network_probe_failed")}catch{t.push("network_probe_failed")}}const d={passed:t.length===0,sampledAt:new Date().toISOString(),secureContext:i,mediaPermission:n,microphoneCount:o,cameraCount:r,probeLatencyMs:l,effectiveConnectionType:Ne()?.effectiveType??null,issues:t};return this.emit("preflight",d),this.options.autoReportStats&&await this.reportQuality({sampleType:"preflight",sampledAt:d.sampledAt,connectionState:d.passed?"ready":"failed",rttMs:d.probeLatencyMs,effectiveConnectionType:d.effectiveConnectionType}).catch(()=>{}),d}async collectQualitySample(e="runtime"){const t=this.options.statsProvider?await this.options.statsProvider():{},i=this.peerConnection?await pt(this.peerConnection,this.previousCounters):null;i?.counters&&(this.previousCounters=i.counters);const s={...{sampleType:e,sampledAt:new Date().toISOString(),connectionState:this.peerConnection?.connectionState??this.state,mediaScore:this.latestMediaScore,networkScore:this.latestMediaScore==null?5:Math.max(0,Math.min(5,Math.round(this.latestMediaScore))),estimatedMos:4.5,rttMs:null,jitterMs:null,packetLossPct:null,packetLossCumulativePct:null,packetsLost:null,packetsReceived:null,inboundBitrateBps:null,outboundBitrateBps:null,availableOutgoingBitrateBps:null,framesPerSecond:null,frameWidth:null,frameHeight:null,inboundFrameWidth:null,inboundFrameHeight:null,outboundFrameWidth:null,outboundFrameHeight:null,framesDropped:null,freezeCount:null,freezeDurationMs:null,concealedSamples:null,codec:null,transportProtocol:null,candidateType:null,qualityLimitationReason:null,deviceMemoryGb:gt(),effectiveConnectionType:Ne()?.effectiveType??null},...i?.sample??{},...t,sampleType:e,sampledAt:new Date().toISOString()},o=ft(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",Q(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 dt(a){return new Me(a).initialize()}let W=null;function Ie(){if(W)return W;const a=globalThis,e=a.AudioContext??a.webkitAudioContext;return e?(W=new e,W):null}async function Ae(){const a=Ie();return a?(a.state==="suspended"&&await a.resume().catch(()=>{}),a.state==="running"):!1}class ht{options;media=null;cadenceTimer=null;oscillators=new Set;constructor(e){this.options=e}unlock(){return Ae()}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=Ie();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 Pe 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;invitationExpiryTimers=new Map;startCallRequest=null;lastDisconnect=null;constructor(e){if(super(),!e.token&&!e.tokenProvider)throw new Error("A client token or tokenProvider is required");if(e.token&&!e.token.startsWith("rtcc_"))throw new Error("A valid RTC client token is required");if(!e.signalingUrl&&!e.callActionsUrl&&!e.apiBaseUrl&&!globalThis.location?.origin)throw new Error("signalingUrl or callActionsUrl is required outside a browser");this.options=e,this.currentToken=e.token??null,this.currentTokenExpiresAt=e.tokenExpiresAt,this.callAudio=new ht(e),this.mount=Le(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=Le(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.clearInvitationExpiry(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.scheduleInvitationExpiry(e.id,e.expiresAt),this.currentCall=e,this.callAudio.startIncoming(),this.renderIncoming(e),this.emit("incomingcall",e)}scheduleInvitationExpiry(e,t){const i=t?Date.parse(t):Date.now()+45e3,n=Math.min(18e4,Math.max(5e3,(Number.isFinite(i)?i:Date.now()+45e3)-Date.now()+1e4));this.clearInvitationExpiry(e),this.invitationExpiryTimers.set(e,setTimeout(()=>{this.invitationExpiryTimers.delete(e),!(!this.activeCallIds.has(e)||this.mediaClients.has(e))&&(this.emit("callexpired",{id:e,expiresAt:t??null}),this.release(e))},n))}clearInvitationExpiry(e){const t=this.invitationExpiryTimers.get(e);t&&clearTimeout(t),this.invitationExpiryTimers.delete(e)}release(e){if(!e.trim())throw new Error("callId is required");this.clearInvitationExpiry(e),this.currentCall?.id===e&&(this.callAudio.stop(),this.dismiss(e)),this.outgoingCallId===e&&(this.outgoingCallId=null),this.releaseMedia(e),this.emit("callreleased",{id:e})}handleCallUpdate(e){const t=e.id===this.currentCall?.id||e.id===this.outgoingCallId,i=["answered_elsewhere","rejected","busy","cancelled","ended","failed","missed"];(e.status==="accepted"||i.includes(e.status))&&this.clearInvitationExpiry(e.id),t&&e.status==="accepted"?(this.activeCallIds.add(e.id),this.callAudio.stop(),this.dismiss(e.id)):t&&i.includes(e.status)&&this.showTerminal(e.id,mt(e.status)),i.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.invitationExpiryTimers.values())clearTimeout(e);this.invitationExpiryTimers.clear();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",Q(s)),!this.stopped&&this.options.reconnect!==!1&&this.scheduleReconnect();return}if(this.stopped||this.socket)return;const i=G(this.options.signalingUrl,globalThis.location?.href),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,actorExternalId:typeof n.actorExternalId=="string"?n.actorExternalId:null});return}i==="presence"?this.emit("presence",{users:t.users??[]}):i!=="pong"&&this.emit(i||"signal",t)}async callAction(e,t){return this.request(`/v1/rtc/client/calls/${encodeURIComponent(e)}/${t}`,{method:"POST",body:"{}"})}async request(e,t,i=!1){const n=this.options.callActionsUrl??this.options.apiBaseUrl??this.options.signalingUrl??globalThis.location?.origin;if(!n)throw new Error("callActionsUrl is required for call actions");const s=Y(n,globalThis.location?.href),o=await this.resolveToken("initial"),r=new Headers(t.headers);r.set("authorization",`Bearer ${o}`),r.set("content-type","application/json");const c=await fetch(new URL(e,s.origin),{...t,headers:r}),l=await c.json().catch(()=>({}));if(c.status===401&&this.options.tokenProvider&&!i)return await this.resolveToken("unauthorized",!0),this.request(e,t,!0);if(!c.ok){const d=l.error;throw new Error(typeof d=="string"?d:d?.message||`RTC API failed with HTTP ${c.status}`)}return l}renderIncoming(e){if(!this.mount)return;this.clearUi();const t=document.createElement("section");t.dataset.rtcIncomingCall=e.id,t.setAttribute("role","dialog"),t.setAttribute("aria-label",`Incoming ${e.mode} call from ${e.caller.name}`),Object.assign(t.style,{display:"grid",gap:"14px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const i=document.createElement("small");i.textContent=e.mode==="video"?"Incoming video call":"Incoming voice call",i.style.color="#5d7079";const n=document.createElement("strong");n.textContent=e.caller.name,n.style.fontSize="20px";const s=document.createElement("div");Object.assign(s.style,{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"});const o=De("Decline","#fff","#a32920","#d9a09b"),r=De("Answer","#fff","#08745f","#08745f");o.addEventListener("click",()=>{this.reject(e.id).catch(c=>this.emit("error",Q(c)))}),r.addEventListener("click",()=>{this.accept(e.id).catch(c=>this.emit("error",Q(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",Q(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",Q(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 ut(a){const e=new Pe(a);return a.autoConnect!==!1&&a.signalingUrl&&e.connect(),e}function Le(a){return a?typeof a=="string"?document.querySelector(a):a:null}function De(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 mt(a){return{rejected:"Call declined",busy:"Line busy",cancelled:"Call cancelled",ended:"Call ended",failed:"Call failed",missed:"No answer",answered_elsewhere:"Answered on another device"}[a]??"Call ended"}function Oe(a){return{id:a.participantId,name:a.displayName,role:"participant"}}async function pt(a,e){const t=await a.getStats(),i=new Map,n=new Map;let s=0,o=0,r=0,c=0,l=0,d=0,u=0,v=0,p=0,S=0,b=0,y=0,T=0,f=0,M=0,I=null,D=null,P=null,L=null,w=null,R=null;t.forEach(g=>{n.set(g.id,g),g.type==="codec"&&i.set(g.id,String(g.mimeType??""))}),t.forEach(g=>{if(g.type==="inbound-rtp"&&!g.isRemote&&(s+=Number(g.bytesReceived??0),r+=Math.max(0,Number(g.packetsLost??0)),c+=Math.max(0,Number(g.packetsReceived??0)),Number.isFinite(g.jitter)&&(l+=Number(g.jitter),d+=1),u=Math.max(u,Number(g.framesPerSecond??0)),v=Math.max(v,Number(g.frameWidth??0)),b=Math.max(b,Number(g.frameHeight??0)),y+=Number(g.framesDropped??0),T+=Number(g.freezeCount??0),f+=Number(g.totalFreezesDuration??0),M+=Number(g.concealedSamples??0),w||=i.get(String(g.codecId??""))??null),g.type==="outbound-rtp"&&!g.isRemote&&(p=Math.max(p,Number(g.frameWidth??0)),S=Math.max(S,Number(g.frameHeight??0)),o+=Number(g.bytesSent??0),R||=g.qualityLimitationReason?String(g.qualityLimitationReason):null,w||=i.get(String(g.codecId??""))??null),g.type==="candidate-pair"&&(g.selected||g.nominated)&&g.state==="succeeded"){I=Number.isFinite(g.currentRoundTripTime)?Number(g.currentRoundTripTime):I,D=Number.isFinite(g.availableOutgoingBitrate)?Number(g.availableOutgoingBitrate):D;const U=n.get(g.localCandidateId);P=U?.candidateType?String(U.candidateType):P,L=U?.protocol?String(U.protocol):L}});const m=Date.now(),h=e?Math.max(.001,(m-e.timestamp)/1e3):0,C=e?Math.max(0,Math.round((s-e.inboundBytes)*8/h)):null,A=e?Math.max(0,Math.round((o-e.outboundBytes)*8/h)):null,E=r+c,x=e?Math.max(0,r-e.packetsLost):r,_=e?Math.max(0,c-e.packetsReceived):c,j=x+_;return{counters:{timestamp:m,inboundBytes:s,outboundBytes:o,packetsLost:r,packetsReceived:c},sample:{rttMs:I===null?null:I*1e3,jitterMs:d?l/d*1e3:null,packetLossPct:j?x/j*100:null,packetLossCumulativePct:E?r/E*100:null,packetsLost:r,packetsReceived:c,inboundBitrateBps:C,outboundBitrateBps:A,availableOutgoingBitrateBps:D,framesPerSecond:u||null,frameWidth:v||null,frameHeight:b||null,inboundFrameWidth:v||null,inboundFrameHeight:b||null,outboundFrameWidth:p||null,outboundFrameHeight:S||null,framesDropped:y,freezeCount:T,freezeDurationMs:Math.round(f*1e3),concealedSamples:M,codec:w,transportProtocol:L,candidateType:P,qualityLimitationReason:R}}}function ft(a){let e=a.mediaScore==null?5:Math.round(Ue(Number(a.mediaScore),0,5));const t=se(a.packetLossPct)??0,i=se(a.rttMs)??0,n=se(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(Ue(4.5-t*.09-i*.0011-n*.006,1,4.5).toFixed(2));return{networkScore:e,estimatedMos:o}}function Ne(){return navigator.connection??null}function gt(){const a=Number(navigator.deviceMemory);return Number.isFinite(a)?a:null}function se(a){const e=Number(a);return Number.isFinite(e)?e:null}function Ue(a,e,t){return Math.min(t,Math.max(e,a))}function Q(a){return a instanceof Error?{name:a.name,message:a.message}:{name:"ServiceError",message:String(a??"Service request failed")}}function qe(a,e){return a instanceof Error?a:new Error(e)}const xe="1.3.25";k.AVATAR_MODEL_MAX_BYTES=Te,k.AVATAR_TUNING_DEFAULTS=te,k.AvatarModelError=N,k.BACKGROUND_PRESETS=ue,k.OVERLAY_EFFECTS=me,k.RtcClient=Me,k.RtcIncomingClient=Pe,k.SPEAKER_NOISE_FLOOR=.02,k.SPEAKER_TAKEOVER_RATIO=1.4,k.createAvatarPipeline=Ke,k.createCallRecorder=Ge,k.createPictureInPictureController=Je,k.createRtcClient=dt,k.createRtcIncomingClient=ut,k.createVideoEffectsPipeline=Ye,k.drawAvatar=ge,k.headPoseFromMatrix=pe,k.importAvatarModel=st,k.listArchiveModels=et,k.listAvatarModels=ot,k.neutralRig=Z,k.pickActiveSpeaker=J,k.readAvatarModel=ct,k.readAvatarModelMetadata=Ce,k.removeAvatarModel=lt,k.rigFromBlendshapes=fe,k.scenePainters=X,k.smoothRig=ee,k.stylizedAvatarRenderer=ie,k.unlockRtcCallAudio=Ae,k.version=xe,Object.defineProperty(k,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(T,W){typeof exports=="object"&&typeof module<"u"?W(exports):typeof define=="function"&&define.amd?define(["exports"],W):(T=typeof globalThis<"u"?globalThis:T||self,W(T.StreamingCdnRtc={}))})(this,(function(T){"use strict";function Y(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 de(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}},Qe={warmupSamples:3,poorSamplesToStepDown:2,goodSamplesToStepUp:6,criticalLossPct:8,criticalRttMs:800,poorLossPct:3,poorRttMs:350,goodLossPct:1,goodRttMs:250,sustainRatio:.8,bandwidthSustainRatio:.7};function Fe(a){const e=String(a??"").trim().toLowerCase();return!e||e==="none"?"":e==="cpu"||e==="bandwidth"?e:"other"}function ze(a,e,t,i,n={}){const s={...Qe,...n.thresholds},o=["low","medium","high"],r=n.ceiling&&n.ceiling!=="audio"?n.ceiling:"high",c=o.indexOf(r),l=o.indexOf(a),d=(n.samplesSeen??s.warmupSamples)+1,u=d<=s.warmupSamples,y=Fe(e.qualityLimitationReason),p=e.packetLossPct??0,S=e.rttMs??0,C=e.availableOutgoingBitrateBps,g=q[a].maxBitrate,k=p>=s.criticalLossPct||S>=s.criticalRttMs||y==="cpu",f=y==="bandwidth"&&(C==null||C<g*s.bandwidthSustainRatio),I=k||p>=s.poorLossPct||S>=s.poorRttMs||f,A=l<o.length-1?q[o[l+1]].maxBitrate:null,D=y==="bandwidth"&&C!=null&&A!=null&&C>=A*s.bandwidthSustainRatio,P=C==null||C>=g*s.sustainRatio,L=p<s.goodLossPct&&S<s.goodRttMs&&(!y||D)&&P,w=I?t+1:0,M=L?i+1:0,m={quality:a,poorSamples:w,goodSamples:M,samplesSeen:d,changed:!1};return l>c?{quality:o[c],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:u?m:k&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:w>=s.poorSamplesToStepDown&&l>0?{quality:o[l-1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:M>=s.goodSamplesToStepUp&&l<c?{quality:o[l+1],poorSamples:0,goodSamples:0,samplesSeen:d,changed:!0}:m}function We(a,e){const t=a instanceof Map?a:new Map(a);let i=0,n=0,s=null,o=null,r=null;for(const d of t.values()){const u=String(d.type??"");if(u==="outbound-rtp"&&String(d.kind??"")==="video"&&(i+=Number(d.packetsSent??0),s=d.qualityLimitationReason??s),u==="remote-inbound-rtp"&&String(d.kind??"")==="video"){n+=Number(d.packetsLost??0);const y=Number(d.roundTripTime??NaN);Number.isFinite(y)&&(o=y*1e3)}if(u==="candidate-pair"&&(d.nominated===!0||String(d.state??"")==="succeeded")){const y=Number(d.currentRoundTripTime??NaN);o==null&&Number.isFinite(y)&&(o=y*1e3);const p=Number(d.availableOutgoingBitrate??NaN);Number.isFinite(p)&&(r=p)}}const c={packetsSent:i,packetsLost:n,timestamp:Date.now()};let l=null;if(e){const d=i-e.packetsSent,u=n-e.packetsLost;d>0&&u>=0&&(l=u/(d+u)*100)}return{sample:{packetLossPct:l,rttMs:o,availableOutgoingBitrateBps:r,qualityLimitationReason:s},counters:c}}function K(a,e){const t=new URL(String(a),e);return t.protocol==="https:"?t.protocol="wss:":t.protocol==="http:"&&(t.protocol="ws:"),t}function X(a,e){const t=new URL(String(a),e);return t.protocol==="wss:"?t.protocol="https:":t.protocol==="ws:"&&(t.protocol="http:"),t}function he(a,e){return{participantId:a,connectionState:String(e.connectionState??"new"),iceState:String(e.iceConnectionState??"new"),signalingState:String(e.signalingState??"stable")}}const He={audio:{active:!1,maxBitrate:0,maxFramerate:0,scaleResolutionDownBy:4}},$e={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1};class H{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.socket?.readyState===WebSocket.OPEN&&this.send({type:"rtc.leave"}),this.stopped=!0,this.clearConnectionTimers(),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null;for(const e of this.peers.values())e.connection.close();this.peers.clear(),this.stopStream(this.localStream),this.stopStream(this.screenStream),this.localStream=null,this.screenStream=null}setMuted(e){for(const t of this.localStream?.getAudioTracks()??[])t.enabled=!e}async setCameraEnabled(e){if(e&&!this.localStream?.getVideoTracks().length){const i=(await navigator.mediaDevices.getUserMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}})).getVideoTracks()[0];if(!i)throw new Error("Camera is unavailable");this.localStream||(this.localStream=new MediaStream),this.localStream.addTrack(i),this.screenStream?.getVideoTracks().some(n=>n.readyState==="live")||await this.replaceVideoTrack(i);return}for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e}async setScreenShareEnabled(e){if(!e){const n=this.screenStream;this.screenStream=null,this.stopStream(n);const s=this.localStream?.getVideoTracks()[0]??null;await this.replaceVideoTrack(s),this.options.onEvent("screensharechange",{enabled:!1,stream:null});return}if(this.screenStream?.getVideoTracks()[0]?.readyState==="live")return;const t=await navigator.mediaDevices.getDisplayMedia({video:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}},audio:!1}),i=t.getVideoTracks()[0];if(!i)throw new Error("Screen sharing is unavailable");i.contentHint="detail",this.screenStream=t,i.addEventListener("ended",()=>{this.screenStream?.getVideoTracks()[0]?.id===i.id&&this.setScreenShareEnabled(!1)});try{await this.replaceVideoTrack(i),this.options.onEvent("screensharechange",{enabled:!0,stream:t})}catch(n){throw this.screenStream=null,this.stopStream(t),n}}async setDevice(e){const t={deviceId:{exact:e.deviceId}},i=await navigator.mediaDevices.getUserMedia(e.kind==="audioinput"?{audio:t}:{video:{...t,width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30,max:30}}}),n=e.kind==="audioinput"?i.getAudioTracks()[0]:i.getVideoTracks()[0];if(!n)throw new Error("Selected media device is unavailable");const s=e.kind==="audioinput"?"audio":"video",o=this.localStream?.getTracks().find(r=>r.kind===s);if(this.localStream||(this.localStream=new MediaStream),o&&(this.localStream.removeTrack(o),o.stop()),this.localStream.addTrack(n),s==="video"){this.screenStream?.getVideoTracks().some(r=>r.readyState==="live")||await this.replaceVideoTrack(n);return}for(const r of this.peers.values()){const c=r.connection.getSenders().find(l=>l.track?.kind===s);c?await c.replaceTrack(n):r.connection.addTrack(n,this.localStream)}await this.applyVideoQuality()}async setQuality(e){this.quality=e;for(const t of this.localStream?.getVideoTracks()??[])t.enabled=e!=="audio";for(const t of this.peers.values())t.quality=H.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}getPeerStates(){return[...this.peers.values()].map(e=>he(e.identity.participantId,e.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 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=K(t.signalingUrl,globalThis.location?.href),n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.startQualitySampling(),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),this.stopQualitySampling(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.resetPeers(),this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0;const n=Array.isArray(t.peers)?t.peers:[];for(const s of n)this.ensurePeer(s);for(const s of n)this.options.onEvent("participantjoined",{...s,initial:!0});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);this.negotiation(n,"offer-received"),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),this.negotiation(n,"remote-description-set");const r=await n.connection.createAnswer();await n.connection.setLocalDescription(r),this.negotiation(n,"answer-created"),this.send({type:"rtc.answer",targetParticipantId:t.sender.participantId,negotiationId:t.negotiationId,description:n.connection.localDescription}),this.negotiation(n,"answer-sent");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;this.negotiation(n,"answer-received"),await n.connection.setRemoteDescription(t.description),this.negotiation(n,"remote-description-set"),n.activeNegotiationId=null;return}if(i==="rtc.ice"&&t.sender&&t.candidate){const n=this.ensurePeer(t.sender);this.negotiation(n,"ice-candidate-received"),await n.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:H.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()}),this.negotiation(s,"ice-candidate-sent"))}),i.addEventListener("icegatheringstatechange",()=>{i.iceGatheringState==="complete"&&this.negotiation(s,"ice-gathering-complete")}),i.addEventListener("iceconnectionstatechange",()=>{this.options.onEvent("icestate",{participantId:e.participantId,state:i.iceConnectionState})}),i.addEventListener("track",c=>{n.getTracks().some(l=>l.id===c.track.id)||n.addTrack(c.track),this.options.onEvent("remotestream",{participant:e,stream:n,track:c.track})}),i.addEventListener("connectionstatechange",()=>{["failed","closed"].includes(i.connectionState)&&this.removePeer(e.participantId),this.options.onEvent("peerconnectionstate",{participant:e,state:i.connectionState})}),s}negotiation(e,t){this.options.onEvent("negotiation",{participantId:e.identity.participantId,phase:t})}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;this.negotiation(e,"offer-created"),await e.connection.setLocalDescription(t);const i=ue();e.activeNegotiationId=i,this.send({type:"rtc.offer",targetParticipantId:e.identity.participantId,negotiationId:i,description:e.connection.localDescription}),this.negotiation(e,"offer-sent")}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?$e:this.quality==="audio"?He.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 de(n.values()))e.push({participantId:i.identity.participantId,level:s.level})}catch{}const t=Y(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}=We(t,e.counters);e.counters=n;const s=ze(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){if(this.socket?.readyState!==WebSocket.OPEN){this.stopped||this.options.onEvent("signalingerror",{code:"send_dropped",messageType:String(e.type??"")});return}this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??ue(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function ue(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}const Z={active:!0,maxBitrate:4e6,maxFramerate:30,scaleResolutionDownBy:1},me=[{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}],pe={high:"a",medium:"b",low:"c"};class Je{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=X(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.socket?.readyState===WebSocket.OPEN&&this.send({type:"rtc.leave"}),this.stopped=!0,this.clearTimers(),this.joined=!1,this.socket?.close(1e3,"Participant left"),this.socket=null,await this.closePublishedTracks().catch(()=>{}),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}getPeerStates(){return this.connection?[he(this.options.credential.participantId,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=K(t.signalingUrl,globalThis.location?.href),n=new Promise((c,l)=>{this.resolveJoin=c,this.rejectJoin=l}),s=new WebSocket(i,["rtc-client",t.signalingToken]),o=++this.socketGeneration;this.socket=s;const r=setTimeout(()=>this.rejectJoin?.(new Error("RTC signaling timed out")),15e3);s.addEventListener("open",()=>{this.heartbeatDeadline=Date.now()+45e3,this.startHeartbeat(s),this.send({type:"rtc.room.join"})}),s.addEventListener("message",c=>{const l=String(c.data);this.signalQueue=this.signalQueue.then(async()=>{this.socket!==s||this.socketGeneration!==o||await this.handleSignal(l)}).catch(d=>{const u=d instanceof Error?d.message:"RTC signaling processing failed";this.options.onEvent("signalingerror",{message:u}),this.joined||this.rejectJoin?.(new Error(u))})}),s.addEventListener("error",()=>this.rejectJoin?.(new Error("RTC signaling connection failed"))),s.addEventListener("close",c=>{if(this.socket!==s)return;this.socket=null,this.stopHeartbeat(),!this.joined&&!e&&this.rejectJoin?.(new Error(c.reason||"RTC signaling connection closed"));const l=!this.stopped&&(this.joined||e);this.joined=!1,l?(this.options.onEvent("reconnecting",{reason:c.reason,code:c.code,attempt:this.reconnectAttempt+1}),this.scheduleReconnect()):this.stopped||this.options.onEvent("disconnected",{reason:c.reason,code:c.code})});try{await n,this.joined=!0,this.options.onEvent("connected",{participantId:t.participantId,resumed:e})}finally{clearTimeout(r),this.resolveJoin=null,this.rejectJoin=null}}async handleSignal(e){let t;try{t=JSON.parse(e)}catch{return}const i=String(t.type??"");if(i!=="ready"){if(i==="pong"){this.heartbeatDeadline=Date.now()+45e3;return}if(i==="rtc.room.peers"){this.reconnectAttempt=0;const n=Array.isArray(t.peers)?t.peers:[];for(const s of n)this.options.onEvent("participantjoined",{...s,initial:!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})}),t.addEventListener("iceconnectionstatechange",()=>{this.options.onEvent("icestate",{participantId:this.options.credential.participantId,state:t.iceConnectionState})}),t.addEventListener("icegatheringstatechange",()=>{t.iceGatheringState==="complete"&&this.negotiation("ice-gathering-complete")});const i=await this.sfuFetch("POST","/v1/rtc/sfu/sessions");if(this.sessionId=i.sessionId,e.length===0)return;const n=e.map(r=>t.addTransceiver(r,{direction:"sendonly",streams:this.localStream?[this.localStream]:[],...r.kind==="video"&&this.simulcastEnabled()?{sendEncodings:me.map(c=>({...c}))}:{}})),s=await t.createOffer();this.negotiation("offer-created"),await t.setLocalDescription(s),this.negotiation("offer-sent");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&&(this.negotiation("answer-received"),await t.setRemoteDescription(o.sessionDescription),this.negotiation("remote-description-set")),this.videoSender=t.getSenders().find(r=>r.track?.kind==="video")??null,await this.applyVideoQuality()}negotiation(e){this.options.onEvent("negotiation",{participantId:this.options.credential.participantId,phase:e})}trackName(e){return`${this.options.credential.participantId}/${e.kind}`}announceTracks(){if(!this.sessionId)return;const e=[...(this.localStream?.getAudioTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:!1})),...(this.localStream?.getVideoTracks()??[]).map(t=>({trackName:this.trackName(t),kind:t.kind,simulcast:this.simulcastEnabled()}))];e.length!==0&&this.sendControl("sfu.tracks",{sessionId:this.sessionId,tracks:e})}async handleTrackAnnouncement(e,t){const i=String(t?.sessionId??""),n=(t?.tracks??[]).map(r=>({trackName:String(r.trackName??""),simulcast:r.simulcast===!0})).filter(r=>!!r.trackName),s=n.map(r=>r.trackName);if(!i||s.length===0||e.participantId===this.options.credential.participantId)return;if(!this.connection||!this.sessionId){this.pendingAnnouncements.set(e.participantId,{sender:e,detail:t??{}});return}const o=this.remotes.get(e.participantId)??{identity:e,stream:new MediaStream,sessionId:i,trackNames:[],simulcastNames:new Set,mids:new Map};o.sessionId=i;for(const r of n)r.simulcast&&o.simulcastNames.add(r.trackName);this.remotes.set(e.participantId,o),this.negotiationQueue=this.negotiationQueue.then(async()=>{const r=s.filter(c=>!o.trackNames.includes(c));r.length!==0&&await this.subscribe(o,r)}).catch(r=>{this.options.onEvent("signalingerror",{message:r instanceof Error?r.message:"SFU subscribe failed"})}),await this.negotiationQueue}async subscribe(e,t){const i=this.connection;if(!i||!this.sessionId||t.length===0)return;const n=await this.sfuFetch("POST",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks`,{tracks:t.map(s=>({location:"remote",sessionId:e.sessionId,trackName:s,...e.simulcastNames.has(s)?{simulcast:{preferredRid:"a",priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}:{}}))});for(const s of n.tracks??[])s.mid&&(e.mids.set(s.mid,String(s.trackName??"")),this.midToParticipant.set(s.mid,e.identity.participantId));if(e.trackNames=[...new Set([...e.trackNames,...t])],n.requiresImmediateRenegotiation&&n.sessionDescription){this.negotiation("offer-received"),await i.setRemoteDescription(n.sessionDescription),this.negotiation("remote-description-set");const s=await i.createAnswer();await i.setLocalDescription(s),this.negotiation("answer-created"),await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/renegotiate`,{sessionDescription:i.localDescription}),this.negotiation("answer-sent")}}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:pe[t],priorityOrdering:"asciibetical",ridNotAvailable:"asciibetical"}}]})}async replaceVideoTrack(e){this.videoSender&&(await this.videoSender.replaceTrack(e),await this.applyVideoQuality())}simulcastEnabled(){return this.options.simulcast!==!1}async applyVideoQuality(){const e=this.videoSender;if(!e)return;const t=this.screenStream?.getVideoTracks()[0]?.id,i=!!(t&&e.track?.id===t),n=e.getParameters(),s=n.encodings?.length?n.encodings:[{}];if(s.length>1){const o=this.quality==="audio"?null:pe[this.quality];n.encodings=s.map(r=>{if(i)return{...r,active:r.rid==="a",...r.rid==="a"?{maxBitrate:Z.maxBitrate,maxFramerate:Z.maxFramerate}:{}};const c=me.find(d=>d.rid===r.rid),l=o!=null&&String(r.rid??"")>=o;return{...r,...c??{},active:l}})}else{const o=this.quality==="audio"?q.audio:q[this.quality],r=i?Z:o;n.encodings=s.map(c=>({...c,...r}))}n.degradationPreference=i?"maintain-resolution":"maintain-framerate",await e.setParameters(n)}async closePublishedTracks(){const e=this.connection;if(!e||!this.sessionId)return;const t=e.getTransceivers().filter(i=>i.sender.track&&i.mid).map(i=>({mid:i.mid}));t.length!==0&&await this.sfuFetch("PUT",`/v1/rtc/sfu/sessions/${encodeURIComponent(this.sessionId)}/tracks/close`,{tracks:t,force:!0})}selfIdentity(){return{participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId??this.options.credential.participantId,displayName:this.options.credential.externalUserId??this.options.credential.participantId,platform:"web"}}async sfuFetch(e,t,i){const n=await fetch(`${this.apiBase}${t}`,{method:e,headers:{authorization:`Bearer ${this.options.credential.signalingToken}`,"content-type":"application/json"},body:i===void 0?void 0:JSON.stringify(i)});if(!n.ok)throw new Error(`SFU request failed with HTTP ${n.status}`);return n.json()}startHeartbeat(e){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(!(this.socket!==e||e.readyState!==WebSocket.OPEN)){if(Date.now()>this.heartbeatDeadline){e.close(4e3,"Signaling heartbeat timed out");return}e.send("ping")}},2e4)}stopHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=null}startSpeakerSampling(){this.stopSpeakerSampling(),this.speakerTimer=setInterval(()=>{this.sampleActiveSpeaker()},1e3)}stopSpeakerSampling(){this.speakerTimer&&clearInterval(this.speakerTimer),this.speakerTimer=null}async sampleActiveSpeaker(){const e=this.connection;if(!e||e.connectionState!=="connected")return;let t;try{t=await e.getStats()}catch{return}const i=[];for(const s of de(t.values())){const o=s.mid?this.midToParticipant.get(s.mid):void 0;o&&i.push({participantId:o,level:s.level})}const n=Y(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){if(this.socket?.readyState!==WebSocket.OPEN){this.stopped||this.options.onEvent("signalingerror",{code:"send_dropped",messageType:String(e.type??"")});return}this.socket.send(JSON.stringify({clientMessageId:e.clientMessageId??Ge(),...e}))}stopStream(e){for(const t of e?.getTracks()??[])t.stop()}}function Ge(){return globalThis.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function Ye(a,e={}){const t=e.document??globalThis.document,i=!!(t&&t.pictureInPictureEnabled);let n=null;function s(){return n||(n=t.createElement("video"),n.muted=!0,n.playsInline=!0,n.style.position="fixed",n.style.width="1px",n.style.height="1px",n.style.opacity="0",n.style.pointerEvents="none",n.addEventListener("leavepictureinpicture",()=>e.onLeave?.()),t.body.appendChild(n),n)}function o(r){const c=a.getRemoteStreams();return r?c.find(l=>l.participant.participantId===r)?.stream??null:c[0]?.stream??a.getLocalStream()}return{supported:i,async enter(r){if(!i)throw new Error("This browser does not support picture-in-picture");const c=o(r);if(!c)throw new Error("No video stream is available for picture-in-picture");const l=s();l.srcObject!==c&&(l.srcObject=c),await l.play().catch(()=>{}),await l.requestPictureInPicture()},async exit(){t.pictureInPictureElement&&typeof t.exitPictureInPicture=="function"&&await t.exitPictureInPicture()},dispose(){n?.remove(),n=null}}}function Ke(a,e){const t=e.document??globalThis.document,i=e.fetchImplementation??globalThis.fetch.bind(globalThis),n=X(e.credential.signalingUrl,globalThis.location?.href).origin,s=e.mimeType??"video/webm",o=e.height??720,r=Math.round(o*16/9);let c=null,l=null,d=null,u=null,y=0,p=[],S=!1;async function C(g,k,f,I="application/json"){const A=await i(`${n}${k}`,{method:g,headers:{authorization:`Bearer ${e.credential.signalingToken}`,"content-type":I},body:f===void 0?void 0:I==="application/json"?JSON.stringify(f):f});if(!A.ok)throw new Error(`Recording request failed with HTTP ${A.status}`);return A.json()}return{get recording(){return S},async start(){if(S)return;u=(await C("POST","/v1/rtc/recordings")).recordingId;const k=t.createElement("canvas");k.width=r,k.height=o;const f=k.getContext("2d"),I=new Map,A=(b,R)=>{let E=I.get(b);return E||(E=t.createElement("video"),E.muted=!0,E.playsInline=!0,E.srcObject=R,E.play().catch(()=>{}),I.set(b,E)),E};l=setInterval(()=>{const b=a.getRemoteStreams(),R=a.getLocalStream(),E=[...b.map(B=>({key:B.participant.participantId,stream:B.stream,label:B.participant.displayName})),...R?[{key:"local",stream:R,label:"me"}]:[]];f.fillStyle="#101418",f.fillRect(0,0,r,o);const x=Math.max(1,Math.ceil(Math.sqrt(E.length))),F=Math.max(1,Math.ceil(E.length/x)),V=r/x,z=o/F;E.forEach((B,v)=>{const Q=A(B.key,B.stream),ce=v%x*V,le=Math.floor(v/x)*z;Q.videoWidth>0&&f.drawImage(Q,ce+2,le+2,V-4,z-4),f.fillStyle="rgba(0,0,0,.6)",f.fillRect(ce+8,le+z-30,Math.min(V-16,12+B.label.length*9),22),f.fillStyle="#fff",f.font="14px system-ui",f.fillText(B.label,ce+14,le+z-14)})},66);const D=k.captureStream(15);d=new AudioContext;const P=d.createMediaStreamDestination(),L=new Set,w=()=>{const b=[...a.getRemoteStreams().map(R=>R.stream),...a.getLocalStream()?[a.getLocalStream()]:[]];for(const R of b)for(const E of R.getAudioTracks())L.has(E.id)||(L.add(E.id),d.createMediaStreamSource(new MediaStream([E])).connect(P))};w();const M=setInterval(w,1e3),m=()=>clearInterval(M);for(const b of P.stream.getAudioTracks())D.addTrack(b);c=(e.mediaRecorderFactory??((b,R)=>new MediaRecorder(b,R)))(D,{mimeType:s}),p=[],c.addEventListener("dataavailable",b=>{b.data&&b.data.size>0&&p.push(b.data)}),c.addEventListener("stop",m),c.start(1e3),y=Date.now(),S=!0},async stop(){if(!S||!c||!u)throw new Error("The recorder is not running");S=!1;const g=c,k=new Promise(P=>g.addEventListener("stop",()=>P(),{once:!0}));g.stop(),await k,l&&clearInterval(l),l=null,await d?.close().catch(()=>{}),d=null;const f=new Blob(p,{type:s});p=[];const I=Math.max(1,Math.round((Date.now()-y)/1e3)),A=await C("PUT",`/v1/rtc/recordings/${encodeURIComponent(u)}/media`,f,s);await C("POST",`/v1/rtc/recordings/${encodeURIComponent(u)}/complete`,{durationSeconds:I});const D={recordingId:u,sizeBytes:A.sizeBytes,durationSeconds:I};return u=null,c=null,D},dispose(){l&&clearInterval(l),l=null;try{c?.stop()}catch{}c=null,d?.close().catch(()=>{}),d=null,S=!1}}}const fe=["none","blur","aurora","cybergrid","bokeh","sunset","image"],ge=["none","snow","confetti","vignette"],ee={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 Xe(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.background??"none",l=e.overlay??"none",d=e.backgroundImage??null,u=e.segmenter??null,y=null,p=!1;const S=t.createElement("video");S.muted=!0,S.playsInline=!0,S.srcObject=new MediaStream([r]),S.play().catch(()=>{});const C=t.createElement("canvas");C.width=i,C.height=n;const g=C.getContext("2d"),k=t.createElement("canvas");k.width=i,k.height=n;const f=k.getContext("2d"),I=()=>Array.from({length:80},(m,h)=>({x:(Math.sin(h*12.9898)*.5+.5)*i,y:(Math.sin(h*78.233)*.5+.5)*n,speed:.6+h%5*.5,drift:Math.sin(h)*.6,size:2+h%4,hue:h*47%360}));let A=I();const D=()=>{if(s)return;const m=S.videoWidth,h=S.videoHeight;if(!m||!h)return;const b=Math.min(1,1280/Math.max(m,h)),R=Math.round(m*b),E=Math.round(h*b);R===i&&E===n||(i=R,n=E,C.width=i,C.height=n,k.width=i,k.height=n,A=I())};S.addEventListener?.("loadedmetadata",D),S.addEventListener?.("resize",D);function P(){if(y){f.clearRect(0,0,i,n),f.drawImage(y,0,0,i,n),f.globalCompositeOperation="source-in",f.drawImage(S,0,0,i,n),f.globalCompositeOperation="source-over",g.drawImage(k,0,0);return}const m=i*.42,h=m*9/16,b=i-m-24,R=n-h-24;g.save(),g.shadowColor="rgba(0,0,0,.5)",g.shadowBlur=24,g.beginPath(),g.roundRect(b,R,m,h,14),g.clip(),g.drawImage(S,b,R,m,h),g.restore()}function L(m){if(c==="none"?g.drawImage(S,0,0,i,n):c==="blur"?(g.filter="blur(14px)",g.drawImage(S,-16,-16,i+32,n+32),g.filter="none",y&&P()):c==="image"&&d?(g.drawImage(d,0,0,i,n),P()):((ee[c]??ee.aurora)(g,i,n,m),P()),l==="snow"||l==="confetti")for(const h of A)h.y+=h.speed*(l==="confetti"?1.6:1),h.x+=h.drift,h.y>n&&(h.y=-6,h.x=Math.random()*i),g.fillStyle=l==="snow"?"rgba(255,255,255,.85)":`hsl(${h.hue}, 90%, 60%)`,l==="confetti"?g.fillRect(h.x,h.y,h.size+2,h.size):(g.beginPath(),g.arc(h.x,h.y,h.size/2,0,Math.PI*2),g.fill());else if(l==="vignette"){const h=g.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)"),g.fillStyle=h,g.fillRect(0,0,i,n)}}const w=setInterval(()=>{L(Date.now()),u&&!p&&c!=="none"&&(p=!0,u.segment(S).then(m=>{y=m}).catch(()=>{y=null}).finally(()=>{p=!1}))},Math.round(1e3/o)),M=C.captureStream(o);return{track:M.getVideoTracks()[0],stream:M,setBackground(m,h){c=fe.includes(m)?m:"none",h!==void 0&&(d=h),c==="none"&&(y=null)},setOverlay(m){l=ge.includes(m)?m:"none"},setSegmenter(m){u=m,m||(y=null)},dispose(){clearInterval(w),u?.close?.();for(const m of M.getTracks())m.stop();S.srcObject=null}}}function te(){return{blinkLeft:0,blinkRight:0,jawOpen:0,smile:0,browUp:0,pupilX:0,pupilY:0,headYaw:0,headPitch:0,headRoll:0,tracked:!1}}function O(a,e){const t=a.find(i=>i.categoryName===e);return t?Math.max(0,Math.min(1,t.score)):0}function ve(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 ye(a,e,t=1){if(!a||a.length===0)return te();const i=ve(e),n=(O(a,"eyeLookOutRight")+O(a,"eyeLookInLeft"))/2,s=(O(a,"eyeLookOutLeft")+O(a,"eyeLookInRight"))/2,o=(O(a,"eyeLookUpLeft")+O(a,"eyeLookUpRight"))/2,r=(O(a,"eyeLookDownLeft")+O(a,"eyeLookDownRight"))/2,c=(O(a,"mouthSmileLeft")+O(a,"mouthSmileRight"))/2,l=(O(a,"mouthFrownLeft")+O(a,"mouthFrownRight"))/2,d=O(a,"eyeBlinkLeft"),u=O(a,"eyeBlinkRight"),y=Math.min(1,Math.abs(i.yaw)/.35)*Math.max(0,Math.min(1,t)),p=(d+u)/2;return{blinkLeft:d+(p-d)*y,blinkRight:u+(p-u)*y,jawOpen:O(a,"jawOpen"),smile:c-l,browUp:(O(a,"browInnerUp")+O(a,"browOuterUpLeft")+O(a,"browOuterUpRight"))/3,pupilX:n-s,pupilY:r-o,headYaw:i.yaw,headPitch:i.pitch,headRoll:i.roll,tracked:!0}}function ie(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 Se(a,e,t,i,n){n||(a.fillStyle="#101822",a.fillRect(0,0,t,i));const s=n?n.x+e.headYaw*n.radius*.1:t/2+e.headYaw*t*.18,o=n?n.y+e.headPitch*n.radius*.1:i/2+e.headPitch*i*.18,r=n?n.radius:Math.min(t,i)*.28;a.save(),a.translate(s,o),a.rotate(e.headRoll*.6);const c=Math.max(-1.35,Math.min(1.35,e.headYaw)),l=Math.max(-.9,Math.min(.9,e.headPitch)),d=Math.sin(c),u=L=>Math.sin(L+c)*r,y=L=>Math.cos(L+c),p=Math.sin(l)*r*.22;a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.35,-r*.25,r*.98,r*.95,0,0,Math.PI*2),a.fill(),a.fillStyle=e.tracked?"#f2c9a0":"#5a6470",a.beginPath(),a.ellipse(0,0,r*(1-Math.abs(d)*.12),r*1.12,0,0,Math.PI*2),a.fill(),a.fillStyle="#3a2a1c",a.beginPath(),a.ellipse(-d*r*.28,-r*.72+p*.4,r*.92,r*.5,0,Math.PI,Math.PI*2),a.fill();const S=0;a.fillStyle="#e5b58a",a.beginPath();const C=u(S)*.9,g=r*.12+p;a.ellipse(C+d*r*.14,g,r*.11,r*.14,0,0,Math.PI*2),a.fill();for(const L of[-1,1]){const w=L*.42,M=y(w);if(M<=.12)continue;const m=u(w)*.82,h=Math.max(.2,M),b=L<0?e.blinkLeft:e.blinkRight,R=Math.max(.06,1-b),E=e.browUp*r*.14;a.strokeStyle="#4a2e18",a.lineWidth=r*.07,a.lineCap="round",a.beginPath(),a.moveTo(m-r*.2*h,-r*.42-E+p),a.quadraticCurveTo(m,-r*.52-E+p,m+r*.2*h,-r*.44-E+p),a.stroke();const x=-r*.18+p;a.fillStyle="#ffffff",a.beginPath(),a.ellipse(m,x,r*.16*h,r*.12*R,0,0,Math.PI*2),a.fill(),a.fillStyle="#22303c",a.beginPath(),a.ellipse(m+e.pupilX*r*.07*h,x+e.pupilY*r*.05,r*.06*h,r*.06*R,0,0,Math.PI*2),a.fill()}const k=Math.max(.25,y(0)),f=u(0)*.7,I=r*.55*k,A=r*(.04+e.jawOpen*.34),D=e.smile*r*.12,P=r*.42+p;a.fillStyle="#8c2f2f",a.beginPath(),a.moveTo(f-I/2,P-D),a.quadraticCurveTo(f,P+A,f+I/2,P-D),a.quadraticCurveTo(f,P-A*.25,f-I/2,P-D),a.closePath(),a.fill(),a.restore(),!e.tracked&&!n&&(a.fillStyle="#9fb0bd",a.font=`${Math.round(i*.045)}px system-ui`,a.textAlign="center",a.fillText("No face detected",t/2,i*.9),a.textAlign="left")}const ne={mouthGain:1.7,blinkGain:1,eyeSync:1,smoothing:.4,avatarScale:1,avatarLift:0,lostHoldMs:400};function ae(){return{render(a,e,t,i,n){Se(a,e,t,i,n??void 0)}}}const se=a=>Math.max(0,Math.min(1,a));function Ze(a,e={}){const t=e.document??globalThis.document;let i=e.width??1280,n=e.height??720;const s=!!(e.width||e.height),o=e.frameRate??24,r=a instanceof MediaStreamTrack?a:a.getVideoTracks()[0];if(!r)throw new Error("A video track is required");let c=e.tracker??null,l=e.renderer??ae(),d=e.mode??"replace";const u={...ne,...e.tuning},y=e.backdrop??"#101822",p=t.createElement("video");p.muted=!0,p.playsInline=!0,p.srcObject=new MediaStream([r]),p.play().catch(()=>{});const S=t.createElement("canvas");S.width=i,S.height=n;const C=S.getContext("2d"),g=()=>{if(s)return;const w=p.videoWidth,M=p.videoHeight;if(!w||!M)return;const m=Math.min(1,1280/Math.max(w,M)),h=Math.round(w*m),b=Math.round(M*m);(!(h===i||b===n)||h!==i||b!==n)&&(i=h,n=b,S.width=i,S.height=n)};p.addEventListener?.("loadedmetadata",g),p.addEventListener?.("resize",g);let k=te(),f=null,I=0;function A(w){if(w.length<=454)return;const M=w[10],m=w[152],h=w[234],b=w[454],R=(h.x+b.x)/2*i,E=(h.y+b.y)/2*n,x=Math.hypot((m.x-M.x)*i,(m.y-M.y)*n),F=Math.max(.6,Math.cos(k.headPitch)),V={x:R,y:E,radius:Math.max(24,x/F*.8)};f=f?{x:f.x+(V.x-f.x)*.4,y:f.y+(V.y-f.y)*.4,radius:f.radius+(V.radius-f.radius)*.15}:V}function D(w){if(c&&p.readyState>=2){let m=null;try{m=c.detect(p,w)}catch{}const h=ye(m?.categories??null,m?.matrix??null,u.eyeSync);h.jawOpen=se(h.jawOpen*u.mouthGain),h.blinkLeft=se(h.blinkLeft*u.blinkGain),h.blinkRight=se(h.blinkRight*u.blinkGain),h.tracked?(I=w,k=ie(k,h,u.smoothing),m?.landmarks&&A(m.landmarks)):k.tracked&&w-I>u.lostHoldMs&&(k=ie(k,{...k,jawOpen:0,browUp:0,smile:k.smile*.5,blinkLeft:.1,blinkRight:.1},.05))}d==="overlay"&&p.readyState>=2?C.drawImage(p,0,0,i,n):(C.fillStyle=y,C.fillRect(0,0,i,n));const M=f?{x:f.x,y:f.y+u.avatarLift*f.radius,radius:f.radius*u.avatarScale}:null;l?.render(C,k,i,n,M)}const P=setInterval(()=>D(Date.now()),Math.round(1e3/o)),L=S.captureStream(o);return{track:L.getVideoTracks()[0],stream:L,getRig:()=>({...k}),setMode(w){d=w==="overlay"?"overlay":"replace"},setTracker(w){c?.close?.(),c=w},setRenderer(w){l?.dispose?.(),l=w??ae()},setTuning(w){for(const M of Object.keys(ne)){const m=w[M];typeof m=="number"&&Number.isFinite(m)&&(u[M]=m)}},getTuning:()=>({...u}),dispose(){clearInterval(P),c?.close?.(),l?.dispose?.();for(const w of L.getTracks())w.stop();p.srcObject=null}}}class j extends Error{constructor(e,t){super(t),this.code=e,this.name="ZipError"}code}const we=101010256,Te=33639248,ke=67324752,re=4294967295;function be(a){if(a.byteLength<4)return!1;const e=new DataView(a).getUint32(0,!0);return e===ke||e===Te||e===we}function et(a){const e=Math.max(0,a.byteLength-22-65535);for(let t=a.byteLength-22;t>=e;t-=1)if(a.getUint32(t,!0)===we)return t;throw new j("zip_unreadable","The archive index could not be found.")}function Ce(a){const e=new DataView(a),t=et(e),i=e.getUint16(t+10,!0);let n=e.getUint32(t+16,!0);if(n===re)throw new j("zip_unsupported","ZIP64 archives are not supported.");const s=new TextDecoder,o=[];for(let r=0;r<i;r+=1){if(n+46>a.byteLength||e.getUint32(n,!0)!==Te)throw new j("zip_unreadable","The archive index is malformed.");const c=e.getUint16(n+8,!0),l=e.getUint16(n+10,!0),d=e.getUint32(n+20,!0),u=e.getUint32(n+24,!0),y=e.getUint16(n+28,!0),p=e.getUint16(n+30,!0),S=e.getUint16(n+32,!0),C=e.getUint32(n+42,!0),g=s.decode(new Uint8Array(a,n+46,y));o.push({path:g,bytes:u,compressedBytes:d,compressionMethod:l,encrypted:(c&1)!==0,localHeaderOffset:C}),n+=46+y+p+S}return o}async function tt(a,e){if(e.encrypted)throw new j("zip_encrypted","The archive is password protected.");if(e.bytes===re||e.compressedBytes===re)throw new j("zip_unsupported","ZIP64 archives are not supported.");const t=new DataView(a),i=e.localHeaderOffset;if(i+30>a.byteLength||t.getUint32(i,!0)!==ke)throw new j("zip_unreadable","An archive entry header is malformed.");const n=t.getUint16(i+26,!0),s=t.getUint16(i+28,!0),o=i+30+n+s;if(o+e.compressedBytes>a.byteLength)throw new j("zip_unreadable","An archive entry runs past the end of the file.");const r=a.slice(o,o+e.compressedBytes);if(e.compressionMethod===0)return r;if(e.compressionMethod!==8)throw new j("zip_unsupported",`Archive compression method ${e.compressionMethod} is not supported.`);if(typeof DecompressionStream>"u")throw new j("zip_unsupported","This browser cannot expand compressed archives.");const c=new Blob([r]).stream().pipeThrough(new DecompressionStream("deflate-raw"));return new Response(c).arrayBuffer()}async function it(a){const e=typeof File<"u"&&a instanceof File?await a.arrayBuffer():a;if(!be(e))return[];try{return Me(Ce(e)).map(t=>t.path)}catch{return[]}}const Ee=64*1024*1024,nt="streaming-cdn-avatars",$="models",at=1179937895,st=1313821514;class N extends Error{constructor(e,t,i=[]){super(t),this.code=e,this.name="AvatarModelError",this.choices=i}code;choices}function Me(a){return a.filter(e=>/\.vrm$/i.test(e.path)&&!e.path.endsWith("/")&&!e.path.split("/").some(t=>t==="__MACOSX"||t.startsWith("._")))}function U(a){const e=typeof a=="string"?a.trim():"";return e.length>0?e:null}function Re(a){const e=new DataView(a);if(a.byteLength<20||e.getUint32(0,!0)!==at)throw new N("model_not_glb","The file is not a binary glTF (.vrm / .glb) model.");let t=12,i=null;for(;t+8<=a.byteLength;){const r=e.getUint32(t,!0),c=e.getUint32(t+4,!0),l=t+8;if(l+r>a.byteLength)break;if(c===st){try{const d=new TextDecoder().decode(new Uint8Array(a,l,r)).replace(/[\s\u0000]+$/,"");i=JSON.parse(d)}catch{throw new N("model_unreadable","The model's glTF descriptor could not be read.")}break}t=l+r+(4-r%4)%4}const n=i?.extensions??{},s=n.VRM?.meta,o=n.VRMC_vrm?.meta;if(!s&&!o)return null;if(o){const r=Array.isArray(o.authors)?o.authors.filter(c=>typeof c=="string"):[];return{title:U(o.name),author:r.length?r.join(", "):null,allowedUser:U(o.avatarPermission),commercialUse:U(o.commercialUsage),licenseName:U(o.licenseUrl)?"VRM 1.0":null,licenseUrl:U(o.licenseUrl)??U(o.otherLicenseUrl)}}return{title:U(s?.title),author:U(s?.author),allowedUser:U(s?.allowedUserName),commercialUse:U(s?.commercialUssageName),licenseName:U(s?.licenseName),licenseUrl:U(s?.otherLicenseUrl)}}function rt(){return new Promise((a,e)=>{if(typeof indexedDB>"u"){e(new N("storage_unavailable","This browser cannot store imported avatar models."));return}const t=indexedDB.open(nt,1);t.onupgradeneeded=()=>{t.result.objectStoreNames.contains($)||t.result.createObjectStore($,{keyPath:"id"})},t.onsuccess=()=>a(t.result),t.onerror=()=>e(new N("storage_unavailable","Imported avatar storage could not be opened."))})}async function J(a,e){const t=await rt();try{return await new Promise((i,n)=>{const s=e(t.transaction($,a).objectStore($));s.onsuccess=()=>i(s.result),s.onerror=()=>n(new N("storage_unavailable","The imported avatar could not be stored."))})}finally{t.close()}}const Ie=a=>({id:a.id,name:a.name,bytes:a.bytes,importedAt:a.importedAt,license:a.license});async function ot(a,e={}){const t=e.maxBytes??Ee,i=typeof File<"u"&&a instanceof File;let n=i?a.name:null,s=i?await a.arrayBuffer():a;if(be(s)){const l=await ct(s,e.entryPath,t);s=l.buffer,n=l.path.split("/").pop()??n}if(s.byteLength>t)throw new N("model_too_large","The model is larger than the "+Math.round(t/1024/1024)+" MB limit.");const o=Re(s);if(!o)throw new N("model_not_vrm","The file is a glTF model but carries no VRM character data.");const r=o.title||(n?n.replace(/\.(vrm|glb)$/i,""):null),c={id:"avatar_"+Date.now().toString(36)+"_"+Math.random().toString(36).slice(2,8),name:e.name?.trim()||r||"Imported avatar",bytes:s.byteLength,importedAt:new Date().toISOString(),license:o,data:s};return await J("readwrite",l=>l.put(c)),Ie(c)}async function ct(a,e,t){let i;try{i=Me(Ce(a))}catch(s){throw s instanceof j?new N(Ae(s.code),s.message):new N("archive_unreadable","The archive could not be read.")}if(i.length===0)throw new N("archive_no_model","The archive contains no .vrm character.");const n=e?i.find(s=>s.path===e):i.length===1?i[0]:null;if(!n)throw new N("archive_many_models","The archive contains several characters; choose one.",i.map(s=>s.path));if(n.bytes>t)throw new N("model_too_large","The character inside the archive is larger than the "+Math.round(t/1024/1024)+" MB limit.");try{return{buffer:await tt(a,n),path:n.path}}catch(s){throw s instanceof j?new N(Ae(s.code),s.message):new N("archive_unreadable","The character could not be expanded from the archive.")}}function Ae(a){return a==="zip_encrypted"?"archive_encrypted":a==="zip_unsupported"?"archive_unsupported":"archive_unreadable"}async function lt(){return(await J("readonly",e=>e.getAll())??[]).map(Ie).sort((e,t)=>t.importedAt.localeCompare(e.importedAt))}async function dt(a){return(await J("readonly",t=>t.get(a)))?.data??null}async function ht(a){await J("readwrite",e=>e.delete(a))}const Pe=3e4;class Le extends EventTarget{mode;options;client=null;peerConnection;state="initialized";latestMediaScore=null;latestQuality=null;statsTimer=null;statsTask=null;previousCounters=null;lastTelemetryAt=0;joinPromise=null;requestedQuality;effectiveQuality;poorQualitySamples=0;goodQualitySamples=0;transport="mesh";joinStartedAt=null;connectTimer=null;connectTimedOut=!1;lastConnectTimeout=null;mediaConnectedPeers=new Set;firstMediaConnectedElapsedMs=null;lastNegotiationPhase=new Map;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 e=this.options.transport==="auto"?this.mode==="meeting"?"sfu":"mesh":this.options.transport??"mesh";this.transport=e;const t=e==="sfu"?Je:H;return this.client=new t({credential:{signalingToken:this.options.credential.signalingToken,signalingUrl:this.options.credential.signalingUrl,roomId:this.options.credential.roomId,participantId:this.options.credential.participantId,externalUserId:this.options.credential.externalUserId,iceServers:this.options.credential.iceServers},audio:this.options.audio,video:this.options.video,receiveAudio:this.options.receiveAudio,receiveVideo:this.options.receiveVideo,quality:this.effectiveQuality,preferredVideoCodec:this.options.preferredVideoCodec??"auto",iceTransportPolicy:this.options.iceTransportPolicy??"all",localStream:this.options.localStream,simulcast:this.options.simulcast??!0,onEvent:(i,n)=>this.handleNativeEvent(i,n)}),await this.client.initialize(),this.startStats(),this.emit("initialized",{mode:this.mode,sdkVersion:_e}),this.options.autoJoin&&await this.join(),this}catch(e){this.state="failed";const t=Be(e,"RTC initialization failed");throw this.emit("error",_(t)),t}}async join(){const e=this.requireClient();if(this.joinPromise)return this.joinPromise;const t=this.state;return this.state="joining",(this.joinStartedAt===null||t==="failed"||t==="disconnected")&&(this.joinStartedAt=Date.now(),this.connectTimedOut=!1,this.lastConnectTimeout=null,this.mediaConnectedPeers.clear(),this.firstMediaConnectedElapsedMs=null,this.lastNegotiationPhase.clear(),this.armConnectTimeout()),this.emit("joining",{}),this.joinPromise=e.join().catch(i=>{this.state="failed",this.clearConnectTimeout();const n=Be(i,"RTC connection failed");throw this.emit("error",_(n)),n}).finally(()=>{this.joinPromise=null}),this.joinPromise}async leave(){if(!this.client)return;this.clearConnectTimeout(),await this.joinPromise?.catch(()=>{});const e=this.client.getPeerStates();await this.sampleAndReport("final",St(e)).catch(()=>{}),await this.client.leave(),this.state="disconnected",this.stopStats()}getConnectPhase(){return{joinedAt:this.joinStartedAt===null?null:new Date(this.joinStartedAt).toISOString(),mediaConnected:this.mediaConnectedPeers.size>0,mediaConnectedElapsedMs:this.firstMediaConnectedElapsedMs,timedOut:this.connectTimedOut,lastConnectTimeout:this.lastConnectTimeout}}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(je)}getLocalStream(){return this.client?.getLocalStream()??null}getRemoteStreams(){return(this.client?.getRemoteStreams()??[]).map(e=>({participant:je(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 l=null;if(c){const u=performance.now();try{const y=await fetch(c,{method:"HEAD",cache:"no-store",credentials:"omit"});l=Math.round(performance.now()-u),y.ok||t.push("network_probe_failed")}catch{t.push("network_probe_failed")}}const d={passed:t.length===0,sampledAt:new Date().toISOString(),secureContext:i,mediaPermission:n,microphoneCount:o,cameraCount:r,probeLatencyMs:l,effectiveConnectionType:xe()?.effectiveType??null,issues:t};return this.emit("preflight",d),this.options.autoReportStats&&await this.reportQuality({sampleType:"preflight",sampledAt:d.sampledAt,connectionState:d.passed?"ready":"failed",rttMs:d.probeLatencyMs,effectiveConnectionType:d.effectiveConnectionType}).catch(()=>{}),d}async collectQualitySample(e="runtime",t){const i=this.options.statsProvider?await this.options.statsProvider():{},n=this.peerConnection?await vt(this.peerConnection,this.previousCounters):null;n?.counters&&(this.previousCounters=n.counters);const o={...{sampleType:e,sampledAt:new Date().toISOString(),connectionState:t??this.peerConnection?.connectionState??this.state,statsTransport:this.client?this.transport:null,statsConnectionCount:this.client?this.client.getPeerStates().length:null,statsRowCount:n?.rowCount??null,statsRowTypes:n?.rowTypes??null,mediaScore:this.latestMediaScore,networkScore:this.latestMediaScore==null?5:Math.max(0,Math.min(5,Math.round(this.latestMediaScore))),estimatedMos:4.5,rttMs:null,jitterMs:null,packetLossPct:null,packetLossCumulativePct:null,packetsLost:null,packetsReceived:null,inboundBitrateBps:null,outboundBitrateBps:null,availableOutgoingBitrateBps:null,framesPerSecond:null,frameWidth:null,frameHeight:null,inboundFrameWidth:null,inboundFrameHeight:null,outboundFrameWidth:null,outboundFrameHeight:null,framesDropped:null,freezeCount:null,freezeDurationMs:null,concealedSamples:null,codec:null,transportProtocol:null,candidateType:null,qualityLimitationReason:null,deviceMemoryGb:wt(),effectiveConnectionType:xe()?.effectiveType??null},...n?.sample??{},...i,sampleType:e,sampledAt:new Date().toISOString()},r=yt(o);return o.networkScore=r.networkScore,o.estimatedMos=r.estimatedMos,this.latestQuality=o,o}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.clearConnectTimeout(),this.client&&this.client.leave(),this.client=null,this.joinPromise=null,this.peerConnection=null,this.state="disconnected"}handleNativeEvent(e,t){if(e==="connected")this.state="connected",this.peerConnection=this.client?.getPrimaryPeerConnection()??null;else if(e==="disconnected")this.state="disconnected";else if(e==="peerconnectionstate"){this.peerConnection=this.client?.getPrimaryPeerConnection()??null;const{participant:i,state:n}=t;n==="connected"&&i?.participantId&&this.markMediaConnected(i.participantId)}else if(e==="remotestream"){const{participant:i}=t;i?.participantId&&this.markMediaConnected(i.participantId)}else if(e==="negotiation"){const i=t;this.lastNegotiationPhase.set(i.participantId,i.phase),this.emit("negotiation",{...i,at:this.elapsedSinceJoin()});return}this.emit(e,t)}elapsedSinceJoin(){return this.joinStartedAt===null?0:Math.max(0,Date.now()-this.joinStartedAt)}markMediaConnected(e){if(this.mediaConnectedPeers.has(e))return;this.mediaConnectedPeers.add(e);const t=this.mediaConnectedPeers.size===1,i=this.elapsedSinceJoin();t&&(this.firstMediaConnectedElapsedMs=i,this.clearConnectTimeout()),this.emit("mediaconnected",{participantId:e,elapsedMs:i,first:t})}armConnectTimeout(){this.clearConnectTimeout();const e=this.options.connectTimeoutMs??Pe;!(e>0)||this.connectTimedOut||(this.connectTimer=setTimeout(()=>{if(this.connectTimer=null,this.mediaConnectedPeers.size>0||this.connectTimedOut||!this.client)return;this.connectTimedOut=!0;const t=this.client.getPeerStates().map(n=>({...n,lastNegotiationPhase:this.lastNegotiationPhase.get(n.participantId)??null})),i={elapsedMs:this.elapsedSinceJoin(),peers:t,participantCount:this.client.getParticipants().length+1};this.lastConnectTimeout=i,this.emit("connecttimeout",i)},e))}clearConnectTimeout(){this.connectTimer!==null&&clearTimeout(this.connectTimer),this.connectTimer=null}startStats(){this.stopStats(),this.statsTimer=setInterval(()=>{this.sampleAndReport("runtime")},this.options.statsInterval)}async sampleAndReport(e,t){if(this.statsTask){if(e==="runtime")return;await this.statsTask.catch(()=>{})}const i=(async()=>{try{const n=await this.collectQualitySample(e,t);await this.adaptQuality(n),this.emit("stats",{...this.getStats(),latestQuality:n});const s=Date.now(),o=e!=="runtime"||s-this.lastTelemetryAt>=this.options.telemetryInterval;this.options.autoReportStats&&o&&(await this.reportQuality(n),this.lastTelemetryAt=s)}catch(n){this.emit("telemetryerror",_(n))}})();this.statsTask=i;try{await i}finally{this.statsTask===i&&(this.statsTask=null)}}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 ut(a){return new Le(a).initialize()}let G=null;function De(){if(G)return G;const a=globalThis,e=a.AudioContext??a.webkitAudioContext;return e?(G=new e,G):null}async function Oe(){const a=De();return a?(a.state==="suspended"&&await a.resume().catch(()=>{}),a.state==="running"):!1}class mt{options;media=null;cadenceTimer=null;oscillators=new Set;constructor(e){this.options=e}unlock(){return Oe()}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)}}}const pt=6e4;class Ne 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;mediaSubscriptions=new Map;activeCallIds=new Set;seenInvitationIds=new Set;invitationExpiryTimers=new Map;startCallRequest=null;callGeneration=0;lastDisconnect=null;constructor(e){if(super(),!e.token&&!e.tokenProvider)throw new Error("A client token or tokenProvider is required");if(e.token&&!e.token.startsWith("rtcc_"))throw new Error("A valid RTC client token is required");if(!e.signalingUrl&&!e.callActionsUrl&&!e.apiBaseUrl&&!globalThis.location?.origin)throw new Error("signalingUrl or callActionsUrl is required outside a browser");this.options=e,this.currentToken=e.token??null,this.currentTokenExpiresAt=e.tokenExpiresAt,this.callAudio=new mt(e),this.mount=Ue(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=Ue(e),this.currentCall&&this.renderIncoming(this.currentCall)}attachMediaClient(e,t){if(!e.trim())throw new Error("callId is required");this.mediaSubscriptions.get(e)?.(),this.mediaClients.set(e,t);const i=c=>{c.detail?.detail?.resumed||this.reportSignalingDiagnostic({event:"media_joined",callId:e})},n=c=>{const l=c.detail.detail;l.first&&(this.clearInvitationExpiry(e),this.reportSignalingDiagnostic({event:"media_connected",callId:e,elapsedMs:l.elapsedMs}))},s=c=>{const l=c.detail.detail;this.reportSignalingDiagnostic({event:"media_timeout",callId:e,...l})};t.addEventListener("connected",i),t.addEventListener("mediaconnected",n),t.addEventListener("connecttimeout",s);const o=()=>{t.removeEventListener("connected",i),t.removeEventListener("mediaconnected",n),t.removeEventListener("connecttimeout",s),this.mediaSubscriptions.get(e)===o&&this.mediaSubscriptions.delete(e)};this.mediaSubscriptions.set(e,o);const r=t.getConnectPhase();return t.getStats().state==="connected"&&this.reportSignalingDiagnostic({event:"media_joined",callId:e}),r.mediaConnected?(this.clearInvitationExpiry(e),this.reportSignalingDiagnostic({event:"media_connected",callId:e,elapsedMs:r.mediaConnectedElapsedMs??0})):r.timedOut&&r.lastConnectTimeout&&this.reportSignalingDiagnostic({event:"media_timeout",callId:e,...r.lastConnectTimeout}),()=>{this.mediaClients.get(e)===t&&(o(),this.mediaClients.delete(e),this.activeCallIds.has(e)&&this.armCallWatchdog(e))}}mediaEstablished(e){return this.mediaClients.get(e)?.getConnectPhase().mediaConnected===!0}armCallWatchdog(e){this.scheduleInvitationExpiry(e,new Date(Date.now()+pt).toISOString())}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=this.callGeneration,s=(async()=>{await this.callAudio.unlock(),this.callAudio.startRingback();let o;try{o=await this.request("/v1/rtc/client/calls",{method:"POST",headers:e.idempotencyKey?{"idempotency-key":e.idempotencyKey}:void 0,body:JSON.stringify(i)})}catch(c){throw this.callAudio.stop(),c}!o.credential&&o.callerCredential&&(o.credential=o.callerCredential);const r=String(o.call?.id??"")||null;if(r&&n===this.callGeneration){this.outgoingCallId=r,this.activeCallIds.add(r);const c=o.call?.expiresAt;this.scheduleInvitationExpiry(r,typeof c=="string"?c:void 0)}else r&&this.callAction(r,"cancel").catch(()=>{});return this.emit("outgoingcall",o),o})();this.startCallRequest=s;try{return await s}finally{this.startCallRequest===s&&(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.armCallWatchdog(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");let t;try{return t=await this.callAction(e,"reject")}finally{this.releaseMedia(e),this.showTerminal(e,"Call declined"),this.emit("callrejected",t??{call:{id:e}})}}async busy(e=this.currentCall?.id){if(!e)throw new Error("No incoming call is available");try{return await this.callAction(e,"busy")}finally{this.releaseMedia(e),this.showTerminal(e,"Line busy")}}async end(e){this.callAudio.stop();try{return await this.callAction(e,"end")}finally{this.releaseMedia(e),this.showTerminal(e,"Call ended")}}async cancel(e){this.callAudio.stop();try{return await this.callAction(e,"cancel")}finally{this.releaseMedia(e),this.showTerminal(e,"Call cancelled")}}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.scheduleInvitationExpiry(e.id,e.expiresAt),this.currentCall=e,this.callAudio.startIncoming(),this.renderIncoming(e),this.emit("incomingcall",e)}scheduleInvitationExpiry(e,t){const i=t?Date.parse(t):Date.now()+45e3,n=Math.min(18e4,Math.max(5e3,(Number.isFinite(i)?i:Date.now()+45e3)-Date.now()+1e4));this.clearInvitationExpiry(e),this.invitationExpiryTimers.set(e,setTimeout(()=>{this.invitationExpiryTimers.delete(e),!(!this.activeCallIds.has(e)||this.mediaEstablished(e))&&(this.emit("callexpired",{id:e,expiresAt:t??null}),this.release(e))},n))}clearInvitationExpiry(e){const t=this.invitationExpiryTimers.get(e);t&&clearTimeout(t),this.invitationExpiryTimers.delete(e)}release(e){if(!e.trim())throw new Error("callId is required");this.clearInvitationExpiry(e),this.currentCall?.id===e&&(this.callAudio.stop(),this.dismiss(e)),this.outgoingCallId===e&&(this.outgoingCallId=null),this.releaseMedia(e),this.emit("callreleased",{id:e})}handleCallUpdate(e){const t=e.id===this.currentCall?.id||e.id===this.outgoingCallId,i=["answered_elsewhere","rejected","busy","cancelled","ended","failed","missed"];i.includes(e.status)&&this.clearInvitationExpiry(e.id),e.status==="accepted"&&(this.mediaEstablished(e.id)?this.clearInvitationExpiry(e.id):this.armCallWatchdog(e.id)),t&&e.status==="accepted"?(this.activeCallIds.add(e.id),this.callAudio.stop(),this.dismiss(e.id)):t&&i.includes(e.status)&&this.showTerminal(e.id,gt(e.status)),i.includes(e.status)&&this.releaseMedia(e.id),this.emit("callupdated",e)}disconnect(){this.stopped=!0,this.callGeneration+=1,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.tokenRefreshTimer&&clearTimeout(this.tokenRefreshTimer),this.reconnectTimer=null,this.heartbeatTimer=null,this.tokenRefreshTimer=null,this.socket?.close(1e3,"Client disconnected"),this.socket=null,this.callAudio.stop(),this.dismiss();for(const e of this.invitationExpiryTimers.values())clearTimeout(e);this.invitationExpiryTimers.clear();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",_(s)),!this.stopped&&this.options.reconnect!==!1&&this.scheduleReconnect();return}if(this.stopped||this.socket)return;const i=K(this.options.signalingUrl,globalThis.location?.href),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,actorExternalId:typeof n.actorExternalId=="string"?n.actorExternalId:null});return}i==="presence"?this.emit("presence",{users:t.users??[]}):i!=="pong"&&this.emit(i||"signal",t)}async callAction(e,t){return this.request(`/v1/rtc/client/calls/${encodeURIComponent(e)}/${t}`,{method:"POST",body:"{}"})}async request(e,t,i=!1){const n=this.options.callActionsUrl??this.options.apiBaseUrl??this.options.signalingUrl??globalThis.location?.origin;if(!n)throw new Error("callActionsUrl is required for call actions");const s=X(n,globalThis.location?.href),o=await this.resolveToken("initial"),r=new Headers(t.headers);r.set("authorization",`Bearer ${o}`),r.set("content-type","application/json");const c=await fetch(new URL(e,s.origin),{...t,headers:r}),l=await c.json().catch(()=>({}));if(c.status===401&&this.options.tokenProvider&&!i)return await this.resolveToken("unauthorized",!0),this.request(e,t,!0);if(!c.ok){const d=l.error;throw new Error(typeof d=="string"?d:d?.message||`RTC API failed with HTTP ${c.status}`)}return l}renderIncoming(e){if(!this.mount)return;this.clearUi();const t=document.createElement("section");t.dataset.rtcIncomingCall=e.id,t.setAttribute("role","dialog"),t.setAttribute("aria-label",`Incoming ${e.mode} call from ${e.caller.name}`),Object.assign(t.style,{display:"grid",gap:"14px",padding:"20px",border:"1px solid #cbd8dc",borderRadius:"8px",background:"#fff",color:"#102027",boxShadow:"0 14px 38px #07101429",fontFamily:"system-ui, sans-serif"});const i=document.createElement("small");i.textContent=e.mode==="video"?"Incoming video call":"Incoming voice call",i.style.color="#5d7079";const n=document.createElement("strong");n.textContent=e.caller.name,n.style.fontSize="20px";const s=document.createElement("div");Object.assign(s.style,{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"});const o=qe("Decline","#fff","#a32920","#d9a09b"),r=qe("Answer","#fff","#08745f","#08745f");o.addEventListener("click",()=>{this.reject(e.id).catch(c=>this.emit("error",_(c)))}),r.addEventListener("click",()=>{this.accept(e.id).catch(c=>this.emit("error",_(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",_(i)),this.stopped||(this.tokenRefreshTimer=setTimeout(()=>this.scheduleTokenRefresh(),3e4))})},Math.min(t,2147e6))}releaseMedia(e){this.clearInvitationExpiry(e),this.activeCallIds.delete(e),this.seenInvitationIds.delete(e);const t=this.mediaClients.get(e);t&&(this.mediaSubscriptions.get(e)?.(),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",_(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 ft(a){const e=new Ne(a);return a.autoConnect!==!1&&a.signalingUrl&&e.connect(),e}function Ue(a){return a?typeof a=="string"?document.querySelector(a):a:null}function qe(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 gt(a){return{rejected:"Call declined",busy:"Line busy",cancelled:"Call cancelled",ended:"Call ended",failed:"Call failed",missed:"No answer",answered_elsewhere:"Answered on another device"}[a]??"Call ended"}function je(a){return{id:a.participantId,name:a.displayName,role:"participant"}}async function vt(a,e){const t=await a.getStats(),i=new Map,n=new Map;let s=0,o=0,r=0,c=0,l=0,d=0,u=0,y=0,p=0,S=0,C=0,g=0,k=0,f=0,I=0,A=null,D=null,P=null,L=null,w=null,M=null;const m=new Set;let h=0;t.forEach(v=>{h+=1,m.add(String(v.type??"")),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)&&(l+=Number(v.jitter),d+=1),u=Math.max(u,Number(v.framesPerSecond??0)),y=Math.max(y,Number(v.frameWidth??0)),C=Math.max(C,Number(v.frameHeight??0)),g+=Number(v.framesDropped??0),k+=Number(v.freezeCount??0),f+=Number(v.totalFreezesDuration??0),I+=Number(v.concealedSamples??0),w||=i.get(String(v.codecId??""))??null),v.type==="outbound-rtp"&&!v.isRemote&&(p=Math.max(p,Number(v.frameWidth??0)),S=Math.max(S,Number(v.frameHeight??0)),o+=Number(v.bytesSent??0),M||=v.qualityLimitationReason?String(v.qualityLimitationReason):null,w||=i.get(String(v.codecId??""))??null),v.type==="candidate-pair"&&(v.selected||v.nominated)&&v.state==="succeeded"){A=Number.isFinite(v.currentRoundTripTime)?Number(v.currentRoundTripTime):A,D=Number.isFinite(v.availableOutgoingBitrate)?Number(v.availableOutgoingBitrate):D;const Q=n.get(v.localCandidateId);P=Q?.candidateType?String(Q.candidateType):P,L=Q?.protocol?String(Q.protocol):L}});const b=Date.now(),R=e?Math.max(.001,(b-e.timestamp)/1e3):0,E=e?Math.max(0,Math.round((s-e.inboundBytes)*8/R)):null,x=e?Math.max(0,Math.round((o-e.outboundBytes)*8/R)):null,F=r+c,V=e?Math.max(0,r-e.packetsLost):r,z=e?Math.max(0,c-e.packetsReceived):c,B=V+z;return{counters:{timestamp:b,inboundBytes:s,outboundBytes:o,packetsLost:r,packetsReceived:c},rowCount:h,rowTypes:[...m].filter(Boolean).sort().join(","),sample:{rttMs:A===null?null:A*1e3,jitterMs:d?l/d*1e3:null,packetLossPct:B?V/B*100:null,packetLossCumulativePct:F?r/F*100:null,packetsLost:r,packetsReceived:c,inboundBitrateBps:E,outboundBitrateBps:x,availableOutgoingBitrateBps:D,framesPerSecond:u||null,frameWidth:y||null,frameHeight:C||null,inboundFrameWidth:y||null,inboundFrameHeight:C||null,outboundFrameWidth:p||null,outboundFrameHeight:S||null,framesDropped:g,freezeCount:k,freezeDurationMs:Math.round(f*1e3),concealedSamples:I,codec:w,transportProtocol:L,candidateType:P,qualityLimitationReason:M}}}function yt(a){let e=a.mediaScore==null?5:Math.round(Ve(Number(a.mediaScore),0,5));const t=oe(a.packetLossPct)??0,i=oe(a.rttMs)??0,n=oe(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(Ve(4.5-t*.09-i*.0011-n*.006,1,4.5).toFixed(2));return{networkScore:e,estimatedMos:o}}function St(a){const e=["failed","disconnected","closed","checking","connecting","new","connected"],t=a.map(i=>i.connectionState);return e.find(i=>t.includes(i))??t[0]}function xe(){return navigator.connection??null}function wt(){const a=Number(navigator.deviceMemory);return Number.isFinite(a)?a:null}function oe(a){const e=Number(a);return Number.isFinite(e)?e:null}function Ve(a,e,t){return Math.min(t,Math.max(e,a))}function _(a){return a instanceof Error?{name:a.name,message:a.message}:{name:"ServiceError",message:String(a??"Service request failed")}}function Be(a,e){return a instanceof Error?a:new Error(e)}const _e="1.5.0";T.AVATAR_MODEL_MAX_BYTES=Ee,T.AVATAR_TUNING_DEFAULTS=ne,T.AvatarModelError=N,T.BACKGROUND_PRESETS=fe,T.DEFAULT_CONNECT_TIMEOUT_MS=Pe,T.OVERLAY_EFFECTS=ge,T.RtcClient=Le,T.RtcIncomingClient=Ne,T.SPEAKER_NOISE_FLOOR=.02,T.SPEAKER_TAKEOVER_RATIO=1.4,T.createAvatarPipeline=Ze,T.createCallRecorder=Ke,T.createPictureInPictureController=Ye,T.createRtcClient=ut,T.createRtcIncomingClient=ft,T.createVideoEffectsPipeline=Xe,T.drawAvatar=Se,T.headPoseFromMatrix=ve,T.importAvatarModel=ot,T.listArchiveModels=it,T.listAvatarModels=lt,T.neutralRig=te,T.pickActiveSpeaker=Y,T.readAvatarModel=dt,T.readAvatarModelMetadata=Re,T.removeAvatarModel=ht,T.rigFromBlendshapes=ye,T.scenePainters=ee,T.smoothRig=ie,T.stylizedAvatarRenderer=ae,T.unlockRtcCallAudio=Oe,T.version=_e,Object.defineProperty(T,Symbol.toStringTag,{value:"Module"})}));
@@ -14,7 +14,7 @@
14
14
  * re-broadcasts it whenever somebody joins, so a late joiner learns every
15
15
  * publisher without a directory service.
16
16
  */
17
- import type { NativeRoomCredential, NativeRoomIdentity, NativeRoomOptions, NativeRoomQuality, NativeRoomVideoCodec } from "./native-room";
17
+ import { type NativeRoomCredential, type NativeRoomIdentity, type NativeRoomOptions, type NativeRoomPeerState, type NativeRoomQuality, type NativeRoomVideoCodec } from "./native-room";
18
18
  import { type PeerQuality } from "./peer-quality";
19
19
  export declare class SfuRoomClient {
20
20
  private readonly options;
@@ -61,11 +61,19 @@ export declare class SfuRoomClient {
61
61
  stream: MediaStream;
62
62
  }>;
63
63
  getPrimaryPeerConnection(): RTCPeerConnection | null;
64
+ /**
65
+ * The single uplink/downlink connection to the SFU, reported under this
66
+ * participant's own id — the same attribution `peerconnectionstate` uses on
67
+ * this transport. Remote participants have no connection of their own here.
68
+ */
69
+ getPeerStates(): NativeRoomPeerState[];
64
70
  sendChat(message: string, participantIds?: string[]): void;
65
71
  sendControl(action: string, detail?: Record<string, unknown>): void;
66
72
  private openSocket;
67
73
  private handleSignal;
68
74
  private publish;
75
+ /** Negotiation on this transport is with the SFU, so it is attributed to this participant's own id. */
76
+ private negotiation;
69
77
  private trackName;
70
78
  private announceTracks;
71
79
  private handleTrackAnnouncement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streaming-cdn/rtc-web",
3
- "version": "1.3.28",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "main": "dist/rtc-web.umd.js",
6
6
  "module": "dist/rtc-web.es.js",