whepts 1.1.4 → 1.1.5
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/dist/index.js +1 -1
- package/dist/whep.d.ts +0 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"eventemitter3";import{atom as t}from"nanostores";const s={SIGNAL_ERROR:"SignalError",STATE_ERROR:"StateError",REQUEST_ERROR:"RequestError",NOT_FOUND_ERROR:"NotFoundError",CONNECT_ERROR:"ConnectError",MEDIA_ERROR:"MediaError",OTHER_ERROR:"OtherError"};class n extends Error{constructor(e,t,s){super(t,s),this.type=e}}class i{static async supportsNonAdvertisedCodec(e,t){return new Promise(s=>{const n=new RTCPeerConnection({iceServers:[]}),r="audio";let o="";n.addTransceiver(r,{direction:"recvonly"}),n.createOffer().then(s=>{if(!s.sdp)throw new Error("SDP not present");if(s.sdp.includes(` ${e}`))throw new Error("already present");const a=s.sdp.split(`m=${r}`),c=a.slice(1).map(e=>e.split("\r\n")[0].split(" ").slice(3)).reduce((e,t)=>[...e,...t],[]);o=i.reservePayloadType(c);const h=a[1].split("\r\n");return h[0]+=` ${o}`,h.splice(h.length-1,0,`a=rtpmap:${o} ${e}`),void 0!==t&&h.splice(h.length-1,0,`a=fmtp:${o} ${t}`),a[1]=h.join("\r\n"),s.sdp=a.join(`m=${r}`),n.setLocalDescription(s)}).then(()=>n.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:`v=0\r\no=- 6539324223450680508 0 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\na=fingerprint:sha-256 0D:9F:78:15:42:B5:4B:E6:E2:94:3E:5B:37:78:E1:4B:54:59:A3:36:3A:E5:05:EB:27:EE:8F:D2:2D:41:29:25\r\nm=${r} 9 UDP/TLS/RTP/SAVPF ${o}\r\nc=IN IP4 0.0.0.0\r\na=ice-pwd:7c3bf4770007e7432ee4ea4d697db675\r\na=ice-ufrag:29e036dc\r\na=sendonly\r\na=rtcp-mux\r\na=rtpmap:${o} ${e}\r\n${void 0!==t?`a=fmtp:${o} ${t}\r\n`:""}`}))).then(()=>s(!0)).catch(()=>s(!1)).finally(()=>n.close())})}static unquoteCredential(e){return JSON.parse(`"${e}"`)}static linkToIceServers(e){return e?e.split(", ").map(e=>{const t=e.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);if(!t)throw new n(s.SIGNAL_ERROR,"Invalid ICE server link format");const r={urls:[t[1]]};return t[3]&&(r.username=i.unquoteCredential(t[3]),r.credential=i.unquoteCredential(t[4]),r.credentialType="password"),r}):[]}static reservePayloadType(e){for(let t=30;t<=127;t++)if((t<=63||t>=96)&&!e.includes(t.toString())){const s=t.toString();return e.push(s),s}throw new Error("unable to find a free payload type")}}class r{constructor(e){this.options=e}detect(){Promise.all([["pcma/8000/2"],["multiopus/48000/6","channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2"],["L16/48000/2"]].map(e=>i.supportsNonAdvertisedCodec(e[0],e[1]).then(t=>!!t&&e[0]))).then(e=>e.filter(e=>!1!==e)).then(e=>{if("getting_codecs"!==this.options.getState())throw new n(s.STATE_ERROR,"closed");this.options.emitter.emit("codecs:detected",e)}).catch(e=>this.options.emitter.emit("error",e))}}class o{static parseOffer(e){const t={iceUfrag:"",icePwd:"",medias:[]};for(const s of e.split("\r\n"))s.startsWith("m=")?t.medias.push(s.slice(2)):""===t.iceUfrag&&s.startsWith("a=ice-ufrag:")?t.iceUfrag=s.slice(12):""===t.icePwd&&s.startsWith("a=ice-pwd:")&&(t.icePwd=s.slice(10));return t}static reservePayloadType(e){for(let t=30;t<=127;t++)if((t<=63||t>=96)&&!e.includes(t.toString())){const s=t.toString();return e.push(s),s}throw new Error("unable to find a free payload type")}static enableStereoPcmau(e,t){const s=t.split("\r\n");let n=o.reservePayloadType(e);return s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} PCMU/8000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(e),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} PCMA/8000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),s.join("\r\n")}static enableMultichannelOpus(e,t){const s=t.split("\r\n");let n=o.reservePayloadType(e);return s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/3`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,2,1;num_streams=2;coupled_streams=1`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(e),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/4`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(e),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/5`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(e),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/6`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(e),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/7`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(e),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/8`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),s.join("\r\n")}static enableL16(e,t){const s=t.split("\r\n");let n=o.reservePayloadType(e);return s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} L16/8000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(e),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} L16/16000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(e),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} L16/48000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),s.join("\r\n")}static enableStereoOpus(e){let t="";const s=e.split("\r\n");for(let e=0;e<s.length;e++)if(s[e].startsWith("a=rtpmap:")&&s[e].toLowerCase().includes("opus/")){t=s[e].slice(9).split(" ")[0];break}if(""===t)return e;for(let e=0;e<s.length;e++)s[e].startsWith(`a=fmtp:${t} `)&&(s[e].includes("stereo")||(s[e]+=";stereo=1"),s[e].includes("sprop-stereo")||(s[e]+=";sprop-stereo=1"));return s.join("\r\n")}static editOffer(e,t){const s=e.split("m="),n=s.slice(1).map(e=>e.split("\r\n")[0].split(" ").slice(3)).reduce((e,t)=>[...e,...t],[]);for(let e=1;e<s.length;e++)if(s[e].startsWith("audio")){s[e]=o.enableStereoOpus(s[e]),t.includes("pcma/8000/2")&&(s[e]=o.enableStereoPcmau(n,s[e])),t.includes("multiopus/48000/6")&&(s[e]=o.enableMultichannelOpus(n,s[e])),t.includes("L16/48000/2")&&(s[e]=o.enableL16(n,s[e]));break}return s.join("m=")}static generateSdpFragment(e,t){const s={};for(const e of t){const t=e.sdpMLineIndex;t&&(void 0===s[t]&&(s[t]=[]),s[t].push(e))}let n=`a=ice-ufrag:${e.iceUfrag}\r\na=ice-pwd:${e.icePwd}\r\n`,i=0;for(const t of e.medias){if(void 0!==s[i]){n+=`m=${t}\r\na=mid:${i}\r\n`;for(const e of s[i])n+=`a=${e.candidate}\r\n`}i++}return n}}class a{constructor(e){this.options=e}async setupPeerConnection(e){if("running"!==this.options.getState())throw new n(s.STATE_ERROR,"closed");const t=new RTCPeerConnection({iceServers:e,sdpSemantics:"unified-plan"});this.pc=t;const i="recvonly";return t.addTransceiver("video",{direction:i}),t.addTransceiver("audio",{direction:i}),t.onicecandidate=e=>this.onLocalCandidate(e),t.onconnectionstatechange=()=>this.onConnectionState(),t.oniceconnectionstatechange=()=>this.onIceConnectionState(),t.ontrack=e=>this.options.emitter.emit("track",e),t.createOffer().then(e=>{if(!e.sdp)throw new n(s.SIGNAL_ERROR,"Failed to create offer SDP");return e.sdp=o.editOffer(e.sdp,this.options.getNonAdvertisedCodecs()),this.offerData=o.parseOffer(e.sdp),t.setLocalDescription(e).then(()=>e.sdp)})}async setAnswer(e){if("running"!==this.options.getState())throw new n(s.STATE_ERROR,"closed");return this.pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:e}))}getPeerConnection(){return this.pc}getOfferData(){return this.offerData}close(){this.pc?.close(),this.pc=void 0,this.offerData=void 0}onLocalCandidate(e){"running"===this.options.getState()&&e.candidate&&this.options.emitter.emit("candidate",e.candidate)}onConnectionState(){"running"===this.options.getState()&&this.pc&&("failed"!==this.pc.connectionState&&"closed"!==this.pc.connectionState||this.options.emitter.emit("error",new n(s.OTHER_ERROR,"peer connection closed")))}onIceConnectionState(){"running"===this.options.getState()&&this.pc&&"failed"===this.pc.iceConnectionState&&this.pc.restartIce()}}class c{constructor(e){this.options=e}authHeader(){if(this.options.conf.user&&""!==this.options.conf.user){return{Authorization:`Basic ${btoa(`${this.options.conf.user}:${this.options.conf.pass}`)}`}}return this.options.conf.token&&""!==this.options.conf.token?{Authorization:`Bearer ${this.options.conf.token}`}:{}}async requestICEServers(){return this.options.conf.iceServers&&this.options.conf.iceServers.length>0?this.options.conf.iceServers:fetch(this.options.conf.url,{method:"OPTIONS",headers:{...this.authHeader()}}).then(e=>i.linkToIceServers(e.headers.get("Link")))}async sendOffer(e){if("running"!==this.options.getState())throw new n(s.STATE_ERROR,"closed");return fetch(this.options.conf.url,{method:"POST",headers:{...this.authHeader(),"Content-Type":"application/sdp"},body:e}).then(e=>{switch(e.status){case 201:break;case 404:case 406:throw new n(s.NOT_FOUND_ERROR,"stream not found");case 400:return e.json().then(e=>{throw new n(s.REQUEST_ERROR,e.error)});default:throw new n(s.REQUEST_ERROR,`bad status code ${e.status}`)}const t=e.headers.get("Location"),i=t?new URL(t,this.options.conf.url).toString():void 0;return e.text().then(e=>({sessionUrl:i,answer:e}))})}sendLocalCandidates(e,t,i){fetch(e,{method:"PATCH",headers:{"Content-Type":"application/trickle-ice-sdpfrag","If-Match":"*"},body:o.generateSdpFragment(t,i)}).then(e=>{switch(e.status){case 204:break;case 404:throw new n(s.NOT_FOUND_ERROR,"stream not found");default:throw new n(s.REQUEST_ERROR,`bad status code ${e.status}`)}}).catch(e=>this.options.emitter.emit("error",e))}}class h{constructor(e,s=!0){this.container=e,this.lazyLoad=s,this.showStore=t(!1),this.showStore.subscribe(e=>{e?this.resume():this.pause()}),this.lazyLoad?(this.observer=new IntersectionObserver(([e])=>{e.isIntersecting?this.showStore.set(!0):this.showStore.set(!1)},{threshold:.5}),this.observer.observe(this.container)):this.showStore.set(!0)}onTrack(e){this.stream=e.streams[0],this.showStore.value&&(this.container.srcObject=this.stream)}get paused(){return null===this.container.srcObject}pause(){this.container.srcObject=null}resume(){this.stream&&(this.container.srcObject=this.stream)}stop(){this.stream=void 0}}class l{constructor(e){this.options=e,this.lastBytesReceived=0,this.consecutiveNoProgress=0,this.startTime=0,this.isStable=!1,this.baseInterval=e.interval,this.stableInterval=e.stableInterval||2*e.interval,this.maxNoProgress=e.maxNoProgress||3,this.stabilizationTime=e.stabilizationTime||3e4}setPeerConnection(e){this.pc=e}start(){this.close(),this.startTime=Date.now(),this.isStable=!1,this.consecutiveNoProgress=0,this.scheduleNextCheck()}close(){this.checkTimer&&(clearTimeout(this.checkTimer),this.checkTimer=void 0),this.pc=void 0}scheduleNextCheck(){const e=this.getNextCheckInterval();this.checkTimer=setTimeout(()=>{this.checkFlowState().then(()=>{this.pc&&"connected"===this.pc.connectionState&&this.scheduleNextCheck()})},e)}getNextCheckInterval(){const e=Date.now()-this.startTime;return this.isStable=e>this.stabilizationTime,this.isStable?this.stableInterval:this.baseInterval}async checkFlowState(){if(!this.pc)return;if("connected"!==this.pc.connectionState)return;const e=await this.pc.getStats();let t=0;if(e.forEach(e=>{const s=e;"inbound-rtp"===e.type&&"video"===s.kind&&(t=s.bytesReceived||0)}),t===this.lastBytesReceived){if(this.consecutiveNoProgress++,this.consecutiveNoProgress>=this.maxNoProgress)return this.options.emitter.emit("error",new n(s.CONNECT_ERROR,"data stream interruption")),void(this.consecutiveNoProgress=0)}else this.consecutiveNoProgress=0;this.lastBytesReceived=t}}class p extends e{constructor(e){super(),this.retryPause=2e3,this.stateStore=t("getting_codecs"),this.queuedCandidates=[],this.nonAdvertisedCodecs=[],this.conf=e,this.stateStore.subscribe((e,t)=>{this.emit("state:change",{from:t,to:e})}),this.trackManager=new h(this.conf.container,this.conf.lazyLoad),this.flowCheck=new l({interval:5e3,emitter:this}),this.httpClient=new c({conf:this.conf,getState:()=>this.stateStore.get(),emitter:this}),this.connectionManager=new a({getState:()=>this.stateStore.get(),emitter:this,getNonAdvertisedCodecs:()=>this.nonAdvertisedCodecs}),this.codecDetector=new r({getState:()=>this.stateStore.get(),emitter:this}),this.on("codecs:detected",e=>{this.handleCodecsDetected(e)}),this.on("candidate",e=>this.handleCandidate(e)),this.on("track",e=>{this.trackManager.onTrack(e),this.flowCheck.start()}),this.codecDetector.detect()}get state(){return this.stateStore.get()}get isRunning(){return"running"===this.state}close(){this.stateStore.set("closed"),this.connectionManager.close(),this.trackManager.stop(),this.flowCheck.close(),this.restartTimeout&&clearTimeout(this.restartTimeout),this.emit("close")}cleanupSession(){this.connectionManager.close(),this.flowCheck.close(),this.queuedCandidates=[],this.sessionUrl&&(fetch(this.sessionUrl,{method:"DELETE"}).catch(()=>{}),this.sessionUrl=void 0)}handleError(e){this.flowCheck.close(),"getting_codecs"===this.stateStore.get()||e instanceof n&&[s.SIGNAL_ERROR,s.NOT_FOUND_ERROR,s.REQUEST_ERROR].includes(e.type)?this.stateStore.set("failed"):"running"===this.stateStore.get()&&(this.cleanupSession(),this.stateStore.set("restarting"),this.emit("restart"),this.restartTimeout=setTimeout(()=>{this.restartTimeout=void 0,this.stateStore.set("running"),this.start()},this.retryPause),e.message=`${e.message}, retrying in some seconds`),e instanceof n?this.emit("error",e):this.emit("error",new n(s.OTHER_ERROR,e.message))}handleCodecsDetected(e){this.nonAdvertisedCodecs=e,this.stateStore.set("running"),this.start()}start(){this.httpClient.requestICEServers().then(e=>this.connectionManager.setupPeerConnection(e)).then(e=>{const t=this.connectionManager.getPeerConnection();return t&&this.flowCheck.setPeerConnection(t),e}).then(e=>this.httpClient.sendOffer(e)).then(({sessionUrl:e,answer:t})=>this.handleOfferResponse(e,t)).catch(e=>this.handleError(e))}handleOfferResponse(e,t){return e&&(this.sessionUrl=e),this.connectionManager.setAnswer(t).then(()=>{if("running"===this.stateStore.get()&&0!==this.queuedCandidates.length){const e=this.connectionManager.getOfferData();e&&this.sessionUrl&&(this.httpClient.sendLocalCandidates(this.sessionUrl,e,this.queuedCandidates),this.queuedCandidates=[])}})}handleCandidate(e){if(this.sessionUrl){const t=this.connectionManager.getOfferData();t&&this.httpClient.sendLocalCandidates(this.sessionUrl,t,[e])}else this.queuedCandidates.push(e)}get paused(){return this.trackManager.paused}pause(){this.trackManager.pause()}resume(){this.trackManager.resume()}updateUrl(e){"closed"!==this.stateStore.get()?(this.conf.url=e,this.restartTimeout&&(clearTimeout(this.restartTimeout),this.restartTimeout=void 0),this.cleanupSession(),this.stateStore.set("running"),this.start()):this.emit("error",new n(s.OTHER_ERROR,"Cannot update URL: instance is closed"))}}export{s as ErrorTypes,n as WebRTCError,p as default};
|
|
1
|
+
import t from"eventemitter3";import{atom as e}from"nanostores";const s={SIGNAL_ERROR:"SignalError",STATE_ERROR:"StateError",REQUEST_ERROR:"RequestError",NOT_FOUND_ERROR:"NotFoundError",CONNECT_ERROR:"ConnectError",MEDIA_ERROR:"MediaError",OTHER_ERROR:"OtherError"};class n extends Error{constructor(t,e,s){super(e,s),this.type=t}}class i{static async supportsNonAdvertisedCodec(t,e){return new Promise(s=>{const n=new RTCPeerConnection({iceServers:[]}),r="audio";let o="";n.addTransceiver(r,{direction:"recvonly"}),n.createOffer().then(s=>{if(!s.sdp)throw new Error("SDP not present");if(s.sdp.includes(` ${t}`))throw new Error("already present");const a=s.sdp.split(`m=${r}`),c=a.slice(1).map(t=>t.split("\r\n")[0].split(" ").slice(3)).reduce((t,e)=>[...t,...e],[]);o=i.reservePayloadType(c);const h=a[1].split("\r\n");return h[0]+=` ${o}`,h.splice(h.length-1,0,`a=rtpmap:${o} ${t}`),void 0!==e&&h.splice(h.length-1,0,`a=fmtp:${o} ${e}`),a[1]=h.join("\r\n"),s.sdp=a.join(`m=${r}`),n.setLocalDescription(s)}).then(()=>n.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:`v=0\r\no=- 6539324223450680508 0 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\na=fingerprint:sha-256 0D:9F:78:15:42:B5:4B:E6:E2:94:3E:5B:37:78:E1:4B:54:59:A3:36:3A:E5:05:EB:27:EE:8F:D2:2D:41:29:25\r\nm=${r} 9 UDP/TLS/RTP/SAVPF ${o}\r\nc=IN IP4 0.0.0.0\r\na=ice-pwd:7c3bf4770007e7432ee4ea4d697db675\r\na=ice-ufrag:29e036dc\r\na=sendonly\r\na=rtcp-mux\r\na=rtpmap:${o} ${t}\r\n${void 0!==e?`a=fmtp:${o} ${e}\r\n`:""}`}))).then(()=>s(!0)).catch(()=>s(!1)).finally(()=>n.close())})}static unquoteCredential(t){return JSON.parse(`"${t}"`)}static linkToIceServers(t){return t?t.split(", ").map(t=>{const e=t.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);if(!e)throw new n(s.SIGNAL_ERROR,"Invalid ICE server link format");const r={urls:[e[1]]};return e[3]&&(r.username=i.unquoteCredential(e[3]),r.credential=i.unquoteCredential(e[4]),r.credentialType="password"),r}):[]}static reservePayloadType(t){for(let e=30;e<=127;e++)if((e<=63||e>=96)&&!t.includes(e.toString())){const s=e.toString();return t.push(s),s}throw new Error("unable to find a free payload type")}}class r{constructor(t){this.options=t}detect(){Promise.all([["pcma/8000/2"],["multiopus/48000/6","channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2"],["L16/48000/2"]].map(t=>i.supportsNonAdvertisedCodec(t[0],t[1]).then(e=>!!e&&t[0]))).then(t=>t.filter(t=>!1!==t)).then(t=>{if("getting_codecs"!==this.options.getState())throw new n(s.STATE_ERROR,"closed");this.options.emitter.emit("codecs:detected",t)}).catch(t=>this.options.emitter.emit("error",t))}}class o{static parseOffer(t){const e={iceUfrag:"",icePwd:"",medias:[]};for(const s of t.split("\r\n"))s.startsWith("m=")?e.medias.push(s.slice(2)):""===e.iceUfrag&&s.startsWith("a=ice-ufrag:")?e.iceUfrag=s.slice(12):""===e.icePwd&&s.startsWith("a=ice-pwd:")&&(e.icePwd=s.slice(10));return e}static reservePayloadType(t){for(let e=30;e<=127;e++)if((e<=63||e>=96)&&!t.includes(e.toString())){const s=e.toString();return t.push(s),s}throw new Error("unable to find a free payload type")}static enableStereoPcmau(t,e){const s=e.split("\r\n");let n=o.reservePayloadType(t);return s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} PCMU/8000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(t),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} PCMA/8000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),s.join("\r\n")}static enableMultichannelOpus(t,e){const s=e.split("\r\n");let n=o.reservePayloadType(t);return s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/3`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,2,1;num_streams=2;coupled_streams=1`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(t),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/4`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(t),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/5`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(t),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/6`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(t),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/7`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(t),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} multiopus/48000/8`),s.splice(s.length-1,0,`a=fmtp:${n} channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),s.join("\r\n")}static enableL16(t,e){const s=e.split("\r\n");let n=o.reservePayloadType(t);return s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} L16/8000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(t),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} L16/16000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),n=o.reservePayloadType(t),s[0]+=` ${n}`,s.splice(s.length-1,0,`a=rtpmap:${n} L16/48000/2`),s.splice(s.length-1,0,`a=rtcp-fb:${n} transport-cc`),s.join("\r\n")}static enableStereoOpus(t){let e="";const s=t.split("\r\n");for(let t=0;t<s.length;t++)if(s[t].startsWith("a=rtpmap:")&&s[t].toLowerCase().includes("opus/")){e=s[t].slice(9).split(" ")[0];break}if(""===e)return t;for(let t=0;t<s.length;t++)s[t].startsWith(`a=fmtp:${e} `)&&(s[t].includes("stereo")||(s[t]+=";stereo=1"),s[t].includes("sprop-stereo")||(s[t]+=";sprop-stereo=1"));return s.join("\r\n")}static editOffer(t,e){const s=t.split("m="),n=s.slice(1).map(t=>t.split("\r\n")[0].split(" ").slice(3)).reduce((t,e)=>[...t,...e],[]);for(let t=1;t<s.length;t++)if(s[t].startsWith("audio")){s[t]=o.enableStereoOpus(s[t]),e.includes("pcma/8000/2")&&(s[t]=o.enableStereoPcmau(n,s[t])),e.includes("multiopus/48000/6")&&(s[t]=o.enableMultichannelOpus(n,s[t])),e.includes("L16/48000/2")&&(s[t]=o.enableL16(n,s[t]));break}return s.join("m=")}static generateSdpFragment(t,e){const s={};for(const t of e){const e=t.sdpMLineIndex;e&&(void 0===s[e]&&(s[e]=[]),s[e].push(t))}let n=`a=ice-ufrag:${t.iceUfrag}\r\na=ice-pwd:${t.icePwd}\r\n`,i=0;for(const e of t.medias){if(void 0!==s[i]){n+=`m=${e}\r\na=mid:${i}\r\n`;for(const t of s[i])n+=`a=${t.candidate}\r\n`}i++}return n}}class a{constructor(t){this.options=t}async setupPeerConnection(t){if("running"!==this.options.getState())throw new n(s.STATE_ERROR,"closed");const e=new RTCPeerConnection({iceServers:t,sdpSemantics:"unified-plan"});this.pc=e;const i="recvonly";return e.addTransceiver("video",{direction:i}),e.addTransceiver("audio",{direction:i}),e.onicecandidate=t=>this.onLocalCandidate(t),e.onconnectionstatechange=()=>this.onConnectionState(),e.oniceconnectionstatechange=()=>this.onIceConnectionState(),e.ontrack=t=>this.options.emitter.emit("track",t),e.createOffer().then(t=>{if(!t.sdp)throw new n(s.SIGNAL_ERROR,"Failed to create offer SDP");return t.sdp=o.editOffer(t.sdp,this.options.getNonAdvertisedCodecs()),this.offerData=o.parseOffer(t.sdp),e.setLocalDescription(t).then(()=>t.sdp)})}async setAnswer(t){if("running"!==this.options.getState())throw new n(s.STATE_ERROR,"closed");return this.pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:t}))}getPeerConnection(){return this.pc}getOfferData(){return this.offerData}close(){this.pc?.close(),this.pc=void 0,this.offerData=void 0}onLocalCandidate(t){"running"===this.options.getState()&&t.candidate&&this.options.emitter.emit("candidate",t.candidate)}onConnectionState(){"running"===this.options.getState()&&this.pc&&("failed"!==this.pc.connectionState&&"closed"!==this.pc.connectionState||this.options.emitter.emit("error",new n(s.OTHER_ERROR,"peer connection closed")))}onIceConnectionState(){"running"===this.options.getState()&&this.pc&&"failed"===this.pc.iceConnectionState&&this.pc.restartIce()}}class c{constructor(t){this.options=t}authHeader(){if(this.options.conf.user&&""!==this.options.conf.user){return{Authorization:`Basic ${btoa(`${this.options.conf.user}:${this.options.conf.pass}`)}`}}return this.options.conf.token&&""!==this.options.conf.token?{Authorization:`Bearer ${this.options.conf.token}`}:{}}async requestICEServers(){return this.options.conf.iceServers&&this.options.conf.iceServers.length>0?this.options.conf.iceServers:fetch(this.options.conf.url,{method:"OPTIONS",headers:{...this.authHeader()}}).then(t=>i.linkToIceServers(t.headers.get("Link")))}async sendOffer(t){if("running"!==this.options.getState())throw new n(s.STATE_ERROR,"closed");return fetch(this.options.conf.url,{method:"POST",headers:{...this.authHeader(),"Content-Type":"application/sdp"},body:t}).then(t=>{switch(t.status){case 201:break;case 404:case 406:throw new n(s.NOT_FOUND_ERROR,"stream not found");case 400:return t.json().then(t=>{throw new n(s.REQUEST_ERROR,t.error)});default:throw new n(s.REQUEST_ERROR,`bad status code ${t.status}`)}const e=t.headers.get("Location"),i=e?new URL(e,this.options.conf.url).toString():void 0;return t.text().then(t=>({sessionUrl:i,answer:t}))})}sendLocalCandidates(t,e,i){fetch(t,{method:"PATCH",headers:{"Content-Type":"application/trickle-ice-sdpfrag","If-Match":"*"},body:o.generateSdpFragment(e,i)}).then(t=>{switch(t.status){case 204:break;case 404:throw new n(s.NOT_FOUND_ERROR,"stream not found");default:throw new n(s.REQUEST_ERROR,`bad status code ${t.status}`)}}).catch(t=>this.options.emitter.emit("error",t))}}class h{constructor(t,s=!0){this.container=t,this.lazyLoad=s,this.showStore=e(!1),this.showStore.subscribe(t=>{t?this.resume():this.pause()}),this.lazyLoad?(this.observer=new IntersectionObserver(([t])=>{t.isIntersecting?this.showStore.set(!0):this.showStore.set(!1)},{threshold:.5}),this.observer.observe(this.container)):this.showStore.set(!0)}onTrack(t){this.stream=t.streams[0],this.showStore.get()&&(this.container.srcObject=this.stream)}get paused(){return null===this.container.srcObject}pause(){this.container.srcObject=null}resume(){this.stream&&(this.container.srcObject=this.stream)}stop(){this.stream=void 0}}class l{constructor(t){this.options=t,this.lastBytesReceived=0,this.consecutiveNoProgress=0,this.startTime=0,this.isStable=!1,this.baseInterval=t.interval,this.stableInterval=t.stableInterval||2*t.interval,this.maxNoProgress=t.maxNoProgress||3,this.stabilizationTime=t.stabilizationTime||3e4}setPeerConnection(t){this.pc=t}start(){this.close(),this.startTime=Date.now(),this.isStable=!1,this.consecutiveNoProgress=0,this.scheduleNextCheck()}close(){this.checkTimer&&(clearTimeout(this.checkTimer),this.checkTimer=void 0),this.pc=void 0}scheduleNextCheck(){const t=this.getNextCheckInterval();this.checkTimer=setTimeout(()=>{this.checkFlowState().then(()=>{this.pc&&"connected"===this.pc.connectionState&&this.scheduleNextCheck()})},t)}getNextCheckInterval(){const t=Date.now()-this.startTime;return this.isStable=t>this.stabilizationTime,this.isStable?this.stableInterval:this.baseInterval}async checkFlowState(){if(!this.pc)return;if("connected"!==this.pc.connectionState)return;const t=await this.pc.getStats();let e=0;if(t.forEach(t=>{const s=t;"inbound-rtp"===t.type&&"video"===s.kind&&(e=s.bytesReceived||0)}),e===this.lastBytesReceived){if(this.consecutiveNoProgress++,this.consecutiveNoProgress>=this.maxNoProgress)return this.options.emitter.emit("error",new n(s.CONNECT_ERROR,"data stream interruption")),void(this.consecutiveNoProgress=0)}else this.consecutiveNoProgress=0;this.lastBytesReceived=e}}class p extends t{constructor(t){super(),this.retryPause=2e3,this.stateStore=e("getting_codecs"),this.queuedCandidates=[],this.nonAdvertisedCodecs=[],this.conf=t,this.stateStore.subscribe((t,e)=>{this.emit("state:change",{from:e,to:t})}),this.trackManager=new h(this.conf.container,this.conf.lazyLoad),this.flowCheck=new l({interval:5e3,emitter:this}),this.httpClient=new c({conf:this.conf,getState:()=>this.stateStore.get(),emitter:this}),this.connectionManager=new a({getState:()=>this.stateStore.get(),emitter:this,getNonAdvertisedCodecs:()=>this.nonAdvertisedCodecs}),this.codecDetector=new r({getState:()=>this.stateStore.get(),emitter:this}),this.on("codecs:detected",t=>{this.handleCodecsDetected(t)}),this.on("candidate",t=>this.handleCandidate(t)),this.on("track",t=>{this.trackManager.onTrack(t),this.flowCheck.start()}),this.on("error",t=>this.handleError(t)),this.codecDetector.detect()}get state(){return this.stateStore.get()}close(){this.stateStore.set("closed"),this.connectionManager.close(),this.trackManager.stop(),this.flowCheck.close(),this.restartTimeout&&clearTimeout(this.restartTimeout),this.emit("close")}cleanupSession(){this.restartTimeout&&(clearTimeout(this.restartTimeout),this.restartTimeout=void 0),this.connectionManager.close(),this.flowCheck.close(),this.queuedCandidates=[],this.sessionUrl&&(fetch(this.sessionUrl,{method:"DELETE"}).catch(()=>{}),this.sessionUrl=void 0)}handleError(t){this.flowCheck.close(),"getting_codecs"===this.stateStore.get()||t instanceof n&&[s.SIGNAL_ERROR,s.NOT_FOUND_ERROR,s.REQUEST_ERROR].includes(t.type)?this.stateStore.set("failed"):"running"===this.stateStore.get()&&(this.cleanupSession(),this.stateStore.set("restarting"),this.emit("restart"),this.restartTimeout=setTimeout(()=>{this.restartTimeout=void 0,this.stateStore.set("running"),this.start()},this.retryPause))}handleCodecsDetected(t){this.nonAdvertisedCodecs=t,this.stateStore.set("running"),this.start()}start(){this.httpClient.requestICEServers().then(t=>this.connectionManager.setupPeerConnection(t)).then(t=>{const e=this.connectionManager.getPeerConnection();return e&&this.flowCheck.setPeerConnection(e),t}).then(t=>this.httpClient.sendOffer(t)).then(({sessionUrl:t,answer:e})=>this.handleOfferResponse(t,e)).catch(t=>this.handleError(t))}handleOfferResponse(t,e){return t&&(this.sessionUrl=t),this.connectionManager.setAnswer(e).then(()=>{if("running"===this.stateStore.get()&&0!==this.queuedCandidates.length){const t=this.connectionManager.getOfferData();t&&this.sessionUrl&&(this.httpClient.sendLocalCandidates(this.sessionUrl,t,this.queuedCandidates),this.queuedCandidates=[])}})}handleCandidate(t){if(this.sessionUrl){const e=this.connectionManager.getOfferData();e&&this.httpClient.sendLocalCandidates(this.sessionUrl,e,[t])}else this.queuedCandidates.push(t)}get paused(){return this.trackManager.paused}pause(){this.trackManager.pause()}resume(){this.trackManager.resume()}updateUrl(t){"closed"!==this.state?(this.conf.url=t,this.cleanupSession(),this.stateStore.set("running"),this.start()):this.emit("error",new n(s.OTHER_ERROR,"Cannot update URL: instance is closed"))}}export{s as ErrorTypes,n as WebRTCError,p as default};
|
package/dist/whep.d.ts
CHANGED