@qencode/calls 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/call.d.ts +11 -3
- package/dist/devices.d.ts +58 -2
- package/dist/index.d.ts +1 -1
- package/dist/qencode-calls.esm.bundle.js +7 -7
- package/dist/qencode-calls.esm.bundle.js.map +3 -3
- package/dist/qencode-calls.esm.js +2 -2
- package/dist/qencode-calls.esm.js.map +3 -3
- package/dist/qencode-calls.umd.js +7 -7
- package/dist/qencode-calls.umd.js.map +3 -3
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{Room as Le,RoomEvent as v,ConnectionState as ce,DisconnectReason as b,Track as p,setLogLevel as Ve,DataPacket_Kind as De,ConnectionError as Ae,ConnectionErrorReason as ue}from"livekit-client";import{ConnectionError as be,ConnectionErrorReason as T}from"livekit-client";var l=class extends Error{constructor(e,t,i={}){super(t),this.name="CallError",this.code=e,this.retryable=i.retryable??Te[e],this.cause=i.cause}},Te={credentialInvalid:!1,credentialExpired:!0,roomFull:!1,roomClosed:!1,permissionDenied:!0,deviceUnavailable:!0,network:!0,unsupported:!1,internal:!0};function J(r){return r instanceof l}function f(r){if(J(r))return r;let e=we(r);if(/room is full|max(imum)? participants|participant limit/i.test(e))return new l("roomFull","The call already has two participants.",{cause:r});if(r instanceof be)switch(r.reason){case T.NotAllowed:return new l("credentialInvalid",`The credential was rejected by the media server (${r.status??"no status"}): ${e}`,{cause:r});case T.ServerUnreachable:case T.WebSocket:case T.Timeout:return new l("network",`Could not reach the media server: ${e}`,{cause:r});case T.ServiceNotFound:return new l("credentialInvalid",`The credential's url does not point at a media server: ${e}`,{cause:r});case T.Cancelled:case T.LeaveRequest:return new l("internal",`Connection was cancelled: ${e}`,{retryable:!1,cause:r});default:return new l("internal",`Media server error: ${e}`,{cause:r})}let t=r?.name??"";return t==="NotAllowedError"||t==="PermissionDeniedError"||t==="SecurityError"?new l("permissionDenied","Camera or microphone permission was not granted.",{cause:r}):t==="NotFoundError"||t==="NotReadableError"||t==="OverconstrainedError"||t==="AbortError"||t==="TrackInvalidError"?new l("deviceUnavailable",`The requested device is unavailable: ${e}`,{cause:r}):new l("internal",e||"Unknown error",{cause:r})}function we(r){return typeof r=="string"?r:r&&typeof r=="object"&&"message"in r&&typeof r.message=="string"?r.message:String(r)}var k=class{constructor(){this.handlers=new Map}on(e,t){let i=this.handlers.get(e);return i||(i=new Set,this.handlers.set(e,i)),i.add(t),()=>this.off(e,t)}once(e,t){let i=this.on(e,(...n)=>{i(),t(...n)});return i}off(e,t){this.handlers.get(e)?.delete(t)}emit(e,...t){let i=this.handlers.get(e);if(i)for(let n of Array.from(i))try{n(...t)}catch(o){console.error(`[qencode-calls] handler for "${String(e)}" threw`,o)}}removeAll(){this.handlers.clear()}};var Me=2*60*1e3;function I(r){if(!r||typeof r!="object")throw new l("credentialInvalid","Credential must be an object.");let e=typeof r.token=="string"?r.token.trim():"";if(!e||e.split(".").length!==3)throw new l("credentialInvalid","Credential token is missing or not a JWT.");let t=typeof r.url=="string"?r.url.trim():"";if(!/^(wss?|https?):\/\/\S+$/i.test(t))throw new l("credentialInvalid","Credential url must be a ws(s) or http(s) URL.");let i=Ee(e),n=i.video??{},o=P(r.identity,i.sub),a=P(r.room_name,n.room);if(!o)throw new l("credentialInvalid","Credential has no identity (neither identity nor the JWT sub claim).");if(!a)throw new l("credentialInvalid","Credential has no room (neither room_name nor the JWT video.room grant).");let s=ke(r.expires_at);s===null&&typeof i.exp=="number"&&(s=i.exp*1e3);let u=Array.isArray(r.regions)?r.regions.filter(y=>!!y&&typeof y.name=="string"&&typeof y.url=="string"):[];return u.length===0&&u.push({name:"default",url:t}),{token:e,url:t,regions:u,identity:o,roomName:a,expiresAt:s,callId:P(r.call_id)||null,apiBase:P(r.api_base)||null}}function j(r,e=Date.now()){return r.expiresAt!==null&&e>=r.expiresAt}function W(r,e=Date.now()){return r.expiresAt===null?null:Math.max(0,r.expiresAt-Me-e)}function ke(r){if(r==null||r==="")return null;if(typeof r=="number"&&Number.isFinite(r))return r<1e12?r*1e3:r;if(typeof r=="string"){let e=Number(r);if(r.trim()!==""&&Number.isFinite(e))return e<1e12?e*1e3:e;let t=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(r)?r.replace(" ","T")+"Z":r,i=Date.parse(t);return Number.isFinite(i)?i:null}return null}function Ee(r){try{let e=r.split(".")[1]??"",t=e.replace(/-/g,"+").replace(/_/g,"/").padEnd(Math.ceil(e.length/4)*4,"=");if(typeof atob!="function")return{};let i=atob(t),n=new Uint8Array(i.length);for(let a=0;a<i.length;a++)n[a]=i.charCodeAt(a);let o=JSON.parse(new TextDecoder().decode(n));return o&&typeof o=="object"?o:{}}catch{return{}}}function P(...r){for(let e of r)if(typeof e=="string"&&e.trim())return e.trim();return""}var B=Object.freeze({audioOnly:{name:"audioOnly",width:0,height:0,fps:0,maxBitrate:0,simulcast:!1,video:!1},p360_30:{name:"p360_30",width:640,height:360,fps:30,maxBitrate:45e4,simulcast:!1,video:!0},p540_30:{name:"p540_30",width:960,height:540,fps:30,maxBitrate:8e5,simulcast:!1,video:!0},p540_60:{name:"p540_60",width:960,height:540,fps:60,maxBitrate:12e5,simulcast:!1,video:!0},p720_30:{name:"p720_30",width:1280,height:720,fps:30,maxBitrate:12e5,simulcast:!0,video:!0},p720_60:{name:"p720_60",width:1280,height:720,fps:60,maxBitrate:18e5,simulcast:!0,video:!0},p1080_30:{name:"p1080_30",width:1920,height:1080,fps:30,maxBitrate:22e5,simulcast:!0,video:!0}}),w="p540_60",Q=48e3;function E(r){let e=B[r??w];if(!e)throw new RangeError(`Unknown video profile "${String(r)}". Known: ${Object.keys(B).join(", ")}`);return e}var Y=Object.freeze({lowest:{jitterBufferTargetMs:0,degradationPreference:"maintain-framerate",audioFrameMs:10},smooth:{jitterBufferTargetMs:150,degradationPreference:"maintain-resolution",audioFrameMs:20}}),R="lowest";function M(r){let e=Y[r??R];if(!e)throw new RangeError(`Unknown latency mode "${String(r)}". Known: lowest, smooth`);return e}function G(r,e){if(!r)return null;let t=r;try{if("jitterBufferTarget"in t)return r.jitterBufferTarget=e,"jitterBufferTarget";if("playoutDelayHint"in t)return r.playoutDelayHint=e/1e3,"playoutDelayHint"}catch{}return null}async function X(r,e){if(!r||typeof r.getParameters!="function")return!1;try{let t=r.getParameters();return t.degradationPreference=e,await r.setParameters(t),!0}catch{return!1}}function Se(r){let e=new URL(r);return`${e.protocol==="ws:"||e.protocol==="http:"?"http:":"https:"}//${e.host}/`}async function Ce(r,e={}){let t=e.timeoutMs??3e3,i=e.fetchFn??(typeof fetch=="function"?fetch:void 0),n=e.now??(()=>typeof performance<"u"?performance.now():Date.now());if(!i)return 1/0;let o;try{o=Se(r.url)}catch{return 1/0}let a=async()=>{let u=typeof AbortController=="function"?new AbortController:void 0,y=setTimeout(()=>u?.abort(),t),c=n();try{return await i(o,{mode:"no-cors",cache:"no-store",credentials:"omit",signal:u?.signal}),n()-c}catch{return 1/0}finally{clearTimeout(y)}};return await a()===1/0?1/0:a()}async function Z(r,e,t={}){if(r.length===0)throw new RangeError("No regions to choose from");if(e){let a=r.find(s=>s.name===e);if(!a)throw new RangeError(`Region "${e}" is not in the credential. Available: ${r.map(s=>s.name).join(", ")}`);return{region:a,probe:{},how:"pinned"}}if(r.length===1)return{region:r[0],probe:{},how:"single"};let i=await Promise.all(r.map(a=>Ce(a,t))),n={};r.forEach((a,s)=>{n[a.name]=Math.round(i[s]*10)/10});let o=-1;return i.forEach((a,s)=>{a!==1/0&&(o<0||a<i[o])&&(o=s)}),o<0?{region:r[0],probe:n,how:"fallback"}:{region:r[o],probe:n,how:"probe"}}var C=class{constructor(){this.prev=null}step(e,t){let i={t:e};for(let[a,s]of Object.entries(t))i[a]=typeof s=="number"&&Number.isFinite(s)?s:0;let n=this.prev;return this.prev=i,n?{dt:Math.max(1,e-n.t)/1e3,d:a=>(i[a]??0)-(n[a]??0)}:null}reset(){this.prev=null}};function ee(r){let e=null;return r.forEach(t=>{t.type==="transport"&&t.selectedCandidatePairId&&(e=r.get(t.selectedCandidatePairId)??e)}),e||r.forEach(t=>{t.type==="candidate-pair"&&t.nominated&&t.state==="succeeded"&&(e=t)}),e}function te(r,e){if(!e)return{transport:null,candidateType:null,rttMs:null};let t=r.get(e.localCandidateId)??{},i=String(t.relayProtocol??t.protocol??"").toLowerCase(),n=t.candidateType??null,o=i==="tcp"||i==="tls"?"tcp":"udp",a=n==="relay"?`relay-${o}`:i?o:null,s=typeof e.currentRoundTripTime=="number"?d(e.currentRoundTripTime*1e3):null;return{transport:a,candidateType:n,rttMs:s}}function re(r,e){if(!e)return null;let i=r.get(e)?.mimeType;return i?i.replace(/^video\//i,"").replace(/^audio\//i,"").toUpperCase():null}function ie(r,e,t){let i=null;r.forEach(m=>{m.type==="inbound-rtp"&&(m.kind==="video"||m.mediaType==="video")&&(i=m)});let n=ee(r),o=te(r,n),a={rttMs:o.rttMs,jitterBufferMs:null,decodeMs:null,fps:null,width:null,height:null,kbps:null,lossPct:null,jitterMs:null,freezes:null,freezeMs:null,processingMs:null,absCaptureLatencyMs:null,codec:null,transport:o.transport,candidateType:o.candidateType};if(!i)return a;let s=e.step(t,{jbd:i.jitterBufferDelay,jbc:i.jitterBufferEmittedCount,dec:i.totalDecodeTime,frames:i.framesDecoded,proc:i.totalProcessingDelay,lost:i.packetsLost,recv:i.packetsReceived,bytes:i.bytesReceived,freezes:i.freezeCount,freezeMs:i.totalFreezesDuration}),u={...a,fps:typeof i.framesPerSecond=="number"?d(i.framesPerSecond):null,width:i.frameWidth??null,height:i.frameHeight??null,jitterMs:typeof i.jitter=="number"?d(i.jitter*1e3):null,codec:re(r,i.codecId)};if(!s)return u;let{dt:y,d:c}=s;u.jitterBufferMs=c("jbc")>0?d(c("jbd")/c("jbc")*1e3):null,u.decodeMs=c("frames")>0?d(c("dec")/c("frames")*1e3):null,u.processingMs=c("frames")>0?d(c("proc")/c("frames")*1e3):null;let g=c("lost")+c("recv");return u.lossPct=g>0?d(c("lost")/g*100):0,u.kbps=d(c("bytes")*8/y/1e3),u.freezes=Math.max(0,c("freezes")),u.freezeMs=d(Math.max(0,c("freezeMs"))*1e3),u}function ne(r,e,t){let i=null;r.forEach(h=>{h.type==="outbound-rtp"&&(h.kind==="video"||h.mediaType==="video")&&(i===null||(h.bytesSent??0)>(i.bytesSent??0))&&(i=h)});let n=ee(r),o=te(r,n),a=null;r.forEach(h=>{h.type==="remote-inbound-rtp"&&(h.kind==="video"||h.mediaType==="video")&&(a=h)});let s=a&&typeof a.fractionLost=="number"?d(a.fractionLost*100):null,u={rttMs:o.rttMs,encodeMs:null,fps:null,width:null,height:null,kbps:null,targetKbps:null,qualityLimitation:null,lossPct:s,codec:null,transport:o.transport,candidateType:o.candidateType};if(!i)return u;let y=e.step(t,{enc:i.totalEncodeTime,frames:i.framesEncoded,bytes:i.bytesSent}),c={...u,fps:typeof i.framesPerSecond=="number"?d(i.framesPerSecond):null,width:i.frameWidth??null,height:i.frameHeight??null,targetKbps:typeof i.targetBitrate=="number"?d(i.targetBitrate/1e3):null,qualityLimitation:i.qualityLimitationReason??null,codec:re(r,i.codecId)};if(!y)return c;let{dt:g,d:m}=y;return c.encodeMs=m("frames")>0?d(m("enc")/m("frames")*1e3):null,c.kbps=d(m("bytes")*8/g/1e3),c}var _e=22089888e5;function oe(r,e,t){let i=r&&r.length?r[0]:void 0;if(!i||typeof i.captureTimestamp!="number"||typeof i.senderCaptureTimeOffset!="number")return null;let n=i.captureTimestamp+i.senderCaptureTimeOffset,o=typeof i.timestamp=="number"?i.timestamp>1e12?e-i.timestamp:t-i.timestamp:0,a=e+_e-n-Math.max(0,o);return Number.isFinite(a)?Math.round(a):null}function ae(r){if(r.rttMs===null||r.jitterBufferMs===null)return null;let e=r.peerRttMs??r.rttMs,t=r.fps&&r.fps>0?1e3/r.fps:33;return d(r.rttMs/2+e/2+r.jitterBufferMs+(r.decodeMs??0)+t)}var _=class r{constructor(){this.current="good";this.candidate=null;this.streak=0}static classify(e){let t=e.lossPct??0,i=e.jitterBufferMs??0,n=e.freezes??0,o=e.rttMs??0;return t>3||i>200||n>0?"poor":t>1||i>120||o>300?"fair":"good"}update(e){let t=r.classify(e);return t===this.current?(this.candidate=null,this.streak=0,this.current):(t===this.candidate?this.streak+=1:(this.candidate=t,this.streak=1),this.streak>=2&&(this.current=t,this.candidate=null,this.streak=0),this.current)}get value(){return this.current}reset(){this.current="good",this.candidate=null,this.streak=0}};function d(r){return Math.round(r*10)/10}var Pe=new Set(["g2g_p50","g2g_p95","g2g_samples"]),x=class{constructor(e){this.opts=e;this.queue=[];this.timer=null;this.firstPost=!0;this.joinMs=null;this.enabled=!!e.enabled&&!!e.callId&&typeof(e.fetchFn??(typeof fetch=="function"?fetch:void 0))=="function",this.url=e.callId?`${e.apiBase.replace(/\/+$/,"")}/v1/calls/${encodeURIComponent(e.callId)}/stats`:null}start(e){this.joinMs=e,!(!this.enabled||this.timer)&&(this.timer=setInterval(()=>{this.flush()},this.opts.intervalMs??5e3))}push(e,t,i){if(!this.enabled)return;let n={ts:e.ts,peer_identity:t,server_region:e.region,server_version:e.serverVersion,node_id:e.nodeId},o=e.recv.rttMs!==null?Math.round((e.recv.rttMs+(e.peerRttMs??e.recv.rttMs))/2*10)/10:null;this.queue.push(se({...n,direction:"recv",rtt_ms:e.recv.rttMs,net_one_way_ms:o,jb_ms:e.recv.jitterBufferMs,decode_ms:e.recv.decodeMs,fps:e.recv.fps,kbps:e.recv.kbps,loss_pct:e.recv.lossPct,jitter_ms:e.recv.jitterMs,freezes:e.recv.freezes,freeze_ms:e.recv.freezeMs,transport:e.recv.transport,candidate_type:e.recv.candidateType,codec:e.recv.codec,width:e.recv.width,height:e.recv.height,extra:{estimated_latency_ms:e.estimatedLatencyMs,abs_capture_ms:e.recv.absCaptureLatencyMs,processing_ms:e.recv.processingMs,quality:e.quality.recv,audio_route:e.audioRoute}},i?.recv)),this.queue.push(se({...n,direction:"send",rtt_ms:e.send.rttMs,encode_ms:e.send.encodeMs,fps:e.send.fps,kbps:e.send.kbps,transport:e.send.transport,candidate_type:e.send.candidateType,codec:e.send.codec,width:e.send.width,height:e.send.height,extra:{quality_limitation:e.send.qualityLimitation,target_kbps:e.send.targetKbps,quality:e.quality.send}},i?.send)),this.queue.length>120&&this.queue.splice(0,this.queue.length-120)}async flush(){if(!this.enabled||!this.url||this.queue.length===0)return;let e=this.queue.splice(0,this.queue.length),t=this.opts.userAgent??(typeof navigator<"u"?navigator.userAgent:"");for(let n of e)n.extra={...n.extra??{},sdk:this.opts.sdkVersion,platform:"web",user_agent:t},this.firstPost&&this.joinMs!==null&&(n.join_ms=this.joinMs);this.firstPost=!1;let i=this.opts.fetchFn??fetch;try{await i(this.url,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.opts.token}`},body:JSON.stringify({samples:e}),keepalive:!0})}catch{}}async stop(){this.timer&&(clearInterval(this.timer),this.timer=null),await this.flush()}};function se(r,e){if(!e||typeof e!="object")return r;let t={};for(let[i,n]of Object.entries(e))Pe.has(i)?r[i]=typeof n=="number"&&Number.isFinite(n)?n:null:t[i]=n;return r.extra={...t,...r.extra??{}},r}var L=class{constructor(){this.emitter=new k;this.room=null;this.audioElements=new Set;this.speakerId="";this.customVideo=!1;this.onDeviceChange=()=>{this.list().then(e=>this.emitter.emit("change",e))}}_attachRoom(e){this.room=e}_setCustomVideo(e){this.customVideo=e}_registerAudioElement(e){this.audioElements.add(e),this.speakerId&&this.applySink(e)}_start(){typeof navigator<"u"&&navigator.mediaDevices?.addEventListener&&navigator.mediaDevices.addEventListener("devicechange",this.onDeviceChange)}_stop(){typeof navigator<"u"&&navigator.mediaDevices?.removeEventListener&&navigator.mediaDevices.removeEventListener("devicechange",this.onDeviceChange),this.audioElements.clear()}async list(){let e={cameras:[],microphones:[],speakers:[]};if(typeof navigator>"u"||!navigator.mediaDevices?.enumerateDevices)return e;let t=[];try{t=await navigator.mediaDevices.enumerateDevices()}catch{return e}return t.forEach((i,n)=>{let o={id:i.deviceId,label:i.label||`${Re(i.kind)} ${n+1}`};i.kind==="videoinput"?e.cameras.push(o):i.kind==="audioinput"?e.microphones.push(o):i.kind==="audiooutput"&&this.canSelectSpeaker&&e.speakers.push(o)}),e}get canSelectSpeaker(){return typeof HTMLMediaElement<"u"&&typeof HTMLMediaElement.prototype.setSinkId=="function"}async setCamera(e){await this.switch("videoinput",e)}async setMicrophone(e){await this.switch("audioinput",e)}async setSpeaker(e){if(!this.canSelectSpeaker)throw new l("unsupported","This browser cannot choose the audio output device.",{retryable:!1});this.speakerId=e;for(let t of this.audioElements)await this.applySink(t);if(this.room)try{await this.room.switchActiveDevice("audiooutput",e,!0)}catch{}}onChange(e){return this.emitter.on("change",e)}async switch(e,t){if(!this.room)throw new l("internal","Devices can be switched after connect(); before that, pass the device id in options.",{retryable:!1});if(e==="videoinput"&&this.customVideo)throw new l("unsupported","The video comes from a custom videoSource; switch cameras in the code that produces it.",{retryable:!1});try{if(!await this.room.switchActiveDevice(e,t,!0))throw new l("deviceUnavailable",`Could not switch to device ${t}.`)}catch(i){throw f(i)}}async applySink(e){let t=e.setSinkId;if(typeof t=="function")try{await t.call(e,this.speakerId)}catch(i){throw new l("deviceUnavailable",`Speaker switch failed: ${i?.message??i}`,{cause:i})}}};function Re(r){return r==="videoinput"?"camera":r==="audioinput"?"microphone":"speaker"}function le(r){return!!r&&typeof r=="object"&&"videoElement"in r&&"track"in r}function D(r){let e={kind:r.kind==="audio"?"audio":"video",get mediaStreamTrack(){return r.mediaStreamTrack},attach:t=>le(t)?(t.track=e,t.videoElement):r.attach(t),detach:t=>{if(le(t)){t.track===e&&(t.track=null);return}t?r.detach(t):r.detach()}};return e}var xe=`
|
|
1
|
+
import{Room as Oe,RoomEvent as g,ConnectionState as ue,DisconnectReason as b,Track as h,setLogLevel as De,DataPacket_Kind as Ae,ConnectionError as je,ConnectionErrorReason as de}from"livekit-client";import{ConnectionError as Te,ConnectionErrorReason as T}from"livekit-client";var l=class extends Error{constructor(e,t,i={}){super(t),this.name="CallError",this.code=e,this.retryable=i.retryable??we[e],this.cause=i.cause}},we={credentialInvalid:!1,credentialExpired:!0,roomFull:!1,roomClosed:!1,permissionDenied:!0,deviceUnavailable:!0,network:!0,unsupported:!1,internal:!0};function z(r){return r instanceof l}function d(r){if(z(r))return r;let e=ke(r);if(/room is full|max(imum)? participants|participant limit/i.test(e))return new l("roomFull","The call already has two participants.",{cause:r});if(r instanceof Te)switch(r.reason){case T.NotAllowed:return new l("credentialInvalid",`The credential was rejected by the media server (${r.status??"no status"}): ${e}`,{cause:r});case T.ServerUnreachable:case T.WebSocket:case T.Timeout:return new l("network",`Could not reach the media server: ${e}`,{cause:r});case T.ServiceNotFound:return new l("credentialInvalid",`The credential's url does not point at a media server: ${e}`,{cause:r});case T.Cancelled:case T.LeaveRequest:return new l("internal",`Connection was cancelled: ${e}`,{retryable:!1,cause:r});default:return new l("internal",`Media server error: ${e}`,{cause:r})}let t=r?.name??"";return t==="NotAllowedError"||t==="PermissionDeniedError"||t==="SecurityError"?new l("permissionDenied","Camera or microphone permission was not granted.",{cause:r}):t==="NotFoundError"||t==="NotReadableError"||t==="OverconstrainedError"||t==="AbortError"||t==="TrackInvalidError"?new l("deviceUnavailable",`The requested device is unavailable: ${e}`,{cause:r}):new l("internal",e||"Unknown error",{cause:r})}function ke(r){return typeof r=="string"?r:r&&typeof r=="object"&&"message"in r&&typeof r.message=="string"?r.message:String(r)}var M=class{constructor(){this.handlers=new Map}on(e,t){let i=this.handlers.get(e);return i||(i=new Set,this.handlers.set(e,i)),i.add(t),()=>this.off(e,t)}once(e,t){let i=this.on(e,(...n)=>{i(),t(...n)});return i}off(e,t){this.handlers.get(e)?.delete(t)}emit(e,...t){let i=this.handlers.get(e);if(i)for(let n of Array.from(i))try{n(...t)}catch(o){console.error(`[qencode-calls] handler for "${String(e)}" threw`,o)}}removeAll(){this.handlers.clear()}};var Me=2*60*1e3;function A(r){if(!r||typeof r!="object")throw new l("credentialInvalid","Credential must be an object.");let e=typeof r.token=="string"?r.token.trim():"";if(!e||e.split(".").length!==3)throw new l("credentialInvalid","Credential token is missing or not a JWT.");let t=typeof r.url=="string"?r.url.trim():"";if(!/^(wss?|https?):\/\/\S+$/i.test(t))throw new l("credentialInvalid","Credential url must be a ws(s) or http(s) URL.");let i=Ee(e),n=i.video??{},o=P(r.identity,i.sub),a=P(r.room_name,n.room);if(!o)throw new l("credentialInvalid","Credential has no identity (neither identity nor the JWT sub claim).");if(!a)throw new l("credentialInvalid","Credential has no room (neither room_name nor the JWT video.room grant).");let s=Se(r.expires_at);s===null&&typeof i.exp=="number"&&(s=i.exp*1e3);let u=Array.isArray(r.regions)?r.regions.filter(y=>!!y&&typeof y.name=="string"&&typeof y.url=="string"):[];return u.length===0&&u.push({name:"default",url:t}),{token:e,url:t,regions:u,identity:o,roomName:a,expiresAt:s,callId:P(r.call_id)||null,apiBase:P(r.api_base)||null}}function j(r,e=Date.now()){return r.expiresAt!==null&&e>=r.expiresAt}function J(r,e=Date.now()){return r.expiresAt===null?null:Math.max(0,r.expiresAt-Me-e)}function Se(r){if(r==null||r==="")return null;if(typeof r=="number"&&Number.isFinite(r))return r<1e12?r*1e3:r;if(typeof r=="string"){let e=Number(r);if(r.trim()!==""&&Number.isFinite(e))return e<1e12?e*1e3:e;let t=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(r)?r.replace(" ","T")+"Z":r,i=Date.parse(t);return Number.isFinite(i)?i:null}return null}function Ee(r){try{let e=r.split(".")[1]??"",t=e.replace(/-/g,"+").replace(/_/g,"/").padEnd(Math.ceil(e.length/4)*4,"=");if(typeof atob!="function")return{};let i=atob(t),n=new Uint8Array(i.length);for(let a=0;a<i.length;a++)n[a]=i.charCodeAt(a);let o=JSON.parse(new TextDecoder().decode(n));return o&&typeof o=="object"?o:{}}catch{return{}}}function P(...r){for(let e of r)if(typeof e=="string"&&e.trim())return e.trim();return""}var B=Object.freeze({audioOnly:{name:"audioOnly",width:0,height:0,fps:0,maxBitrate:0,simulcast:!1,video:!1},p360_30:{name:"p360_30",width:640,height:360,fps:30,maxBitrate:45e4,simulcast:!1,video:!0},p540_30:{name:"p540_30",width:960,height:540,fps:30,maxBitrate:8e5,simulcast:!1,video:!0},p540_60:{name:"p540_60",width:960,height:540,fps:60,maxBitrate:12e5,simulcast:!1,video:!0},p720_30:{name:"p720_30",width:1280,height:720,fps:30,maxBitrate:12e5,simulcast:!0,video:!0},p720_60:{name:"p720_60",width:1280,height:720,fps:60,maxBitrate:18e5,simulcast:!0,video:!0},p1080_30:{name:"p1080_30",width:1920,height:1080,fps:30,maxBitrate:22e5,simulcast:!0,video:!0}}),w="p540_60",Q=48e3;function S(r){let e=B[r??w];if(!e)throw new RangeError(`Unknown video profile "${String(r)}". Known: ${Object.keys(B).join(", ")}`);return e}var Y=Object.freeze({lowest:{jitterBufferTargetMs:0,degradationPreference:"maintain-framerate",audioFrameMs:10},smooth:{jitterBufferTargetMs:150,degradationPreference:"maintain-resolution",audioFrameMs:20}}),R="lowest";function k(r){let e=Y[r??R];if(!e)throw new RangeError(`Unknown latency mode "${String(r)}". Known: lowest, smooth`);return e}function G(r,e){if(!r)return null;let t=r;try{if("jitterBufferTarget"in t)return r.jitterBufferTarget=e,"jitterBufferTarget";if("playoutDelayHint"in t)return r.playoutDelayHint=e/1e3,"playoutDelayHint"}catch{}return null}async function X(r,e){if(!r||typeof r.getParameters!="function")return!1;try{let t=r.getParameters();return t.degradationPreference=e,await r.setParameters(t),!0}catch{return!1}}function Ce(r){let e=new URL(r);return`${e.protocol==="ws:"||e.protocol==="http:"?"http:":"https:"}//${e.host}/`}async function _e(r,e={}){let t=e.timeoutMs??3e3,i=e.fetchFn??(typeof fetch=="function"?fetch:void 0),n=e.now??(()=>typeof performance<"u"?performance.now():Date.now());if(!i)return 1/0;let o;try{o=Ce(r.url)}catch{return 1/0}let a=async()=>{let u=typeof AbortController=="function"?new AbortController:void 0,y=setTimeout(()=>u?.abort(),t),c=n();try{return await i(o,{mode:"no-cors",cache:"no-store",credentials:"omit",signal:u?.signal}),n()-c}catch{return 1/0}finally{clearTimeout(y)}};return await a()===1/0?1/0:a()}async function Z(r,e,t={}){if(r.length===0)throw new RangeError("No regions to choose from");if(e){let a=r.find(s=>s.name===e);if(!a)throw new RangeError(`Region "${e}" is not in the credential. Available: ${r.map(s=>s.name).join(", ")}`);return{region:a,probe:{},how:"pinned"}}if(r.length===1)return{region:r[0],probe:{},how:"single"};let i=await Promise.all(r.map(a=>_e(a,t))),n={};r.forEach((a,s)=>{n[a.name]=Math.round(i[s]*10)/10});let o=-1;return i.forEach((a,s)=>{a!==1/0&&(o<0||a<i[o])&&(o=s)}),o<0?{region:r[0],probe:n,how:"fallback"}:{region:r[o],probe:n,how:"probe"}}var C=class{constructor(){this.prev=null}step(e,t){let i={t:e};for(let[a,s]of Object.entries(t))i[a]=typeof s=="number"&&Number.isFinite(s)?s:0;let n=this.prev;return this.prev=i,n?{dt:Math.max(1,e-n.t)/1e3,d:a=>(i[a]??0)-(n[a]??0)}:null}reset(){this.prev=null}};function ee(r){let e=null;return r.forEach(t=>{t.type==="transport"&&t.selectedCandidatePairId&&(e=r.get(t.selectedCandidatePairId)??e)}),e||r.forEach(t=>{t.type==="candidate-pair"&&t.nominated&&t.state==="succeeded"&&(e=t)}),e}function te(r,e){if(!e)return{transport:null,candidateType:null,rttMs:null};let t=r.get(e.localCandidateId)??{},i=String(t.relayProtocol??t.protocol??"").toLowerCase(),n=t.candidateType??null,o=i==="tcp"||i==="tls"?"tcp":"udp",a=n==="relay"?`relay-${o}`:i?o:null,s=typeof e.currentRoundTripTime=="number"?p(e.currentRoundTripTime*1e3):null;return{transport:a,candidateType:n,rttMs:s}}function re(r,e){if(!e)return null;let i=r.get(e)?.mimeType;return i?i.replace(/^video\//i,"").replace(/^audio\//i,"").toUpperCase():null}function ie(r,e,t){let i=null;r.forEach(m=>{m.type==="inbound-rtp"&&(m.kind==="video"||m.mediaType==="video")&&(i=m)});let n=ee(r),o=te(r,n),a={rttMs:o.rttMs,jitterBufferMs:null,decodeMs:null,fps:null,width:null,height:null,kbps:null,lossPct:null,jitterMs:null,freezes:null,freezeMs:null,processingMs:null,absCaptureLatencyMs:null,codec:null,transport:o.transport,candidateType:o.candidateType};if(!i)return a;let s=e.step(t,{jbd:i.jitterBufferDelay,jbc:i.jitterBufferEmittedCount,dec:i.totalDecodeTime,frames:i.framesDecoded,proc:i.totalProcessingDelay,lost:i.packetsLost,recv:i.packetsReceived,bytes:i.bytesReceived,freezes:i.freezeCount,freezeMs:i.totalFreezesDuration}),u={...a,fps:typeof i.framesPerSecond=="number"?p(i.framesPerSecond):null,width:i.frameWidth??null,height:i.frameHeight??null,jitterMs:typeof i.jitter=="number"?p(i.jitter*1e3):null,codec:re(r,i.codecId)};if(!s)return u;let{dt:y,d:c}=s;u.jitterBufferMs=c("jbc")>0?p(c("jbd")/c("jbc")*1e3):null,u.decodeMs=c("frames")>0?p(c("dec")/c("frames")*1e3):null,u.processingMs=c("frames")>0?p(c("proc")/c("frames")*1e3):null;let v=c("lost")+c("recv");return u.lossPct=v>0?p(c("lost")/v*100):0,u.kbps=p(c("bytes")*8/y/1e3),u.freezes=Math.max(0,c("freezes")),u.freezeMs=p(Math.max(0,c("freezeMs"))*1e3),u}function ne(r,e,t){let i=null;r.forEach(f=>{f.type==="outbound-rtp"&&(f.kind==="video"||f.mediaType==="video")&&(i===null||(f.bytesSent??0)>(i.bytesSent??0))&&(i=f)});let n=ee(r),o=te(r,n),a=null;r.forEach(f=>{f.type==="remote-inbound-rtp"&&(f.kind==="video"||f.mediaType==="video")&&(a=f)});let s=a&&typeof a.fractionLost=="number"?p(a.fractionLost*100):null,u={rttMs:o.rttMs,encodeMs:null,fps:null,width:null,height:null,kbps:null,targetKbps:null,qualityLimitation:null,lossPct:s,codec:null,transport:o.transport,candidateType:o.candidateType};if(!i)return u;let y=e.step(t,{enc:i.totalEncodeTime,frames:i.framesEncoded,bytes:i.bytesSent}),c={...u,fps:typeof i.framesPerSecond=="number"?p(i.framesPerSecond):null,width:i.frameWidth??null,height:i.frameHeight??null,targetKbps:typeof i.targetBitrate=="number"?p(i.targetBitrate/1e3):null,qualityLimitation:i.qualityLimitationReason??null,codec:re(r,i.codecId)};if(!y)return c;let{dt:v,d:m}=y;return c.encodeMs=m("frames")>0?p(m("enc")/m("frames")*1e3):null,c.kbps=p(m("bytes")*8/v/1e3),c}var Pe=22089888e5;function oe(r,e,t){let i=r&&r.length?r[0]:void 0;if(!i||typeof i.captureTimestamp!="number"||typeof i.senderCaptureTimeOffset!="number")return null;let n=i.captureTimestamp+i.senderCaptureTimeOffset,o=typeof i.timestamp=="number"?i.timestamp>1e12?e-i.timestamp:t-i.timestamp:0,a=e+Pe-n-Math.max(0,o);return Number.isFinite(a)?Math.round(a):null}function ae(r){if(r.rttMs===null||r.jitterBufferMs===null)return null;let e=r.peerRttMs??r.rttMs,t=r.fps&&r.fps>0?1e3/r.fps:33;return p(r.rttMs/2+e/2+r.jitterBufferMs+(r.decodeMs??0)+t)}var _=class r{constructor(){this.current="good";this.candidate=null;this.streak=0}static classify(e){let t=e.lossPct??0,i=e.jitterBufferMs??0,n=e.freezes??0,o=e.rttMs??0;return t>3||i>200||n>0?"poor":t>1||i>120||o>300?"fair":"good"}update(e){let t=r.classify(e);return t===this.current?(this.candidate=null,this.streak=0,this.current):(t===this.candidate?this.streak+=1:(this.candidate=t,this.streak=1),this.streak>=2&&(this.current=t,this.candidate=null,this.streak=0),this.current)}get value(){return this.current}reset(){this.current="good",this.candidate=null,this.streak=0}};function p(r){return Math.round(r*10)/10}var Re=new Set(["g2g_p50","g2g_p95","g2g_samples"]),x=class{constructor(e){this.opts=e;this.queue=[];this.timer=null;this.firstPost=!0;this.joinMs=null;this.enabled=!!e.enabled&&!!e.callId&&typeof(e.fetchFn??(typeof fetch=="function"?fetch:void 0))=="function",this.url=e.callId?`${e.apiBase.replace(/\/+$/,"")}/v1/calls/${encodeURIComponent(e.callId)}/stats`:null}start(e){this.joinMs=e,!(!this.enabled||this.timer)&&(this.timer=setInterval(()=>{this.flush()},this.opts.intervalMs??5e3))}push(e,t,i){if(!this.enabled)return;let n={ts:e.ts,peer_identity:t,server_region:e.region,server_version:e.serverVersion,node_id:e.nodeId},o=e.recv.rttMs!==null?Math.round((e.recv.rttMs+(e.peerRttMs??e.recv.rttMs))/2*10)/10:null;this.queue.push(se({...n,direction:"recv",rtt_ms:e.recv.rttMs,net_one_way_ms:o,jb_ms:e.recv.jitterBufferMs,decode_ms:e.recv.decodeMs,fps:e.recv.fps,kbps:e.recv.kbps,loss_pct:e.recv.lossPct,jitter_ms:e.recv.jitterMs,freezes:e.recv.freezes,freeze_ms:e.recv.freezeMs,transport:e.recv.transport,candidate_type:e.recv.candidateType,codec:e.recv.codec,width:e.recv.width,height:e.recv.height,extra:{estimated_latency_ms:e.estimatedLatencyMs,abs_capture_ms:e.recv.absCaptureLatencyMs,processing_ms:e.recv.processingMs,quality:e.quality.recv,audio_route:e.audioRoute}},i?.recv)),this.queue.push(se({...n,direction:"send",rtt_ms:e.send.rttMs,encode_ms:e.send.encodeMs,fps:e.send.fps,kbps:e.send.kbps,transport:e.send.transport,candidate_type:e.send.candidateType,codec:e.send.codec,width:e.send.width,height:e.send.height,extra:{quality_limitation:e.send.qualityLimitation,target_kbps:e.send.targetKbps,quality:e.quality.send}},i?.send)),this.queue.length>120&&this.queue.splice(0,this.queue.length-120)}async flush(){if(!this.enabled||!this.url||this.queue.length===0)return;let e=this.queue.splice(0,this.queue.length),t=this.opts.userAgent??(typeof navigator<"u"?navigator.userAgent:"");for(let n of e)n.extra={...n.extra??{},sdk:this.opts.sdkVersion,platform:"web",user_agent:t},this.firstPost&&this.joinMs!==null&&(n.join_ms=this.joinMs);this.firstPost=!1;let i=this.opts.fetchFn??fetch;try{await i(this.url,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.opts.token}`},body:JSON.stringify({samples:e}),keepalive:!0})}catch{}}async stop(){this.timer&&(clearInterval(this.timer),this.timer=null),await this.flush()}};function se(r,e){if(!e||typeof e!="object")return r;let t={};for(let[i,n]of Object.entries(e))Re.has(i)?r[i]=typeof n=="number"&&Number.isFinite(n)?n:null:t[i]=n;return r.extra={...t,...r.extra??{}},r}import{Track as xe}from"livekit-client";var V=class{constructor(){this.emitter=new M;this.room=null;this.audioElements=new Set;this.speakerId="";this.customVideo=!1;this.cameraIdValue=null;this.microphoneIdValue=null;this.facingWish=null;this.pending=!1;this.captureOptions=null;this.onDeviceChange=()=>{this.list().then(e=>this.emitter.emit("change",e))}}_attachRoom(e){this.room=e}_setCustomVideo(e){this.customVideo=e}_setInitial(e,t){this.cameraIdValue=e||null,this.microphoneIdValue=t||null}_setCaptureOptions(e){this.captureOptions=e}_videoSelection(){return this.facingWish?{facingMode:this.facingWish}:this.cameraIdValue?{deviceId:{exact:this.cameraIdValue}}:{}}_clearCamera(){this.cameraIdValue=null,this.facingWish=null}_microphoneSelection(){return this.microphoneIdValue??void 0}async _cameraPublished(){let e=this.cameraTrack();if(e){if(this.pending&&!e.isMuted&&this.captureOptions)try{await e.restartTrack(this.captureOptions())}catch(t){throw d(t)}this.pending=!1,this.syncFromTrack(e)}}_registerAudioElement(e){this.audioElements.add(e),this.speakerId&&this.applySink(e)}_start(){typeof navigator<"u"&&navigator.mediaDevices?.addEventListener&&navigator.mediaDevices.addEventListener("devicechange",this.onDeviceChange)}_stop(){typeof navigator<"u"&&navigator.mediaDevices?.removeEventListener&&navigator.mediaDevices.removeEventListener("devicechange",this.onDeviceChange),this.audioElements.clear()}async list(){let e={cameras:[],microphones:[],speakers:[]};if(typeof navigator>"u"||!navigator.mediaDevices?.enumerateDevices)return e;let t=[];try{t=await navigator.mediaDevices.enumerateDevices()}catch{return e}return t.forEach((i,n)=>{let o={id:i.deviceId,label:i.label||`${Le(i.kind)} ${n+1}`};i.kind==="videoinput"?(o.facing=Ve(i.label),e.cameras.push(o)):i.kind==="audioinput"?e.microphones.push(o):i.kind==="audiooutput"&&this.canSelectSpeaker&&e.speakers.push(o)}),e}get canSelectSpeaker(){return typeof HTMLMediaElement<"u"&&typeof HTMLMediaElement.prototype.setSinkId=="function"}get cameraId(){return this.cameraIdValue}get microphoneId(){return this.microphoneIdValue}get cameraFacing(){let e=this.trackSettings()?.facingMode;return e==="user"||e==="environment"?e:this.facingWish}async setCamera(e){this.refuseIfCustomVideo(),await this.select(e,null)}async setCameraFacing(e){if(e!=="user"&&e!=="environment")throw new RangeError(`Unknown camera facing "${String(e)}". Known: user, environment`);if(this.refuseIfCustomVideo(),!await this.select(null,e)||this.trackSettings()?.facingMode===e)return;let n=(await this.list()).cameras.find(o=>o.facing===e);if(!n)throw new l("deviceUnavailable",e==="user"?"No camera facing the user was found.":"No camera facing away from the user was found.");await this.select(n.id,null),this.facingWish=e}async setMicrophone(e){this.microphoneIdValue=e,this.room&&await this.switch("audioinput",e)}async setSpeaker(e){if(!this.canSelectSpeaker)throw new l("unsupported","This browser cannot choose the audio output device.",{retryable:!1});this.speakerId=e;for(let t of this.audioElements)await this.applySink(t);if(this.room)try{await this.room.switchActiveDevice("audiooutput",e,!0)}catch{}}onChange(e){return this.emitter.on("change",e)}async select(e,t){let i=this.cameraIdValue,n=this.facingWish;this.cameraIdValue=e,this.facingWish=t;let o=this.cameraTrack();if(!o||o.isMuted||!this.captureOptions)return this.pending=!0,!1;try{await o.restartTrack(this.captureOptions())}catch(a){this.cameraIdValue=i,this.facingWish=n;try{await o.restartTrack(this.captureOptions())}catch{}throw d(a)}return this.pending=!1,this.syncFromTrack(o),!0}syncFromTrack(e){let t=le(e);t?.deviceId&&(this.cameraIdValue=t.deviceId,this.facingWish=null)}cameraTrack(){return this.room?.localParticipant.getTrackPublication(xe.Source.Camera)?.track}trackSettings(){let e=this.cameraTrack();return e?le(e):void 0}refuseIfCustomVideo(){if(this.customVideo)throw new l("unsupported","The video comes from a custom videoSource; switch cameras in the code that produces it.",{retryable:!1})}async switch(e,t){if(!this.room)throw new l("internal","Not connected.",{retryable:!1});try{if(!await this.room.switchActiveDevice(e,t,!0))throw new l("deviceUnavailable",`Could not switch to device ${t}.`)}catch(i){throw d(i)}}async applySink(e){let t=e.setSinkId;if(typeof t=="function")try{await t.call(e,this.speakerId)}catch(i){throw new l("deviceUnavailable",`Speaker switch failed: ${i?.message??i}`,{cause:i})}}};function le(r){let e=r.mediaStreamTrack;return typeof e?.getSettings=="function"?e.getSettings():void 0}function Ve(r){let e=(r||"").toLowerCase();return/\b(back|rear|environment|world)\b/.test(e)?"environment":/\b(front|user|face|selfie)\b/.test(e)?"user":null}function Le(r){return r==="videoinput"?"camera":r==="audioinput"?"microphone":"speaker"}function ce(r){return!!r&&typeof r=="object"&&"videoElement"in r&&"track"in r}function I(r){let e={kind:r.kind==="audio"?"audio":"video",get mediaStreamTrack(){return r.mediaStreamTrack},attach:t=>ce(t)?(t.track=e,t.videoElement):r.attach(t),detach:t=>{if(ce(t)){t.track===e&&(t.track=null);return}t?r.detach(t):r.detach()}};return e}var Ie=`
|
|
2
2
|
<style>
|
|
3
3
|
:host{display:block;position:relative;overflow:hidden;background:#000;aspect-ratio:16/9}
|
|
4
4
|
video{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#000}
|
|
5
5
|
:host([fit="cover"]) video{object-fit:cover}
|
|
6
6
|
:host([mirror]) video{transform:scaleX(-1)}
|
|
7
7
|
</style>
|
|
8
|
-
<video autoplay playsinline muted></video>`,V=class extends HTMLElement{constructor(){super();this._track=null;let t=this.attachShadow({mode:"open"});t.innerHTML=xe,this.video=t.querySelector("video"),this.video.addEventListener("loadedmetadata",()=>this.updateAspect()),this.video.addEventListener("resize",()=>this.updateAspect())}get videoElement(){return this.video}get track(){return this._track}set track(t){this._track!==t&&(this._track&&this._track.detach(this.video),this._track=t,t?t.attach(this.video):this.video.srcObject=null)}disconnectedCallback(){this._track&&this._track.detach(this.video)}updateAspect(){let t=this.video.videoWidth,i=this.video.videoHeight;t&&i&&this.getAttribute("fit")!=="cover"&&(this.style.aspectRatio=`${t} / ${i}`)}};function N(r="qencode-video"){return typeof customElements>"u"||typeof HTMLElement>"u"?!1:(customElements.get(r)||customElements.define(r,V),!0)}var A="0.2.0";var U=Object.freeze(["h264","vp8","vp9","av1"]),Oe="https://api.qencode.com",de="qencode:msg",F="qencode:stats",pe=15*1024,me=30,Ie=1e3,je=6e4,O=new TextEncoder,q=new TextDecoder,K=class r{constructor(e,t){this.devices=new L;this.emitter=new k;this._state="idle";this._endReason=null;this.room=null;this.jbSupport=null;this.sourceTrack=null;this.sourceOwned=!1;this.regionChoice=null;this._peer=null;this.agentPresent=!1;this.remoteVideoTrack=null;this.remoteAudioTrack=null;this._localVideo=null;this._remoteVideo=null;this._remoteAudio=null;this.audioEl=null;this.statsTimer=null;this.expiryTimer=null;this.reconnectTimer=null;this.telemetry=null;this.recvTracker=new C;this.sendTracker=new C;this.recvQuality=new _;this.sendQuality=new _;this.peerRttMs=null;this.joinMs=null;this._stats=null;this.msgTimes=[];this.leaving=!1;this.pendingEnd=null;this.cred=e,this.options=Object.freeze({audio:t.audio??!0,video:t.video??!0,videoProfile:t.videoProfile??w,latencyMode:t.latencyMode??R,telemetry:t.telemetry??!0,autoReconnect:t.autoReconnect??!0,logLevel:t.logLevel??"warn",apiBase:t.apiBase??e.apiBase??Oe,endOnPeerLeft:t.endOnPeerLeft??!0,adaptiveStream:t.adaptiveStream??!0,codec:t.codec??"h264",cameraId:t.cameraId,microphoneId:t.microphoneId,region:t.region??null,forceRelay:t.forceRelay,videoSource:t.videoSource,simulcast:t.simulcast,jitterBufferTargetMs:t.jitterBufferTargetMs,telemetryExtra:t.telemetryExtra}),this.profile=E(this.options.videoProfile),this.latency=this.options.latencyMode,M(this.latency),this._codec=fe(this.options.codec),this.simulcastOverride=typeof t.simulcast=="boolean"?t.simulcast:null,this.jbOverride=t.jitterBufferTargetMs===void 0?null:ye(t.jitterBufferTargetMs),this.videoSource=t.videoSource??null,this.devices._setCustomVideo(this.videoSource!==null);try{Ve(this.options.logLevel)}catch{}}static create(e,t={}){return new r(I(e),t)}get state(){return this._state}get endReason(){return this._endReason}get peer(){return this._peer?{...this._peer}:null}get localVideo(){return this._localVideo}get remoteVideo(){return this._remoteVideo}get remoteAudio(){return this._remoteAudio}get stats(){return this._stats}get videoProfile(){return this.profile.name}get latencyMode(){return this.latency}get videoCodec(){return this._codec}get simulcast(){return this.simulcastOverride??this.profile.simulcast}get jitterBufferTargetMs(){return this.jbOverride??M(this.latency).jitterBufferTargetMs}get jitterBufferSupport(){return this.jbSupport}get regionProbe(){let e=this.regionChoice?.probe;return e&&Object.keys(e).length?{...e}:null}get identity(){return this.cred.identity}get callId(){return this.cred.callId}get region(){return this.regionChoice?.region.name??null}get credentialExpiresAt(){return this.cred.expiresAt}get agentJoined(){return this.agentPresent}on(e,t){return this.emitter.on(e,t)}once(e,t){return this.emitter.once(e,t)}off(e,t){this.emitter.off(e,t)}async connect(){if(this._state!=="idle")throw new l("internal",`connect() called in state "${this._state}"; create a new Call to connect again.`,{retryable:!1});if(typeof navigator>"u"||!navigator.mediaDevices)throw new l("unsupported","WebRTC media devices are not available here; a secure context (HTTPS or localhost) is required.");if(j(this.cred))throw this.emitter.emit("credentialExpiring",this.cred.expiresAt??Date.now()),new l("credentialExpired","The credential has expired; fetch a new one from your backend.");this.setState("connecting",null);let e=H();try{this.regionChoice=await Z(this.cred.regions,this.options.region);let t=this.buildRoom();this.room=t,this.devices._attachRoom(t),this.devices._start();let i={autoSubscribe:!0};if(this.options.forceRelay&&(i.rtcConfig={iceTransportPolicy:"relay"}),await t.connect(this.regionChoice.region.url,this.cred.token,i),this.leaving)throw new l("internal","Left during connect.",{retryable:!1});if(this.adoptExistingParticipants(t),this.countHumans(t)>=2)throw this.pendingEnd="roomFull",await this.teardown(),new l("roomFull","The call already has two participants.");this.options.audio&&await this.publishAudio(),this.options.video&&this.profile.video&&await this.publishVideo(),this.joinMs=Math.round(H()-e),this.setState("connected",null),this._peer&&this.emitter.emit("peerJoined",{...this._peer}),this.scheduleExpiryWarning(),this.startStats(),this.telemetry=new x({apiBase:this.options.apiBase,callId:this.cred.callId,token:this.cred.token,enabled:this.options.telemetry,sdkVersion:A}),this.telemetry.start(this.joinMs),this.sendHello()}catch(t){let i=f(t);throw i.code==="network"&&await this.joinRefusedByServer(t)&&(i=new l("roomFull","The call already has two participants.",{cause:t})),this.pendingEnd=this.pendingEnd??Ne(i),this.finish(this.pendingEnd,i),await this.teardown(),i}}async leave(){this._state!=="ended"&&(this.leaving=!0,this.pendingEnd=this.pendingEnd??"left",await this.teardown(),this.finish(this.pendingEnd,null))}updateCredential(e){let t=I(e);if(t.roomName!==this.cred.roomName||t.identity!==this.cred.identity)throw new l("credentialInvalid","The new credential is for a different call or identity.",{retryable:!1});this.cred=t,(this._state==="connected"||this._state==="reconnecting")&&this.scheduleExpiryWarning()}async joinRefusedByServer(e){if(!(e instanceof Ae)||e.reason!==ue.WebSocket&&e.reason!==ue.InternalError)return!1;let t=this.regionChoice?.region;if(!t||typeof fetch!="function")return!1;try{let i=new URL(t.url);i.protocol=i.protocol==="ws:"||i.protocol==="http:"?"http:":"https:",i.pathname=`${i.pathname.replace(/\/+$/,"")}/rtc/validate`,i.search=`access_token=${encodeURIComponent(this.cred.token)}`;let n=typeof AbortController=="function"?new AbortController:void 0,o=setTimeout(()=>n?.abort(),3e3);try{return(await fetch(i.toString(),{cache:"no-store",credentials:"omit",signal:n?.signal})).status===200}finally{clearTimeout(o)}}catch{return!1}}async setMicrophoneEnabled(e){let t=this.requireRoom();try{e&&!t.localParticipant.getTrackPublication(p.Source.Microphone)?await this.publishAudio():await t.localParticipant.setMicrophoneEnabled(e)}catch(i){throw f(i)}}async setCameraEnabled(e){let t=this.requireRoom();try{e?(this.profile.video||(this.profile=E(w)),t.localParticipant.getTrackPublication(p.Source.Camera)?await t.localParticipant.setCameraEnabled(!0):await this.publishVideo()):await t.localParticipant.setCameraEnabled(!1)}catch(i){throw f(i)}}async setVideoProfile(e){let t=E(e),i=this.profile;this.profile=t;let n=this.room;if(!(!n||this._state==="ended"))try{let o=n.localParticipant.getTrackPublication(p.Source.Camera);if(!t.video){await this.unpublishVideo();return}if(!o?.track){await this.publishVideo();return}if((this.simulcastOverride??i.simulcast)!==(this.simulcastOverride??t.simulcast)||typeof this.videoSource=="function"){await this.republishVideo();return}let s=o.track;this.videoSource||await s.restartTrack(this.captureOptions()),await this.applyEncoding(s.sender,t)}catch(o){throw f(o)}}async setLatencyMode(e){let t=M(e);this.latency=e,this.jbOverride=null,this.applyJitterTarget();let n=this.room?.localParticipant.getTrackPublication(p.Source.Camera)?.track?.sender;n&&await X(n,t.degradationPreference)}setJitterBufferTarget(e){return this.jbOverride=ye(e),this.applyJitterTarget()}async setVideoEncoding(e){if(e.codec!==void 0&&(this._codec=fe(e.codec)),e.simulcast!==void 0&&(this.simulcastOverride=e.simulcast),!(!this.room||this._state==="ended"))try{await this.republishVideo()}catch(t){throw f(t)}}async setVideoSource(e){if(this.videoSource=e,this.devices._setCustomVideo(e!==null),!(!this.room||this._state==="ended"))try{await this.republishVideo()}catch(t){throw f(t)}}async sendMessage(e,t=!0){let i=this.requireRoom(),n=ve(e);if(n.byteLength>pe)throw new l("internal",`Message is ${n.byteLength} bytes; the limit is ${pe}.`,{retryable:!1});let o=Date.now();if(this.msgTimes=this.msgTimes.filter(a=>o-a<1e3),this.msgTimes.length>=me)throw new l("internal",`Message rate limit: at most ${me} messages per second.`,{retryable:!0});this.msgTimes.push(o);try{await i.localParticipant.publishData(Be(n),{reliable:t,topic:de})}catch(a){throw f(a)}}buildRoom(){let e=M(this.latency),t=this.options.adaptiveStream!==!1,i=new Le({adaptiveStream:t,dynacast:t,publishDefaults:this.publishOptions(e.degradationPreference),videoCaptureDefaults:this.captureOptions(),audioCaptureDefaults:this.audioCaptureOptions()});return i.on(v.ParticipantConnected,n=>this.onParticipantConnected(n)),i.on(v.ParticipantDisconnected,n=>this.onParticipantDisconnected(n)),i.on(v.TrackSubscribed,(n,o,a)=>this.onTrackSubscribed(n,a)),i.on(v.TrackUnsubscribed,n=>this.onTrackUnsubscribed(n)),i.on(v.TrackMuted,(n,o)=>this.onMuteChange(n,o,!0)),i.on(v.TrackUnmuted,(n,o)=>this.onMuteChange(n,o,!1)),i.on(v.DataReceived,(n,o,a,s)=>this.onData(n,o,s,a!==De.LOSSY)),i.on(v.Reconnecting,()=>this.onReconnecting()),i.on(v.Reconnected,()=>this.onReconnected()),i.on(v.Disconnected,n=>this.onDisconnected(n)),i.on(v.MediaDevicesError,n=>this.emitter.emit("error",f(n))),i.on(v.MediaDevicesChanged,()=>{this.devices.list().then(n=>this.emitter.emit("devicesChanged",n))}),i}publishOptions(e){return{videoCodec:this._codec,simulcast:this.simulcast,videoEncoding:this.profile.video?{maxBitrate:this.profile.maxBitrate,maxFramerate:this.profile.fps}:void 0,degradationPreference:e,dtx:!1,audioPreset:{maxBitrate:Q}}}captureOptions(){let e=this.profile.video?this.profile:E(w);return{resolution:{width:e.width,height:e.height,frameRate:e.fps,aspectRatio:e.width/e.height},deviceId:this.options.cameraId}}audioCaptureOptions(){return{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,deviceId:this.options.microphoneId}}async publishAudio(){let e=this.requireRoom();try{await e.localParticipant.setMicrophoneEnabled(!0,this.audioCaptureOptions(),this.publishOptions(M(this.latency).degradationPreference))}catch(t){throw f(t)}}async publishVideo(){let e=this.requireRoom();try{let t=this.publishOptions(M(this.latency).degradationPreference),i;if(this.videoSource){let n=await this.resolveSource();i=(await e.localParticipant.publishTrack(n,{...t,source:p.Source.Camera,name:"camera"}))?.track}else i=(await e.localParticipant.setCameraEnabled(!0,this.captureOptions(),t))?.track;this._localVideo=i?D(i):null}catch(t){throw this.releaseSource(),f(t)}}async resolveSource(){let e=this.videoSource,t=typeof e=="function"?await e(this.profile.video?this.profile:E(w)):e;if(!t||typeof t!="object"||t.kind!=="video")throw new l("internal","videoSource must be, or return, a video MediaStreamTrack.",{retryable:!1});return this.sourceTrack=t,this.sourceOwned=typeof e=="function",t}releaseSource(){if(this.sourceTrack&&this.sourceOwned)try{this.sourceTrack.stop()}catch{}this.sourceTrack=null,this.sourceOwned=!1}async unpublishVideo(){let e=this.room,t=e?.localParticipant.getTrackPublication(p.Source.Camera);e&&t?.track&&await e.localParticipant.unpublishTrack(t.track,this.sourceTrack?this.sourceOwned:!0),this.releaseSource(),this._localVideo=null,this.sendTracker.reset()}async republishVideo(){let e=this.room;if(!e)return;let t=e.localParticipant.getTrackPublication(p.Source.Camera);if(!t?.track)return;let i=!!t.isMuted;await this.unpublishVideo(),!i&&this.profile.video&&await this.publishVideo()}async applyEncoding(e,t){if(!e)return;let i=e.getParameters();i.encodings=(i.encodings&&i.encodings.length?i.encodings:[{}]).map(n=>({...n,maxBitrate:t.maxBitrate,maxFramerate:t.fps})),await e.setParameters(i)}applyJitterTarget(){let e=this.remoteVideoTrack?.receiver;if(!e)return this.jbSupport;let t=G(e,this.jitterBufferTargetMs);return this.jbSupport=t??"unsupported",this.jbSupport}adoptExistingParticipants(e){e.remoteParticipants.forEach(t=>{if(S(t)){this.agentPresent=!0;return}this._peer||(this._peer=he(t))})}countHumans(e){let t=0;return e.remoteParticipants.forEach(i=>{S(i)||(t+=1)}),t}onParticipantConnected(e){if(S(e)){this.agentPresent=!0;return}this._peer&&this._peer.identity!==e.identity||(this._peer=he(e),this.emitter.emit("peerJoined",{...this._peer}),this.sendHello())}onParticipantDisconnected(e){if(S(e)){this.agentPresent=!1;return}if(!this._peer||this._peer.identity!==e.identity)return;let t={...this._peer};this._peer=null,this.peerRttMs=null,this.emitter.emit("peerLeft",t),this.options.endOnPeerLeft&&this.endWith("peerLeft")}onTrackSubscribed(e,t){if(!S(t)){if(e.kind===p.Kind.Video)this.remoteVideoTrack=e,this.recvTracker.reset(),this.applyJitterTarget(),this._remoteVideo=D(e),this.emitter.emit("remoteVideo",this._remoteVideo);else if(e.kind===p.Kind.Audio){this.remoteAudioTrack=e;let i=this.ensureAudioElement();i&&e.attach(i),this._remoteAudio=D(e),this.emitter.emit("remoteAudio",this._remoteAudio)}}}onTrackUnsubscribed(e){e===this.remoteVideoTrack&&(this.remoteVideoTrack=null,this._remoteVideo=null,this.emitter.emit("remoteVideo",null)),e===this.remoteAudioTrack&&(this.remoteAudioTrack=null,this._remoteAudio=null,this.emitter.emit("remoteAudio",null));try{e.detach()}catch{}}onMuteChange(e,t,i){if(!this._peer||t.identity!==this._peer.identity)return;let n=e.kind===p.Kind.Audio?"audio":"video";n==="audio"?this._peer.audioMuted=i:this._peer.videoMuted=i,this.emitter.emit("peerMuted",n,i)}onData(e,t,i,n=!0){if(!(t&&S(t))){if(i===F){try{let o=JSON.parse(q.decode(e));typeof o.rtt=="number"&&(this.peerRttMs=o.rtt)}catch{}return}if(i===de)try{this.emitter.emit("message",ge(e),n)}catch{}}}onReconnecting(){if(this._state==="connected"){if(this.setState("reconnecting",null),this.cred.expiresAt!==null&&j(this.cred)&&this.emitter.emit("credentialExpiring",this.cred.expiresAt),!this.options.autoReconnect){this.endWith("network");return}this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this._state==="reconnecting"&&this.endWith("network")},je)}}onReconnected(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this._state==="reconnecting"&&this.setState("connected",null),this.recvTracker.reset(),this.sendTracker.reset()}onDisconnected(e){this._state!=="ended"&&this.endWith(this.pendingEnd??Fe(e,this.leaving))}startStats(){this.statsTimer||(this.statsTimer=setInterval(()=>{this.pollStats()},Ie))}async pollStats(){let e=this.room;if(!e||this._state!=="connected"&&this._state!=="reconnecting")return;let t=Date.now();try{let i=await(this.remoteVideoTrack??this.remoteAudioTrack)?.receiver?.getStats?.(),n=e.localParticipant,a=await(n.getTrackPublication(p.Source.Camera)?.track??n.getTrackPublication(p.Source.Microphone)?.track)?.sender?.getStats?.(),s=i?ie(i,this.recvTracker,t):He(),u=a?ne(a,this.sendTracker,t):Ue();try{let z=this.remoteVideoTrack?.receiver;z?.getSynchronizationSources&&(s.absCaptureLatencyMs=oe(z.getSynchronizationSources(),t,H()))}catch{}let y=ae({rttMs:s.rttMs??u.rttMs,peerRttMs:this.peerRttMs,jitterBufferMs:s.jitterBufferMs,decodeMs:s.decodeMs,fps:s.fps}),c=this.recvQuality.update({lossPct:s.lossPct,jitterBufferMs:s.jitterBufferMs,freezes:s.freezes,rttMs:s.rttMs}),g=this.sendQuality.update({lossPct:u.lossPct,jitterBufferMs:null,freezes:null,rttMs:u.rttMs}),m=this._stats,h={ts:t,recv:s,send:u,peerRttMs:this.peerRttMs,estimatedLatencyMs:y,quality:{recv:c,send:g},region:e.serverInfo?.region??this.regionChoice?.region.name??null,nodeId:e.serverInfo?.nodeId??null,serverVersion:e.serverInfo?.version??null,joinMs:this.joinMs,audioRoute:this.audioRoute()};this._stats=h,this.emitter.emit("stats",h),(!m||m.quality.recv!==c)&&this.emitter.emit("qualityChanged",c,"recv"),(!m||m.quality.send!==g)&&this.emitter.emit("qualityChanged",g,"send"),this.telemetry?.enabled&&this.telemetry.push(h,this._peer?.identity??null,this.extraFields());let $=s.rttMs??u.rttMs;if($!==null&&e.state===ce.Connected)try{await n.publishData(O.encode(JSON.stringify({t:"rtt",rtt:$})),{reliable:!1,topic:F})}catch{}}catch(i){this.emitter.emit("error",new l("internal",`Stats collection failed: ${i?.message??i}`,{cause:i}))}}extraFields(){let e=this.options.telemetryExtra;if(!e)return;let t=i=>{try{let n=e(i);return n&&typeof n=="object"?n:null}catch(n){return this.emitter.emit("error",new l("internal",`telemetryExtra threw: ${n?.message??n}`,{cause:n})),null}};return{recv:t("recv"),send:t("send")}}audioRoute(){return typeof navigator>"u"||!(this.audioEl?.sinkId??""),"unknown"}sendHello(){let e=this.room;!e||e.state!==ce.Connected||e.localParticipant.publishData(O.encode(JSON.stringify({t:"hello",sdk:A})),{reliable:!0,topic:F}).catch(()=>{})}scheduleExpiryWarning(){this.expiryTimer&&(clearTimeout(this.expiryTimer),this.expiryTimer=null);let e=W(this.cred);if(e===null)return;let t=this.cred.expiresAt;this.expiryTimer=setTimeout(()=>{this.expiryTimer=null,(this._state==="connected"||this._state==="reconnecting")&&this.emitter.emit("credentialExpiring",t)},e)}ensureAudioElement(){if(this.audioEl)return this.audioEl;if(typeof document>"u")return null;let e=document.createElement("audio");return e.autoplay=!0,e.setAttribute("playsinline",""),e.style.display="none",document.body?.appendChild(e),this.audioEl=e,this.devices._registerAudioElement(e),e}requireRoom(){if(!this.room||this._state==="ended"||this._state==="idle")throw new l("internal",`Not connected (state "${this._state}").`,{retryable:!1});return this.room}setState(e,t){this._state===e&&e!=="ended"||(this._state=e,e==="ended"&&(this._endReason=t),this.emitter.emit("stateChanged",e,e==="ended"?t:null))}async endWith(e){this._state!=="ended"&&(this.pendingEnd=this.pendingEnd??e,await this.teardown(),this.finish(this.pendingEnd,null))}finish(e,t){this._state!=="ended"&&(t&&this.emitter.emit("error",t),this.setState("ended",e))}async teardown(){this.statsTimer&&(clearInterval(this.statsTimer),this.statsTimer=null),this.expiryTimer&&(clearTimeout(this.expiryTimer),this.expiryTimer=null),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.telemetry&&(await this.telemetry.stop(),this.telemetry=null),this.devices._stop(),this.devices._attachRoom(null);let e=this.room;if(this.room=null,e){if(this.sourceTrack&&!this.sourceOwned){let t=e.localParticipant.getTrackPublication(p.Source.Camera);if(t?.track)try{await e.localParticipant.unpublishTrack(t.track,!1)}catch{}}try{await e.disconnect(!0)}catch{}}if(this.releaseSource(),this.jbSupport=null,this.audioEl){try{this.audioEl.srcObject=null,this.audioEl.remove()}catch{}this.audioEl=null}this.remoteVideoTrack=null,this.remoteAudioTrack=null,this._remoteVideo=null,this._remoteAudio=null,this._localVideo=null}};function H(){return typeof performance<"u"?performance.now():Date.now()}function Be(r){let e=new Uint8Array(new ArrayBuffer(r.byteLength));return e.set(r),e}function S(r){let e=r;return typeof e.isAgent=="boolean"?e.isAgent:e.kind===4}function he(r){return{identity:r.identity,name:r.name||null,audioMuted:r.getTrackPublication(p.Source.Microphone)?.isMuted??!1,videoMuted:r.getTrackPublication(p.Source.Camera)?.isMuted??!1}}function Ne(r){switch(r.code){case"roomFull":return"roomFull";case"roomClosed":return"roomClosed";case"credentialInvalid":case"credentialExpired":return"credential";case"network":return"network";default:return"error"}}function Fe(r,e){if(e)return"left";switch(r){case b.CLIENT_INITIATED:return"left";case b.ROOM_DELETED:case b.ROOM_CLOSED:case b.SERVER_SHUTDOWN:case b.PARTICIPANT_REMOVED:return"roomClosed";case b.DUPLICATE_IDENTITY:case b.USER_REJECTED:return"credential";case b.JOIN_FAILURE:return"error";default:return"network"}}function He(){return{rttMs:null,jitterBufferMs:null,decodeMs:null,fps:null,width:null,height:null,kbps:null,lossPct:null,jitterMs:null,freezes:null,freezeMs:null,processingMs:null,absCaptureLatencyMs:null,codec:null,transport:null,candidateType:null}}function Ue(){return{rttMs:null,encodeMs:null,fps:null,width:null,height:null,kbps:null,targetKbps:null,qualityLimitation:null,lossPct:null,codec:null,transport:null,candidateType:null}}function fe(r){if(typeof r=="string"&&U.includes(r))return r;throw new RangeError(`Unknown video codec "${String(r)}". Known: ${U.join(", ")}`)}function ye(r){if(typeof r!="number"||!Number.isFinite(r)||r<0||r>4e3)throw new RangeError("jitterBufferTargetMs must be a number between 0 and 4000.");return r}function ve(r){let e,t;typeof r=="string"?(e=1,t=O.encode(r)):r instanceof Uint8Array?(e=3,t=r):(e=2,t=O.encode(JSON.stringify(r)));let i=new Uint8Array(t.byteLength+1);return i[0]=e,i.set(t,1),i}function ge(r){let e=r[0],t=r.subarray(1);if(e===1)return q.decode(t);if(e===2)return JSON.parse(q.decode(t));if(e===3)return t.slice();throw new Error("unknown message envelope")}N();export{K as Call,l as CallError,R as DEFAULT_LATENCY_MODE,w as DEFAULT_PROFILE,Y as LATENCY_SETTINGS,B as PROFILES,V as QencodeVideoElement,A as SDK_VERSION,U as VIDEO_CODECS,ge as decodeMessage,ve as encodeMessage,J as isCallError,N as registerVideoElement};
|
|
8
|
+
<video autoplay playsinline muted></video>`,L=class extends HTMLElement{constructor(){super();this._track=null;let t=this.attachShadow({mode:"open"});t.innerHTML=Ie,this.video=t.querySelector("video"),this.video.addEventListener("loadedmetadata",()=>this.updateAspect()),this.video.addEventListener("resize",()=>this.updateAspect())}get videoElement(){return this.video}get track(){return this._track}set track(t){this._track!==t&&(this._track&&this._track.detach(this.video),this._track=t,t?t.attach(this.video):this.video.srcObject=null)}disconnectedCallback(){this._track&&this._track.detach(this.video)}updateAspect(){let t=this.video.videoWidth,i=this.video.videoHeight;t&&i&&this.getAttribute("fit")!=="cover"&&(this.style.aspectRatio=`${t} / ${i}`)}};function F(r="qencode-video"){return typeof customElements>"u"||typeof HTMLElement>"u"?!1:(customElements.get(r)||customElements.define(r,L),!0)}var O="0.3.0";var U=Object.freeze(["h264","vp8","vp9","av1"]),Be="https://api.qencode.com",pe="qencode:msg",N="qencode:stats",he=15*1024,me=30,Fe=1e3,Ne=6e4,D=new TextEncoder,q=new TextDecoder,K=class r{constructor(e,t){this.devices=new V;this.emitter=new M;this._state="idle";this._endReason=null;this.room=null;this.jbSupport=null;this.sourceTrack=null;this.sourceOwned=!1;this.regionChoice=null;this._peer=null;this.agentPresent=!1;this.remoteVideoTrack=null;this.remoteAudioTrack=null;this._localVideo=null;this._remoteVideo=null;this._remoteAudio=null;this.audioEl=null;this.statsTimer=null;this.expiryTimer=null;this.reconnectTimer=null;this.telemetry=null;this.recvTracker=new C;this.sendTracker=new C;this.recvQuality=new _;this.sendQuality=new _;this.peerRttMs=null;this.joinMs=null;this._stats=null;this.msgTimes=[];this.leaving=!1;this.pendingEnd=null;this.cred=e,this.options=Object.freeze({audio:t.audio??!0,video:t.video??!0,videoProfile:t.videoProfile??w,latencyMode:t.latencyMode??R,telemetry:t.telemetry??!0,autoReconnect:t.autoReconnect??!0,logLevel:t.logLevel??"warn",apiBase:t.apiBase??e.apiBase??Be,endOnPeerLeft:t.endOnPeerLeft??!0,adaptiveStream:t.adaptiveStream??!0,pauseVideoInBackground:t.pauseVideoInBackground??!1,codec:t.codec??"h264",cameraId:t.cameraId,microphoneId:t.microphoneId,region:t.region??null,forceRelay:t.forceRelay,videoSource:t.videoSource,simulcast:t.simulcast,jitterBufferTargetMs:t.jitterBufferTargetMs,telemetryExtra:t.telemetryExtra}),this.profile=S(this.options.videoProfile),this.latency=this.options.latencyMode,k(this.latency),this._codec=ye(this.options.codec),this.simulcastOverride=typeof t.simulcast=="boolean"?t.simulcast:null,this.jbOverride=t.jitterBufferTargetMs===void 0?null:ge(t.jitterBufferTargetMs),this.videoSource=t.videoSource??null,this.devices._setCustomVideo(this.videoSource!==null),this.devices._setInitial(t.cameraId,t.microphoneId),this.devices._setCaptureOptions(()=>this.captureOptions());try{De(this.options.logLevel)}catch{}}static create(e,t={}){return new r(A(e),t)}get state(){return this._state}get endReason(){return this._endReason}get peer(){return this._peer?{...this._peer}:null}get localVideo(){return this._localVideo}get remoteVideo(){return this._remoteVideo}get remoteAudio(){return this._remoteAudio}get stats(){return this._stats}get videoProfile(){return this.profile.name}get latencyMode(){return this.latency}get videoCodec(){return this._codec}get simulcast(){return this.simulcastOverride??this.profile.simulcast}get jitterBufferTargetMs(){return this.jbOverride??k(this.latency).jitterBufferTargetMs}get jitterBufferSupport(){return this.jbSupport}get regionProbe(){let e=this.regionChoice?.probe;return e&&Object.keys(e).length?{...e}:null}get identity(){return this.cred.identity}get callId(){return this.cred.callId}get region(){return this.regionChoice?.region.name??null}get credentialExpiresAt(){return this.cred.expiresAt}get agentJoined(){return this.agentPresent}on(e,t){return this.emitter.on(e,t)}once(e,t){return this.emitter.once(e,t)}off(e,t){this.emitter.off(e,t)}async connect(){if(this._state!=="idle")throw new l("internal",`connect() called in state "${this._state}"; create a new Call to connect again.`,{retryable:!1});if(typeof navigator>"u"||!navigator.mediaDevices)throw new l("unsupported","WebRTC media devices are not available here; a secure context (HTTPS or localhost) is required.");if(j(this.cred))throw this.emitter.emit("credentialExpiring",this.cred.expiresAt??Date.now()),new l("credentialExpired","The credential has expired; fetch a new one from your backend.");this.setState("connecting",null);let e=H();try{this.regionChoice=await Z(this.cred.regions,this.options.region);let t=this.buildRoom();this.room=t,this.devices._attachRoom(t),this.devices._start();let i={autoSubscribe:!0};if(this.options.forceRelay&&(i.rtcConfig={iceTransportPolicy:"relay"}),await t.connect(this.regionChoice.region.url,this.cred.token,i),this.leaving)throw new l("internal","Left during connect.",{retryable:!1});if(this.adoptExistingParticipants(t),this.countHumans(t)>=2)throw this.pendingEnd="roomFull",await this.teardown(),new l("roomFull","The call already has two participants.");this.options.audio&&await this.publishAudio(),this.options.video&&this.profile.video&&await this.publishVideo(),this.joinMs=Math.round(H()-e),this.setState("connected",null),this._peer&&this.emitter.emit("peerJoined",{...this._peer}),this.scheduleExpiryWarning(),this.startStats(),this.telemetry=new x({apiBase:this.options.apiBase,callId:this.cred.callId,token:this.cred.token,enabled:this.options.telemetry,sdkVersion:O}),this.telemetry.start(this.joinMs),this.sendHello()}catch(t){let i=d(t);throw i.code==="network"&&await this.joinRefusedByServer(t)&&(i=new l("roomFull","The call already has two participants.",{cause:t})),this.pendingEnd=this.pendingEnd??Ue(i),this.finish(this.pendingEnd,i),await this.teardown(),i}}async leave(){this._state!=="ended"&&(this.leaving=!0,this.pendingEnd=this.pendingEnd??"left",await this.teardown(),this.finish(this.pendingEnd,null))}updateCredential(e){let t=A(e);if(t.roomName!==this.cred.roomName||t.identity!==this.cred.identity)throw new l("credentialInvalid","The new credential is for a different call or identity.",{retryable:!1});this.cred=t,(this._state==="connected"||this._state==="reconnecting")&&this.scheduleExpiryWarning()}async joinRefusedByServer(e){if(!(e instanceof je)||e.reason!==de.WebSocket&&e.reason!==de.InternalError)return!1;let t=this.regionChoice?.region;if(!t||typeof fetch!="function")return!1;try{let i=new URL(t.url);i.protocol=i.protocol==="ws:"||i.protocol==="http:"?"http:":"https:",i.pathname=`${i.pathname.replace(/\/+$/,"")}/rtc/validate`,i.search=`access_token=${encodeURIComponent(this.cred.token)}`;let n=typeof AbortController=="function"?new AbortController:void 0,o=setTimeout(()=>n?.abort(),3e3);try{return(await fetch(i.toString(),{cache:"no-store",credentials:"omit",signal:n?.signal})).status===200}finally{clearTimeout(o)}}catch{return!1}}async setMicrophoneEnabled(e){let t=this.requireRoom();try{e&&!t.localParticipant.getTrackPublication(h.Source.Microphone)?await this.publishAudio():await t.localParticipant.setMicrophoneEnabled(e)}catch(i){throw d(i)}}async setCameraEnabled(e){let t=this.requireRoom();try{e?(this.profile.video||(this.profile=S(w)),t.localParticipant.getTrackPublication(h.Source.Camera)?(await t.localParticipant.setCameraEnabled(!0),this.videoSource||await this.devices._cameraPublished()):await this.publishVideo()):await t.localParticipant.setCameraEnabled(!1)}catch(i){throw d(i)}}async setVideoProfile(e){let t=S(e),i=this.profile;this.profile=t;let n=this.room;if(!(!n||this._state==="ended"))try{let o=n.localParticipant.getTrackPublication(h.Source.Camera);if(!t.video){await this.unpublishVideo();return}if(!o?.track){await this.publishVideo();return}if((this.simulcastOverride??i.simulcast)!==(this.simulcastOverride??t.simulcast)||typeof this.videoSource=="function"){await this.republishVideo();return}let s=o.track;this.videoSource||await s.restartTrack(this.captureOptions()),await this.applyEncoding(s.sender,t)}catch(o){throw d(o)}}async setLatencyMode(e){let t=k(e);this.latency=e,this.jbOverride=null,this.applyJitterTarget();let n=this.room?.localParticipant.getTrackPublication(h.Source.Camera)?.track?.sender;n&&await X(n,t.degradationPreference)}setJitterBufferTarget(e){return this.jbOverride=ge(e),this.applyJitterTarget()}async setVideoEncoding(e){if(e.codec!==void 0&&(this._codec=ye(e.codec)),e.simulcast!==void 0&&(this.simulcastOverride=e.simulcast),!(!this.room||this._state==="ended"))try{await this.republishVideo()}catch(t){throw d(t)}}async setVideoSource(e){if(this.videoSource=e,this.devices._setCustomVideo(e!==null),!(!this.room||this._state==="ended"))try{await this.republishVideo()}catch(t){throw d(t)}}async sendMessage(e,t=!0){let i=this.requireRoom(),n=ve(e);if(n.byteLength>he)throw new l("internal",`Message is ${n.byteLength} bytes; the limit is ${he}.`,{retryable:!1});let o=Date.now();if(this.msgTimes=this.msgTimes.filter(a=>o-a<1e3),this.msgTimes.length>=me)throw new l("internal",`Message rate limit: at most ${me} messages per second.`,{retryable:!0});this.msgTimes.push(o);try{await i.localParticipant.publishData(He(n),{reliable:t,topic:pe})}catch(a){throw d(a)}}buildRoom(){let e=k(this.latency),t=this.options.adaptiveStream!==!1,i=new Oe({adaptiveStream:t?{pauseVideoInBackground:this.options.pauseVideoInBackground===!0,pixelDensity:"screen"}:!1,dynacast:t,publishDefaults:this.publishOptions(e.degradationPreference),videoCaptureDefaults:this.captureOptions(),audioCaptureDefaults:this.audioCaptureOptions()});return i.on(g.ParticipantConnected,n=>this.onParticipantConnected(n)),i.on(g.ParticipantDisconnected,n=>this.onParticipantDisconnected(n)),i.on(g.TrackSubscribed,(n,o,a)=>this.onTrackSubscribed(n,a)),i.on(g.TrackUnsubscribed,n=>this.onTrackUnsubscribed(n)),i.on(g.TrackMuted,(n,o)=>this.onMuteChange(n,o,!0)),i.on(g.TrackUnmuted,(n,o)=>this.onMuteChange(n,o,!1)),i.on(g.DataReceived,(n,o,a,s)=>this.onData(n,o,s,a!==Ae.LOSSY)),i.on(g.Reconnecting,()=>this.onReconnecting()),i.on(g.Reconnected,()=>this.onReconnected()),i.on(g.Disconnected,n=>this.onDisconnected(n)),i.on(g.MediaDevicesError,n=>this.emitter.emit("error",d(n))),i.on(g.MediaDevicesChanged,()=>{this.devices.list().then(n=>this.emitter.emit("devicesChanged",n))}),i}publishOptions(e){return{videoCodec:this._codec,simulcast:this.simulcast,videoEncoding:this.profile.video?{maxBitrate:this.profile.maxBitrate,maxFramerate:this.profile.fps}:void 0,degradationPreference:e,dtx:!1,audioPreset:{maxBitrate:Q}}}captureOptions(){let e=this.profile.video?this.profile:S(w);return{resolution:{width:e.width,height:e.height,frameRate:e.fps,aspectRatio:e.width/e.height},...this.devices._videoSelection()}}audioCaptureOptions(){return{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,deviceId:this.devices._microphoneSelection()}}async publishAudio(){let e=this.requireRoom();try{await e.localParticipant.setMicrophoneEnabled(!0,this.audioCaptureOptions(),this.publishOptions(k(this.latency).degradationPreference))}catch(t){throw d(t)}}async publishVideo(){let e=this.requireRoom();try{let t=this.publishOptions(k(this.latency).degradationPreference),i;if(this.videoSource){let n=await this.resolveSource();i=(await e.localParticipant.publishTrack(n,{...t,source:h.Source.Camera,name:"camera"}))?.track}else{let n;try{n=await e.localParticipant.setCameraEnabled(!0,this.captureOptions(),t)}catch(o){if(!this.devices.cameraId||d(o).code!=="deviceUnavailable")throw o;this.devices._clearCamera(),n=await e.localParticipant.setCameraEnabled(!0,this.captureOptions(),t)}i=n?.track,await this.devices._cameraPublished()}this._localVideo=i?I(i):null}catch(t){throw this.releaseSource(),d(t)}}async resolveSource(){let e=this.videoSource,t=typeof e=="function"?await e(this.profile.video?this.profile:S(w)):e;if(!t||typeof t!="object"||t.kind!=="video")throw new l("internal","videoSource must be, or return, a video MediaStreamTrack.",{retryable:!1});return this.sourceTrack=t,this.sourceOwned=typeof e=="function",t}releaseSource(){if(this.sourceTrack&&this.sourceOwned)try{this.sourceTrack.stop()}catch{}this.sourceTrack=null,this.sourceOwned=!1}async unpublishVideo(){let e=this.room,t=e?.localParticipant.getTrackPublication(h.Source.Camera);e&&t?.track&&await e.localParticipant.unpublishTrack(t.track,this.sourceTrack?this.sourceOwned:!0),this.releaseSource(),this._localVideo=null,this.sendTracker.reset()}async republishVideo(){let e=this.room;if(!e)return;let t=e.localParticipant.getTrackPublication(h.Source.Camera);if(!t?.track)return;let i=!!t.isMuted;await this.unpublishVideo(),!i&&this.profile.video&&await this.publishVideo()}async applyEncoding(e,t){if(!e)return;let i=e.getParameters();i.encodings=(i.encodings&&i.encodings.length?i.encodings:[{}]).map(n=>({...n,maxBitrate:t.maxBitrate,maxFramerate:t.fps})),await e.setParameters(i)}applyJitterTarget(){let e=this.remoteVideoTrack?.receiver;if(!e)return this.jbSupport;let t=G(e,this.jitterBufferTargetMs);return this.jbSupport=t??"unsupported",this.jbSupport}adoptExistingParticipants(e){e.remoteParticipants.forEach(t=>{if(E(t)){this.agentPresent=!0;return}this._peer||(this._peer=fe(t))})}countHumans(e){let t=0;return e.remoteParticipants.forEach(i=>{E(i)||(t+=1)}),t}onParticipantConnected(e){if(E(e)){this.agentPresent=!0;return}this._peer&&this._peer.identity!==e.identity||(this._peer=fe(e),this.emitter.emit("peerJoined",{...this._peer}),this.sendHello())}onParticipantDisconnected(e){if(E(e)){this.agentPresent=!1;return}if(!this._peer||this._peer.identity!==e.identity)return;let t={...this._peer};this._peer=null,this.peerRttMs=null,this.emitter.emit("peerLeft",t),this.options.endOnPeerLeft&&this.endWith("peerLeft")}onTrackSubscribed(e,t){if(!E(t)){if(e.kind===h.Kind.Video)this.remoteVideoTrack=e,this.recvTracker.reset(),this.applyJitterTarget(),this._remoteVideo=I(e),this.emitter.emit("remoteVideo",this._remoteVideo);else if(e.kind===h.Kind.Audio){this.remoteAudioTrack=e;let i=this.ensureAudioElement();i&&e.attach(i),this._remoteAudio=I(e),this.emitter.emit("remoteAudio",this._remoteAudio)}}}onTrackUnsubscribed(e){e===this.remoteVideoTrack&&(this.remoteVideoTrack=null,this._remoteVideo=null,this.emitter.emit("remoteVideo",null)),e===this.remoteAudioTrack&&(this.remoteAudioTrack=null,this._remoteAudio=null,this.emitter.emit("remoteAudio",null));try{e.detach()}catch{}}onMuteChange(e,t,i){if(!this._peer||t.identity!==this._peer.identity)return;let n=e.kind===h.Kind.Audio?"audio":"video";n==="audio"?this._peer.audioMuted=i:this._peer.videoMuted=i,this.emitter.emit("peerMuted",n,i)}onData(e,t,i,n=!0){if(!(t&&E(t))){if(i===N){try{let o=JSON.parse(q.decode(e));typeof o.rtt=="number"&&(this.peerRttMs=o.rtt)}catch{}return}if(i===pe)try{this.emitter.emit("message",be(e),n)}catch{}}}onReconnecting(){if(this._state==="connected"){if(this.setState("reconnecting",null),this.cred.expiresAt!==null&&j(this.cred)&&this.emitter.emit("credentialExpiring",this.cred.expiresAt),!this.options.autoReconnect){this.endWith("network");return}this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=setTimeout(()=>{this._state==="reconnecting"&&this.endWith("network")},Ne)}}onReconnected(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this._state==="reconnecting"&&this.setState("connected",null),this.recvTracker.reset(),this.sendTracker.reset()}onDisconnected(e){this._state!=="ended"&&this.endWith(this.pendingEnd??qe(e,this.leaving))}startStats(){this.statsTimer||(this.statsTimer=setInterval(()=>{this.pollStats()},Fe))}async pollStats(){let e=this.room;if(!e||this._state!=="connected"&&this._state!=="reconnecting")return;let t=Date.now();try{let i=await(this.remoteVideoTrack??this.remoteAudioTrack)?.receiver?.getStats?.(),n=e.localParticipant,a=await(n.getTrackPublication(h.Source.Camera)?.track??n.getTrackPublication(h.Source.Microphone)?.track)?.sender?.getStats?.(),s=i?ie(i,this.recvTracker,t):Ke(),u=a?ne(a,this.sendTracker,t):$e();try{let W=this.remoteVideoTrack?.receiver;W?.getSynchronizationSources&&(s.absCaptureLatencyMs=oe(W.getSynchronizationSources(),t,H()))}catch{}let y=ae({rttMs:s.rttMs??u.rttMs,peerRttMs:this.peerRttMs,jitterBufferMs:s.jitterBufferMs,decodeMs:s.decodeMs,fps:s.fps}),c=this.recvQuality.update({lossPct:s.lossPct,jitterBufferMs:s.jitterBufferMs,freezes:s.freezes,rttMs:s.rttMs}),v=this.sendQuality.update({lossPct:u.lossPct,jitterBufferMs:null,freezes:null,rttMs:u.rttMs}),m=this._stats,f={ts:t,recv:s,send:u,peerRttMs:this.peerRttMs,estimatedLatencyMs:y,quality:{recv:c,send:v},region:e.serverInfo?.region??this.regionChoice?.region.name??null,nodeId:e.serverInfo?.nodeId??null,serverVersion:e.serverInfo?.version??null,joinMs:this.joinMs,audioRoute:this.audioRoute()};this._stats=f,this.emitter.emit("stats",f),(!m||m.quality.recv!==c)&&this.emitter.emit("qualityChanged",c,"recv"),(!m||m.quality.send!==v)&&this.emitter.emit("qualityChanged",v,"send"),this.telemetry?.enabled&&this.telemetry.push(f,this._peer?.identity??null,this.extraFields());let $=s.rttMs??u.rttMs;if($!==null&&e.state===ue.Connected)try{await n.publishData(D.encode(JSON.stringify({t:"rtt",rtt:$})),{reliable:!1,topic:N})}catch{}}catch(i){this.emitter.emit("error",new l("internal",`Stats collection failed: ${i?.message??i}`,{cause:i}))}}extraFields(){let e=this.options.telemetryExtra;if(!e)return;let t=i=>{try{let n=e(i);return n&&typeof n=="object"?n:null}catch(n){return this.emitter.emit("error",new l("internal",`telemetryExtra threw: ${n?.message??n}`,{cause:n})),null}};return{recv:t("recv"),send:t("send")}}audioRoute(){return typeof navigator>"u"||!(this.audioEl?.sinkId??""),"unknown"}sendHello(){let e=this.room;!e||e.state!==ue.Connected||e.localParticipant.publishData(D.encode(JSON.stringify({t:"hello",sdk:O})),{reliable:!0,topic:N}).catch(()=>{})}scheduleExpiryWarning(){this.expiryTimer&&(clearTimeout(this.expiryTimer),this.expiryTimer=null);let e=J(this.cred);if(e===null)return;let t=this.cred.expiresAt;this.expiryTimer=setTimeout(()=>{this.expiryTimer=null,(this._state==="connected"||this._state==="reconnecting")&&this.emitter.emit("credentialExpiring",t)},e)}ensureAudioElement(){if(this.audioEl)return this.audioEl;if(typeof document>"u")return null;let e=document.createElement("audio");return e.autoplay=!0,e.setAttribute("playsinline",""),e.style.display="none",document.body?.appendChild(e),this.audioEl=e,this.devices._registerAudioElement(e),e}requireRoom(){if(!this.room||this._state==="ended"||this._state==="idle")throw new l("internal",`Not connected (state "${this._state}").`,{retryable:!1});return this.room}setState(e,t){this._state===e&&e!=="ended"||(this._state=e,e==="ended"&&(this._endReason=t),this.emitter.emit("stateChanged",e,e==="ended"?t:null))}async endWith(e){this._state!=="ended"&&(this.pendingEnd=this.pendingEnd??e,await this.teardown(),this.finish(this.pendingEnd,null))}finish(e,t){this._state!=="ended"&&(t&&this.emitter.emit("error",t),this.setState("ended",e))}async teardown(){this.statsTimer&&(clearInterval(this.statsTimer),this.statsTimer=null),this.expiryTimer&&(clearTimeout(this.expiryTimer),this.expiryTimer=null),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.telemetry&&(await this.telemetry.stop(),this.telemetry=null),this.devices._stop(),this.devices._attachRoom(null);let e=this.room;if(this.room=null,e){if(this.sourceTrack&&!this.sourceOwned){let t=e.localParticipant.getTrackPublication(h.Source.Camera);if(t?.track)try{await e.localParticipant.unpublishTrack(t.track,!1)}catch{}}try{await e.disconnect(!0)}catch{}}if(this.releaseSource(),this.jbSupport=null,this.audioEl){try{this.audioEl.srcObject=null,this.audioEl.remove()}catch{}this.audioEl=null}this.remoteVideoTrack=null,this.remoteAudioTrack=null,this._remoteVideo=null,this._remoteAudio=null,this._localVideo=null}};function H(){return typeof performance<"u"?performance.now():Date.now()}function He(r){let e=new Uint8Array(new ArrayBuffer(r.byteLength));return e.set(r),e}function E(r){let e=r;return typeof e.isAgent=="boolean"?e.isAgent:e.kind===4}function fe(r){return{identity:r.identity,name:r.name||null,audioMuted:r.getTrackPublication(h.Source.Microphone)?.isMuted??!1,videoMuted:r.getTrackPublication(h.Source.Camera)?.isMuted??!1}}function Ue(r){switch(r.code){case"roomFull":return"roomFull";case"roomClosed":return"roomClosed";case"credentialInvalid":case"credentialExpired":return"credential";case"network":return"network";default:return"error"}}function qe(r,e){if(e)return"left";switch(r){case b.CLIENT_INITIATED:return"left";case b.ROOM_DELETED:case b.ROOM_CLOSED:case b.SERVER_SHUTDOWN:case b.PARTICIPANT_REMOVED:return"roomClosed";case b.DUPLICATE_IDENTITY:case b.USER_REJECTED:return"credential";case b.JOIN_FAILURE:return"error";default:return"network"}}function Ke(){return{rttMs:null,jitterBufferMs:null,decodeMs:null,fps:null,width:null,height:null,kbps:null,lossPct:null,jitterMs:null,freezes:null,freezeMs:null,processingMs:null,absCaptureLatencyMs:null,codec:null,transport:null,candidateType:null}}function $e(){return{rttMs:null,encodeMs:null,fps:null,width:null,height:null,kbps:null,targetKbps:null,qualityLimitation:null,lossPct:null,codec:null,transport:null,candidateType:null}}function ye(r){if(typeof r=="string"&&U.includes(r))return r;throw new RangeError(`Unknown video codec "${String(r)}". Known: ${U.join(", ")}`)}function ge(r){if(typeof r!="number"||!Number.isFinite(r)||r<0||r>4e3)throw new RangeError("jitterBufferTargetMs must be a number between 0 and 4000.");return r}function ve(r){let e,t;typeof r=="string"?(e=1,t=D.encode(r)):r instanceof Uint8Array?(e=3,t=r):(e=2,t=D.encode(JSON.stringify(r)));let i=new Uint8Array(t.byteLength+1);return i[0]=e,i.set(t,1),i}function be(r){let e=r[0],t=r.subarray(1);if(e===1)return q.decode(t);if(e===2)return JSON.parse(q.decode(t));if(e===3)return t.slice();throw new Error("unknown message envelope")}F();export{K as Call,l as CallError,R as DEFAULT_LATENCY_MODE,w as DEFAULT_PROFILE,Y as LATENCY_SETTINGS,B as PROFILES,L as QencodeVideoElement,O as SDK_VERSION,U as VIDEO_CODECS,be as decodeMessage,ve as encodeMessage,z as isCallError,F as registerVideoElement};
|
|
9
9
|
//# sourceMappingURL=qencode-calls.esm.js.map
|