UNPKG

ovenlivekit

Version:

OvenLiveKit for Web is an open source JavaScript SDK suite for live streaming from web browsers to OvenMediaEngine.

2 lines 16.1 kB
!function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.OvenLiveKit=n():e.OvenLiveKit=n()}(self,(()=>(()=>{"use strict";var e={d:(n,o)=>{for(var t in o)e.o(o,t)&&!e.o(n,t)&&Object.defineProperty(n,t,{enumerable:!0,get:o[t]})},o:(e,n)=>Object.prototype.hasOwnProperty.call(e,n)},n={};e.d(n,{default:()=>u});const o={},t="1.5.8",i="OvenLiveKit.js :",c="OvenLiveKit.js ====";function r(e){try{const n=new URL(e).searchParams.get("transport");if(null===n)return;return n.toLowerCase()}catch(e){return}}function a(e,n){e&&e.send(JSON.stringify(n))}function s(e){let n,o="";return(n=e.match(/^(?:wss?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n\?\=]+)/im))&&(o=n[1]),o}function l(e){let n,o="";return(n=e.match(new RegExp("\\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b","gi")))&&(o=n[0]),o}function d(e,n){const o=function(e,n){const o=e.split("\r\n");let t=-1;for(let e=0;e<o.length-1;e++)if(o[e]=o[e].toLowerCase(),0===o[e].indexOf("a=rtpmap")&&o[e].indexOf(n.toLowerCase())>-1){t=o[e].split(" ")[0].split(":")[1];break}return t}(e,n);if(-1===o)return e;let t=[];const i=e.split("\r\n");for(let e=0;e<i.length-1;e++){const n=i[e];if(0===n.indexOf("m=video")){const e=n.split(" ").slice(0,3),i=n.split(" ").slice(3);i.sort((function(e,n){return e==o?-1:n==o?1:0})),t.push(e.concat(i).join(" "))}else t.push(n)}return t.join("\r\n")+"\r\n"}async function f(e){const n={};return"both"===e?(n.audio=!0,n.video=!0):"audio"===e?n.audio=!0:"video"===e&&(n.video=!0),(await navigator.mediaDevices.getUserMedia(n)).getTracks().forEach((e=>e.stop())),await new Promise((e=>setTimeout(e,500))),!0}function p(e){function n(n){e.callbacks.error&&e.callbacks.error(n)}async function o(e,n){let o=await fetch(e,n);for(;o.redirected;)e=o.url,o=await fetch(e,n);return o}function t(e){return Number.isInteger(e)?e:parseFloat(e).toFixed(2)}async function f(){if(!e.peerConnection)return console.error("No connection to close"),void n("No connection to close");if(u(),e.resourceUrl){const n={};e.connectionConfig.httpHeaders&&Object.assign(n,e.connectionConfig.httpHeaders),await o(e.resourceUrl,{method:"DELETE",headers:n})}}function p(n){const o=e.connectionConfig.sdp.appendFmtp,t=n.split("\r\n"),i=[];for(let e=0;e<t.length;e++)if(0===t[e].indexOf("m=video")){let n=t[e].split(" ");for(let e=3;e<n.length;e++)i.push(n[e]);break}for(let e=0;e<i.length;e++){let n=!1;for(let c=0;c<t.length;c++)0===t[c].indexOf("a=fmtp:"+i[e])&&(n=!0,t[c]+=";"+o);if(!n)for(let n=0;n<t.length;n++)0===t[n].indexOf("a=rtpmap:"+i[e])&&(t[n]+="\r\na=fmtp:"+i[e]+" "+o)}return t.join("\r\n")}function u(){e.peerConnection&&(e.peerConnection.getSenders().forEach((function(n){e.peerConnection.removeTrack(n)})),e.peerConnection.close(),e.peerConnection=null,delete e.peerConnection)}function g(){e.webSocket&&(e.webSocket.close(),e.webSocket=null,delete e.webSocket)}e.attachMedia=function(n){e.videoElement=n},e.getUserMedia=function(o){return function(o){return o||(o={video:{deviceId:void 0},audio:{deviceId:void 0}}),console.info(i,"Request Stream To Input Devices With Constraints",o),navigator.mediaDevices.getUserMedia(o).then((function(n){console.info(i,"Received Media Stream From Input Device",n),n.getVideoTracks().forEach((function(e){console.info(i,"Video Track Settings From Input Stream",e.getSettings())})),e.inputStream=n;let o=e.videoElement;return o&&(o.srcObject=n,o.onloadedmetadata=function(e){o.play()}),new Promise((function(e){e(n)}))})).catch((function(e){return console.error(i,"Can't Get Media Stream From Input Device",e),n(e),new Promise((function(n,o){o(e)}))}))}(o)},e.getDisplayMedia=function(o){return function(o){return o||(o={}),console.info(i,"Request Stream To Display With Constraints",o),navigator.mediaDevices.getDisplayMedia(o).then((function(n){console.info(i,"Received Media Stream From Display",n),e.inputStream=n;let o=e.videoElement;return o&&(o.srcObject=n,o.onloadedmetadata=function(e){o.play()}),new Promise((function(e){e(n)}))})).catch((function(e){return console.error(i,"Can't Get Media Stream From Display",e),n(e),new Promise((function(n,o){o(e)}))}))}(o)},e.setMediaStream=function(o){return function(o){if(!(o&&o instanceof MediaStream)){const e=new Error("Invalid MediaStream provided");return console.error(i,"Invalid MediaStream",e),n(e),new Promise((function(n,o){o(e)}))}console.info(i,"Received Media Stream",o),e.inputStream=o;let t=e.videoElement;return t&&(t.srcObject=o,t.onloadedmetadata=function(e){t.play()}),new Promise((function(e){e(o)}))}(o)},e.startStreaming=function(f,g){if(console.info(c,`Start Streaming to ${f} with connectionConfig`,g),!f)return console.error("endpointUrl is required"),void n("endpointUrl is required");e.endpointUrl=f,g&&(e.connectionConfig=g);try{const c=new URL(f).protocol;"wss:"===c||"ws:"===c?(e.streamingMode="webrtc",function(o){if(!o)return void n("endpointUrl is required");let t=null;try{t=new WebSocket(o)}catch(e){n(e)}e.webSocket=t,t.onopen=function(){a(t,{command:"request_offer"})},t.onmessage=function(o){let t=JSON.parse(o.data);t.error&&(console.error("webSocket.onmessage",t.error),n(t.error)),"offer"===t.command&&function(o,t,c,f,u){let g={};if(e.connectionConfig.iceServers)g.iceServers=e.connectionConfig.iceServers,e.connectionConfig.iceTransportPolicy&&(g.iceTransportPolicy=e.connectionConfig.iceTransportPolicy);else if(u){g.iceServers=[];for(let n=0;n<u.length;n++){let o=u[n],t={};t.urls=o.urls;let i=!1,c=s(e.endpointUrl);for(let e=0;e<t.urls.length;e++)if(t.urls[e].indexOf(c)>-1){i=!0;break}if(!i&&t.urls.length>0){let e=t.urls[0],n=l(e);c&&n&&t.urls.push(e.replace(n,c))}t.username=o.user_name,t.credential=o.credential,g.iceServers.push(t)}e.connectionConfig.iceTransportPolicy?g.iceTransportPolicy=e.connectionConfig.iceTransportPolicy:"all"==r(e.endpointUrl)?(g.iceTransportPolicy="all",console.log(i,"Set Peer Connection iceTransportPolicy to all")):(g.iceTransportPolicy="relay",console.log(i,"Set Peer Connection iceTransportPolicy to relay"))}console.info(i,"Create Peer Connection With Config",g);let m=new RTCPeerConnection(g);e.peerConnection=m,e.inputStream.getTracks().forEach((function(n){console.info(i,"Add Track To Peer Connection",n),m.addTrack(n,e.inputStream)})),e.connectionConfig.maxVideoBitrate&&(c.sdp=function(e,n,o){let t=e.split("\r\n"),i=-1;for(let e=0;e<t.length;e++)if(0===t[e].indexOf("m=video")){i=e;break}if(-1===i)return e;for(i++;0===t[i].indexOf("i=")||0===t[i].indexOf("c=");)i++;if(0===t[i].indexOf("b"))return t[i]="b=AS:"+o,t.join("\r\n");let c=t.slice(0,i);return c.push("b=AS:"+o),c=c.concat(t.slice(i,t.length)),c.join("\r\n")}(c.sdp,0,e.connectionConfig.maxVideoBitrate)),e.connectionConfig.sdp&&e.connectionConfig.sdp.appendFmtp&&(c.sdp=p(c.sdp)),e.connectionConfig.preferredVideoFormat&&(c.sdp=d(c.sdp,e.connectionConfig.preferredVideoFormat)),console.info(i,"Modified offer sdp\n\n"+c.sdp),m.setRemoteDescription(new RTCSessionDescription(c)).then((function(){m.createAnswer().then((function(c){e.connectionConfig.sdp&&e.connectionConfig.sdp.appendFmtp&&(c.sdp=p(c.sdp)),e.connectionConfig.preferredVideoFormat&&(c.sdp=d(c.sdp,e.connectionConfig.preferredVideoFormat)),console.info(i,"Modified answer sdp\n\n"+c.sdp),m.setLocalDescription(c).then((function(){a(e.webSocket,{id:o,peer_id:t,command:"answer",sdp:c})})).catch((function(e){console.error("peerConnection.setLocalDescription",e),n(e)}))})).catch((function(e){console.error("peerConnection.createAnswer",e),n(e)}))})).catch((function(e){console.error("peerConnection.setRemoteDescription",e),n(e)})),f&&function(e,o){for(let t=0;t<o.length;t++)if(o[t]&&o[t].candidate){let i=o[t];e.addIceCandidate(new RTCIceCandidate(i)).then((function(){})).catch((function(e){console.error("peerConnection.addIceCandidate",e),n(e)}))}}(m,f),m.onicecandidate=function(n){n.candidate&&n.candidate.candidate&&a(e.webSocket,{id:o,peer_id:t,command:"candidate",candidates:[n.candidate]})},m.oniceconnectionstatechange=function(n){let o=m.iceConnectionState;e.callbacks.iceStateChange&&(console.info(i,"ICE State","["+o+"]"),e.callbacks.iceStateChange(o)),"connected"===o&&e.callbacks.connected&&e.callbacks.connected(n),"failed"!==o&&"disconnected"!==o&&"closed"!==o||e.callbacks.connectionClosed&&(console.error(i,"Ice connection closed",n),e.callbacks.connectionClosed("ice",n))}}(t.id,t.peer_id,t.sdp,t.candidates,t.ice_servers)},t.onerror=function(e){console.error("webSocket.onerror",e),n(e)},t.onclose=function(n){e.webSocketClosedByUser||e.callbacks.connectionClosed&&e.callbacks.connectionClosed("websocket",n)}}(f)):"https:"===c||"http:"===c?(e.streamingMode="whip",async function(c){if(e.peerConnection)return console.error("Connection already established"),void n("Connection already established");const a={bundlePolicy:"max-bundle"};e.connectionConfig.iceServers?(a.iceServers=e.connectionConfig.iceServers,e.connectionConfig.iceTransportPolicy&&(a.iceTransportPolicy=e.connectionConfig.iceTransportPolicy)):e.connectionConfig.iceTransportPolicy&&(a.iceTransportPolicy=e.connectionConfig.iceTransportPolicy),console.info(i,"Create Peer Connection With Config",a);const s=new RTCPeerConnection(a);if(e.peerConnection=s,!e.inputStream)return console.error("No input stream in OvenLiveKit"),void n("No input stream in OvenLiveKit");for(const n of e.inputStream.getTracks()){console.log(i,"Adding track: ",n);const o={direction:"sendonly"},t=e.connectionConfig.simulcast;if("video"===n.kind&&t&&t.length>0){o.sendEncodings||(o.sendEncodings=[]);for(let e=0;e<t.length;e++){const c={rid:e,active:!0,scalabilityMode:"L1T1",...t[e]};console.log(i,`Adding simulcast layer to: ${n.kind}`,c),o.sendEncodings.push(c)}}"video"===n.kind&&e.connectionConfig.maxVideoBitrate&&(o.sendEncodings||(o.sendEncodings=[]),o.sendEncodings.push({maxBitrate:1e3*e.connectionConfig.maxVideoBitrate}),console.log(i,`Setting max bitrate to: ${e.connectionConfig.maxVideoBitrate} kbps`)),s.addTransceiver(n,o)}s.oniceconnectionstatechange=function(n){let o=s.iceConnectionState;e.callbacks.iceStateChange&&(console.info(i,"ICE State","["+o+"]"),e.callbacks.iceStateChange(o)),"connected"===o&&e.callbacks.connected&&e.callbacks.connected(n),"failed"===o&&e.callbacks.connectionClosed&&(console.error(i,"Ice connection failed",n),e.callbacks.errorHandler(n)),"disconnected"!==o&&"closed"!==o||e.callbacks.connectionClosed&&(console.error(i,"Ice connection disconnected or closed",n),e.callbacks.connectionClosed("ice",n))};const l=await s.createOffer();console.log(i,"Offer SDP from browser: ",l.sdp),e.connectionConfig.sdp&&e.connectionConfig.sdp.appendFmtp&&(l.sdp=p(l.sdp)),e.connectionConfig.preferredVideoFormat?l.sdp=d(l.sdp,e.connectionConfig.preferredVideoFormat):l.sdp=d(l.sdp,"H264");const f={"Content-Type":"application/sdp"},g=e.inputStream.getVideoTracks();if(g&&1===g.length){const n=g[0].getSettings();console.log(i,"Video track settings for Oven-Capabilities:",n);const o=n.width,c=n.height,r=n.frameRate,a=e.connectionConfig.simulcast,s=a?a.length:0;if(s>=2){const e=[];for(let n=0;n<s;n++){const i=a[n]||{},s=void 0!==i.rid?i.rid:n,l=parseFloat(i.scaleResolutionDownBy);if(!isNaN(l)&&l>0&&"number"==typeof o&&"number"==typeof c){const n=2*Math.floor(Math.floor(o/l)/2),t=2*Math.floor(Math.floor(c/l)/2);e.push(`rid:${s}:max_width=${n}`),e.push(`rid:${s}:max_height=${t}`)}r&&e.push(`rid:${s}:max_fps=${t(r)}`)}e.length>0&&(console.log(i,`Setting Oven-Capabilities header: ${e.join(", ")}`),f["Oven-Capabilities"]=e.join(", "))}else if("number"==typeof o&&"number"==typeof c){const e=[`max_width=${o}`,`max_height=${c}`];r&&e.push(`max_fps=${t(r)}`),console.log(i,`Setting Oven-Capabilities header: ${e.join(", ")}`),f["Oven-Capabilities"]=e.join(", ")}}else g&&0!==g.length||console.log(i,"No video tracks found, skipping Oven-Capabilities header."),g&&g.length>1&&console.log(i,`Multiple (${g.length}) video tracks found, skipping Oven-Capabilities header.`);e.connectionConfig.httpHeaders&&Object.assign(f,e.connectionConfig.httpHeaders),console.log(i,"Offer SDP modified by OvenLiveKit: ",l.sdp);const m=await o(c,{method:"POST",body:l.sdp,headers:f});if(!m.ok)return console.error("Failed to fetch",m.status),n(`Failed to fetch ${m.status}`),void u();if(!m.headers.get("location"))return console.error("No location header on answer response"),void n("No location header on answer response");e.endpointUrl=m.url,console.log(i,"Endpoint Url: ",e.endpointUrl);const h=m.headers.get("location");if(console.log(i,"Location header ",h),e.resourceUrl=new URL(h,m.url).toString(),console.log(i,"Resource URL: ",e.resourceUrl),m.headers.get("Link")){console.log(i,"Link Header Found. Update ");const n=m.headers.get("Link").split(",").map((e=>e.trim())),o=[];if(n.forEach((e=>{console.log(i,"Link: ",e);const n=e.match(/<([^>]+)>/),t=e.match(/rel="([^"]+)"/),c=e.match(/username="([^"]+)"/),r=e.match(/credential="([^"]+)"/),a=e.match(/credential-type="([^"]+)"/);if(n&&t&&"ice-server"===t[1]){const e={urls:n[1]};c&&(e.username=c[1]),r&&(e.credential=r[1]),a&&(e.credentialType=a[1]),o.push(e)}})),console.log(i,"Parsed ICE Servers from Link header: ",o),o.length>0){const n=s.getConfiguration();e.connectionConfig.iceServers||(n.iceServers=o,console.log(i,"Updated Peer Connection ICE Servers from Link header")),e.connectionConfig.iceTransportPolicy||("all"==r(e.endpointUrl)?(n.iceTransportPolicy="all",console.log(i,"Set Peer Connection iceTransportPolicy to all")):(n.iceTransportPolicy="relay",console.log(i,"Set Peer Connection iceTransportPolicy to relay"))),s.setConfiguration(n)}}const C=await m.text();console.log(i,"Response Answer SDP: ",C);try{await s.setLocalDescription(l)}catch(e){console.error("peerConnection.setLocalDescription",e),n(e)}try{await s.setRemoteDescription({type:"answer",sdp:C})}catch(e){console.error("peerConnection.setRemoteDescription",e),n(e)}}(f)):(console.error("Invalid protocol",error),n(error))}catch(e){console.error("Cannot parse connection URL",e),n(e)}},e.stopStreaming=async function(){"webrtc"===e.streamingMode?(e.webSocketClosedByUser=!0,g(),u()):"whip"===e.streamingMode&&await f(),e.callbacks.connectionClosed&&(console.log(i,"Connection closed by user"),e.callbacks.connectionClosed("user","Connection closed by user"))},e.remove=function(){"webrtc"===e.streamingMode?(e.webSocketClosedByUser=!0,g(),u()):"whip"===e.streamingMode&&f(),e.inputStream&&(e.inputStream.getTracks().forEach((n=>{n.stop(),e.inputStream.removeTrack(n)})),e.videoElement&&(e.videoElement.srcObject=null),e.inputStream=null,delete e.inputStream),console.info(c,"Removed")}}o.getVersion=function(){return t},o.create=function(e){console.info(c,"Create WebRTC Input "+t);let n={webSocketClosedByUser:!1};return function(e,n){e.streamingMode=null,e.inputStream=null,e.webSocket=null,e.peerConnection=null,e.connectionConfig={},e.videoElement=null,e.endpointUrl=null,e.resourceUrl=null,n&&n.callbacks?e.callbacks=n.callbacks:e.callbacks={}}(n,e),p(n),n},o.getDevices=async function(e="both"){try{await f(e)}catch(e){console.warn(i,"Can not find Video and Audio devices.",e);let n=null,o=null;try{n=await f("video")}catch(e){console.warn(i,"Can not find Video devices.",e)}try{o=await f("audio")}catch(e){console.warn(i,"Can not find Audio devices.",e)}if(!n&&!o)throw new Error("No input devices were found.")}return function(e){let n={audioinput:[],audiooutput:[],videoinput:[],other:[]};for(let o=0;o!==e.length;++o){const t=e[o];let i={};i.deviceId=t.deviceId,"audioinput"===t.kind?(i.label=t.label||`microphone ${n.audioinput.length+1}`,n.audioinput.push(i)):"audiooutput"===t.kind?(i.label=t.label||`speaker ${n.audiooutput.length+1}`,n.audiooutput.push(i)):"videoinput"===t.kind?(i.label=t.label||`camera ${n.videoinput.length+1}`,n.videoinput.push(i)):(i.label=t.label||`other ${n.other.length+1}`,n.other.push(i))}return n}(await async function(){return await navigator.mediaDevices.enumerateDevices()}())};const u=o;return n.default})())); //# sourceMappingURL=OvenLiveKit.min.js.map