@ceeblue/webrtc-client
Version:
Ceeblue WebRTC Client
2 lines (1 loc) • 38.6 kB
JavaScript
import*as t from"@ceeblue/web-utils";import{EventEmitter as e,Util as i,WebSocketReliable as s,Connect as o,NetAddress as r,Loggable as n,Numbers as a}from"@ceeblue/web-utils";export{t as utils};import*as c from"sdp-transform";function h(t,e,i,s){return new(i||(i=Promise))(function(o,r){function n(t){try{c(s.next(t))}catch(t){r(t)}}function a(t){try{c(s.throw(t))}catch(t){r(t)}}function c(t){var e;t.done?o(t.value):(e=t.value,e instanceof i?e:new i(function(t){t(e)})).then(n,a)}c((s=s.apply(t,e||[])).next())})}var d;function l(t,e,i){for(let s=0;s<e.length;++s){const o=e[s];i.has(o.codec.toLowerCase())||(t.delete(o.idx),e.splice(s--,1))}}"function"==typeof SuppressedError&&SuppressedError,function(t){t.AUDIO="audio",t.VIDEO="video",t.DATA="data"}(d||(d={}));class u{constructor(){this.type="",this.width=0,this.height=0,this.sources=new Map,this.tracks=new Map,this.audios=[],this.videos=[],this.datas=[]}subset(t){const e=Object.assign({},this);if(t){l(e.tracks,e.audios,t),l(e.tracks,e.videos,t);for(const[,t]of e.tracks){for(;t.up&&!e.tracks.has(t.up.idx);)t.up=t.up.up;for(;t.down&&!e.tracks.has(t.down.idx);)t.down=t.down.down}}return e}}const _=(t,e)=>e.maxbps-t.maxbps;var m;!function(t){t.UNKNOWN="",t.ONLINE="Stream is online",t.OFFLINE="Stream is offline",t.INITIALIZING="Stream is initializing",t.BOOTING="Stream is booting",t.WAITING="Stream is waiting for data"}(m||(m={}));class p extends e{onState(t){this.log("onState",t).info()}onClose(t){this.log("onClose").info()}onMetadata(t){this.log(i.stringify(t)).info()}get url(){return this._ws.url}get streamState(){return this._streamState}get connectParams(){return this._connectParams}get metadata(){return this._metadata}get closed(){return this._ws.closed}constructor(t){super();const e=new Map;for(const t of Object.values(m))e.set(t,t);e.set("Stream status is unknown?!",m.UNKNOWN),this._connectParams=t,this._streamState=m.UNKNOWN,this._ws=new s(o.buildURL(o.Type.META,t)),this._ws.onClose=t=>this.close(t),this._ws.onMessage=s=>{var o,r;try{const n=JSON.parse(s);if(n.error){const i=e.get(n.error);return void(i?this.onState(this._streamState=i):this.close({type:"StreamMetadataError",name:n.error,stream:null!==(o=t.streamName)&&void 0!==o?o:""}))}this._metadata=new u,this._metadata.type=n.type,this._metadata.width=n.width,this._metadata.height=n.height,this._metadata.sources.clear();for(const t of n.source||[])this._metadata.sources.set(t.hrn,t);const a=[];if(this._metadata.tracks.clear(),null===(r=n.meta)||void 0===r?void 0:r.tracks)for(const[t,e]of i.iterableEntries(n.meta.tracks)){switch(e.name=t,e.type=e.type.toLowerCase(),e.type){case"audio":this._metadata.audios.push(e);continue;case"video":this._metadata.videos.push(e);continue;case"meta":e.type=d.DATA,this._metadata.datas.push(e);break;default:this.log(`Unknown track type ${e.type}`).warn()}a.push(e)}this._addSortedTrack(this._metadata.audios,this._metadata.tracks),this._addSortedTrack(this._metadata.videos,this._metadata.tracks);for(const t of a)this._metadata.tracks.set(t.idx,t);this.onState(this._streamState=m.ONLINE)}catch(t){return void this.log(i.stringify(t)).error()}this.onMetadata(this._metadata)}}close(t){this._ws.onClose!==i.EMPTY_FUNCTION&&(this._ws.onClose=i.EMPTY_FUNCTION,this._ws.close(),this._metadata=new u,this.onClose(t))}_addSortedTrack(t,e){t.sort(_);for(let i=0;i<t.length;++i){const s=t[i];e.set(s.idx,s),i&&(s.up=t[i-1],t[i-1].down=s)}}}class v extends t.PlayerStats{constructor(){super(),this._prevTime=0,this._prevAudioBytes=0,this._prevVideoBytes=0,this._prevVideoEmittedCount=0,this._prevAudioEmittedCount=0,this._prevVideoJitterDelay=0,this._prevAudioJitterDelay=0,this._prevSkippedAudio=0,this._prevAudioConcealedSamples=0,this._prevSkippedVideo=0,this._prevVideoDroppedFrames=0,this._prevVideoTime=0,this._prevRealTime=0,this.protocol="WebRTC"}onRelease(){}serialize(){return h(this,void 0,void 0,function*(){return this})}compute(t,e,i,s,o){var r,n,a,c,h,d,l,u,_,m,p;const v=null===(r=t.inputs)||void 0===r?void 0:r.audio,f=null===(n=t.inputs)||void 0===n?void 0:n.video;this.videoTrackId=o;const g=null!=o?e.tracks.get(o):void 0;this.audioTrackId=s;const T=null!=s?e.tracks.get(s):void 0;let k=performance.now();const y=Math.max(1,k-this._prevTime);this._prevTime=k;const C=null==v?void 0:v.bytesReceived;null!=C?(this.audioByteRate=Math.max(0,C-this._prevAudioBytes)/y,this._prevAudioBytes=C):this.audioByteRate=void 0;const S=null==f?void 0:f.bytesReceived;null!=S?(this.videoByteRate=Math.max(0,S-this._prevVideoBytes)/y,this._prevVideoBytes=S):this.videoByteRate=void 0;const b=null==f?void 0:f.jitterBufferDelay,w=null==f?void 0:f.jitterBufferEmittedCount;let B;null!=b&&null!=w&&(w>this._prevVideoEmittedCount&&(B=1e3*Math.max(0,b-this._prevVideoJitterDelay)/(w-this._prevVideoEmittedCount)),this._prevVideoEmittedCount=w,this._prevVideoJitterDelay=b);const R=null==v?void 0:v.jitterBufferDelay,x=null==v?void 0:v.jitterBufferEmittedCount;let P;null!=R&&null!=x&&(x>this._prevAudioEmittedCount&&(P=1e3*Math.max(0,R-this._prevAudioJitterDelay)/(x-this._prevAudioEmittedCount)),this._prevAudioEmittedCount=x,this._prevAudioJitterDelay=R),this.bufferAmount=null!=B||null!=P?Math.max(null!=B?B:0,null!=P?P:0):void 0,this.videoPerSecond=null==f?void 0:f.framesPerSecond;const I=null==v?void 0:v.concealedSamples;if(null!=I&&T&&T.rate){const t=Math.max(I-this._prevAudioConcealedSamples,0);this._prevAudioConcealedSamples=I,this.skippedAudio=this._prevSkippedAudio+t/T.rate*1e3,this._prevSkippedAudio=this.skippedAudio}else this.skippedAudio=void 0;const M=null==f?void 0:f.framesDropped;if(null!=M&&this.videoPerSecond){const t=Math.max(M-this._prevVideoDroppedFrames,0);this._prevVideoDroppedFrames=M,this.skippedVideo=this._prevSkippedVideo+t/this.videoPerSecond*1e3,this._prevSkippedVideo=this.skippedVideo}else this.skippedVideo=void 0;this.stallCount=null==f?void 0:f.freezeCount,this.audioTrackBandwidth=null!==(a=null==T?void 0:T.ebps)&&void 0!==a?a:null==T?void 0:T.bps,this.videoTrackBandwidth=null!==(c=null==g?void 0:g.ebps)&&void 0!==c?c:null==g?void 0:g.bps,k=performance.now();const N=i;let E=0;if(void 0!==this._prevVideoTime&&void 0!==this._prevRealTime){const t=N-this._prevVideoTime,e=(k-this._prevRealTime)/1e3;t>=0&&e>.05&&(E=t/e)}this._prevVideoTime=N,this._prevRealTime=k,this.playbackSpeed=E,this.rtt=null===(h=null==t?void 0:t.candidate)||void 0===h?void 0:h.currentRoundTripTime,null!=(null==f?void 0:f.jitter)||null!=(null==v?void 0:v.jitter)?this.jitter=Math.max(null!==(d=null==f?void 0:f.jitter)&&void 0!==d?d:0,null!==(l=null==v?void 0:v.jitter)&&void 0!==l?l:0):this.jitter=void 0,null!=(null==f?void 0:f.packetsLost)||null!=(null==v?void 0:v.packetsLost)?this.lostPacketCount=(null!==(u=null==f?void 0:f.packetsLost)&&void 0!==u?u:0)+(null!==(_=null==v?void 0:v.packetsLost)&&void 0!==_?_:0):this.lostPacketCount=void 0,null!=(null==f?void 0:f.nackCount)||null!=(null==v?void 0:v.nackCount)?this.nackCount=(null!==(m=null==f?void 0:f.nackCount)&&void 0!==m?m:0)+(null!==(p=null==v?void 0:v.nackCount)&&void 0!==p?p:0):this.nackCount=void 0}}function f(t){return"send"in t}const g=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);class T extends e{onOpen(t){this.log("onOpen").info()}onClose(t){t?this.log("onClose",t).error():this.log("onClose").info()}get opened(){return!(!this._peerConnection||this._peerConnection.ontrack!==i.EMPTY_FUNCTION)}get closed(){return this._closed}get stream(){return this._stream}get streamName(){return this._streamName}get codecs(){return this._codecs}constructor(t,e){var i;super(),this._closed=!1,this._streamName=null!==(i=t.streamName)&&void 0!==i?i:"",this._endPoint=t.endPoint,this._stream=e,this._connectionInfosTime=0,this._codecs=new Set}connectionInfos(){return h(this,arguments,void 0,function*(t=1e3){if(!this._peerConnection)return Promise.reject("Not connected");if(!this._connectionInfos||i.time()-t>this._connectionInfosTime){const t=yield this._peerConnection.getStats(null);this._connectionInfos={inputs:{},outputs:{}};const e=new Map,s=new Map;for(const i of t.values())switch(i.type){case"track":e.set(i.id,i);break;case"outbound-rtp":s.set(i.trackId,i),this._connectionInfos.outputs["audio"===(i.kind||i.mediaType)?"audio":"video"]=i;break;case"inbound-rtp":s.set(i.trackId,i),this._connectionInfos.inputs["audio"===(i.kind||i.mediaType)?"audio":"video"]=i;break;case"candidate-pair":if(null!=i.selected){if(!i.selected)continue}else if(null!=i.nominated&&!i.nominated)continue;this._connectionInfos.candidate=i}for(const[t,i]of e){const e=s.get(t);e&&Object.assign(e,Object.assign(Object.assign({},i),e))}this._connectionInfosTime=i.time()}return this._connectionInfos})}close(t){if(this._closed)return;this._closed=!0,this._clearPeerConnectionIdleTimeout();const e=this._peerConnection;e&&(this._peerConnection=void 0,e.getReceivers().forEach(t=>t.track&&t.track.stop()),e.getSenders().forEach(t=>t.track&&t.track.stop()),e.close()),this._stream&&this._stream.getTracks().forEach(t=>t.stop()),this.onClose(t)}_open(t){if(!t){const e=new r(this._endPoint,443).domain;t={urls:["turn:"+e+":3478?transport=tcp","turn:"+e+":3478"],username:"ceeblue",credential:"ceeblue"}}try{this._peerConnection=new RTCPeerConnection({iceServers:[t]})}catch(t){return void this.close({type:"ConnectorError",name:"RTCPeerConnection failed",detail:i.stringify(t)})}if(this._peerConnection.onconnectionstatechange=t=>{const e=t.target;if(e){const t=null==e?void 0:e.connectionState;switch(this.log(`Peer connection state: ${t}`).info(),t){case"connected":case"connecting":this._clearPeerConnectionIdleTimeout();break;case"disconnected":case"failed":this.log(`Peer connection state: ${t}`).warn(),this._startPeerConnectionIdleTimeout();break;case"closed":this.log(`Peer connection state: ${t}`).warn(),this.close()}}},this._stream)for(const t of this._stream.getTracks())this._peerConnection.addTrack(t);else this._peerConnection.ontrack=t=>{this._stream=t.streams[0],this._tryToOpen()};if(g)if(this._stream){const t=this._peerConnection.getTransceivers();for(const e of t)"audio"!==e.receiver.track.kind&&"video"!==e.receiver.track.kind||(e.direction="sendonly")}else this._peerConnection.addTransceiver("audio",{direction:"recvonly"}),this._peerConnection.addTransceiver("video",{direction:"recvonly"});let e;this._peerConnection.createOffer({offerToReceiveAudio:!this._stream,offerToReceiveVideo:!this._stream}).then(t=>{if(this._peerConnection)return t.sdp=e=t.sdp?function(t){const e=c.parse(t);for(const t of e.media)if("audio"===t.type){const e=[];for(const i of t.rtp)"opus"===i.codec&&e.push(i.payload);if(!e.length)continue;for(const i of t.fmtp)if(e.includes(i.payload)){const t=c.parseParams(i.config);t.stereo=1,i.config="";for(const e in t)i.config+=(i.config?";":"")+e+"="+t[e]}}return c.write(e)}(t.sdp):"",this.log(`Offer\r\n${e}`).debug(),this._peerConnection.setLocalDescription(t)}).then(t=>{if(this._peerConnection)return e?this._sip(e):Promise.reject("invalid empty sdp offer")}).then(t=>{if(t&&this._peerConnection)return this.log(`Answer\r\n${t}`).debug(),this.updateCodecs(t),this._peerConnection.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:t}))}).then(()=>this._tryToOpen()).catch(t=>this.close({type:"ConnectorError",name:"SIP failed",detail:i.stringify(t)}))}replaceTrack(t,e){return h(this,void 0,void 0,function*(){if(this._closed||!this._peerConnection)throw Error("Connector is closed");if(!this._stream)throw Error("No local stream to update");let i;"function"==typeof this._peerConnection.getTransceivers&&(i=this._peerConnection.getTransceivers().find(e=>e.sender&&(e.receiver&&e.receiver.track&&e.receiver.track.kind===t||e.sender.track&&e.sender.track.kind===t)));let s=null==i?void 0:i.sender;if(s||(s=this._peerConnection.getSenders().find(e=>e.track&&e.track.kind===t)),!s)return void this.close({type:"ConnectorError",name:"Replace track failed",detail:`No existing ${t} sender to replace; restart is required for this direction.`});yield s.replaceTrack(e);const o=this._stream;("video"===t?o.getVideoTracks():o.getAudioTracks()).forEach(t=>o.removeTrack(t)),e&&o.addTrack(e)})}updateCodecs(t){const e=c.parse(t);for(const t of e.media)for(const e of t.rtp)this._codecs.add(e.codec.toLowerCase())}_tryToOpen(){this._stream&&this._peerConnection&&this._peerConnection.ontrack!==i.EMPTY_FUNCTION&&(this._peerConnection.ontrack=i.EMPTY_FUNCTION,this.onOpen(this._stream))}_startPeerConnectionIdleTimeout(){this._peerConnectionIdleTimeout||(this._peerConnectionIdleTimeout=setTimeout(()=>{this.log("Peer connection idle timeout!").error(),this.close({type:"ConnectorError",name:"Connection idle"})},15e3))}_clearPeerConnectionIdleTimeout(){this._peerConnectionIdleTimeout&&(clearTimeout(this._peerConnectionIdleTimeout),this._peerConnectionIdleTimeout=void 0)}}const k=3e4;class y extends T{onRTPProps(t){}onMediaReport(t){}onVideoBitrate(t,e){this.log(`onVideoBitrate ${i.stringify({video_bitrate:t,video_bitrate_constraint:e})}`).info()}onPlaying(t){this.log(`onPlaying ${i.stringify(t)}`).debug()}constructor(t,e){super(t,e),this._reportReceivedTimestamp=i.time(),this._ws=new s(o.buildURL(o.Type.WEBRTC,t,"wss")),this._ws.onClose=t=>this.close(t),this._ws.onOpen=()=>{this._startReportWatchdog(),this._open(t.iceServer)},this._ws.onMessage=t=>{try{this._eventHandler(JSON.parse(t))}catch(t){this.log(`Invalid signaling message, ${i.stringify(t)}`).warn()}}}setRTPProps(t,e){this.send("rtp_props",{nack:t,drop:e})}setVideoBitrate(t){this.send("video_bitrate",{video_bitrate:t})}setTracks(t){this.send("tracks",t)}send(t,e){try{this.log(`Command ${t} ${i.stringify(e)}`).info(),this._ws.send(JSON.stringify(Object.assign({type:t},e)))}catch(t){this.log(i.stringify(t)).error()}}close(t){this._ws.onClose!==i.EMPTY_FUNCTION&&(this._ws.onClose=i.EMPTY_FUNCTION,this._ws.close(),this._clearReportWatchdog(),this._promise&&(this._promise(Error("closing")),this._promise=void 0),super.close(t))}_sip(t){return new Promise((e,i)=>{this._promise=t=>{t instanceof Error?i(t):e(t)},this._ws.send(JSON.stringify({type:"offer_sdp",offer_sdp:t}))})}_eventHandler(t){var e,s;switch(this.log(`EventHandler ${i.stringify(t,{recursion:2})}`).debug(),t.type){case"on_answer_sdp":if(!0!==t.result)return void this.close({type:"ConnectorError",name:"Access denied"});this._promise&&this._promise(t.answer_sdp);break;case"on_error":if(!this.opened)return void this.close({type:"ConnectorError",name:"Connection failed",detail:i.stringify(t)});this.log(i.stringify(t)).warn();break;case"on_video_bitrate":this.onVideoBitrate(t.video_bitrate,t.video_bitrate_constraint);break;case"on_stop":return this.log("on_stop").info(),void this.close();case"on_track_drop":{const i=null!==(e=t.mediatype)&&void 0!==e?e:"?",o=null!==(s=t.track)&&void 0!==s?s:"?";this.log(`${i} track #${o} dropped`).warn();break}case"on_rtp_props":this.onRTPProps(t);break;case"on_media_receive":this._reportReceivedTimestamp=i.time(),t.stats.loss_perc&&!t.stats.loss_num&&(t.stats.loss_perc=0),this.onMediaReport(t);break;case"set_speed":case"on_seek":break;case"on_time":this._reportReceivedTimestamp=i.time(),this.onPlaying(t);break;default:this.log(`Unhandled event: ${t.type}`).warn()}}_startReportWatchdog(){this._reportReceivedTimestamp=i.time(),this._reportWatchdogInterval=setInterval(()=>{const t=i.time()-this._reportReceivedTimestamp;t>=1e4&&this.log(`No updates received for the last ${(t/1e3).toFixed(1)}s`).warn(),t>=k&&this.close({type:"ConnectorError",name:"Connection idle"})},5e3)}_clearReportWatchdog(){this._reportWatchdogInterval&&(clearInterval(this._reportWatchdogInterval),this._reportWatchdogInterval=void 0)}}class C extends T{constructor(t,e){super(t,e),this._url=o.buildURL(o.Type.WEBRTC,t,"https"),this._fetch=new AbortController,setTimeout(()=>{this._open(t.iceServer)},0)}close(t){this._fetch.abort(),super.close(t)}_sip(t){return h(this,void 0,void 0,function*(){const e=yield fetch(this._url,{method:"POST",body:t,headers:{"Content-Type":"application/sdp"},signal:this._fetch.signal});return e.ok?e.text():Promise.reject(`HTTP ${e.status} ${e.statusText} status`)})}}class S extends e{onClose(t){this.log("onClose").info()}onData(t,e,s){this.log(`Data received on track ${t} at ${e}: ${i.stringify(s)}`).info()}get url(){return this._url}get tracks(){return[...this._tracks]}set tracks(t){this._tracks=[...t],this._sendTracks()}get closed(){return!this._ws||this._ws.closed}constructor(t){super(),this._url=o.buildURL(o.Type.DATA,t).toString(),this._tracks=Array(),this._ws=new s,this._ws.onOpen=()=>this._sendTracks(),this._ws.onClose=t=>this.onClose(t),this._ws.onMessage=t=>{let e;try{if(e=JSON.parse(t),e.error)throw Error(e.error)}catch(t){return void this.log(i.stringify(t)).error()}"time"in e&&"track"in e&&"data"in e?this.onData(e.track,e.time,e.data):"on_time"!==e.type&&this.log(`Internal JSON: ${i.stringify(e)}`).debug()}}close(t){this._ws.close(t)}_sendTracks(){this._tracks.length?this._ws.closed?this._ws.open(this._url):this._ws.opened&&this._ws.send(JSON.stringify({type:"tracks",tracks:this._tracks.join(",")})):this.close()}}class b extends n{get upDelay(){return this._upDelay}get learningUpStep(){return this._learningUpStep}get maximumUpDelay(){return this._maximumUpDelay}constructor(t){super();const e=Object.assign({learningUpStep:1400,maximumUpDelay:28e3},t);this._learningUpStep=e.learningUpStep,this._maximumUpDelay=e.maximumUpDelay,this._upDelay=0,this._testTime=0}reset(){this._upDelay=0,this._mTrack=void 0}compute(t,e,s){var o;const r=null!==(o=e.video)&&void 0!==o?o:e.audio;if(null==r)return this._mTrack=void 0,!1;const n=i.time();if(!(this._mTrack&&this._mTrack.idx===r||(this._appreciationTime=void 0,this._testTime=n,this._mTrack=t.tracks.get(r),this._mTrack)))return this.log(`Can't find track ${r} absent from metadata`).error(),!1;const a=r===e.video?s.video:s.audio;if(!a)return this.log(`Can't compute ${this._mTrack.type} track ${this._mTrack.idx} without statistics`).error(),!1;const c=this._downBitrate(n-this._testTime,this._mTrack,a);if(c)this._appreciationTime=void 0;else{this._appreciationTime||(this._appreciationTime=n);const t=n-this._appreciationTime;if(!this._upBitrate(t,this._mTrack,a)||t<this._upDelay)return!1}let h=this.updateTrack(e.audio,t,c);if(h)e.audio=h.idx;else{if(h=this.updateTrack(e.video,t,c),!h)return!1;e.video=h.idx}return c&&(this._upDelay=Math.min(this._upDelay+this._learningUpStep,this._maximumUpDelay)),this.log(`${c?"DOWN":"UP"} from ${this._mTrack.type} track ${this._mTrack.idx} (${this._mTrack.maxbps}bps) to ${h.type} track ${h.idx} (${h.maxbps}bps)`).info(),!0}updateTrack(t,e,i){if(null==t)return;const s=i?"down":"up",o=e.tracks.get(t);if(o)return o[s];this.log(`Can't find track ${t} from metadata`).error()}}class w extends b{constructor(t){super(t),this._keyFramesDecoded=0,this._lost=0,this._nackCount=0}_downBitrate(t,e,s){const o=s.packetsLost,r=s.nackCount;if(null==o)return this.log(`No packetsLost information in ${i.stringify(s)}`).warn(),!1;const n=t>0&&o>this._lost&&(!r||r>this._nackCount);return this._lost=o,this._nackCount=r||0,n}_upBitrate(t,e,i){if(e.type===d.AUDIO)return!0;if(t>1e4)return!0;const s=i.keyFramesDecoded;if(null==s)return!1;if(t){if(s>this._keyFramesDecoded){if(!this._keyFramesDecoded)return!0;this._keyFramesDecoded=0}}else this._keyFramesDecoded=s;return!1}}const B=3e3;class R extends e{onStart(t){this.log("onStart").info()}onStop(t){t?this.log("onStop",t).error():this.log("onStop").info()}onState(t){this.log("onState",t).info()}onPlaying(t){this.log(`onPlaying ${i.stringify(t)}`).debug()}onMetadata(t){this.log(i.stringify(t)).info()}onData(t,e,s){this.log(`Data received on track ${e} at ${t} : ${i.stringify(s)}`).info()}get streamName(){return this._connector?this._connector.streamName:""}get stream(){return this._connector&&this._connector.stream}get running(){return!!this._connector}get controller(){return this._controller}get connector(){return this._connector}get streamState(){var t;return(null===(t=this._streamMetadata)||void 0===t?void 0:t.streamState)||m.UNKNOWN}get metadata(){return this._metadata}get playingInfos(){return this._playingInfos}get audioTrack(){return this._audioTrack}set audioTrack(t){if(this._audioTrackFixed=null!=t,this._audioTrackFixed&&this._audioTrack!==t&&(this._audioTrack=t,this._connector)){if(!this._controller)throw Error("Cannot set audioTrack without start a controllable session");this._controller.setTracks({audio:t})}}get videoTrack(){return this._videoTrack}set videoTrack(t){if(this._videoTrackFixed=null!=t,this._videoTrackFixed&&this._videoTrack!==t&&(this._videoTrack=t,this._connector)){if(!this._controller)throw Error("Cannot set videoTrack without start a controllable session");this._controller.setTracks({video:t})}}get dataTracks(){return[...this._dataTracks]}set dataTracks(t){this._dataTracks=[...t],this._streamData&&(this._streamData.tracks=t)}constructor(t,e){super(),this.Connector=e,this._dataTracks=new Array,this._videoElement=t,this._playerStats=new v}connectionInfos(){return this._connector?this._connector.connectionInfos():Promise.reject("Start player before to request connection infos")}start(t,e={}){let s,o,n,a;this.stop(),"connectParams"in t&&(n=t,t=t.connectParams),t.query=new URLSearchParams(t.query),null!=this._audioTrack&&t.query.set("audio",this._audioTrack.toFixed()),null!=this._videoTrack&&t.query.set("video",this._videoTrack.toFixed()),this._audioTrackFixed=!1,this._videoTrackFixed=!1,this._connector=new(this.Connector||(t.endPoint.startsWith("http")?C:y))(t),this._connector.log=this.log.bind(this,"Signaling:"),this._connector.onOpen=t=>{var e,i;this._pollStats(),this.onStart(t),(null===(e=this._streamMetadata)||void 0===e?void 0:e.metadata)&&this._streamMetadata.onMetadata(null===(i=this._streamMetadata)||void 0===i?void 0:i.metadata),o&&this._controller&&this._controller.onPlaying(o)},this._connector.onClose=e=>{var i,o;null==s||s.reset(),this.streamState===m.OFFLINE?this.stop({type:"StreamMetadataError",name:m.OFFLINE,stream:null!==(i=t.streamName)&&void 0!==i?i:""}):"WebSocketReliableError"===(null==e?void 0:e.type)&&"Socket disconnection"===e.name?this.stop({type:"StreamMetadataError",name:"Resource not available",stream:null!==(o=t.streamName)&&void 0!==o?o:""}):this.stop(e)},t.endPoint=new r(t.endPoint).host,this._initStreamMetadata(Object.assign({},t),null!=n?n:new p(t)),this._newStreamData(Object.assign({},t)),f(this._connector)?(e&&("compute"in e?s=e:(s=new w(e),s.log=this.log.bind(this,"MultiBitrate:"))),this._controller=this._connector,this._controller.onPlaying=t=>h(this,void 0,void 0,function*(){if(o=t,this._controller&&this._controller.opened&&(this._playingInfos=o,this._updateTracks(),this.onPlaying(o),s))if(this._audioTrackFixed&&this._videoTrackFixed)s.reset();else try{const t=yield this._controller.connectionInfos(1e3);if(t===a)return;const e={audio:this._audioTrackFixed?void 0:this._audioTrack,video:this._videoTrackFixed?void 0:this._videoTrack};this._controller&&this._metadata&&s.compute(this._metadata,e,(a=t).inputs)&&(this._audioTrack=e.audio,this._videoTrack=e.video,this._controller.setTracks(e))}catch(t){this.log(`Can't compute MBR, ${i.stringify(t)}`).error()}})):e&&this.log(`Cannot use a multiple bitrate without a controller: Connector ${this._connector.constructor.name} doesn't implement IController`).warn()}stop(t){const e=this._connector;e&&(this._connector=void 0,clearTimeout(this._statsPollingTimeout),this._playerStats=new v,this._videoElement.pause(),this._videoElement.srcObject=null,this._videoElement.load(),clearTimeout(this._streamMetadataReconnectTimeout),this._streamMetadata&&(this._streamMetadata.onClose=i.EMPTY_FUNCTION,this._streamMetadata.close(),this._streamMetadata=void 0),clearTimeout(this._streamDataReconnectTimeout),this._streamData&&(this._streamData.onClose=i.EMPTY_FUNCTION,this._streamData.close(),this._streamData=void 0),e.close(),this._audioTrack=void 0,this._videoTrack=void 0,this._playingInfos=void 0,this._metadata=void 0,this._dataTracks.length=0,this.onStop(t))}computeStats(){return this._playerStats}_updateTracks(){if(!this._playingInfos||!this._metadata)return;const t=new Array,e=new Array;for(const i of this._playingInfos.tracks){const s=this._metadata.tracks.get(i);s&&(s.type===d.AUDIO?t.push(i):s.type===d.VIDEO&&e.push(i))}1===t.length&&(this._audioTrack=t[0]),1===e.length&&(this._videoTrack=e[0])}_initStreamMetadata(t,e){this._streamMetadata=e,e.log=this.log.bind(this,"StreamMetadata:"),e.onState=t=>this.onState(t),e.onMetadata=t=>{this._connector&&this._connector.opened&&(this._metadata=t.subset(this._connector.codecs),this._updateTracks(),this.onMetadata(this._metadata))},e.onClose=i=>{"StreamMetadataError"!==(null==i?void 0:i.type)?(e.log(`${i||"disconnection"}, try to reconnect to ${t.endPoint} in 3000 ms`).warn(),this._streamMetadataReconnectTimeout=setTimeout(()=>{this._initStreamMetadata(t,new p(t))},B)):this.stop(i)}}_newStreamData(t){const e=this._streamData=new S(t);e.log=this.log.bind("Timed Metadatas:"),e.onData=(t,e,i)=>this.onData(e,t,i),e.tracks=this._dataTracks,e.onClose=i=>{e.log(`${i||"disconnection"}, try to reconnect to ${t.endPoint} in 3000 ms`).warn(),this._streamDataReconnectTimeout=setTimeout(()=>{e.tracks=this._dataTracks},B)}}_pollStats(){this._statsPollingTimeout=setTimeout(()=>h(this,void 0,void 0,function*(){var t;if(this._connector){try{yield this._playerStats.compute(yield this._connector.connectionInfos(100),null!==(t=this._metadata)&&void 0!==t?t:new u,this._videoElement.currentTime,this._audioTrack,this._videoTrack)}catch(t){}this.running&&this._pollStats()}}),1e3)}}class x extends e{get startup(){return this._startup}set startup(t){this._startup=Math.max(this._minimum,Math.min(Math.round(t),this._maximum))}get minimum(){return this._minimum}set minimum(t){t=Math.round(t),(this._minimum=t)>this._maximum?this._maximum=this._startup=t:this._startup<t&&(this._startup=t)}get maximum(){return this._maximum}set maximum(t){t=Math.round(t),(this._maximum=t)<this._minimum?this._minimum=this._startup=t:this._startup>t&&(this._startup=t)}get constraint(){return this._bitrateConstraint}get recoverySteps(){return this._recoverySteps}set recoverySteps(t){this._recoverySteps=Math.max(1,t)}get appreciationDuration(){return this._appreciationDuration}set appreciationDuration(t){this._appreciationDuration=t}get value(){return this._bitrate}valueOf(){return this.value}get stream(){return this._stream}constructor(t,e){super();const i=Object.assign({startup:2e6,maximum:3e6,minimum:2e5,recoverySteps:2,appreciationDuration:4e3},t);this._startup=i.startup,this._minimum=i.minimum,this._maximum=i.maximum,this._appreciationDuration=i.appreciationDuration,this._stream=e,this._recoverySteps=0,this.recoverySteps=i.recoverySteps}compute(t,e,i){const s=null==t?this.startup:Math.max(this.minimum,Math.min(this._computeBitrate(t,e,i),this.maximum));return null==t?this.log(`Set startup bitrate to ${s}`).info():s>t?this.log(`Increase bitrate ${t} => ${s}`).info():s<t&&this.log(`Decrease bitrate ${t} => ${s}`).info(),this._bitrate=t,this._bitrateConstraint=e,s}reset(){this._bitrate=void 0,this._bitrateConstraint=void 0}_updateVideoConstraints(t){const e=this._stream;if(!e)return;const i=e.getVideoTracks()[0];if(!i)return;const s=i.getSettings();if(!s.width||!s.height)return;const o=s.width*s.height;t>=12e5?o<645120&&this._upgradeVideoConstraint(i,2):o>645120&&o<1198080&&this._upgradeVideoConstraint(i,.5)}_upgradeVideoConstraint(t,e){const i={},s=t.getSettings().width;null!=s&&(i.width=s*e);const o=t.getSettings().height;null!=o&&(i.height=o*e),this.log(`Resolution change ${s}X${o} => ${i.width}X${i.height}`).info(),t.applyConstraints(i)}}let P=class{constructor(t){this.recoverySteps=t,this.stableTime=0,this.stableBitrate=0,this.lastLoss=Number.POSITIVE_INFINITY}};class I extends x{constructor(t,e){super(t,e),this._vars=new P(this.recoverySteps-1)}reset(){super.reset(),this._vars=new P(this.recoverySteps-1)}_computeBitrate(t,e,s){const o=s&&s.stats,r=this._vars;if(e&&t>e)r.stableTime=0,t=e;else if(o&&o.loss_perc)o.loss_perc>=r.lastLoss&&(t=Math.round((1-o.loss_perc/100)*t)),r.stableTime=0,r.lastLoss=o.loss_perc;else{r.lastLoss=Number.POSITIVE_INFINITY;const e=i.time();e>=r.stableTime&&(r.stableTime?(this._updateVideoConstraints(t),t+=Math.ceil((this.maximum-r.stableBitrate)/r.recoverySteps),r.recoverySteps=Math.max(r.recoverySteps-1,this.recoverySteps)):(r.stableBitrate=t,++r.recoverySteps),r.stableTime=e+this.appreciationDuration)}return t}}class M extends e{onStart(t){this.log("onStart").info()}onStop(t){t?this.log("onStop",t).error():this.log("onStop").info()}onRTPProps(t){}onMediaReport(t){}onVideoBitrate(t,e){this.log(`onVideoBitrate ${i.stringify({videoBitrate:t,videoBitrateConstraint:e})}`).info()}get streamName(){return this._connector?this._connector.streamName:""}get stream(){return this._connector&&this._connector.stream}get running(){return!!this._connector}get controller(){return this._controller}get connector(){return this._connector}get mediaReport(){return this._mediaReport}get rtpProps(){return this._rtpProps}get videoBitrate(){return this._videoBitrate}set videoBitrate(t){if(t!==this._videoBitrate){if(!this._controller)throw Error("Cannot set videoBitrate without start a controllable session");null!=t?(this._videoBitrateFixed=!0,this._videoBitrate=t,this._controller.setVideoBitrate(t)):this._videoBitrateFixed=!1}}get videoBitrateConstraint(){return this._videoBitrateConstraint}get audioTrack(){var t,e;const i=this._connector&&this._connector.stream;return null!==(e=null===(t=null==i?void 0:i.getAudioTracks())||void 0===t?void 0:t[0])&&void 0!==e?e:null}get videoTrack(){var t,e;const i=this._connector&&this._connector.stream;return null!==(e=null===(t=null==i?void 0:i.getVideoTracks())||void 0===t?void 0:t[0])&&void 0!==e?e:null}constructor(t){super(),this.Connector=t,this._videoBitrateFixed=!1}setAudioTrack(t){return this._replaceTrack("audio",t)}setVideoTrack(t){return this._replaceTrack("video",t)}setRTPProps(t,e){if(!this._controller)throw Error("Cannot set rtpProps without start a controllable session");this._controller.setRTPProps(t,e)}connectionInfos(){return this._connector?this._connector.connectionInfos():Promise.reject("Start streamer before to request connection infos")}start(t,e,i={}){let s;this.stop(),this._videoBitrateFixed=!1,this._connector=new(this.Connector||(e.endPoint.startsWith("http")?C:y))(e,t),this._connector.log=this.log.bind(this,"Signaling:"),this._connector.onOpen=t=>this.onStart(t),this._connector.onClose=t=>{null==s||s.reset(),this.stop(t)},f(this._connector)?(i&&("compute"in i?s=i:(s=new I(i),s.log=this.log.bind(this,"AdaptiveBitrate:"))),this._controller=this._connector,this._controller.onOpen=t=>{this._computeVideoBitrate(s),this.onStart(t)},this._controller.onRTPProps=t=>{this._rtpProps=t,this.onRTPProps(t)},this._controller.onMediaReport=t=>h(this,void 0,void 0,function*(){this._mediaReport=t,this._computeVideoBitrate(s),this.onMediaReport(t)}),this._controller.onVideoBitrate=(t,e)=>{this._videoBitrate=t,this._videoBitrateConstraint=e,this.onVideoBitrate(t,e)}):i&&this.log(`Cannot use an adaptive bitrate without a controller: Connector ${this._connector.constructor.name} doesn't implement IController`).error()}stop(t){const e=this._connector;e&&(this._connector=void 0,e.close(),this._controller=void 0,this._mediaReport=void 0,this._videoBitrate=void 0,this._videoBitrateConstraint=void 0,this._rtpProps=void 0,this.onStop(t))}_replaceTrack(t,e){return h(this,void 0,void 0,function*(){if(!this._connector)throw Error("Cannot call _replaceTrack before start()");const i=this._connector.stream;if(!i)throw Error("Cannot call _replaceTrack without a stream");let s=null;if(null!==e){if(e.kind!==t)throw Error(`_replaceTrack: provided track kind "${e.kind}" does not match requested kind "${t}"`);s=e}yield this._connector.replaceTrack(t,s),"video"===t?i.getVideoTracks().forEach(t=>i.removeTrack(t)):i.getAudioTracks().forEach(t=>i.removeTrack(t)),s&&i.addTrack(s)})}_computeVideoBitrate(t){if(!this._controller||this._videoBitrateFixed)return;const e=t.compute(this._videoBitrate,this.videoBitrateConstraint,this.mediaReport);e!==this._videoBitrate&&(this._videoBitrate=e,this._controller.setVideoBitrate(e))}}class N{constructor(){this.lossPercents=new a(5),this.stableBitrates=new a(15),this.stableBitrateUpdateTime=0,this.bitrateRecoveryTimeout=1e4,this.bitrateRecoveryNextTime=0}}class E extends x{constructor(t,e){super(t,e),this._vars=new N}reset(){super.reset(),this._vars=new N}_computeBitrate(t,e,s){var o;const r=this._vars,n=s&&s.stats;n&&null!=n.loss_perc&&r.lossPercents.push(n.loss_perc);const a=i.time();a>=r.stableBitrateUpdateTime&&r.lossPercents.average<.2&&(r.stableBitrateUpdateTime=a+4e3,r.stableBitrates.push(Math.min(null!==(o=null!=e?e:this.constraint)&&void 0!==o?o:0,t,this.maximum)));const c=r.stableBitrates.average,h=r.stableBitrates.minimum,d=r.stableBitrates.maximum;if(h>=.8*c&&d<=1.2*c&&this._updateVideoConstraints(c),null!=e&&null!=this.constraint&&e<this.constraint){if(this.log(`onVideoBitrateConstraint: ${this._formatBitrate(e-this.constraint)}`).info(),r.bitrateConstraintTime){if(r.bitrateRecoveryTime){(a-r.bitrateRecoveryTime<r.bitrateRecoveryTimeout||2500===r.bitrateRecoveryTimeout)&&(this._increaseRecoveryTimeout(),r.bitrateRecoveryTime=void 0)}}else r.bitrateRecoveryTime||(this.log("VideoBitrateConstraint: First constrain! Halve bitrate!").info(),t=Math.round(t/2));r.bitrateConstraintTime=a}if(e&&(e<this.minimum&&(t=this.minimum),this.maximum>e&&a>=r.bitrateRecoveryNextTime)){const i=r.bitrateRecoveryNextTime&&this._bitrateRecoveryHandler(e);i?(r.bitrateRecoveryTimeout=a,r.bitrateRecoveryNextTime=0,t=i):(r.bitrateRecoveryNextTime=a+r.bitrateRecoveryTimeout,this.log(`startVideoBitrateRecoveryTimer ${r.bitrateRecoveryTimeout}`).info())}return t}_bitrateRecoveryHandler(t){const e=this._vars,i=e.lossPercents.average;if(this.log(`videoBitrateRecoveryHandler loss: ${i.toFixed(2)}`).info(),i<.2){let i=this._increaseTargetBitrate(t);return e.bitrateConstraintTime&&i>e.stableBitrates.average&&(i=this._increaseTargetBitrate(t,1.005)),this._decreaseRecoveryTimeout(),this.log(`videoBitrateRecoveryHandler increases bitrate to ${this._formatBitrate(i)}`).info(),i}if(i<5){this._increaseRecoveryTimeout();const e=this._decreaseTargetBitrate(t);return this.log(`videoBitrateRecoveryHandler decreases bitrate to ${this._formatBitrate(e)}`).info(),e}}_increaseTargetBitrate(t,e=1.05){let i=Math.round(t*e);return i>this.maximum&&(i=this.maximum),i}_decreaseTargetBitrate(t){let e=Math.round(.99*t);return e<this.minimum&&(e=this.minimum),e}_increaseRecoveryTimeout(){this._vars.bitrateRecoveryTimeout=Math.min(6e4,2*this._vars.bitrateRecoveryTimeout)}_decreaseRecoveryTimeout(){this._vars.bitrateRecoveryTimeout=Math.max(2500,Math.round(.75*this._vars.bitrateRecoveryTimeout))}_formatBitrate(t){return(t/1e6).toFixed(3)}}class D extends n{get url(){return this._url}get reporting(){return this._reporting}constructor(t){if(super(),(t=new URL(t)).protocol.startsWith("ws"))this._ws=new s;else if(!t.protocol.startsWith("http"))throw Error("Protocol "+t.protocol+" not supported");this._url=t.toString(),this._reporting=0}report(t,e){t.log=this.log.bind(this,t.constructor.name);let s=t.onRelease=()=>{s&&(s=void 0,clearInterval(o),this.log(`Stop ${t.constructor.name} reporting`).info(),--this._reporting>0||(this._ws&&this._ws.close(),this._fetch&&this._fetch.abort()))};const o=(e?setInterval:setTimeout)(()=>h(this,void 0,void 0,function*(){!e&&s&&s();try{yield this._send(t)}catch(e){t.log(i.stringify(e)).error()}}),1e3*e);++this._reporting,this.log(`Start ${t.constructor.name} reporting every ${e} seconds`).info()}_send(t){return h(this,void 0,void 0,function*(){let e,i,s;try{e=yield t.serialize()}catch(t){if(!t)return;throw t}e instanceof ArrayBuffer?(s="application/octet-stream",i=e):"string"==typeof e?(s="text/plain",i=e):(s="application/json",i=JSON.stringify(e)),this._ws?(this._ws.closed&&this._ws.open(this._url),this._ws.queueing.length=0,this._ws.send(i)):(this._fetch&&this._fetch.abort(),this._fetch=new AbortController,fetch(this._url,{method:"POST",body:i,headers:{"Content-Type":s},signal:this._fetch.signal}))})}}class O extends e{onRelease(){}constructor(t){super(),this._streamer=t,this._lastBytesSend=0,this._lastBytesSendTime=Date.now(),t.once("stop",()=>this.onRelease())}serialize(){return h(this,void 0,void 0,function*(){if(!this._streamer.running)return Promise.reject();const t={streamId:this._streamer.streamName,vbt:this._streamer.videoBitrate,vbc:this._streamer.videoBitrateConstraint},e=this._streamer.mediaReport;let s;e&&(t.server={millis:e.millis,tracks:e.tracks},e.stats&&(t.server.jitterMs=e.stats.jitter_ms,t.server.lossNum=e.stats.loss_num,t.server.lossPerc=e.stats.loss_perc,t.server.nackNum=e.stats.nack_num));try{s=yield this._streamer.connectionInfos()}catch(e){return this.log(`Report stats without connection infos, ${i.stringify(e)}`).warn(),t}const o=s.candidate;if(o){if(t.sessionId=o.id,t.timestamp=o.timestamp,t.currentRoundTripTime=o.currentRoundTripTime,t.totalRoundTripTime=o.totalRoundTripTime,t.requestsReceived=o.requestsReceived,t.requestsSent=o.requestsSent,t.responsesReceived=o.responsesReceived,t.responsesSent=o.responsesSent,t.bytesSent=o.bytesSent,t.bytesReceived=o.bytesReceived,null==o.availableOutgoingBitrate){const e=t.bytesSent-this._lastBytesSend;this._lastBytesSend=t.bytesSent;const i=Date.now(),s=(i-this._lastBytesSendTime)/1e3;this._lastBytesSendTime=i,o.availableOutgoingBitrate=8*e/s}t.availableOutgoingBitrate=o.availableOutgoingBitrate}const r=s.outputs.audio;r&&(t.audio={bytesSent:r.bytesSent,packetsSent:r.packetsSent,retransmittedBytesSent:r.retransmittedBytesSent,retransmittedPacketsSent:r.retransmittedPacketsSent});const n=s.outputs.audio;return n&&(t.video={bytesSent:n.bytesSent,packetsSent:n.packetsSent,retransmittedBytesSent:n.retransmittedBytesSent,retransmittedPacketsSent:n.retransmittedPacketsSent,firCount:n.firCount,framesEncoded:n.framesEncoded,nackCount:n.nackCount,totalEncodeTime:n.totalEncodeTime,hugeFramesSent:n.hugeFramesSent,framesSent:n.framesSent,frameHeight:n.frameHeight,frameWidth:n.frameWidth}),t})}}const $="5.2.0";export{x as ABRAbstract,E as ABRGrade,I as ABRLinear,C as HTTPConnector,b as MBRAbstract,w as MBRLinear,d as MType,u as Metadata,R as Player,v as PlayerStats,T as SIPConnector,p as StreamMetadata,m as StreamState,M as Streamer,O as StreamerStats,D as Telemetry,$ as VERSION,y as WSController,S as WSStreamData};//# sourceMappingURL=webrtc-client.min.js.map