From e57a6b356b9398817efc29930020f2d6b5ab4429 Mon Sep 17 00:00:00 2001 From: Anthony Minessale Date: Wed, 8 Jun 2016 16:20:18 -0500 Subject: [PATCH] FS-9242 convert to adapter.js --- html5/verto/js/Makefile | 2 +- html5/verto/js/src/jquery.FSRTC.js | 369 +- html5/verto/js/src/vendor/adapter-latest.js | 3842 +++++++++++++++++ html5/verto/verto_communicator/src/index.html | 1 + .../src/partials/preview.html | 2 +- .../storageService/services/splash_screen.js | 6 +- .../src/storageService/services/storage.js | 1 + .../controllers/DialPadController.js | 4 +- .../controllers/PreviewController.js | 31 +- .../controllers/SettingsController.js | 9 + .../controllers/SplashScreenController.js | 2 +- .../vertoDirectives/directives/videoTag.js | 24 +- .../src/vertoService/services/vertoService.js | 45 +- html5/verto/video_demo/js/verto-min.js | 291 +- html5/verto/video_demo/verto.js | 60 +- src/switch_core_media.c | 5 +- src/switch_rtp.c | 23 +- 17 files changed, 4385 insertions(+), 332 deletions(-) create mode 100644 html5/verto/js/src/vendor/adapter-latest.js diff --git a/html5/verto/js/Makefile b/html5/verto/js/Makefile index e2c8dab068..05aa6617b0 100644 --- a/html5/verto/js/Makefile +++ b/html5/verto/js/Makefile @@ -1,4 +1,4 @@ -JSFILES=src/jquery.FSRTC.js src/jquery.jsonrpcclient.js src/jquery.verto.js +JSFILES=src/jquery.FSRTC.js src/jquery.jsonrpcclient.js src/jquery.verto.js src/vendor/adapter-latest.js all: jsmin verto-min.js diff --git a/html5/verto/js/src/jquery.FSRTC.js b/html5/verto/js/src/jquery.FSRTC.js index 225c1b16ec..5cfd11bd05 100644 --- a/html5/verto/js/src/jquery.FSRTC.js +++ b/html5/verto/js/src/jquery.FSRTC.js @@ -100,22 +100,10 @@ candidateList: [] }; - - if (moz) { - this.constraints = { - offerToReceiveAudio: this.options.useSpeak === "none" ? false : true, - offerToReceiveVideo: this.options.useVideo ? true : false, - }; - } else { - this.constraints = { - optional: [{ - 'DtlsSrtpKeyAgreement': 'true' - }],mandatory: { - OfferToReceiveAudio: this.options.useSpeak === "none" ? false : true, - OfferToReceiveVideo: this.options.useVideo ? true : false, - } - }; - } + this.constraints = { + offerToReceiveAudio: this.options.useSpeak === "none" ? false : true, + offerToReceiveVideo: this.options.useVideo ? true : false, + }; if (self.options.useVideo) { self.options.useVideo.style.display = 'none'; @@ -133,19 +121,11 @@ if (obj) { self.options.useVideo = obj; self.options.localVideo = local; - if (moz) { - self.constraints.offerToReceiveVideo = true; - } else { - self.constraints.mandatory.OfferToReceiveVideo = true; - } + self.constraints.offerToReceiveVideo = true; } else { self.options.useVideo = null; self.options.localVideo = null; - if (moz) { - self.constraints.offerToReceiveVideo = false; - } else { - self.constraints.mandatory.OfferToReceiveVideo = false; - } + self.constraints.offerToReceiveVideo = false; } if (self.options.useVideo) { @@ -193,18 +173,9 @@ }; function setCompat() { - $.FSRTC.moz = !!navigator.mozGetUserMedia; - //navigator.getUserMedia || (navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia); - if (!navigator.getUserMedia) { - navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia; - } } function checkCompat() { - if (!navigator.getUserMedia) { - alert('This application cannot function in this browser.'); - return false; - } return true; } @@ -258,6 +229,21 @@ doCallback(self, "onICESDP", msg); } + FSRTCattachMediaStream = function(element, stream) { + if (element && element.id && attachMediaStream) { + attachMediaStream(element, stream); + } else { + if (typeof element.srcObject !== 'undefined') { + element.srcObject = stream; + } else if (typeof element.src !== 'undefined') { + element.src = URL.createObjectURL(stream); + } else { + console.error('Error attaching stream to element.'); + } + } + } + + function onRemoteStream(self, stream) { if (self.options.useVideo) { self.options.useVideo.style.display = 'block'; @@ -266,16 +252,8 @@ var element = self.options.useAudio; console.log("REMOTE STREAM", stream, element); - if (typeof element.srcObject !== 'undefined') { - element.srcObject = stream; - } else if (typeof element.mozSrcObject !== 'undefined') { - element.mozSrcObject = stream; - } else if (typeof element.src !== 'undefined') { - element.src = URL.createObjectURL(stream); - } else { - console.error('Error attaching stream to element.'); - } - + FSRTCattachMediaStream(element, stream); + self.options.useAudio.play(); self.remoteStream = stream; } @@ -306,11 +284,7 @@ if (self.options.useVideo) { self.options.useVideo.style.display = 'none'; - if (moz) { - self.options.useVideo['mozSrcObject'] = null; - } else { - self.options.useVideo['src'] = ''; - } + self.options.useVideo['src'] = ''; } if (self.localStream) { @@ -331,11 +305,7 @@ if (self.options.localVideo) { self.options.localVideo.style.display = 'none'; - if (moz) { - self.options.localVideo['mozSrcObject'] = null; - } else { - self.options.localVideo['src'] = ''; - } + self.options.localVideo['src'] = ''; } if (self.options.localVideoStream) { @@ -426,7 +396,7 @@ function onSuccess(stream) { self.localStream = stream; - self.peer = RTCPeerConnection({ + self.peer = FSRTCPeerConnection({ type: self.type, attachStream: self.localStream, onICE: function(candidate) { @@ -469,8 +439,8 @@ constraints: { audio: false, video: { - mandatory: self.options.videoParams, - optional: [] + //mandatory: self.options.videoParams, + //optional: [] }, }, localVideo: self.options.localVideo, @@ -501,31 +471,29 @@ console.log("Microphone Disabled"); audio = false; } else if (obj.options.videoParams && obj.options.screenShare) {//obj.options.videoParams.chromeMediaSource == 'desktop') { - - //obj.options.videoParams = { - // chromeMediaSource: 'screen', - // maxWidth:screen.width, - // maxHeight:screen.height - // chromeMediaSourceId = sourceId; - // }; - - console.error("SCREEN SHARE"); + console.error("SCREEN SHARE", obj.options.videoParams); audio = false; } else { audio = { - mandatory: {}, - optional: [] + //mandatory: {}, + //optional: [] + advanced: [] }; if (obj.options.useMic !== "any") { - audio.optional = [{sourceId: obj.options.useMic}] + //audio.optional = [{sourceId: obj.options.useMic}] + audio.deviceId = {exact: obj.options.useMic}; } + //FIXME if (obj.options.audioParams) { for (var key in obj.options.audioParams) { var con = {}; - con[key] = obj.options.audioParams[key]; - audio.optional.push(con); + //con[key] = obj.options.audioParams[key]; + if (obj.options.audioParams[key]) { + con.exact = key; + audio.advanced.push(con); + } } } @@ -536,10 +504,8 @@ getUserMedia({ constraints: { audio: false, - video: { - mandatory: obj.options.videoParams, - optional: [] - }, + video: obj.options.videoParams + }, localVideo: obj.options.localVideo, onsuccess: function(e) {self.options.localVideoStream = e; console.log("local video ready");}, @@ -549,33 +515,48 @@ var video = {}; var bestFrameRate = obj.options.videoParams.vertoBestFrameRate; + var minFrameRate = obj.options.videoParams.minFrameRate || 15; delete obj.options.videoParams.vertoBestFrameRate; - video = { - mandatory: obj.options.videoParams, - optional: [] - } - - var useVideo = obj.options.useVideo; - - if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") { - if (!video.optional) { - video.optional = []; - } - - if (obj.options.useCamera !== "any") { - video.optional.push({sourceId: obj.options.useCamera}); - } - - if (bestFrameRate) { - video.optional.push({minFrameRate: bestFrameRate}); - video.optional.push({maxFrameRate: bestFrameRate}); - } - + if (obj.options.screenShare) { + // fix for chrome to work for now, will need to change once we figure out how to do this in a non-mandatory style constraint. + video = { + mandatory: obj.options.videoParams + }; } else { - console.log("Camera Disabled"); - video = false; - useVideo = false; + + video = { + //mandatory: obj.options.videoParams, + width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth}, + height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight} + }; + + + + var useVideo = obj.options.useVideo; + + if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") { + //if (!video.optional) { + //video.optional = []; + //} + + + if (obj.options.useCamera !== "any") { + //video.optional.push({sourceId: obj.options.useCamera}); + video.deviceId = obj.options.useCamera; + } + + if (bestFrameRate) { + //video.optional.push({minFrameRate: bestFrameRate}); + //video.optional.push({maxFrameRate: bestFrameRate}); + video.frameRate = {ideal: bestFrameRate, min: minFrameRate, max: 30}; + } + + } else { + console.log("Camera Disabled"); + video = false; + useVideo = false; + } } return {audio: audio, video: video, useVideo: useVideo}; @@ -597,14 +578,10 @@ self.localStream = stream; if (screen) { - if (moz) { - self.constraints.OfferToReceiveVideo = false; - } else { - self.constraints.mandatory.OfferToReceiveVideo = false; - } + self.constraints.offerToReceiveVideo = false; } - self.peer = RTCPeerConnection({ + self.peer = FSRTCPeerConnection({ type: self.type, attachStream: self.localStream, onICE: function(candidate) { @@ -671,57 +648,23 @@ // 2013, @muazkh - github.com/muaz-khan // MIT License - https://www.webrtc-experiment.com/licence/ // Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RTCPeerConnection - window.moz = !!navigator.mozGetUserMedia; - - function RTCPeerConnection(options) { + + function FSRTCPeerConnection(options) { var gathering = false, done = false; - - var w = window, - PeerConnection = w.mozRTCPeerConnection || w.webkitRTCPeerConnection, - SessionDescription = w.mozRTCSessionDescription || w.RTCSessionDescription, - IceCandidate = w.mozRTCIceCandidate || w.RTCIceCandidate; - - var STUN = { - url: !moz ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121' - }; - - var iceServers = null; + var config = {}; + var default_ice = { + urls: ['stun:stun.l.google.com:19302'] + }; if (options.iceServers) { - var tmp = options.iceServers; - - if (typeof(tmp) === "boolean") { - tmp = null; - } - - if (tmp && !(typeof(tmp) == "object" && tmp.constructor === Array)) { - console.warn("iceServers must be an array, reverting to default ice servers"); - tmp = null; - } - - iceServers = { - iceServers: tmp || [STUN] - }; - - if (!moz && !tmp) { - iceServers.iceServers = [STUN]; - } + if (typeof(options.iceServers) === "boolean") { + config.iceServers = [default_ice]; + } else { + config.iceServers = options.iceServers; + } } - var optional = { - optional: [] - }; - - if (!moz) { - optional.optional = [{ - DtlsSrtpKeyAgreement: true - }, - { - RtpDataChannels: options.onChannelMessage ? true : false - }]; - } - - var peer = new PeerConnection(iceServers, optional); + var peer = new window.RTCPeerConnection(config); openOffererChannel(); var x = 0; @@ -736,34 +679,10 @@ } if (options.type == "offer") { - if ((!moz || (!options.sentICESDP && peer.localDescription.sdp.match(/a=candidate/)) && !x && options.onICESDP)) { - options.onICESDP(peer.localDescription); - //x = 1; - /* - x = 1; - peer.createOffer(function(sessionDescription) { - sessionDescription.sdp = serializeSdp(sessionDescription.sdp); - peer.setLocalDescription(sessionDescription); - if (options.onICESDP) { - options.onICESDP(sessionDescription); - } - }, onSdpError, constraints); - */ - } + options.onICESDP(peer.localDescription); } else { if (!x && options.onICESDP) { options.onICESDP(peer.localDescription); - //x = 1; - /* - x = 1; - peer.createAnswer(function(sessionDescription) { - sessionDescription.sdp = serializeSdp(sessionDescription.sdp); - peer.setLocalDescription(sessionDescription); - if (options.onICESDP) { - options.onICESDP(sessionDescription); - } - }, onSdpError, constraints); - */ } } } @@ -821,10 +740,10 @@ //console.debug('on:add:stream', remoteMediaStream); }; - var constraints = options.constraints || { - offerToReceiveAudio: true, - offerToReceiveVideo: true - }; + //var constraints = options.constraints || { + // offerToReceiveAudio: true, + //offerToReceiveVideo: true + //}; // onOfferSDP(RTCSessionDescription) function createOffer() { @@ -834,13 +753,8 @@ sessionDescription.sdp = serializeSdp(sessionDescription.sdp); peer.setLocalDescription(sessionDescription); options.onOfferSDP(sessionDescription); - /* old mozilla behaviour the SDP was already great right away */ - if (moz && options.onICESDP && sessionDescription.sdp.match(/a=candidate/)) { - options.onICESDP(sessionDescription); - options.sentICESDP = 1; - } }, - onSdpError, constraints); + onSdpError, options.constraints); } // onAnswerSDP(RTCSessionDescription) @@ -848,7 +762,7 @@ if (options.type != "answer") return; //options.offerSDP.sdp = addStereo(options.offerSDP.sdp); - peer.setRemoteDescription(new SessionDescription(options.offerSDP), onSdpSuccess, onSdpError); + peer.setRemoteDescription(new window.RTCSessionDescription(options.offerSDP), onSdpSuccess, onSdpError); peer.createAnswer(function(sessionDescription) { sessionDescription.sdp = serializeSdp(sessionDescription.sdp); peer.setLocalDescription(sessionDescription); @@ -856,11 +770,11 @@ options.onAnswerSDP(sessionDescription); } }, - onSdpError, constraints); + onSdpError); } - // if Mozilla Firefox & DataChannel; offer/answer will be created later - if ((options.onChannelMessage && !moz) || !options.onChannelMessage) { + + if ((options.onChannelMessage) || !options.onChannelMessage) { createOffer(); createAnswer(); } @@ -899,9 +813,6 @@ } function serializeSdp(sdp) { - //if (!moz) sdp = setBandwidth(sdp); - //sdp = getInteropSDP(sdp); - //console.debug(sdp); return sdp; } @@ -909,29 +820,18 @@ var channel; function openOffererChannel() { - if (!options.onChannelMessage || (moz && !options.onOfferSDP)) return; + if (!options.onChannelMessage) return; _openOffererChannel(); - if (!moz) return; - navigator.mozGetUserMedia({ - audio: true, - fake: true - }, - function(stream) { - peer.addStream(stream); - createOffer(); - }, - useless); + return; } function _openOffererChannel() { - channel = peer.createDataChannel(options.channel || 'RTCDataChannel', moz ? {} : { + channel = peer.createDataChannel(options.channel || 'RTCDataChannel', { reliable: false }); - if (moz) channel.binaryType = 'blob'; - setChannelEvents(); } @@ -955,8 +855,6 @@ }; } - if (options.onAnswerSDP && moz && options.onChannelMessage) openAnswererChannel(); - function openAnswererChannel() { peer.ondatachannel = function(event) { channel = event.channel; @@ -964,16 +862,7 @@ setChannelEvents(); }; - if (!moz) return; - navigator.mozGetUserMedia({ - audio: true, - fake: true - }, - function(stream) { - peer.addStream(stream); - createAnswer(); - }, - useless); + return; } // fake:true is also available on chrome under a flag! @@ -993,10 +882,10 @@ return { addAnswerSDP: function(sdp, cbSuccess, cbError) { - peer.setRemoteDescription(new SessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError); + peer.setRemoteDescription(new window.RTCSessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError); }, addICE: function(candidate) { - peer.addIceCandidate(new IceCandidate({ + peer.addIceCandidate(new window.RTCIceCandidate({ sdpMLineIndex: candidate.sdpMLineIndex, candidate: candidate.candidate })); @@ -1026,14 +915,14 @@ // getUserMedia var video_constraints = { - mandatory: {}, - optional: [] + //mandatory: {}, + //optional: [] }; function getUserMedia(options) { var n = navigator, media; - n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia; + n.getMedia = n.getUserMedia; n.getMedia(options.constraints || { audio: true, video: video_constraints @@ -1044,15 +933,8 @@ }); function streaming(stream) { - //var video = options.video; - //var localVideo = options.localVideo; - //if (video) { - // video[moz ? 'mozSrcObject' : 'src'] = moz ? stream : window.webkitURL.createObjectURL(stream); - //video.play(); - //} - if (options.localVideo) { - options.localVideo[moz ? 'mozSrcObject' : 'src'] = moz ? stream : window.webkitURL.createObjectURL(stream); + options.localVideo['src'] = window.URL.createObjectURL(stream); options.localVideo.style.display = 'block'; } @@ -1108,23 +990,26 @@ } var video = { - mandatory: {}, - optional: [] + //mandatory: {}, + //optional: [] } - + //FIXME if (cam) { - video.optional = [{sourceId: cam}]; + //video.optional = [{sourceId: cam}]; + video.deviceId = {exact: cam}; } w = resList[resI][0]; h = resList[resI][1]; resI++; - video.mandatory = { - "minWidth": w, - "minHeight": h, - "maxWidth": w, - "maxHeight": h + video = { + width: w, + height: h + //"minWidth": w, + //"minHeight": h, + //"maxWidth": w, + //"maxHeight": h }; getUserMedia({ diff --git a/html5/verto/js/src/vendor/adapter-latest.js b/html5/verto/js/src/vendor/adapter-latest.js new file mode 100644 index 0000000000..e390add1da --- /dev/null +++ b/html5/verto/js/src/vendor/adapter-latest.js @@ -0,0 +1,3842 @@ +/*! adapterjs - v0.13.3 - 2016-08-03 */ + +// Adapter's interface. +var AdapterJS = AdapterJS || {}; + +// Browserify compatibility +if(typeof exports !== 'undefined') { + module.exports = AdapterJS; +} + +AdapterJS.options = AdapterJS.options || {}; + +// uncomment to get virtual webcams +// AdapterJS.options.getAllCams = true; + +// uncomment to prevent the install prompt when the plugin in not yet installed +// AdapterJS.options.hidePluginInstallPrompt = true; + +// AdapterJS version +AdapterJS.VERSION = '0.13.3'; + +// This function will be called when the WebRTC API is ready to be used +// Whether it is the native implementation (Chrome, Firefox, Opera) or +// the plugin +// You may Override this function to synchronise the start of your application +// with the WebRTC API being ready. +// If you decide not to override use this synchronisation, it may result in +// an extensive CPU usage on the plugin start (once per tab loaded) +// Params: +// - isUsingPlugin: true is the WebRTC plugin is being used, false otherwise +// +AdapterJS.onwebrtcready = AdapterJS.onwebrtcready || function(isUsingPlugin) { + // The WebRTC API is ready. + // Override me and do whatever you want here +}; + +// New interface to store multiple callbacks, private +AdapterJS._onwebrtcreadies = []; + +// Sets a callback function to be called when the WebRTC interface is ready. +// The first argument is the function to callback.\ +// Throws an error if the first argument is not a function +AdapterJS.webRTCReady = function (callback) { + if (typeof callback !== 'function') { + throw new Error('Callback provided is not a function'); + } + + if (true === AdapterJS.onwebrtcreadyDone) { + // All WebRTC interfaces are ready, just call the callback + callback(null !== AdapterJS.WebRTCPlugin.plugin); + } else { + // will be triggered automatically when your browser/plugin is ready. + AdapterJS._onwebrtcreadies.push(callback); + } +}; + +// Plugin namespace +AdapterJS.WebRTCPlugin = AdapterJS.WebRTCPlugin || {}; + +// The object to store plugin information +/* jshint ignore:start */ +AdapterJS.WebRTCPlugin.pluginInfo = { + prefix : 'Tem', + plugName : 'TemWebRTCPlugin', + pluginId : 'plugin0', + type : 'application/x-temwebrtcplugin', + onload : '__TemWebRTCReady0', + portalLink : 'http://skylink.io/plugin/', + downloadLink : null, //set below + companyName: 'Temasys' +}; +if(!!navigator.platform.match(/^Mac/i)) { + AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1n77hco'; +} +else if(!!navigator.platform.match(/^Win/i)) { + AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN'; +} +/* jshint ignore:end */ + +AdapterJS.WebRTCPlugin.TAGS = { + NONE : 'none', + AUDIO : 'audio', + VIDEO : 'video' +}; + +// Unique identifier of each opened page +AdapterJS.WebRTCPlugin.pageId = Math.random().toString(36).slice(2); + +// Use this whenever you want to call the plugin. +AdapterJS.WebRTCPlugin.plugin = null; + +// Set log level for the plugin once it is ready. +// The different values are +// This is an asynchronous function that will run when the plugin is ready +AdapterJS.WebRTCPlugin.setLogLevel = null; + +// Defines webrtc's JS interface according to the plugin's implementation. +// Define plugin Browsers as WebRTC Interface. +AdapterJS.WebRTCPlugin.defineWebRTCInterface = null; + +// This function detects whether or not a plugin is installed. +// Checks if Not IE (firefox, for example), else if it's IE, +// we're running IE and do something. If not it is not supported. +AdapterJS.WebRTCPlugin.isPluginInstalled = null; + + // Lets adapter.js wait until the the document is ready before injecting the plugin +AdapterJS.WebRTCPlugin.pluginInjectionInterval = null; + +// Inject the HTML DOM object element into the page. +AdapterJS.WebRTCPlugin.injectPlugin = null; + +// States of readiness that the plugin goes through when +// being injected and stated +AdapterJS.WebRTCPlugin.PLUGIN_STATES = { + NONE : 0, // no plugin use + INITIALIZING : 1, // Detected need for plugin + INJECTING : 2, // Injecting plugin + INJECTED: 3, // Plugin element injected but not usable yet + READY: 4 // Plugin ready to be used +}; + +// Current state of the plugin. You cannot use the plugin before this is +// equal to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY +AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE; + +// True is AdapterJS.onwebrtcready was already called, false otherwise +// Used to make sure AdapterJS.onwebrtcready is only called once +AdapterJS.onwebrtcreadyDone = false; + +// Log levels for the plugin. +// To be set by calling AdapterJS.WebRTCPlugin.setLogLevel +/* +Log outputs are prefixed in some cases. + INFO: Information reported by the plugin. + ERROR: Errors originating from within the plugin. + WEBRTC: Error originating from within the libWebRTC library +*/ +// From the least verbose to the most verbose +AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS = { + NONE : 'NONE', + ERROR : 'ERROR', + WARNING : 'WARNING', + INFO: 'INFO', + VERBOSE: 'VERBOSE', + SENSITIVE: 'SENSITIVE' +}; + +// Does a waiting check before proceeding to load the plugin. +AdapterJS.WebRTCPlugin.WaitForPluginReady = null; + +// This methid will use an interval to wait for the plugin to be ready. +AdapterJS.WebRTCPlugin.callWhenPluginReady = null; + +// !!!! WARNING: DO NOT OVERRIDE THIS FUNCTION. !!! +// This function will be called when plugin is ready. It sends necessary +// details to the plugin. +// The function will wait for the document to be ready and the set the +// plugin state to AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY, +// indicating that it can start being requested. +// This function is not in the IE/Safari condition brackets so that +// TemPluginLoaded function might be called on Chrome/Firefox. +// This function is the only private function that is not encapsulated to +// allow the plugin method to be called. +__TemWebRTCReady0 = function () { + if (document.readyState === 'complete') { + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; + AdapterJS.maybeThroughWebRTCReady(); + } else { + var timer = setInterval(function () { + if (document.readyState === 'complete') { + // TODO: update comments, we wait for the document to be ready + clearInterval(timer); + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY; + AdapterJS.maybeThroughWebRTCReady(); + } + }, 100); + } +}; + +AdapterJS.maybeThroughWebRTCReady = function() { + if (!AdapterJS.onwebrtcreadyDone) { + AdapterJS.onwebrtcreadyDone = true; + + // If new interface for multiple callbacks used + if (AdapterJS._onwebrtcreadies.length) { + AdapterJS._onwebrtcreadies.forEach(function (callback) { + if (typeof(callback) === 'function') { + callback(AdapterJS.WebRTCPlugin.plugin !== null); + } + }); + // Else if no callbacks on new interface assuming user used old(deprecated) way to set callback through AdapterJS.onwebrtcready = ... + } else if (typeof(AdapterJS.onwebrtcready) === 'function') { + AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin !== null); + } + } +}; + +// Text namespace +AdapterJS.TEXT = { + PLUGIN: { + REQUIRE_INSTALLATION: 'This website requires you to install a WebRTC-enabling plugin ' + + 'to work on this browser.', + NOT_SUPPORTED: 'Your browser does not support WebRTC.', + BUTTON: 'Install Now' + }, + REFRESH: { + REQUIRE_REFRESH: 'Please refresh page', + BUTTON: 'Refresh Page' + } +}; + +// The result of ice connection states. +// - starting: Ice connection is starting. +// - checking: Ice connection is checking. +// - connected Ice connection is connected. +// - completed Ice connection is connected. +// - done Ice connection has been completed. +// - disconnected Ice connection has been disconnected. +// - failed Ice connection has failed. +// - closed Ice connection is closed. +AdapterJS._iceConnectionStates = { + starting : 'starting', + checking : 'checking', + connected : 'connected', + completed : 'connected', + done : 'completed', + disconnected : 'disconnected', + failed : 'failed', + closed : 'closed' +}; + +//The IceConnection states that has been fired for each peer. +AdapterJS._iceConnectionFiredStates = []; + + +// Check if WebRTC Interface is defined. +AdapterJS.isDefined = null; + +// This function helps to retrieve the webrtc detected browser information. +// This sets: +// - webrtcDetectedBrowser: The browser agent name. +// - webrtcDetectedVersion: The browser version. +// - webrtcMinimumVersion: The minimum browser version still supported by AJS. +// - webrtcDetectedType: The types of webRTC support. +// - 'moz': Mozilla implementation of webRTC. +// - 'webkit': WebKit implementation of webRTC. +// - 'plugin': Using the plugin implementation. +AdapterJS.parseWebrtcDetectedBrowser = function () { + var hasMatch = null; + if ((!!window.opr && !!opr.addons) || + !!window.opera || + navigator.userAgent.indexOf(' OPR/') >= 0) { + // Opera 8.0+ + webrtcDetectedBrowser = 'opera'; + webrtcDetectedType = 'webkit'; + webrtcMinimumVersion = 26; + hasMatch = /OPR\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (typeof InstallTrigger !== 'undefined') { + // Firefox 1.0+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'moz'; + } else if (Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0) { + // Safari + webrtcDetectedBrowser = 'safari'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 7; + hasMatch = /version\/(\d+)/i.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1], 10); + } else if (/*@cc_on!@*/false || !!document.documentMode) { + // Internet Explorer 6-11 + webrtcDetectedBrowser = 'IE'; + webrtcDetectedType = 'plugin'; + webrtcMinimumVersion = 9; + hasMatch = /\brv[ :]+(\d+)/g.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); + if (!webrtcDetectedVersion) { + hasMatch = /\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent) || []; + webrtcDetectedVersion = parseInt(hasMatch[1] || '0', 10); + } + } else if (!!window.StyleMedia) { + // Edge 20+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = ''; + } else if (!!window.chrome && !!window.chrome.webstore) { + // Chrome 1+ + // Bowser and Version set in Google's adapter + webrtcDetectedType = 'webkit'; + } else if ((webrtcDetectedBrowser === 'chrome'|| webrtcDetectedBrowser === 'opera') && + !!window.CSS) { + // Blink engine detection + webrtcDetectedBrowser = 'blink'; + // TODO: detected WebRTC version + } + + window.webrtcDetectedBrowser = webrtcDetectedBrowser; + window.webrtcDetectedVersion = webrtcDetectedVersion; + window.webrtcMinimumVersion = webrtcMinimumVersion; +}; + +AdapterJS.addEvent = function(elem, evnt, func) { + if (elem.addEventListener) { // W3C DOM + elem.addEventListener(evnt, func, false); + } else if (elem.attachEvent) {// OLD IE DOM + elem.attachEvent('on'+evnt, func); + } else { // No much to do + elem[evnt] = func; + } +}; + +AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNewTab, displayRefreshBar) { + // only inject once the page is ready + if (document.readyState !== 'complete') { + return; + } + + var w = window; + var i = document.createElement('iframe'); + i.name = 'adapterjs-alert'; + i.style.position = 'fixed'; + i.style.top = '-41px'; + i.style.left = 0; + i.style.right = 0; + i.style.width = '100%'; + i.style.height = '40px'; + i.style.backgroundColor = '#ffffe1'; + i.style.border = 'none'; + i.style.borderBottom = '1px solid #888888'; + i.style.zIndex = '9999999'; + if(typeof i.style.webkitTransition === 'string') { + i.style.webkitTransition = 'all .5s ease-out'; + } else if(typeof i.style.transition === 'string') { + i.style.transition = 'all .5s ease-out'; + } + document.body.appendChild(i); + var c = (i.contentWindow) ? i.contentWindow : + (i.contentDocument.document) ? i.contentDocument.document : i.contentDocument; + c.document.open(); + c.document.write('' + text + ''); + if(buttonText && buttonLink) { + c.document.write(''); + c.document.close(); + + // On click on okay + AdapterJS.addEvent(c.document.getElementById('okay'), 'click', function(e) { + if (!!displayRefreshBar) { + AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION ? + AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH : AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH, + AdapterJS.TEXT.REFRESH.BUTTON, 'javascript:location.reload()'); // jshint ignore:line + } + window.open(buttonLink, !!openNewTab ? '_blank' : '_top'); + + e.preventDefault(); + try { + e.cancelBubble = true; + } catch(error) { } + + var pluginInstallInterval = setInterval(function(){ + if(! isIE) { + navigator.plugins.refresh(false); + } + AdapterJS.WebRTCPlugin.isPluginInstalled( + AdapterJS.WebRTCPlugin.pluginInfo.prefix, + AdapterJS.WebRTCPlugin.pluginInfo.plugName, + function() { // plugin now installed + clearInterval(pluginInstallInterval); + AdapterJS.WebRTCPlugin.defineWebRTCInterface(); + }, + function() { + // still no plugin detected, nothing to do + }); + } , 500); + }); + + // On click on Cancel + AdapterJS.addEvent(c.document.getElementById('cancel'), 'click', function(e) { + w.document.body.removeChild(i); + }); + } else { + c.document.close(); + } + setTimeout(function() { + if(typeof i.style.webkitTransform === 'string') { + i.style.webkitTransform = 'translateY(40px)'; + } else if(typeof i.style.transform === 'string') { + i.style.transform = 'translateY(40px)'; + } else { + i.style.top = '0px'; + } + }, 300); +}; + +// ----------------------------------------------------------- +// Detected webrtc implementation. Types are: +// - 'moz': Mozilla implementation of webRTC. +// - 'webkit': WebKit implementation of webRTC. +// - 'plugin': Using the plugin implementation. +webrtcDetectedType = null; + +// Set the settings for creating DataChannels, MediaStream for +// Cross-browser compability. +// - This is only for SCTP based support browsers. +// the 'urls' attribute. +checkMediaDataChannelSettings = + function (peerBrowserAgent, peerBrowserVersion, callback, constraints) { + if (typeof callback !== 'function') { + return; + } + var beOfferer = true; + var isLocalFirefox = webrtcDetectedBrowser === 'firefox'; + // Nightly version does not require MozDontOfferDataChannel for interop + var isLocalFirefoxInterop = webrtcDetectedType === 'moz' && webrtcDetectedVersion > 30; + var isPeerFirefox = peerBrowserAgent === 'firefox'; + var isPeerFirefoxInterop = peerBrowserAgent === 'firefox' && + ((peerBrowserVersion) ? (peerBrowserVersion > 30) : false); + + // Resends an updated version of constraints for MozDataChannel to work + // If other userAgent is firefox and user is firefox, remove MozDataChannel + if ((isLocalFirefox && isPeerFirefox) || (isLocalFirefoxInterop)) { + try { + delete constraints.mandatory.MozDontOfferDataChannel; + } catch (error) { + console.error('Failed deleting MozDontOfferDataChannel'); + console.error(error); + } + } else if ((isLocalFirefox && !isPeerFirefox)) { + constraints.mandatory.MozDontOfferDataChannel = true; + } + if (!isLocalFirefox) { + // temporary measure to remove Moz* constraints in non Firefox browsers + for (var prop in constraints.mandatory) { + if (constraints.mandatory.hasOwnProperty(prop)) { + if (prop.indexOf('Moz') !== -1) { + delete constraints.mandatory[prop]; + } + } + } + } + // Firefox (not interopable) cannot offer DataChannel as it will cause problems to the + // interopability of the media stream + if (isLocalFirefox && !isPeerFirefox && !isLocalFirefoxInterop) { + beOfferer = false; + } + callback(beOfferer, constraints); +}; + +// Handles the differences for all browsers ice connection state output. +// - Tested outcomes are: +// - Chrome (offerer) : 'checking' > 'completed' > 'completed' +// - Chrome (answerer) : 'checking' > 'connected' +// - Firefox (offerer) : 'checking' > 'connected' +// - Firefox (answerer): 'checking' > 'connected' +checkIceConnectionState = function (peerId, iceConnectionState, callback) { + if (typeof callback !== 'function') { + console.warn('No callback specified in checkIceConnectionState. Aborted.'); + return; + } + peerId = (peerId) ? peerId : 'peer'; + + if (!AdapterJS._iceConnectionFiredStates[peerId] || + iceConnectionState === AdapterJS._iceConnectionStates.disconnected || + iceConnectionState === AdapterJS._iceConnectionStates.failed || + iceConnectionState === AdapterJS._iceConnectionStates.closed) { + AdapterJS._iceConnectionFiredStates[peerId] = []; + } + iceConnectionState = AdapterJS._iceConnectionStates[iceConnectionState]; + if (AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState) < 0) { + AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState); + if (iceConnectionState === AdapterJS._iceConnectionStates.connected) { + setTimeout(function () { + AdapterJS._iceConnectionFiredStates[peerId] + .push(AdapterJS._iceConnectionStates.done); + callback(AdapterJS._iceConnectionStates.done); + }, 1000); + } + callback(iceConnectionState); + } + return; +}; + +// Firefox: +// - Creates iceServer from the url for Firefox. +// - Create iceServer with stun url. +// - Create iceServer with turn url. +// - Ignore the transport parameter from TURN url for FF version <=27. +// - Return null for createIceServer if transport=tcp. +// - FF 27 and above supports transport parameters in TURN url, +// - So passing in the full url to create iceServer. +// Chrome: +// - Creates iceServer from the url for Chrome M33 and earlier. +// - Create iceServer with stun url. +// - Chrome M28 & above uses below TURN format. +// Plugin: +// - Creates Ice Server for Plugin Browsers +// - If Stun - Create iceServer with stun url. +// - Else - Create iceServer with turn url +// - This is a WebRTC Function +createIceServer = null; + +// Firefox: +// - Creates IceServers for Firefox +// - Use .url for FireFox. +// - Multiple Urls support +// Chrome: +// - Creates iceServers from the urls for Chrome M34 and above. +// - .urls is supported since Chrome M34. +// - Multiple Urls support +// Plugin: +// - Creates Ice Servers for Plugin Browsers +// - Multiple Urls support +// - This is a WebRTC Function +createIceServers = null; +//------------------------------------------------------------ + +//The RTCPeerConnection object. +RTCPeerConnection = null; + +// Creates RTCSessionDescription object for Plugin Browsers +RTCSessionDescription = (typeof RTCSessionDescription === 'function') ? + RTCSessionDescription : null; + +// Creates RTCIceCandidate object for Plugin Browsers +RTCIceCandidate = (typeof RTCIceCandidate === 'function') ? + RTCIceCandidate : null; + +// Get UserMedia (only difference is the prefix). +// Code from Adam Barth. +getUserMedia = null; + +// Attach a media stream to an element. +attachMediaStream = null; + +// Re-attach a media stream to an element. +reattachMediaStream = null; + + +// Detected browser agent name. Types are: +// - 'firefox': Firefox browser. +// - 'chrome': Chrome browser. +// - 'opera': Opera browser. +// - 'safari': Safari browser. +// - 'IE' - Internet Explorer browser. +webrtcDetectedBrowser = null; + +// Detected browser version. +webrtcDetectedVersion = null; + +// The minimum browser version still supported by AJS. +webrtcMinimumVersion = null; + +// Check for browser types and react accordingly +if ( navigator.mozGetUserMedia || + navigator.webkitGetUserMedia || + (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) ) { + + /////////////////////////////////////////////////////////////////// + // INJECTION OF GOOGLE'S ADAPTER.JS CONTENT + +/* jshint ignore:start */ + (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 ? 'm=' + part : part).trim() + '\r\n'; + }); + }; + + // Returns lines that start with a certain prefix. + SDPUtils.matchPrefix = function(blob, prefix) { + return SDPUtils.splitLines(blob).filter(function(line) { + return line.indexOf(prefix) === 0; + }); + }; + + // Parses an ICE candidate line. Sample input: + // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 + // rport 55996" + SDPUtils.parseCandidate = function(line) { + var parts; + // Parse both variants. + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { + parts = line.substring(10).split(' '); + } + + var candidate = { + foundation: parts[0], + component: parts[1], + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7] + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case 'raddr': + candidate.relatedAddress = parts[i + 1]; + break; + case 'rport': + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + case 'tcptype': + candidate.tcpType = parts[i + 1]; + break; + default: // Unknown extensions are silently ignored. + break; + } + } + return candidate; + }; + + // Translates a candidate object into SDP candidate attribute. + SDPUtils.writeCandidate = function(candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push('typ'); + sdp.push(type); + if (type !== 'host' && candidate.relatedAddress && + candidate.relatedPort) { + sdp.push('raddr'); + sdp.push(candidate.relatedAddress); // was: relAddr + sdp.push('rport'); + sdp.push(candidate.relatedPort); // was: relPort + } + if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + return 'candidate:' + sdp.join(' '); + }; + + // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: + // a=rtpmap:111 opus/48000/2 + SDPUtils.parseRtpMap = function(line) { + var parts = line.substr(9).split(' '); + var parsed = { + payloadType: parseInt(parts.shift(), 10) // was: id + }; + + parts = parts[0].split('/'); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + // was: channels + parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; + return parsed; + }; + + // Generate an a=rtpmap line from RTCRtpCodecCapability or + // RTCRtpCodecParameters. + SDPUtils.writeRtpMap = function(codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + + (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n'; + }; + + // Parses an a=extmap line (headerextension from RFC 5285). Sample input: + // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset + SDPUtils.parseExtmap = function(line) { + var parts = line.substr(9).split(' '); + return { + id: parseInt(parts[0], 10), + uri: parts[1] + }; + }; + + // Generates a=extmap line from RTCRtpHeaderExtensionParameters or + // RTCRtpHeaderExtension. + SDPUtils.writeExtmap = function(headerExtension) { + return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + + ' ' + headerExtension.uri + '\r\n'; + }; + + // Parses an ftmp line, returns dictionary. Sample input: + // a=fmtp:96 vbr=on;cng=on + // Also deals with vbr=on; cng=on + SDPUtils.parseFmtp = function(line) { + var parsed = {}; + var kv; + var parts = line.substr(line.indexOf(' ') + 1).split(';'); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split('='); + parsed[kv[0].trim()] = kv[1]; + } + return parsed; + }; + + // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. + SDPUtils.writeFmtp = function(codec) { + var line = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function(param) { + params.push(param + '=' + codec.parameters[param]); + }); + line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; + } + return line; + }; + + // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: + // a=rtcp-fb:98 nack rpsi + SDPUtils.parseRtcpFb = function(line) { + var parts = line.substr(line.indexOf(' ') + 1).split(' '); + return { + type: parts.shift(), + parameter: parts.join(' ') + }; + }; + // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. + SDPUtils.writeRtcpFb = function(codec) { + var lines = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function(fb) { + lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + + '\r\n'; + }); + } + return lines; + }; + + // Parses an RFC 5576 ssrc media attribute. Sample input: + // a=ssrc:3735928559 cname:something + SDPUtils.parseSsrcMedia = function(line) { + var sp = line.indexOf(' '); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10) + }; + var colon = line.indexOf(':', sp); + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; + }; + + // Extracts DTLS parameters from SDP media section or sessionpart. + // FIXME: for consistency with other functions this should only + // get the fingerprint line as input. See also getIceParameters. + SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + // Search in session part, too. + lines = lines.concat(SDPUtils.splitLines(sessionpart)); + var fpLine = lines.filter(function(line) { + return line.indexOf('a=fingerprint:') === 0; + })[0].substr(14); + // Note: a=setup line is ignored since we use the 'auto' role. + var dtlsParameters = { + role: 'auto', + fingerprints: [{ + algorithm: fpLine.split(' ')[0], + value: fpLine.split(' ')[1] + }] + }; + return dtlsParameters; + }; + + // Serializes DTLS parameters to SDP. + SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function(fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; + }; + // Parses ICE information from SDP media section or sessionpart. + // FIXME: for consistency with other functions this should only + // get the ice-ufrag and ice-pwd lines as input. + SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + // Search in session part, too. + lines = lines.concat(SDPUtils.splitLines(sessionpart)); + var iceParameters = { + usernameFragment: lines.filter(function(line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function(line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10) + }; + return iceParameters; + }; + + // Serializes ICE parameters to SDP. + SDPUtils.writeIceParameters = function(params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + + 'a=ice-pwd:' + params.password + '\r\n'; + }; + + // Parses the SDP media section and returns RTCRtpParameters. + SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix( + mediaSection, 'a=fmtp:' + pt + ' '); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, 'a=rtcp-fb:' + pt + ' ') + .map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + // parse FEC mechanisms from rtpmap lines. + switch (codec.name.toUpperCase()) { + case 'RED': + case 'ULPFEC': + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + default: // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); + // FIXME: parse rtcp. + return description; + }; + + // Generates parts of the SDP media section describing the capabilities / + // parameters. + SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ''; + + // Build the mline. + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + // FIXME: add headerExtensions, fecMechanismş and rtcp. + sdp += 'a=rtcp-mux\r\n'; + return sdp; + }; + + // Parses the SDP media section and returns an array of + // RTCRtpEncodingParameters. + SDPUtils.parseRtpEncodingParameters = function(mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf('RED') !== -1; + var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; + + // filter a=ssrc:... cname:, ignore PlanB-msid + var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === 'cname'; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc; + + var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') + .map(function(line) { + var parts = line.split(' '); + parts.shift(); + return parts.map(function(part) { + return parseInt(part, 10); + }); + }); + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function(codec) { + if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10), + rtx: { + payloadType: codec.payloadType, + ssrc: secondarySsrc + } + }; + encodingParameters.push(encParam); + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: secondarySsrc, + mechanism: hasUlpfec ? 'red+ulpfec' : 'red' + }; + encodingParameters.push(encParam); + } + } + }); + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc + }); + } + + // we support both b=AS and b=TIAS but interpret AS as TIAS. + var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); + if (bandwidth.length) { + if (bandwidth[0].indexOf('b=TIAS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf('b=AS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(5), 10); + } + encodingParameters.forEach(function(params) { + params.maxBitrate = bandwidth; + }); + } + return encodingParameters; + }; + + SDPUtils.writeSessionBoilerplate = function() { + // FIXME: sess-id should be an NTP timestamp. + return 'v=0\r\n' + + 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; + }; + + SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === 'offer' ? 'actpass' : 'active'); + + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet. + if (transceiver.rtpSender) { + var msid = 'msid:' + stream.id + ' ' + + transceiver.rtpSender.track.id + '\r\n'; + sdp += 'a=' + msid; + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' ' + msid; + } + // FIXME: this should be written by writeRtpDescription. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + return sdp; + }; + + // Gets the direction from the mediaSection or the sessionpart. + SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + default: + // FIXME: What should happen here? + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return 'sendrecv'; + }; + + // Expose public methods. + module.exports = SDPUtils; + + },{}],2:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + + 'use strict'; + + // Shimming starts here. + (function() { + // Utils. + var logging = require('./utils').log; + var browserDetails = require('./utils').browserDetails; + // Export to the adapter global object visible in the browser. + module.exports.browserDetails = browserDetails; + module.exports.extractVersion = require('./utils').extractVersion; + module.exports.disableLog = require('./utils').disableLog; + + // Uncomment the line below if you want logging to occur, including logging + // for the switch statement below. Can also be turned on in the browser via + // adapter.disableLog(false), but then logging from the switch statement below + // will not appear. + // require('./utils').disableLog(false); + + // Browser shims. + var chromeShim = require('./chrome/chrome_shim') || null; + var edgeShim = require('./edge/edge_shim') || null; + var firefoxShim = require('./firefox/firefox_shim') || null; + var safariShim = require('./safari/safari_shim') || null; + + // Shim browser if found. + switch (browserDetails.browser) { + case 'opera': // fallthrough as it uses chrome shims + case 'chrome': + if (!chromeShim || !chromeShim.shimPeerConnection) { + logging('Chrome shim is not included in this adapter release.'); + return; + } + logging('adapter.js shimming chrome.'); + // Export to the adapter global object visible in the browser. + module.exports.browserShim = chromeShim; + + chromeShim.shimGetUserMedia(); + chromeShim.shimMediaStream(); + chromeShim.shimSourceObject(); + chromeShim.shimPeerConnection(); + chromeShim.shimOnTrack(); + break; + case 'firefox': + if (!firefoxShim || !firefoxShim.shimPeerConnection) { + logging('Firefox shim is not included in this adapter release.'); + return; + } + logging('adapter.js shimming firefox.'); + // Export to the adapter global object visible in the browser. + module.exports.browserShim = firefoxShim; + + firefoxShim.shimGetUserMedia(); + firefoxShim.shimSourceObject(); + firefoxShim.shimPeerConnection(); + firefoxShim.shimOnTrack(); + break; + case 'edge': + if (!edgeShim || !edgeShim.shimPeerConnection) { + logging('MS edge shim is not included in this adapter release.'); + return; + } + logging('adapter.js shimming edge.'); + // Export to the adapter global object visible in the browser. + module.exports.browserShim = edgeShim; + + edgeShim.shimGetUserMedia(); + edgeShim.shimPeerConnection(); + break; + case 'safari': + if (!safariShim) { + logging('Safari shim is not included in this adapter release.'); + return; + } + logging('adapter.js shimming safari.'); + // Export to the adapter global object visible in the browser. + module.exports.browserShim = safariShim; + + safariShim.shimGetUserMedia(); + break; + default: + logging('Unsupported browser!'); + } + })(); + + },{"./chrome/chrome_shim":3,"./edge/edge_shim":5,"./firefox/firefox_shim":7,"./safari/safari_shim":9,"./utils":10}],3:[function(require,module,exports){ + + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + var logging = require('../utils.js').log; + var browserDetails = require('../utils.js').browserDetails; + + var chromeShim = { + shimMediaStream: function() { + window.MediaStream = window.MediaStream || window.webkitMediaStream; + }, + + shimOnTrack: function() { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + var self = this; + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + this.removeEventListener('addstream', this._ontrackpoly); + } + this.addEventListener('track', this._ontrack = f); + this.addEventListener('addstream', this._ontrackpoly = function(e) { + // onaddstream does not fire when a track is added to an existing + // stream. But stream.onaddtrack is implemented so we use that. + e.stream.addEventListener('addtrack', function(te) { + var event = new Event('track'); + event.track = te.track; + event.receiver = {track: te.track}; + event.streams = [e.stream]; + self.dispatchEvent(event); + }); + e.stream.getTracks().forEach(function(track) { + var event = new Event('track'); + event.track = track; + event.receiver = {track: track}; + event.streams = [e.stream]; + this.dispatchEvent(event); + }.bind(this)); + }.bind(this)); + } + }); + } + }, + + shimSourceObject: function() { + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this._srcObject; + }, + set: function(stream) { + var self = this; + // Use _srcObject as a private property for this shim + this._srcObject = stream; + if (this.src) { + URL.revokeObjectURL(this.src); + } + + if (!stream) { + this.src = ''; + return; + } + this.src = URL.createObjectURL(stream); + // We need to recreate the blob url when a track is added or + // removed. Doing it manually since we want to avoid a recursion. + stream.addEventListener('addtrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + stream.addEventListener('removetrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + } + }); + } + } + }, + + shimPeerConnection: function() { + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + logging('PeerConnection'); + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; + } + + var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); + var origGetStats = pc.getStats.bind(pc); + pc.getStats = function(selector, successCallback, errorCallback) { + var self = this; + var args = arguments; + + // If selector is a function then we are in the old style stats so just + // pass back the original getStats format to avoid breaking old users. + if (arguments.length > 0 && typeof selector === 'function') { + return origGetStats(selector, successCallback); + } + + var fixChromeStats_ = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + // shim getStats with maplike support + var makeMapStats = function(stats, legacyStats) { + var map = new Map(Object.keys(stats).map(function(key) { + return[key, stats[key]]; + })); + legacyStats = legacyStats || stats; + Object.keys(legacyStats).forEach(function(key) { + map[key] = legacyStats[key]; + }); + return map; + }; + + if (arguments.length >= 2) { + var successCallbackWrapper_ = function(response) { + args[1](makeMapStats(fixChromeStats_(response))); + }; + + return origGetStats.apply(this, [successCallbackWrapper_, + arguments[0]]); + } + + // promise-support + return new Promise(function(resolve, reject) { + if (args.length === 1 && typeof selector === 'object') { + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); + } else { + // Preserve legacy chrome stats only on legacy access of stats obj + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response), + response.result())); + }, reject]); + } + }).then(successCallback, errorCallback); + }; + + return pc; + }; + window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (webkitRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return webkitRTCPeerConnection.generateCertificate; + } + }); + } + + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var self = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof arguments[0] === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [resolve, reject, opts]); + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + // add promise support -- natively available in Chrome 51 + if (browserDetails.version < 51) { + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var args = arguments; + var self = this; + var promise = new Promise(function(resolve, reject) { + nativeMethod.apply(self, [args[0], resolve, reject]); + }); + if (args.length < 2) { + return promise; + } + return promise.then(function() { + args[1].apply(null, []); + }, + function(err) { + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }); + }; + }); + } + + // shim implicit creation of RTCSessionDescription/RTCIceCandidate + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + RTCIceCandidate : RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null) + var nativeAddIceCandidate = + RTCPeerConnection.prototype.addIceCandidate; + RTCPeerConnection.prototype.addIceCandidate = function() { + return arguments[0] === null ? Promise.resolve() + : nativeAddIceCandidate.apply(this, arguments); + }; + } + }; + + + // Expose public methods. + module.exports = { + shimMediaStream: chromeShim.shimMediaStream, + shimOnTrack: chromeShim.shimOnTrack, + shimSourceObject: chromeShim.shimSourceObject, + shimPeerConnection: chromeShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia') + }; + + },{"../utils.js":10,"./getusermedia":4}],4:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + var logging = require('../utils.js').log; + + // Expose public methods. + module.exports = function() { + var constraintsToChrome_ = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname_ = function(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return (name === 'deviceId') ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname_('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname_('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname_('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function(mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + + var shimConstraints_ = function(constraints, func) { + constraints = JSON.parse(JSON.stringify(constraints)); + if (constraints && constraints.audio) { + constraints.audio = constraintsToChrome_(constraints.audio); + } + if (constraints && typeof constraints.video === 'object') { + // Shim facingMode for mobile, where it defaults to "user". + var face = constraints.video.facingMode; + face = face && ((typeof face === 'object') ? face : {ideal: face}); + + if ((face && (face.exact === 'user' || face.exact === 'environment' || + face.ideal === 'user' || face.ideal === 'environment')) && + !(navigator.mediaDevices.getSupportedConstraints && + navigator.mediaDevices.getSupportedConstraints().facingMode)) { + delete constraints.video.facingMode; + if (face.exact === 'environment' || face.ideal === 'environment') { + // Look for "back" in label, or use last cam (typically back cam). + return navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + devices = devices.filter(function(d) { + return d.kind === 'videoinput'; + }); + var back = devices.find(function(d) { + return d.label.toLowerCase().indexOf('back') !== -1; + }) || (devices.length && devices[devices.length - 1]); + if (back) { + constraints.video.deviceId = face.exact ? {exact: back.deviceId} : + {ideal: back.deviceId}; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } + constraints.video = constraintsToChrome_(constraints.video); + } + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }; + + var shimError_ = function(e) { + return { + name: { + PermissionDeniedError: 'NotAllowedError', + ConstraintNotSatisfiedError: 'OverconstrainedError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraintName, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function(constraints, onSuccess, onError) { + shimConstraints_(constraints, function(c) { + navigator.webkitGetUserMedia(c, onSuccess, function(e) { + onError(shimError_(e)); + }); + }); + }; + + navigator.getUserMedia = getUserMedia_; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + navigator.getUserMedia(constraints, resolve, reject); + }); + }; + + if (!navigator.mediaDevices) { + navigator.mediaDevices = { + getUserMedia: getUserMediaPromise_, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + deviceId: device.id, + groupId: ''}; + })); + }); + }); + } + }; + } + + // A shim for getUserMedia method on the mediaDevices object. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (!navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia = function(constraints) { + return getUserMediaPromise_(constraints); + }; + } else { + // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(cs) { + return shimConstraints_(cs, function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }); + }; + } + + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + logging('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + logging('Dummy mediaDevices.removeEventListener called.'); + }; + } + }; + + },{"../utils.js":10}],5:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var SDPUtils = require('sdp'); + + var edgeShim = { + shimPeerConnection: function() { + if (window.RTCIceGatherer) { + // ORTC defines an RTCIceCandidate object but no constructor. + // Not implemented in Edge. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = function(args) { + return args; + }; + } + // ORTC does not have a session description object but + // other browsers (i.e. Chrome) that will support both PC and ORTC + // in the future might have this defined already. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = function(args) { + return args; + }; + } + } + + window.RTCPeerConnection = function(config) { + var self = this; + + var _eventTarget = document.createDocumentFragment(); + ['addEventListener', 'removeEventListener', 'dispatchEvent'] + .forEach(function(method) { + self[method] = _eventTarget[method].bind(_eventTarget); + }); + + this.onicecandidate = null; + this.onaddstream = null; + this.ontrack = null; + this.onremovestream = null; + this.onsignalingstatechange = null; + this.oniceconnectionstatechange = null; + this.onnegotiationneeded = null; + this.ondatachannel = null; + + this.localStreams = []; + this.remoteStreams = []; + this.getLocalStreams = function() { + return self.localStreams; + }; + this.getRemoteStreams = function() { + return self.remoteStreams; + }; + + this.localDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.remoteDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + this.iceGatheringState = 'new'; + + this.iceOptions = { + gatherPolicy: 'all', + iceServers: [] + }; + if (config && config.iceTransportPolicy) { + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + this.iceOptions.gatherPolicy = config.iceTransportPolicy; + break; + case 'none': + // FIXME: remove once implementation and spec have added this. + throw new TypeError('iceTransportPolicy "none" not supported'); + default: + // don't set iceTransportPolicy. + break; + } + } + this.usingBundle = config && config.bundlePolicy === 'max-bundle'; + + if (config && config.iceServers) { + // Edge does not like + // 1) stun: + // 2) turn: that does not have all of turn:host:port?transport=udp + // 3) turn: with ipv6 addresses + var iceServers = JSON.parse(JSON.stringify(config.iceServers)); + this.iceOptions.iceServers = iceServers.filter(function(server) { + if (server && server.urls) { + var urls = server.urls; + if (typeof urls === 'string') { + urls = [urls]; + } + urls = urls.filter(function(url) { + return url.indexOf('turn:') === 0 && + url.indexOf('transport=udp') !== -1 && + url.indexOf('turn:[') === -1; + })[0]; + return !!urls; + } + return false; + }); + } + + // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... + // everything that is needed to describe a SDP m-line. + this.transceivers = []; + + // since the iceGatherer is currently created in createOffer but we + // must not emit candidates until after setLocalDescription we buffer + // them in this array. + this._localIceCandidatesBuffer = []; + }; + + window.RTCPeerConnection.prototype._emitBufferedCandidates = function() { + var self = this; + var sections = SDPUtils.splitSections(self.localDescription.sdp); + // FIXME: need to apply ice candidates in a way which is async but + // in-order + this._localIceCandidatesBuffer.forEach(function(event) { + var end = !event.candidate || Object.keys(event.candidate).length === 0; + if (end) { + for (var j = 1; j < sections.length; j++) { + if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) { + sections[j] += 'a=end-of-candidates\r\n'; + } + } + } else if (event.candidate.candidate.indexOf('typ endOfCandidates') + === -1) { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=' + event.candidate.candidate + '\r\n'; + } + self.localDescription.sdp = sections.join(''); + self.dispatchEvent(event); + if (self.onicecandidate !== null) { + self.onicecandidate(event); + } + if (!event.candidate && self.iceGatheringState !== 'complete') { + var complete = self.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + if (complete) { + self.iceGatheringState = 'complete'; + } + } + }); + this._localIceCandidatesBuffer = []; + }; + + window.RTCPeerConnection.prototype.addStream = function(stream) { + // Clone is necessary for local demos mostly, attaching directly + // to two different senders does not work (build 10547). + this.localStreams.push(stream.clone()); + this._maybeFireNegotiationNeeded(); + }; + + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var idx = this.localStreams.indexOf(stream); + if (idx > -1) { + this.localStreams.splice(idx, 1); + this._maybeFireNegotiationNeeded(); + } + }; + + window.RTCPeerConnection.prototype.getSenders = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpSender; + }) + .map(function(transceiver) { + return transceiver.rtpSender; + }); + }; + + window.RTCPeerConnection.prototype.getReceivers = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpReceiver; + }) + .map(function(transceiver) { + return transceiver.rtpReceiver; + }); + }; + + // Determines the intersection of local and remote capabilities. + window.RTCPeerConnection.prototype._getCommonCapabilities = + function(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + localCapabilities.codecs.forEach(function(lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && + lCodec.clockRate === rCodec.clockRate && + lCodec.numChannels === rCodec.numChannels) { + // push rCodec so we reply with offerer payload type + commonCapabilities.codecs.push(rCodec); + + // determine common feedback mechanisms + rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { + for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { + if (lCodec.rtcpFeedback[j].type === fb.type && + lCodec.rtcpFeedback[j].parameter === fb.parameter) { + return true; + } + } + return false; + }); + // FIXME: also need to determine .parameters + // see https://github.com/openpeer/ortc/issues/569 + break; + } + } + }); + + localCapabilities.headerExtensions + .forEach(function(lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; + i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); + + // FIXME: fecMechanisms + return commonCapabilities; + }; + + // Create ICE gatherer, ICE transport and DTLS transport. + window.RTCPeerConnection.prototype._createIceAndDtlsTransports = + function(mid, sdpMLineIndex) { + var self = this; + var iceGatherer = new RTCIceGatherer(self.iceOptions); + var iceTransport = new RTCIceTransport(iceGatherer); + iceGatherer.onlocalcandidate = function(evt) { + var event = new Event('icecandidate'); + event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; + + var cand = evt.candidate; + var end = !cand || Object.keys(cand).length === 0; + // Edge emits an empty object for RTCIceCandidateComplete‥ + if (end) { + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + if (iceGatherer.state === undefined) { + iceGatherer.state = 'completed'; + } + + // Emit a candidate with type endOfCandidates to make the samples + // work. Edge requires addIceCandidate with this empty candidate + // to start checking. The real solution is to signal + // end-of-candidates to the other side when getting the null + // candidate but some apps (like the samples) don't do that. + event.candidate.candidate = + 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'; + } else { + // RTCIceCandidate doesn't have a component, needs to be added + cand.component = iceTransport.component === 'RTCP' ? 2 : 1; + event.candidate.candidate = SDPUtils.writeCandidate(cand); + } + + // update local description. + var sections = SDPUtils.splitSections(self.localDescription.sdp); + if (event.candidate.candidate.indexOf('typ endOfCandidates') + === -1) { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=' + event.candidate.candidate + '\r\n'; + } else { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=end-of-candidates\r\n'; + } + self.localDescription.sdp = sections.join(''); + + var complete = self.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + + // Emit candidate if localDescription is set. + // Also emits null candidate when all gatherers are complete. + switch (self.iceGatheringState) { + case 'new': + self._localIceCandidatesBuffer.push(event); + if (end && complete) { + self._localIceCandidatesBuffer.push( + new Event('icecandidate')); + } + break; + case 'gathering': + self._emitBufferedCandidates(); + self.dispatchEvent(event); + if (self.onicecandidate !== null) { + self.onicecandidate(event); + } + if (complete) { + self.dispatchEvent(new Event('icecandidate')); + if (self.onicecandidate !== null) { + self.onicecandidate(new Event('icecandidate')); + } + self.iceGatheringState = 'complete'; + } + break; + case 'complete': + // should not happen... currently! + break; + default: // no-op. + break; + } + }; + iceTransport.onicestatechange = function() { + self._updateConnectionState(); + }; + + var dtlsTransport = new RTCDtlsTransport(iceTransport); + dtlsTransport.ondtlsstatechange = function() { + self._updateConnectionState(); + }; + dtlsTransport.onerror = function() { + // onerror does not set state to failed by itself. + dtlsTransport.state = 'failed'; + self._updateConnectionState(); + }; + + return { + iceGatherer: iceGatherer, + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; + + // Start the RTP Sender and Receiver for a transceiver. + window.RTCPeerConnection.prototype._transceive = function(transceiver, + send, recv) { + var params = this._getCommonCapabilities(transceiver.localCapabilities, + transceiver.remoteCapabilities); + if (send && transceiver.rtpSender) { + params.encodings = transceiver.sendEncodingParameters; + params.rtcp = { + cname: SDPUtils.localCName + }; + if (transceiver.recvEncodingParameters.length) { + params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; + } + transceiver.rtpSender.send(params); + } + if (recv && transceiver.rtpReceiver) { + params.encodings = transceiver.recvEncodingParameters; + params.rtcp = { + cname: transceiver.cname + }; + if (transceiver.sendEncodingParameters.length) { + params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; + } + transceiver.rtpReceiver.receive(params); + } + }; + + window.RTCPeerConnection.prototype.setLocalDescription = + function(description) { + var self = this; + var sections; + var sessionpart; + if (description.type === 'offer') { + // FIXME: What was the purpose of this empty if statement? + // if (!this._pendingOffer) { + // } else { + if (this._pendingOffer) { + // VERY limited support for SDP munging. Limited to: + // * changing the order of codecs + sections = SDPUtils.splitSections(description.sdp); + sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var caps = SDPUtils.parseRtpParameters(mediaSection); + self._pendingOffer[sdpMLineIndex].localCapabilities = caps; + }); + this.transceivers = this._pendingOffer; + delete this._pendingOffer; + } + } else if (description.type === 'answer') { + sections = SDPUtils.splitSections(self.remoteDescription.sdp); + sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + sections.forEach(function(mediaSection, sdpMLineIndex) { + var transceiver = self.transceivers[sdpMLineIndex]; + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; + var rejected = mediaSection.split('\n', 1)[0] + .split(' ', 2)[1] === '0'; + + if (!rejected) { + var remoteIceParameters = SDPUtils.getIceParameters( + mediaSection, sessionpart); + if (isIceLite) { + var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') + .map(function(cand) { + return SDPUtils.parseCandidate(cand); + }) + .filter(function(cand) { + return cand.component === '1'; + }); + // ice-lite only includes host candidates in the SDP so we can + // use setRemoteCandidates (which implies an + // RTCIceCandidateComplete) + if (cands.length) { + iceTransport.setRemoteCandidates(cands); + } + } + var remoteDtlsParameters = SDPUtils.getDtlsParameters( + mediaSection, sessionpart); + if (isIceLite) { + remoteDtlsParameters.role = 'server'; + } + + if (!self.usingBundle || sdpMLineIndex === 0) { + iceTransport.start(iceGatherer, remoteIceParameters, + isIceLite ? 'controlling' : 'controlled'); + dtlsTransport.start(remoteDtlsParameters); + } + + // Calculate intersection of capabilities. + var params = self._getCommonCapabilities(localCapabilities, + remoteCapabilities); + + // Start the RTCRtpSender. The RTCRtpReceiver for this + // transceiver has already been started in setRemoteDescription. + self._transceive(transceiver, + params.codecs.length > 0, + false); + } + }); + } + + this.localDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-local-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + + // If a success callback was provided, emit ICE candidates after it + // has been executed. Otherwise, emit callback after the Promise is + // resolved. + var hasCallback = arguments.length > 1 && + typeof arguments[1] === 'function'; + if (hasCallback) { + var cb = arguments[1]; + window.setTimeout(function() { + cb(); + if (self.iceGatheringState === 'new') { + self.iceGatheringState = 'gathering'; + } + self._emitBufferedCandidates(); + }, 0); + } + var p = Promise.resolve(); + p.then(function() { + if (!hasCallback) { + if (self.iceGatheringState === 'new') { + self.iceGatheringState = 'gathering'; + } + // Usually candidates will be emitted earlier. + window.setTimeout(self._emitBufferedCandidates.bind(self), 500); + } + }); + return p; + }; + + window.RTCPeerConnection.prototype.setRemoteDescription = + function(description) { + var self = this; + var stream = new MediaStream(); + var receiverList = []; + var sections = SDPUtils.splitSections(description.sdp); + var sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + this.usingBundle = SDPUtils.matchPrefix(sessionpart, + 'a=group:BUNDLE ').length > 0; + sections.forEach(function(mediaSection, sdpMLineIndex) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].substr(2).split(' '); + var kind = mline[0]; + var rejected = mline[1] === '0'; + var direction = SDPUtils.getDirection(mediaSection, sessionpart); + + var transceiver; + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpSender; + var rtpReceiver; + var sendEncodingParameters; + var recvEncodingParameters; + var localCapabilities; + + var track; + // FIXME: ensure the mediaSection has rtcp-mux set. + var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); + var remoteIceParameters; + var remoteDtlsParameters; + if (!rejected) { + remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + remoteDtlsParameters.role = 'client'; + } + recvEncodingParameters = + SDPUtils.parseRtpEncodingParameters(mediaSection); + + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:'); + if (mid.length) { + mid = mid[0].substr(6); + } else { + mid = SDPUtils.generateIdentifier(); + } + + var cname; + // Gets the first SSRC. Note that with RTX there might be multiple + // SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === 'cname'; + })[0]; + if (remoteSsrc) { + cname = remoteSsrc.value; + } + + var isComplete = SDPUtils.matchPrefix(mediaSection, + 'a=end-of-candidates', sessionpart).length > 0; + var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') + .map(function(cand) { + return SDPUtils.parseCandidate(cand); + }) + .filter(function(cand) { + return cand.component === '1'; + }); + if (description.type === 'offer' && !rejected) { + var transports = self.usingBundle && sdpMLineIndex > 0 ? { + iceGatherer: self.transceivers[0].iceGatherer, + iceTransport: self.transceivers[0].iceTransport, + dtlsTransport: self.transceivers[0].dtlsTransport + } : self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + if (isComplete) { + transports.iceTransport.setRemoteCandidates(cands); + } + + localCapabilities = RTCRtpReceiver.getCapabilities(kind); + sendEncodingParameters = [{ + ssrc: (2 * sdpMLineIndex + 2) * 1001 + }]; + + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + + track = rtpReceiver.track; + receiverList.push([track, rtpReceiver]); + // FIXME: not correct when there are multiple streams but that is + // not currently supported in this shim. + stream.addTrack(track); + + // FIXME: look at direction. + if (self.localStreams.length > 0 && + self.localStreams[0].getTracks().length >= sdpMLineIndex) { + var localTrack; + if (kind === 'audio') { + localTrack = self.localStreams[0].getAudioTracks()[0]; + } else if (kind === 'video') { + localTrack = self.localStreams[0].getVideoTracks()[0]; + } + if (localTrack) { + rtpSender = new RTCRtpSender(localTrack, + transports.dtlsTransport); + } + } + + self.transceivers[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: remoteCapabilities, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + cname: cname, + sendEncodingParameters: sendEncodingParameters, + recvEncodingParameters: recvEncodingParameters + }; + // Start the RTCRtpReceiver now. The RTPSender is started in + // setLocalDescription. + self._transceive(self.transceivers[sdpMLineIndex], + false, + direction === 'sendrecv' || direction === 'sendonly'); + } else if (description.type === 'answer' && !rejected) { + transceiver = self.transceivers[sdpMLineIndex]; + iceGatherer = transceiver.iceGatherer; + iceTransport = transceiver.iceTransport; + dtlsTransport = transceiver.dtlsTransport; + rtpSender = transceiver.rtpSender; + rtpReceiver = transceiver.rtpReceiver; + sendEncodingParameters = transceiver.sendEncodingParameters; + localCapabilities = transceiver.localCapabilities; + + self.transceivers[sdpMLineIndex].recvEncodingParameters = + recvEncodingParameters; + self.transceivers[sdpMLineIndex].remoteCapabilities = + remoteCapabilities; + self.transceivers[sdpMLineIndex].cname = cname; + + if ((isIceLite || isComplete) && cands.length) { + iceTransport.setRemoteCandidates(cands); + } + if (!self.usingBundle || sdpMLineIndex === 0) { + iceTransport.start(iceGatherer, remoteIceParameters, + 'controlling'); + dtlsTransport.start(remoteDtlsParameters); + } + + self._transceive(transceiver, + direction === 'sendrecv' || direction === 'recvonly', + direction === 'sendrecv' || direction === 'sendonly'); + + if (rtpReceiver && + (direction === 'sendrecv' || direction === 'sendonly')) { + track = rtpReceiver.track; + receiverList.push([track, rtpReceiver]); + stream.addTrack(track); + } else { + // FIXME: actually the receiver should be created later. + delete transceiver.rtpReceiver; + } + } + }); + + this.remoteDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-remote-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + if (stream.getTracks().length) { + self.remoteStreams.push(stream); + window.setTimeout(function() { + var event = new Event('addstream'); + event.stream = stream; + self.dispatchEvent(event); + if (self.onaddstream !== null) { + window.setTimeout(function() { + self.onaddstream(event); + }, 0); + } + + receiverList.forEach(function(item) { + var track = item[0]; + var receiver = item[1]; + var trackEvent = new Event('track'); + trackEvent.track = track; + trackEvent.receiver = receiver; + trackEvent.streams = [stream]; + self.dispatchEvent(event); + if (self.ontrack !== null) { + window.setTimeout(function() { + self.ontrack(trackEvent); + }, 0); + } + }); + }, 0); + } + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return Promise.resolve(); + }; + + window.RTCPeerConnection.prototype.close = function() { + this.transceivers.forEach(function(transceiver) { + /* not yet + if (transceiver.iceGatherer) { + transceiver.iceGatherer.close(); + } + */ + if (transceiver.iceTransport) { + transceiver.iceTransport.stop(); + } + if (transceiver.dtlsTransport) { + transceiver.dtlsTransport.stop(); + } + if (transceiver.rtpSender) { + transceiver.rtpSender.stop(); + } + if (transceiver.rtpReceiver) { + transceiver.rtpReceiver.stop(); + } + }); + // FIXME: clean up tracks, local streams, remote streams, etc + this._updateSignalingState('closed'); + }; + + // Update the signaling state. + window.RTCPeerConnection.prototype._updateSignalingState = + function(newState) { + this.signalingState = newState; + var event = new Event('signalingstatechange'); + this.dispatchEvent(event); + if (this.onsignalingstatechange !== null) { + this.onsignalingstatechange(event); + } + }; + + // Determine whether to fire the negotiationneeded event. + window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = + function() { + // Fire away (for now). + var event = new Event('negotiationneeded'); + this.dispatchEvent(event); + if (this.onnegotiationneeded !== null) { + this.onnegotiationneeded(event); + } + }; + + // Update the connection state. + window.RTCPeerConnection.prototype._updateConnectionState = function() { + var self = this; + var newState; + var states = { + 'new': 0, + closed: 0, + connecting: 0, + checking: 0, + connected: 0, + completed: 0, + failed: 0 + }; + this.transceivers.forEach(function(transceiver) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + }); + // ICETransport.completed and connected are the same for this purpose. + states.connected += states.completed; + + newState = 'new'; + if (states.failed > 0) { + newState = 'failed'; + } else if (states.connecting > 0 || states.checking > 0) { + newState = 'connecting'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connected > 0 || states.completed > 0) { + newState = 'connected'; + } + + if (newState !== self.iceConnectionState) { + self.iceConnectionState = newState; + var event = new Event('iceconnectionstatechange'); + this.dispatchEvent(event); + if (this.oniceconnectionstatechange !== null) { + this.oniceconnectionstatechange(event); + } + } + }; + + window.RTCPeerConnection.prototype.createOffer = function() { + var self = this; + if (this._pendingOffer) { + throw new Error('createOffer called while there is a pending offer.'); + } + var offerOptions; + if (arguments.length === 1 && typeof arguments[0] !== 'function') { + offerOptions = arguments[0]; + } else if (arguments.length === 3) { + offerOptions = arguments[2]; + } + + var tracks = []; + var numAudioTracks = 0; + var numVideoTracks = 0; + // Default to sendrecv. + if (this.localStreams.length) { + numAudioTracks = this.localStreams[0].getAudioTracks().length; + numVideoTracks = this.localStreams[0].getVideoTracks().length; + } + // Determine number of audio and video tracks we need to send/recv. + if (offerOptions) { + // Reject Chrome legacy constraints. + if (offerOptions.mandatory || offerOptions.optional) { + throw new TypeError( + 'Legacy mandatory/optional constraints not supported.'); + } + if (offerOptions.offerToReceiveAudio !== undefined) { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + if (offerOptions.offerToReceiveVideo !== undefined) { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + if (this.localStreams.length) { + // Push local streams. + this.localStreams[0].getTracks().forEach(function(track) { + tracks.push({ + kind: track.kind, + track: track, + wantReceive: track.kind === 'audio' ? + numAudioTracks > 0 : numVideoTracks > 0 + }); + if (track.kind === 'audio') { + numAudioTracks--; + } else if (track.kind === 'video') { + numVideoTracks--; + } + }); + } + // Create M-lines for recvonly streams. + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + tracks.push({ + kind: 'audio', + wantReceive: true + }); + numAudioTracks--; + } + if (numVideoTracks > 0) { + tracks.push({ + kind: 'video', + wantReceive: true + }); + numVideoTracks--; + } + } + + var sdp = SDPUtils.writeSessionBoilerplate(); + var transceivers = []; + tracks.forEach(function(mline, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, + // dtls transport, potentially rtpsender and rtpreceiver. + var track = mline.track; + var kind = mline.kind; + var mid = SDPUtils.generateIdentifier(); + + var transports = self.usingBundle && sdpMLineIndex > 0 ? { + iceGatherer: transceivers[0].iceGatherer, + iceTransport: transceivers[0].iceTransport, + dtlsTransport: transceivers[0].dtlsTransport + } : self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + var localCapabilities = RTCRtpSender.getCapabilities(kind); + var rtpSender; + var rtpReceiver; + + // generate an ssrc now, to be used later in rtpSender.send + var sendEncodingParameters = [{ + ssrc: (2 * sdpMLineIndex + 1) * 1001 + }]; + if (track) { + rtpSender = new RTCRtpSender(track, transports.dtlsTransport); + } + + if (mline.wantReceive) { + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + } + + transceivers[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: null, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + sendEncodingParameters: sendEncodingParameters, + recvEncodingParameters: null + }; + }); + if (this.usingBundle) { + sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + tracks.forEach(function(mline, sdpMLineIndex) { + var transceiver = transceivers[sdpMLineIndex]; + sdp += SDPUtils.writeMediaSection(transceiver, + transceiver.localCapabilities, 'offer', self.localStreams[0]); + }); + + this._pendingOffer = transceivers; + var desc = new RTCSessionDescription({ + type: 'offer', + sdp: sdp + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return Promise.resolve(desc); + }; + + window.RTCPeerConnection.prototype.createAnswer = function() { + var self = this; + + var sdp = SDPUtils.writeSessionBoilerplate(); + if (this.usingBundle) { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + this.transceivers.forEach(function(transceiver) { + // Calculate intersection of capabilities. + var commonCapabilities = self._getCommonCapabilities( + transceiver.localCapabilities, + transceiver.remoteCapabilities); + + sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, + 'answer', self.localStreams[0]); + }); + + var desc = new RTCSessionDescription({ + type: 'answer', + sdp: sdp + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return Promise.resolve(desc); + }; + + window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { + if (candidate === null) { + this.transceivers.forEach(function(transceiver) { + transceiver.iceTransport.addRemoteCandidate({}); + }); + } else { + var mLineIndex = candidate.sdpMLineIndex; + if (candidate.sdpMid) { + for (var i = 0; i < this.transceivers.length; i++) { + if (this.transceivers[i].mid === candidate.sdpMid) { + mLineIndex = i; + break; + } + } + } + var transceiver = this.transceivers[mLineIndex]; + if (transceiver) { + var cand = Object.keys(candidate.candidate).length > 0 ? + SDPUtils.parseCandidate(candidate.candidate) : {}; + // Ignore Chrome's invalid candidates since Edge does not like them. + if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { + return; + } + // Ignore RTCP candidates, we assume RTCP-MUX. + if (cand.component !== '1') { + return; + } + // A dirty hack to make samples work. + if (cand.type === 'endOfCandidates') { + cand = {}; + } + transceiver.iceTransport.addRemoteCandidate(cand); + + // update the remoteDescription. + var sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim() + : 'a=end-of-candidates') + '\r\n'; + this.remoteDescription.sdp = sections.join(''); + } + } + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return Promise.resolve(); + }; + + window.RTCPeerConnection.prototype.getStats = function() { + var promises = []; + this.transceivers.forEach(function(transceiver) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', + 'dtlsTransport'].forEach(function(method) { + if (transceiver[method]) { + promises.push(transceiver[method].getStats()); + } + }); + }); + var cb = arguments.length > 1 && typeof arguments[1] === 'function' && + arguments[1]; + return new Promise(function(resolve) { + // shim getStats with maplike support + var results = new Map(); + Promise.all(promises).then(function(res) { + res.forEach(function(result) { + Object.keys(result).forEach(function(id) { + results.set(id, result[id]); + results[id] = result[id]; + }); + }); + if (cb) { + window.setTimeout(cb, 0, results); + } + resolve(results); + }); + }); + }; + } + }; + + // Expose public methods. + module.exports = { + shimPeerConnection: edgeShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia') + }; + + },{"./getusermedia":6,"sdp":1}],6:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + // Expose public methods. + module.exports = function() { + var shimError_ = function(e) { + return { + name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name, + message: e.message, + constraint: e.constraint, + toString: function() { + return this.name; + } + }; + }; + + // getUserMedia error shim. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }; + }; + + },{}],7:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var browserDetails = require('../utils').browserDetails; + + var firefoxShim = { + shimOnTrack: function() { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + this.removeEventListener('addstream', this._ontrackpoly); + } + this.addEventListener('track', this._ontrack = f); + this.addEventListener('addstream', this._ontrackpoly = function(e) { + e.stream.getTracks().forEach(function(track) { + var event = new Event('track'); + event.track = track; + event.receiver = {track: track}; + event.streams = [e.stream]; + this.dispatchEvent(event); + }.bind(this)); + }.bind(this)); + } + }); + } + }, + + shimSourceObject: function() { + // Firefox has supported mozSrcObject since FF22, unprefixed in 42. + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this.mozSrcObject; + }, + set: function(stream) { + this.mozSrcObject = stream; + } + }); + } + } + }, + + shimPeerConnection: function() { + if (typeof window !== 'object' || !(window.RTCPeerConnection || + window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + // The RTCPeerConnection object. + if (!window.RTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (browserDetails.version < 38) { + // .urls is not supported in FF < 38. + // create RTCIceServers with a single url. + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (server.hasOwnProperty('urls')) { + for (var j = 0; j < server.urls.length; j++) { + var newServer = { + url: server.urls[j] + }; + if (server.urls[j].indexOf('turn') === 0) { + newServer.username = server.username; + newServer.credential = server.credential; + } + newIceServers.push(newServer); + } + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + } + return new mozRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (mozRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return mozRTCPeerConnection.generateCertificate; + } + }); + } + + window.RTCSessionDescription = mozRTCSessionDescription; + window.RTCIceCandidate = mozRTCIceCandidate; + } + + // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + RTCIceCandidate : RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null) + var nativeAddIceCandidate = + RTCPeerConnection.prototype.addIceCandidate; + RTCPeerConnection.prototype.addIceCandidate = function() { + return arguments[0] === null ? Promise.resolve() + : nativeAddIceCandidate.apply(this, arguments); + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + var map = new Map(); + Object.keys(stats).forEach(function(key) { + map.set(key, stats[key]); + map[key] = stats[key]; + }); + return map; + }; + + var nativeGetStats = RTCPeerConnection.prototype.getStats; + RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) { + return nativeGetStats.apply(this, [selector || null]) + .then(function(stats) { + return makeMapStats(stats); + }) + .then(onSucc, onErr); + }; + } + }; + + // Expose public methods. + module.exports = { + shimOnTrack: firefoxShim.shimOnTrack, + shimSourceObject: firefoxShim.shimSourceObject, + shimPeerConnection: firefoxShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia') + }; + + },{"../utils":10,"./getusermedia":8}],8:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var logging = require('../utils').log; + var browserDetails = require('../utils').browserDetails; + + // Expose public methods. + module.exports = function() { + var shimError_ = function(e) { + return { + name: { + SecurityError: 'NotAllowedError', + PermissionDeniedError: 'NotAllowedError' + }[e.name] || e.name, + message: { + 'The operation is insecure.': 'The request is not allowed by the ' + + 'user agent or the platform in the current context.' + }[e.message] || e.message, + constraint: e.constraint, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + // getUserMedia constraints shim. + var getUserMedia_ = function(constraints, onSuccess, onError) { + var constraintsToFF37_ = function(c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = c[key] = (typeof c[key] === 'object') ? + c[key] : {ideal: c[key]}; + if (r.min !== undefined || + r.max !== undefined || r.exact !== undefined) { + require.push(key); + } + if (r.exact !== undefined) { + if (typeof r.exact === 'number') { + r. min = r.max = r.exact; + } else { + c[key] = r.exact; + } + delete r.exact; + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[key] = {min: r.ideal, max: r.ideal}; + } else { + oc[key] = r.ideal; + } + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + constraints = JSON.parse(JSON.stringify(constraints)); + if (browserDetails.version < 38) { + logging('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37_(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37_(constraints.video); + } + logging('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, function(e) { + onError(shimError_(e)); + }); + }; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia_(constraints, resolve, reject); + }); + }; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, + addEventListener: function() { }, + removeEventListener: function() { } + }; + } + navigator.mediaDevices.enumerateDevices = + navigator.mediaDevices.enumerateDevices || function() { + return new Promise(function(resolve) { + var infos = [ + {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, + {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} + ]; + resolve(infos); + }); + }; + + if (browserDetails.version < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + if (browserDetails.version < 49) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }; + } + navigator.getUserMedia = function(constraints, onSuccess, onError) { + if (browserDetails.version < 44) { + return getUserMedia_(constraints, onSuccess, onError); + } + // Replace Firefox 44+'s deprecation warning with unprefixed version. + console.warn('navigator.getUserMedia has been replaced by ' + + 'navigator.mediaDevices.getUserMedia'); + navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); + }; + }; + + },{"../utils":10}],9:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + 'use strict'; + var safariShim = { + // TODO: DrAlex, should be here, double check against LayoutTests + // shimOnTrack: function() { }, + + // TODO: once the back-end for the mac port is done, add. + // TODO: check for webkitGTK+ + // shimPeerConnection: function() { }, + + shimGetUserMedia: function() { + navigator.getUserMedia = navigator.webkitGetUserMedia; + } + }; + + // Expose public methods. + module.exports = { + shimGetUserMedia: safariShim.shimGetUserMedia + // TODO + // shimOnTrack: safariShim.shimOnTrack, + // shimPeerConnection: safariShim.shimPeerConnection + }; + + },{}],10:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var logDisabled_ = true; + + // Utility methods. + var utils = { + disableLog: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + logDisabled_ = bool; + return (bool) ? 'adapter.js logging disabled' : + 'adapter.js logging enabled'; + }, + + log: function() { + if (typeof window === 'object') { + if (logDisabled_) { + return; + } + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + } + }, + + /** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ + extractVersion: function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); + }, + + /** + * Browser detector. + * + * @return {object} result containing browser and version + * properties. + */ + detectBrowser: function() { + // Returned result object. + var result = {}; + result.browser = null; + result.version = null; + + // Fail early if it's not a browser + if (typeof window === 'undefined' || !window.navigator) { + result.browser = 'Not a browser.'; + return result; + } + + // Firefox. + if (navigator.mozGetUserMedia) { + result.browser = 'firefox'; + result.version = this.extractVersion(navigator.userAgent, + /Firefox\/([0-9]+)\./, 1); + + // all webkit-based browsers + } else if (navigator.webkitGetUserMedia) { + // Chrome, Chromium, Webview, Opera, all use the chrome shim for now + if (window.webkitRTCPeerConnection) { + result.browser = 'chrome'; + result.version = this.extractVersion(navigator.userAgent, + /Chrom(e|ium)\/([0-9]+)\./, 2); + + // Safari or unknown webkit-based + // for the time being Safari has support for MediaStreams but not webRTC + } else { + // Safari UA substrings of interest for reference: + // - webkit version: AppleWebKit/602.1.25 (also used in Op,Cr) + // - safari UI version: Version/9.0.3 (unique to Safari) + // - safari UI webkit version: Safari/601.4.4 (also used in Op,Cr) + // + // if the webkit version and safari UI webkit versions are equals, + // ... this is a stable version. + // + // only the internal webkit version is important today to know if + // media streams are supported + // + if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) { + result.browser = 'safari'; + result.version = this.extractVersion(navigator.userAgent, + /AppleWebKit\/([0-9]+)\./, 1); + + // unknown webkit-based browser + } else { + result.browser = 'Unsupported webkit-based browser ' + + 'with GUM support but no WebRTC support.'; + return result; + } + } + + // Edge. + } else if (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + result.browser = 'edge'; + result.version = this.extractVersion(navigator.userAgent, + /Edge\/(\d+).(\d+)$/, 2); + + // Default fallthrough: not supported. + } else { + result.browser = 'Not a supported browser.'; + return result; + } + + return result; + } + }; + + // Export. + module.exports = { + log: utils.log, + disableLog: utils.disableLog, + browserDetails: utils.detectBrowser(), + extractVersion: utils.extractVersion + }; + + },{}]},{},[2])(2) +}); +/* jshint ignore:end */ + + // END OF INJECTION OF GOOGLE'S ADAPTER.JS CONTENT + /////////////////////////////////////////////////////////////////// + + AdapterJS.parseWebrtcDetectedBrowser(); + + /////////////////////////////////////////////////////////////////// + // EXTENSION FOR CHROME, FIREFOX AND EDGE + // Includes legacy functions + // -- createIceServer + // -- createIceServers + // -- MediaStreamTrack.getSources + // + // and additional shims + // -- attachMediaStream + // -- reattachMediaStream + // -- requestUserMedia + // -- a call to AdapterJS.maybeThroughWebRTCReady (notifies WebRTC is ready) + + // Add support for legacy functions createIceServer and createIceServers + if ( navigator.mozGetUserMedia ) { + // Shim for MediaStreamTrack.getSources. + MediaStreamTrack.getSources = function(successCb) { + setTimeout(function() { + var infos = [ + { kind: 'audio', id: 'default', label:'', facing:'' }, + { kind: 'video', id: 'default', label:'', facing:'' } + ]; + successCb(infos); + }, 0); + }; + + createIceServer = function (url, username, password) { + console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.'); + // Note: Google's import of AJS will auto-reverse to 'url': '...' for FF < 38 + + var iceServer = null; + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { + iceServer = { urls : [url] }; + } else if (urlParts[0].indexOf('turn') === 0) { + if (webrtcDetectedVersion < 27) { + var turnUrlParts = url.split('?'); + if (turnUrlParts.length === 1 || + turnUrlParts[1].indexOf('transport=udp') === 0) { + iceServer = { + urls : [turnUrlParts[0]], + credential : password, + username : username + }; + } + } else { + iceServer = { + urls : [url], + credential : password, + username : username + }; + } + } + return iceServer; + }; + + createIceServers = function (urls, username, password) { + console.warn('createIceServers is deprecated. It should be replaced with an application level implementation.'); + + var iceServers = []; + for (i = 0; i < urls.length; i++) { + var iceServer = createIceServer(urls[i], username, password); + if (iceServer !== null) { + iceServers.push(iceServer); + } + } + return iceServers; + }; + } else if ( navigator.webkitGetUserMedia ) { + createIceServer = function (url, username, password) { + console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.'); + + var iceServer = null; + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { + iceServer = { 'url' : url }; + } else if (urlParts[0].indexOf('turn') === 0) { + iceServer = { + 'url' : url, + 'credential' : password, + 'username' : username + }; + } + return iceServer; + }; + + createIceServers = function (urls, username, password) { + console.warn('createIceServers is deprecated. It should be replaced with an application level implementation.'); + + var iceServers = []; + if (webrtcDetectedVersion >= 34) { + iceServers = { + 'urls' : urls, + 'credential' : password, + 'username' : username + }; + } else { + for (i = 0; i < urls.length; i++) { + var iceServer = createIceServer(urls[i], username, password); + if (iceServer !== null) { + iceServers.push(iceServer); + } + } + } + return iceServers; + }; + } + + // adapter.js by Google currently doesn't suppport + // attachMediaStream and reattachMediaStream for Egde + if (navigator.mediaDevices && navigator.userAgent.match( + /Edge\/(\d+).(\d+)$/)) { + getUserMedia = window.getUserMedia = navigator.getUserMedia.bind(navigator); + attachMediaStream = function(element, stream) { + element.srcObject = stream; + return element; + }; + reattachMediaStream = function(to, from) { + to.srcObject = from.srcObject; + return to; + }; + } + + // Need to override attachMediaStream and reattachMediaStream + // to support the plugin's logic + if (attachMediaStream) { + attachMediaStream_base = attachMediaStream; + attachMediaStream = function (element, stream) { + if ((webrtcDetectedBrowser === 'chrome' || + webrtcDetectedBrowser === 'opera') && + !stream) { + // Chrome does not support "src = null" + element.src = ''; + } else { + attachMediaStream_base(element, stream); + } + return element; + }; + } + if (reattachMediaStream) { + reattachMediaStream_base = reattachMediaStream; + reattachMediaStream = function (to, from) { + reattachMediaStream_base(to, from); + return to; + }; + } + + // Propagate attachMediaStream and gUM in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + window.getUserMedia = getUserMedia; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.getUserMedia = getUserMedia; + + // Removed Google defined promises when promise is not defined + if (typeof Promise === 'undefined') { + requestUserMedia = null; + } + + AdapterJS.maybeThroughWebRTCReady(); + + // END OF EXTENSION OF CHROME, FIREFOX AND EDGE + /////////////////////////////////////////////////////////////////// + +} else { // TRY TO USE PLUGIN + + /////////////////////////////////////////////////////////////////// + // WEBRTC PLUGIN SHIM + // Will automatically check if the plugin is available and inject it + // into the DOM if it is. + // When the plugin is not available, will prompt a banner to suggest installing it + // Use AdapterJS.options.hidePluginInstallPrompt to prevent this banner from popping + // + // Shims the follwing: + // -- getUserMedia + // -- MediaStreamTrack + // -- MediaStreamTrack.getSources + // -- RTCPeerConnection + // -- RTCSessionDescription + // -- RTCIceCandidate + // -- createIceServer + // -- createIceServers + // -- attachMediaStream + // -- reattachMediaStream + // -- webrtcDetectedBrowser + // -- webrtcDetectedVersion + + // IE 9 is not offering an implementation of console.log until you open a console + if (typeof console !== 'object' || typeof console.log !== 'function') { + /* jshint -W020 */ + console = {} || console; + // Implemented based on console specs from MDN + // You may override these functions + console.log = function (arg) {}; + console.info = function (arg) {}; + console.error = function (arg) {}; + console.dir = function (arg) {}; + console.exception = function (arg) {}; + console.trace = function (arg) {}; + console.warn = function (arg) {}; + console.count = function (arg) {}; + console.debug = function (arg) {}; + console.count = function (arg) {}; + console.time = function (arg) {}; + console.timeEnd = function (arg) {}; + console.group = function (arg) {}; + console.groupCollapsed = function (arg) {}; + console.groupEnd = function (arg) {}; + /* jshint +W020 */ + } + AdapterJS.parseWebrtcDetectedBrowser(); + isIE = webrtcDetectedBrowser === 'IE'; + + /* jshint -W035 */ + AdapterJS.WebRTCPlugin.WaitForPluginReady = function() { + while (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + /* empty because it needs to prevent the function from running. */ + } + }; + /* jshint +W035 */ + + AdapterJS.WebRTCPlugin.callWhenPluginReady = function (callback) { + if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + // Call immediately if possible + // Once the plugin is set, the code will always take this path + callback(); + } else { + // otherwise start a 100ms interval + var checkPluginReadyState = setInterval(function () { + if (AdapterJS.WebRTCPlugin.pluginState === AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + clearInterval(checkPluginReadyState); + callback(); + } + }, 100); + } + }; + + AdapterJS.WebRTCPlugin.setLogLevel = function(logLevel) { + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + AdapterJS.WebRTCPlugin.plugin.setLogLevel(logLevel); + }); + }; + + AdapterJS.WebRTCPlugin.injectPlugin = function () { + // only inject once the page is ready + if (document.readyState !== 'complete') { + return; + } + + // Prevent multiple injections + if (AdapterJS.WebRTCPlugin.pluginState !== AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING) { + return; + } + + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTING; + + if (webrtcDetectedBrowser === 'IE' && webrtcDetectedVersion <= 10) { + var frag = document.createDocumentFragment(); + AdapterJS.WebRTCPlugin.plugin = document.createElement('div'); + AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + + ' ' + + ' ' + + ' ' + + '' + + '' + + // uncomment to be able to use virtual cams + (AdapterJS.options.getAllCams ? '':'') + + + ''; + while (AdapterJS.WebRTCPlugin.plugin.firstChild) { + frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild); + } + document.body.appendChild(frag); + + // Need to re-fetch the plugin + AdapterJS.WebRTCPlugin.plugin = + document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId); + } else { + // Load Plugin + AdapterJS.WebRTCPlugin.plugin = document.createElement('object'); + AdapterJS.WebRTCPlugin.plugin.id = + AdapterJS.WebRTCPlugin.pluginInfo.pluginId; + // IE will only start the plugin if it's ACTUALLY visible + if (isIE) { + AdapterJS.WebRTCPlugin.plugin.width = '1px'; + AdapterJS.WebRTCPlugin.plugin.height = '1px'; + } else { // The size of the plugin on Safari should be 0x0px + // so that the autorisation prompt is at the top + AdapterJS.WebRTCPlugin.plugin.width = '0px'; + AdapterJS.WebRTCPlugin.plugin.height = '0px'; + } + AdapterJS.WebRTCPlugin.plugin.type = AdapterJS.WebRTCPlugin.pluginInfo.type; + AdapterJS.WebRTCPlugin.plugin.innerHTML = '' + + '' + + ' ' + + (AdapterJS.options.getAllCams ? '':'') + + '' + + ''; + document.body.appendChild(AdapterJS.WebRTCPlugin.plugin); + } + + + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED; + }; + + AdapterJS.WebRTCPlugin.isPluginInstalled = + function (comName, plugName, installedCb, notInstalledCb) { + if (!isIE) { + var pluginArray = navigator.plugins; + for (var i = 0; i < pluginArray.length; i++) { + if (pluginArray[i].name.indexOf(plugName) >= 0) { + installedCb(); + return; + } + } + notInstalledCb(); + } else { + try { + var axo = new ActiveXObject(comName + '.' + plugName); + } catch (e) { + notInstalledCb(); + return; + } + installedCb(); + } + }; + + AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () { + if (AdapterJS.WebRTCPlugin.pluginState === + AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) { + console.error('AdapterJS - WebRTC interface has already been defined'); + return; + } + + AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING; + + AdapterJS.isDefined = function (variable) { + return variable !== null && variable !== undefined; + }; + + createIceServer = function (url, username, password) { + var iceServer = null; + var urlParts = url.split(':'); + if (urlParts[0].indexOf('stun') === 0) { + iceServer = { + 'url' : url, + 'hasCredentials' : false + }; + } else if (urlParts[0].indexOf('turn') === 0) { + iceServer = { + 'url' : url, + 'hasCredentials' : true, + 'credential' : password, + 'username' : username + }; + } + return iceServer; + }; + + createIceServers = function (urls, username, password) { + var iceServers = []; + for (var i = 0; i < urls.length; ++i) { + iceServers.push(createIceServer(urls[i], username, password)); + } + return iceServers; + }; + + RTCSessionDescription = function (info) { + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + return AdapterJS.WebRTCPlugin.plugin. + ConstructSessionDescription(info.type, info.sdp); + }; + + RTCPeerConnection = function (servers, constraints) { + // Validate server argumenr + if (!(servers === undefined || + servers === null || + Array.isArray(servers.iceServers))) { + throw new Error('Failed to construct \'RTCPeerConnection\': Malformed RTCConfiguration'); + } + + // Validate constraints argument + if (typeof constraints !== 'undefined' && constraints !== null) { + var invalidConstraits = false; + invalidConstraits |= typeof constraints !== 'object'; + invalidConstraits |= constraints.hasOwnProperty('mandatory') && + constraints.mandatory !== undefined && + constraints.mandatory !== null && + constraints.mandatory.constructor !== Object; + invalidConstraits |= constraints.hasOwnProperty('optional') && + constraints.optional !== undefined && + constraints.optional !== null && + !Array.isArray(constraints.optional); + if (invalidConstraits) { + throw new Error('Failed to construct \'RTCPeerConnection\': Malformed constraints object'); + } + } + + // Call relevant PeerConnection constructor according to plugin version + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + + // RTCPeerConnection prototype from the old spec + var iceServers = null; + if (servers && Array.isArray(servers.iceServers)) { + iceServers = servers.iceServers; + for (var i = 0; i < iceServers.length; i++) { + // Legacy plugin versions compatibility + if (iceServers[i].urls && !iceServers[i].url) { + iceServers[i].url = iceServers[i].urls; + } + iceServers[i].hasCredentials = AdapterJS. + isDefined(iceServers[i].username) && + AdapterJS.isDefined(iceServers[i].credential); + } + } + + if (AdapterJS.WebRTCPlugin.plugin.PEER_CONNECTION_VERSION && + AdapterJS.WebRTCPlugin.plugin.PEER_CONNECTION_VERSION > 1) { + // RTCPeerConnection prototype from the new spec + if (iceServers) { + servers.iceServers = iceServers; + } + return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers); + } else { + var mandatory = (constraints && constraints.mandatory) ? + constraints.mandatory : null; + var optional = (constraints && constraints.optional) ? + constraints.optional : null; + return AdapterJS.WebRTCPlugin.plugin. + PeerConnection(AdapterJS.WebRTCPlugin.pageId, + iceServers, mandatory, optional); + } + }; + + MediaStreamTrack = function(){}; + MediaStreamTrack.getSources = function (callback) { + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + AdapterJS.WebRTCPlugin.plugin.GetSources(callback); + }); + }; + + getUserMedia = function (constraints, successCallback, failureCallback) { + constraints.audio = constraints.audio || false; + constraints.video = constraints.video || false; + + AdapterJS.WebRTCPlugin.callWhenPluginReady(function() { + AdapterJS.WebRTCPlugin.plugin. + getUserMedia(constraints, successCallback, failureCallback); + }); + }; + window.navigator.getUserMedia = getUserMedia; + + // Defined mediaDevices when promises are available + if ( !navigator.mediaDevices && + typeof Promise !== 'undefined') { + requestUserMedia = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); + }; + navigator.mediaDevices = {getUserMedia: requestUserMedia, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + id: device.id, + deviceId: device.id, + groupId: ''}; + })); + }); + }); + }}; + } + + attachMediaStream = function (element, stream) { + if (!element || !element.parentNode) { + return; + } + + var streamId; + if (stream === null) { + streamId = ''; + } else { + if (typeof stream.enableSoundTracks !== 'undefined') { + stream.enableSoundTracks(true); + } + streamId = stream.id; + } + + var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id; + var nodeName = element.nodeName.toLowerCase(); + if (nodeName !== 'object') { // not a plugin tag yet + var tag; + switch(nodeName) { + case 'audio': + tag = AdapterJS.WebRTCPlugin.TAGS.AUDIO; + break; + case 'video': + tag = AdapterJS.WebRTCPlugin.TAGS.VIDEO; + break; + default: + tag = AdapterJS.WebRTCPlugin.TAGS.NONE; + } + + var frag = document.createDocumentFragment(); + var temp = document.createElement('div'); + var classHTML = ''; + if (element.className) { + classHTML = 'class="' + element.className + '" '; + } else if (element.attributes && element.attributes['class']) { + classHTML = 'class="' + element.attributes['class'].value + '" '; + } + + temp.innerHTML = '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + while (temp.firstChild) { + frag.appendChild(temp.firstChild); + } + + var height = ''; + var width = ''; + if (element.clientWidth || element.clientHeight) { + width = element.clientWidth; + height = element.clientHeight; + } + else if (element.width || element.height) { + width = element.width; + height = element.height; + } + + element.parentNode.insertBefore(frag, element); + frag = document.getElementById(elementId); + frag.width = width; + frag.height = height; + element.parentNode.removeChild(element); + } else { // already an tag, just change the stream id + var children = element.children; + for (var i = 0; i !== children.length; ++i) { + if (children[i].name === 'streamId') { + children[i].value = streamId; + break; + } + } + element.setStreamId(streamId); + } + var newElement = document.getElementById(elementId); + AdapterJS.forwardEventHandlers(newElement, element, Object.getPrototypeOf(element)); + + return newElement; + }; + + reattachMediaStream = function (to, from) { + var stream = null; + var children = from.children; + for (var i = 0; i !== children.length; ++i) { + if (children[i].name === 'streamId') { + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + stream = AdapterJS.WebRTCPlugin.plugin + .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, children[i].value); + break; + } + } + if (stream !== null) { + return attachMediaStream(to, stream); + } else { + console.log('Could not find the stream associated with this element'); + } + }; + + // Propagate attachMediaStream and gUM in window and AdapterJS + window.attachMediaStream = attachMediaStream; + window.reattachMediaStream = reattachMediaStream; + window.getUserMedia = getUserMedia; + AdapterJS.attachMediaStream = attachMediaStream; + AdapterJS.reattachMediaStream = reattachMediaStream; + AdapterJS.getUserMedia = getUserMedia; + + AdapterJS.forwardEventHandlers = function (destElem, srcElem, prototype) { + properties = Object.getOwnPropertyNames( prototype ); + for(var prop in properties) { + if (prop) { + propName = properties[prop]; + + if (typeof propName.slice === 'function' && + propName.slice(0,2) === 'on' && + typeof srcElem[propName] === 'function') { + AdapterJS.addEvent(destElem, propName.slice(2), srcElem[propName]); + } + } + } + var subPrototype = Object.getPrototypeOf(prototype); + if(!!subPrototype) { + AdapterJS.forwardEventHandlers(destElem, srcElem, subPrototype); + } + }; + + RTCIceCandidate = function (candidate) { + if (!candidate.sdpMid) { + candidate.sdpMid = ''; + } + + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + return AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate( + candidate.sdpMid, candidate.sdpMLineIndex, candidate.candidate + ); + }; + + // inject plugin + AdapterJS.addEvent(document, 'readystatechange', AdapterJS.WebRTCPlugin.injectPlugin); + AdapterJS.WebRTCPlugin.injectPlugin(); + }; + + // This function will be called if the plugin is needed (browser different + // from Chrome or Firefox), but the plugin is not installed. + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb = AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb || + function() { + AdapterJS.addEvent(document, + 'readystatechange', + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv); + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv(); + }; + + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv = function () { + if (AdapterJS.options.hidePluginInstallPrompt) { + return; + } + + var downloadLink = AdapterJS.WebRTCPlugin.pluginInfo.downloadLink; + if(downloadLink) { // if download link + var popupString; + if (AdapterJS.WebRTCPlugin.pluginInfo.portalLink) { // is portal link + popupString = 'This website requires you to install the ' + + ' ' + AdapterJS.WebRTCPlugin.pluginInfo.companyName + + ' WebRTC Plugin' + + ' to work on this browser.'; + } else { // no portal link, just print a generic explanation + popupString = AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION; + } + + AdapterJS.renderNotificationBar(popupString, AdapterJS.TEXT.PLUGIN.BUTTON, downloadLink); + } else { // no download link, just print a generic explanation + AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED); + } + }; + + // Try to detect the plugin and act accordingly + AdapterJS.WebRTCPlugin.isPluginInstalled( + AdapterJS.WebRTCPlugin.pluginInfo.prefix, + AdapterJS.WebRTCPlugin.pluginInfo.plugName, + AdapterJS.WebRTCPlugin.defineWebRTCInterface, + AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb); + + // END OF WEBRTC PLUGIN SHIM + /////////////////////////////////////////////////////////////////// +} diff --git a/html5/verto/verto_communicator/src/index.html b/html5/verto/verto_communicator/src/index.html index 2f4e2af1f5..16e11f5cee 100644 --- a/html5/verto/verto_communicator/src/index.html +++ b/html5/verto/verto_communicator/src/index.html @@ -108,6 +108,7 @@ + diff --git a/html5/verto/verto_communicator/src/partials/preview.html b/html5/verto/verto_communicator/src/partials/preview.html index 9c58c30d0b..68b4523e72 100644 --- a/html5/verto/verto_communicator/src/partials/preview.html +++ b/html5/verto/verto_communicator/src/partials/preview.html @@ -7,7 +7,7 @@
-
+
diff --git a/html5/verto/verto_communicator/src/storageService/services/splash_screen.js b/html5/verto/verto_communicator/src/storageService/services/splash_screen.js index ae77d000e7..9aa4414bf5 100644 --- a/html5/verto/verto_communicator/src/storageService/services/splash_screen.js +++ b/html5/verto/verto_communicator/src/storageService/services/splash_screen.js @@ -14,11 +14,7 @@ 'status': 'success', 'message': $translate.instant('BROWSER_COMPATIBILITY') }; - navigator.getUserMedia = navigator.getUserMedia || - navigator.webkitGetUserMedia || - navigator.mozGetUserMedia; - - if (!navigator.getUserMedia) { + if (!navigator.mediaDevices.getUserMedia) { result['status'] = 'error'; result['message'] = $translate.instant('BROWSER_WITHOUT_WEBRTC'); reject(result); diff --git a/html5/verto/verto_communicator/src/storageService/services/storage.js b/html5/verto/verto_communicator/src/storageService/services/storage.js index 501b7b7ab6..8aff5ee61a 100644 --- a/html5/verto/verto_communicator/src/storageService/services/storage.js +++ b/html5/verto/verto_communicator/src/storageService/services/storage.js @@ -23,6 +23,7 @@ mutedMic: false, preview: true, selectedVideo: null, + selectedVideoName: null, selectedAudio: null, selectedShare: null, selectedSpeaker: null, diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js index 50bb6ea75b..7800ea3e6f 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js @@ -4,8 +4,8 @@ angular .module('vertoControllers') .controller('DialPadController', ['$rootScope', '$scope', - '$http', '$location', 'toastr', 'verto', 'storage', 'CallHistory', 'eventQueue', - function($rootScope, $scope, $http, $location, toastr, verto, storage, CallHistory, eventQueue) { + '$http', '$location', 'toastr', 'verto', 'storage', 'CallHistory', 'eventQueue', '$timeout', + function($rootScope, $scope, $http, $location, toastr, verto, storage, CallHistory, eventQueue, $timeout) { console.debug('Executing DialPadController.'); eventQueue.process(); diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js index 7a8945ec02..1379409cfe 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js @@ -11,7 +11,10 @@ $scope.storage = storage; console.debug('Executing PreviewController.'); var localVideo = document.getElementById('videopreview'); - var volumes = document.querySelector('#mic-meter .volumes').children; + var volumes = document.querySelector('#mic-meter .volumes'); + if (volumes) { + volumes = volumes.children; + } $scope.localVideo = function() { var constraints = { @@ -31,10 +34,13 @@ }); }; + var audioContext = null; + if (typeof AudioContext !== "undefined") { + audioContext = new AudioContext(); + } - var audioContext = new AudioContext(); var mediaStreamSource = null; - var meter; + var meter = null; var streamObj = {}; function stopMedia(stream) { @@ -55,13 +61,12 @@ } streamObj = stream; - localVideo.src = window.URL.createObjectURL(stream); - - mediaStreamSource = audioContext.createMediaStreamSource(stream); - meter = createAudioMeter(audioContext); - mediaStreamSource.connect(meter); - - renderMic(); + FSRTCattachMediaStream(localVideo, stream); + if (audioContext) { + mediaStreamSource = audioContext.createMediaStreamSource(stream); + meter = createAudioMeter(audioContext); + mediaStreamSource.connect(meter); + }; } function renderMic() { @@ -109,8 +114,10 @@ $scope.endPreview = function() { localVideo.src = null; - meter.shutdown(); - meter.onaudioprocess = null; + if (audioContext) { + meter.shutdown(); + meter.onaudioprocess = null; + }; stopMedia(streamObj); $location.path('/dialpad'); storage.data.preview = false; diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/SettingsController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/SettingsController.js index c52cdb8c82..a7da6f4beb 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/SettingsController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/SettingsController.js @@ -33,6 +33,15 @@ }); $scope.ok = function() { + console.log('Camera Selected is', $scope.mydata.selectedVideo, $scope.verto.data.videoDevices); + + angular.forEach(verto.data.videoDevices, function(video) { + console.log('checking video ', video); + if (video.id == $scope.mydata.selectedVideo) { + $scope.mydata.selectedVideoName = video.label; + console.log('Setting selectedVideoName to ', video.label); + } + }) if ($scope.mydata.selectedSpeaker != storage.data.selectedSpeaker) { $rootScope.$emit('changedSpeaker', $scope.mydata.selectedSpeaker); } diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js index 6fd32f9de7..65ab68d93b 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js @@ -74,7 +74,7 @@ $rootScope.$on('progress.complete', function(ev, current_progress) { $scope.message = 'Complete'; if(verto.data.connected) { - if (storage.data.preview) { + if (0 && storage.data.preview) { $location.path('/preview'); } else { diff --git a/html5/verto/verto_communicator/src/vertoDirectives/directives/videoTag.js b/html5/verto/verto_communicator/src/vertoDirectives/directives/videoTag.js index e40adc879a..15fd7e1a01 100644 --- a/html5/verto/verto_communicator/src/vertoDirectives/directives/videoTag.js +++ b/html5/verto/verto_communicator/src/vertoDirectives/directives/videoTag.js @@ -13,17 +13,35 @@ .module('vertoDirectives') .directive('videoTag', function() { + function link(scope, element, attrs) { // Moving the video tag to the new place inside the incall page. console.log('Moving the video to element.'); - jQuery('video').removeClass('hide').appendTo(element); - jQuery('video').css('display', 'block'); + var videoElem = jQuery('#webcam'); + + var newParent = document.getElementsByClassName('video-tag-wrapper'); + newParent[0].appendChild(document.getElementById('webcam')); + + $("#webcam").resize(function() { + updateVideoSize(); + }); + + $(window).resize(function() { + updateVideoSize(); + }); + + updateVideoSize(); + + videoElem.removeClass('hide'); + videoElem.css('display', 'block'); + scope.callActive("", {useVideo: true}); element.on('$destroy', function() { // Move the video back to the body. console.log('Moving the video back to body.'); - jQuery('video').addClass('hide').appendTo(jQuery('body')); + videoElem.addClass('hide').appendTo(jQuery('body')); + $(window).unbind('resize'); }); } diff --git a/html5/verto/verto_communicator/src/vertoService/services/vertoService.js b/html5/verto/verto_communicator/src/vertoService/services/vertoService.js index b926c77f9e..404c0c4a9f 100644 --- a/html5/verto/verto_communicator/src/vertoService/services/vertoService.js +++ b/html5/verto/verto_communicator/src/vertoService/services/vertoService.js @@ -113,6 +113,35 @@ var framerate = [{ label: '30 FPS' }, ]; +var updateReq; + +var updateVideoSize = function(ms) { + if (!ms) ms = 500; + + clearTimeout(updateReq); + updateReq = setTimeout(function () { + var videoElem = jQuery('#webcam'); + videoElem.width(""); + videoElem.height(""); + + var w = videoElem.width(); + var h = videoElem.height(); + var new_w, new_h; + var aspect = 1920 / 1080; + var videoContainer = jQuery('div.video-wrapper'); + if (w > h) { + new_w = videoContainer.width(); + new_h = Math.round(videoContainer.width() / aspect); + } else { + new_h = videoContainer.height(); + new_w = Math.round(videoContainer.height() / aspect); + } + videoElem.width(new_w); + videoElem.height(new_h); + console.log('Setting video size to ' + new_w + '/' + new_h); + }, ms); +} + var vertoService = angular.module('vertoService', ['ngCookies']); vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'storage', @@ -317,7 +346,13 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora // Verify if selected devices are valid var videoFlag = data.videoDevices.some(function(device) { - return device.id == storage.data.selectedVideo; + console.log('Evaluating device ', device); + if (device.label == storage.data.selectedVideoName) { + console.log('Matched video selection by name: ', device.label); + storage.data.selectedVideo = device.id; + return true; + } + return device.id == storage.data.selectedVideo && storage.data.selectedVideo !== "none"; }); var shareFlag = data.shareDevices.some(function(device) { @@ -332,7 +367,10 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora return device.id == storage.data.selectedSpeaker; }); - if (!videoFlag) storage.data.selectedVideo = data.videoDevices[0].id; + console.log('Storage Video: ', storage.data.selectedVideo); + console.log('Video Flag: ', videoFlag) + + if (!videoFlag) storage.data.selectedVideo = data.videoDevices[data.videoDevices.length - 1].id; if (!shareFlag) storage.data.selectedShare = data.shareDevices[0].id; if (!audioFlag) storage.data.selectedAudio = data.audioDevices[0].id; if (!speakerFlag && data.speakerDevices.length > 0) storage.data.selectedSpeaker = data.speakerDevices[0].id; @@ -484,7 +522,6 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora data.liveArray.onChange = function(obj, args) { // console.log('liveArray.onChange', obj, args); - switch (args.action) { case 'bootObj': $rootScope.$emit('members.boot', args.data); @@ -560,6 +597,7 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora console.log("conference-liveArray-join"); stopConference(); startConference(v, dialog, params.pvtData); + updateVideoSize(); } break; case "conference-liveArray-part": @@ -616,6 +654,7 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora console.debug('Talking to:', d.cidString()); data.callState = 'active'; callActive(d.lastState.name, d.params); + updateVideoSize(); break; case "hangup": console.debug('Call ended with cause: ' + d.cause); diff --git a/html5/verto/video_demo/js/verto-min.js b/html5/verto/video_demo/js/verto-min.js index dbe0017137..52f165af51 100644 --- a/html5/verto/video_demo/js/verto-min.js +++ b/html5/verto/video_demo/js/verto-min.js @@ -6,15 +6,13 @@ function getCodecPayloadType(sdpLine){var pattern=new RegExp('a=rtpmap:(\\d+) \\ function setDefaultCodec(mLine,payload){var elements=mLine.split(' ');var newLine=[];var index=0;for(var i=0;iw&&$.FSRTC.validRes[i][1]>h){w=$.FSRTC.validRes[i][0];h=$.FSRTC.validRes[i][1];}} return[w,h];} -var resList=[[320,180],[320,240],[640,360],[640,480],[1280,720],[1920,1080]];var resI=0;var ttl=0;var checkRes=function(cam,func){if(resI>=resList.length){var res={'validRes':$.FSRTC.validRes,'bestResSupported':$.FSRTC.bestResSupported()};localStorage.setItem("res_"+cam,$.toJSON(res));if(func)return func(res);return;} -var video={mandatory:{},optional:[]} -if(cam){video.optional=[{sourceId:cam}];} -w=resList[resI][0];h=resList[resI][1];resI++;video.mandatory={"minWidth":w,"minHeight":h,"maxWidth":w,"maxHeight":h};getUserMedia({constraints:{audio:ttl++==0,video:video},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info(w+"x"+h+" supported.");$.FSRTC.validRes.push([w,h]);checkRes(cam,func);},onerror:function(e){console.error(w+"x"+h+" not supported.");checkRes(cam,func);}});} +var resList=[[160,120],[320,180],[320,240],[640,360],[640,480],[1280,720],[1920,1080]];var resI=0;var ttl=0;var checkRes=function(cam,func){if(resI>=resList.length){var res={'validRes':$.FSRTC.validRes,'bestResSupported':$.FSRTC.bestResSupported()};localStorage.setItem("res_"+cam,$.toJSON(res));if(func)return func(res);return;} +var video={} +if(cam){video.deviceId={exact:cam};} +w=resList[resI][0];h=resList[resI][1];resI++;video={width:w,height:h};getUserMedia({constraints:{audio:ttl++==0,video:video},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info(w+"x"+h+" supported.");$.FSRTC.validRes.push([w,h]);checkRes(cam,func);},onerror:function(e){console.error(w+"x"+h+" not supported.");checkRes(cam,func);}});} $.FSRTC.getValidRes=function(cam,func){var used=[];var cached=localStorage.getItem("res_"+cam);if(cached){var cache=$.parseJSON(cached);if(cache){$.FSRTC.validRes=cache.validRes;console.log("CACHED RES FOR CAM "+cam,cache);}else{console.error("INVALID CACHE");} return func?func(cache):null;} $.FSRTC.validRes=[];resI=0;checkRes(cam,func);} @@ -254,7 +247,9 @@ if(!dialog.params.remote_caller_id_name){dialog.params.remote_caller_id_name="No if(!dialog.params.remote_caller_id_number){dialog.params.remote_caller_id_number="UNKNOWN";} RTCcallbacks.onMessage=function(rtc,msg){console.debug(msg);};RTCcallbacks.onAnswerSDP=function(rtc,sdp){console.error("answer sdp",sdp);};}else{dialog.params.remote_caller_id_name="Outbound Call";dialog.params.remote_caller_id_number=dialog.params.destination_number;} RTCcallbacks.onICESDP=function(rtc){console.log("RECV "+rtc.type+" SDP",rtc.mediaData.SDP);if(dialog.state==$.verto.enum.state.requesting||dialog.state==$.verto.enum.state.answering||dialog.state==$.verto.enum.state.active){location.reload();return;} -if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.invite",{sdp:rtc.mediaData.SDP});}}else{dialog.setState($.verto.enum.state.answering);dialog.sendMethod(dialog.attach?"verto.attach":"verto.answer",{sdp:dialog.rtc.mediaData.SDP});}};RTCcallbacks.onICE=function(rtc){if(rtc.type=="offer"){console.log("offer",rtc.mediaData.candidate);return;}};RTCcallbacks.onStream=function(rtc,stream){console.log("stream started");};RTCcallbacks.onError=function(e){console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});dialog.rtc.verto=dialog.verto;if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.attach){dialog.answer();}else{dialog.ring();}}};$.verto.dialog.prototype.invite=function(){var dialog=this;dialog.rtc.call();};$.verto.dialog.prototype.sendMethod=function(method,obj){var dialog=this;obj.dialogParams={};for(var i in dialog.params){if(i=="sdp"&&method!="verto.invite"&&method!="verto.attach"){continue;} +if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.invite",{sdp:rtc.mediaData.SDP});}}else{dialog.setState($.verto.enum.state.answering);dialog.sendMethod(dialog.attach?"verto.attach":"verto.answer",{sdp:dialog.rtc.mediaData.SDP});}};RTCcallbacks.onICE=function(rtc){if(rtc.type=="offer"){console.log("offer",rtc.mediaData.candidate);return;}};RTCcallbacks.onStream=function(rtc,stream){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onGranted==='function'){dialog.verto.options.permissionCallback.onGranted();} +console.log("stream started");};RTCcallbacks.onError=function(e){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onDenied==='function'){dialog.verto.options.permissionCallback.onDenied();} +console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});dialog.rtc.verto=dialog.verto;if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.attach){dialog.answer();}else{dialog.ring();}}};$.verto.dialog.prototype.invite=function(){var dialog=this;dialog.rtc.call();};$.verto.dialog.prototype.sendMethod=function(method,obj){var dialog=this;obj.dialogParams={};for(var i in dialog.params){if(i=="sdp"&&method!="verto.invite"&&method!="verto.attach"){continue;} obj.dialogParams[i]=dialog.params[i];} dialog.verto.rpcClient.call(method,obj,function(e){dialog.processReply(method,true,e);},function(e){dialog.processReply(method,false,e);});};function checkStateChange(oldS,newS){if(newS==$.verto.enum.state.purge||$.verto.enum.states[oldS.name][newS.name]){return true;} return false;} @@ -299,4 +294,218 @@ $.verto.videoDevices=vid;$.verto.audioInDevices=aud_in;console.info("Audio Devic navigator.mediaDevices.enumerateDevices().then(function(devices){devices.forEach(function(device){console.log(device);console.log(device.kind+": "+device.label+" id = "+device.deviceId);if(device.kind==="videoinput"){vid.push({id:device.deviceId,kind:"video",label:device.label});}else if(device.kind==="audioinput"){aud_in.push({id:device.deviceId,kind:"audio_in",label:device.label});}else if(device.kind==="audiooutput"){aud_out.push({id:device.deviceId,kind:"audio_out",label:device.label});}});$.verto.videoDevices=vid;$.verto.audioInDevices=aud_in;$.verto.audioOutDevices=aud_out;console.info("Audio IN Devices",$.verto.audioInDevices);console.info("Audio Out Devices",$.verto.audioOutDevices);console.info("Video Devices",$.verto.videoDevices);runtime(true);}).catch(function(err){console.log(" Device Enumeration ERROR: "+err.name+": "+err.message);runtime(false);});}};$.verto.refreshDevices=function(runtime){checkDevices(runtime);} $.verto.init=function(obj,runtime){if(!obj){obj={};} if(!obj.skipPermCheck&&!obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){checkDevices(runtime);},true,true);}else if(obj.skipPermCheck&&!obj.skipDeviceCheck){checkDevices(runtime);}else if(!obj.skipPermCheck&&obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){runtime(status);},true,true);}else{runtime(null);}} -$.verto.genUUID=function(){return generateGUID();}})(jQuery); \ No newline at end of file +$.verto.genUUID=function(){return generateGUID();}})(jQuery);var AdapterJS=AdapterJS||{};if(typeof exports!=='undefined'){module.exports=AdapterJS;} +AdapterJS.options=AdapterJS.options||{};AdapterJS.VERSION='0.13.3';AdapterJS.onwebrtcready=AdapterJS.onwebrtcready||function(isUsingPlugin){};AdapterJS._onwebrtcreadies=[];AdapterJS.webRTCReady=function(callback){if(typeof callback!=='function'){throw new Error('Callback provided is not a function');} +if(true===AdapterJS.onwebrtcreadyDone){callback(null!==AdapterJS.WebRTCPlugin.plugin);}else{AdapterJS._onwebrtcreadies.push(callback);}};AdapterJS.WebRTCPlugin=AdapterJS.WebRTCPlugin||{};AdapterJS.WebRTCPlugin.pluginInfo={prefix:'Tem',plugName:'TemWebRTCPlugin',pluginId:'plugin0',type:'application/x-temwebrtcplugin',onload:'__TemWebRTCReady0',portalLink:'http://skylink.io/plugin/',downloadLink:null,companyName:'Temasys'};if(!!navigator.platform.match(/^Mac/i)){AdapterJS.WebRTCPlugin.pluginInfo.downloadLink='http://bit.ly/1n77hco';} +else if(!!navigator.platform.match(/^Win/i)){AdapterJS.WebRTCPlugin.pluginInfo.downloadLink='http://bit.ly/1kkS4FN';} +AdapterJS.WebRTCPlugin.TAGS={NONE:'none',AUDIO:'audio',VIDEO:'video'};AdapterJS.WebRTCPlugin.pageId=Math.random().toString(36).slice(2);AdapterJS.WebRTCPlugin.plugin=null;AdapterJS.WebRTCPlugin.setLogLevel=null;AdapterJS.WebRTCPlugin.defineWebRTCInterface=null;AdapterJS.WebRTCPlugin.isPluginInstalled=null;AdapterJS.WebRTCPlugin.pluginInjectionInterval=null;AdapterJS.WebRTCPlugin.injectPlugin=null;AdapterJS.WebRTCPlugin.PLUGIN_STATES={NONE:0,INITIALIZING:1,INJECTING:2,INJECTED:3,READY:4};AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.NONE;AdapterJS.onwebrtcreadyDone=false;AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS={NONE:'NONE',ERROR:'ERROR',WARNING:'WARNING',INFO:'INFO',VERBOSE:'VERBOSE',SENSITIVE:'SENSITIVE'};AdapterJS.WebRTCPlugin.WaitForPluginReady=null;AdapterJS.WebRTCPlugin.callWhenPluginReady=null;__TemWebRTCReady0=function(){if(document.readyState==='complete'){AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY;AdapterJS.maybeThroughWebRTCReady();}else{var timer=setInterval(function(){if(document.readyState==='complete'){clearInterval(timer);AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY;AdapterJS.maybeThroughWebRTCReady();}},100);}};AdapterJS.maybeThroughWebRTCReady=function(){if(!AdapterJS.onwebrtcreadyDone){AdapterJS.onwebrtcreadyDone=true;if(AdapterJS._onwebrtcreadies.length){AdapterJS._onwebrtcreadies.forEach(function(callback){if(typeof(callback)==='function'){callback(AdapterJS.WebRTCPlugin.plugin!==null);}});}else if(typeof(AdapterJS.onwebrtcready)==='function'){AdapterJS.onwebrtcready(AdapterJS.WebRTCPlugin.plugin!==null);}}};AdapterJS.TEXT={PLUGIN:{REQUIRE_INSTALLATION:'This website requires you to install a WebRTC-enabling plugin '+'to work on this browser.',NOT_SUPPORTED:'Your browser does not support WebRTC.',BUTTON:'Install Now'},REFRESH:{REQUIRE_REFRESH:'Please refresh page',BUTTON:'Refresh Page'}};AdapterJS._iceConnectionStates={starting:'starting',checking:'checking',connected:'connected',completed:'connected',done:'completed',disconnected:'disconnected',failed:'failed',closed:'closed'};AdapterJS._iceConnectionFiredStates=[];AdapterJS.isDefined=null;AdapterJS.parseWebrtcDetectedBrowser=function(){var hasMatch=null;if((!!window.opr&&!!opr.addons)||!!window.opera||navigator.userAgent.indexOf(' OPR/')>=0){webrtcDetectedBrowser='opera';webrtcDetectedType='webkit';webrtcMinimumVersion=26;hasMatch=/OPR\/(\d+)/i.exec(navigator.userAgent)||[];webrtcDetectedVersion=parseInt(hasMatch[1],10);}else if(typeof InstallTrigger!=='undefined'){webrtcDetectedType='moz';}else if(Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor')>0){webrtcDetectedBrowser='safari';webrtcDetectedType='plugin';webrtcMinimumVersion=7;hasMatch=/version\/(\d+)/i.exec(navigator.userAgent)||[];webrtcDetectedVersion=parseInt(hasMatch[1],10);}else if(false||!!document.documentMode){webrtcDetectedBrowser='IE';webrtcDetectedType='plugin';webrtcMinimumVersion=9;hasMatch=/\brv[ :]+(\d+)/g.exec(navigator.userAgent)||[];webrtcDetectedVersion=parseInt(hasMatch[1]||'0',10);if(!webrtcDetectedVersion){hasMatch=/\bMSIE[ :]+(\d+)/g.exec(navigator.userAgent)||[];webrtcDetectedVersion=parseInt(hasMatch[1]||'0',10);}}else if(!!window.StyleMedia){webrtcDetectedType='';}else if(!!window.chrome&&!!window.chrome.webstore){webrtcDetectedType='webkit';}else if((webrtcDetectedBrowser==='chrome'||webrtcDetectedBrowser==='opera')&&!!window.CSS){webrtcDetectedBrowser='blink';} +window.webrtcDetectedBrowser=webrtcDetectedBrowser;window.webrtcDetectedVersion=webrtcDetectedVersion;window.webrtcMinimumVersion=webrtcMinimumVersion;};AdapterJS.addEvent=function(elem,evnt,func){if(elem.addEventListener){elem.addEventListener(evnt,func,false);}else if(elem.attachEvent){elem.attachEvent('on'+evnt,func);}else{elem[evnt]=func;}};AdapterJS.renderNotificationBar=function(text,buttonText,buttonLink,openNewTab,displayRefreshBar){if(document.readyState!=='complete'){return;} +var w=window;var i=document.createElement('iframe');i.name='adapterjs-alert';i.style.position='fixed';i.style.top='-41px';i.style.left=0;i.style.right=0;i.style.width='100%';i.style.height='40px';i.style.backgroundColor='#ffffe1';i.style.border='none';i.style.borderBottom='1px solid #888888';i.style.zIndex='9999999';if(typeof i.style.webkitTransition==='string'){i.style.webkitTransition='all .5s ease-out';}else if(typeof i.style.transition==='string'){i.style.transition='all .5s ease-out';} +document.body.appendChild(i);var c=(i.contentWindow)?i.contentWindow:(i.contentDocument.document)?i.contentDocument.document:i.contentDocument;c.document.open();c.document.write(''+text+'');if(buttonText&&buttonLink){c.document.write('');c.document.close();AdapterJS.addEvent(c.document.getElementById('okay'),'click',function(e){if(!!displayRefreshBar){AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION?AdapterJS.TEXT.EXTENSION.REQUIRE_REFRESH:AdapterJS.TEXT.REFRESH.REQUIRE_REFRESH,AdapterJS.TEXT.REFRESH.BUTTON,'javascript:location.reload()');} +window.open(buttonLink,!!openNewTab?'_blank':'_top');e.preventDefault();try{e.cancelBubble=true;}catch(error){} +var pluginInstallInterval=setInterval(function(){if(!isIE){navigator.plugins.refresh(false);} +AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,function(){clearInterval(pluginInstallInterval);AdapterJS.WebRTCPlugin.defineWebRTCInterface();},function(){});},500);});AdapterJS.addEvent(c.document.getElementById('cancel'),'click',function(e){w.document.body.removeChild(i);});}else{c.document.close();} +setTimeout(function(){if(typeof i.style.webkitTransform==='string'){i.style.webkitTransform='translateY(40px)';}else if(typeof i.style.transform==='string'){i.style.transform='translateY(40px)';}else{i.style.top='0px';}},300);};webrtcDetectedType=null;checkMediaDataChannelSettings=function(peerBrowserAgent,peerBrowserVersion,callback,constraints){if(typeof callback!=='function'){return;} +var beOfferer=true;var isLocalFirefox=webrtcDetectedBrowser==='firefox';var isLocalFirefoxInterop=webrtcDetectedType==='moz'&&webrtcDetectedVersion>30;var isPeerFirefox=peerBrowserAgent==='firefox';var isPeerFirefoxInterop=peerBrowserAgent==='firefox'&&((peerBrowserVersion)?(peerBrowserVersion>30):false);if((isLocalFirefox&&isPeerFirefox)||(isLocalFirefoxInterop)){try{delete constraints.mandatory.MozDontOfferDataChannel;}catch(error){console.error('Failed deleting MozDontOfferDataChannel');console.error(error);}}else if((isLocalFirefox&&!isPeerFirefox)){constraints.mandatory.MozDontOfferDataChannel=true;} +if(!isLocalFirefox){for(var prop in constraints.mandatory){if(constraints.mandatory.hasOwnProperty(prop)){if(prop.indexOf('Moz')!==-1){delete constraints.mandatory[prop];}}}} +if(isLocalFirefox&&!isPeerFirefox&&!isLocalFirefoxInterop){beOfferer=false;} +callback(beOfferer,constraints);};checkIceConnectionState=function(peerId,iceConnectionState,callback){if(typeof callback!=='function'){console.warn('No callback specified in checkIceConnectionState. Aborted.');return;} +peerId=(peerId)?peerId:'peer';if(!AdapterJS._iceConnectionFiredStates[peerId]||iceConnectionState===AdapterJS._iceConnectionStates.disconnected||iceConnectionState===AdapterJS._iceConnectionStates.failed||iceConnectionState===AdapterJS._iceConnectionStates.closed){AdapterJS._iceConnectionFiredStates[peerId]=[];} +iceConnectionState=AdapterJS._iceConnectionStates[iceConnectionState];if(AdapterJS._iceConnectionFiredStates[peerId].indexOf(iceConnectionState)<0){AdapterJS._iceConnectionFiredStates[peerId].push(iceConnectionState);if(iceConnectionState===AdapterJS._iceConnectionStates.connected){setTimeout(function(){AdapterJS._iceConnectionFiredStates[peerId].push(AdapterJS._iceConnectionStates.done);callback(AdapterJS._iceConnectionStates.done);},1000);} +callback(iceConnectionState);} +return;};createIceServer=null;createIceServers=null;RTCPeerConnection=null;RTCSessionDescription=(typeof RTCSessionDescription==='function')?RTCSessionDescription:null;RTCIceCandidate=(typeof RTCIceCandidate==='function')?RTCIceCandidate:null;getUserMedia=null;attachMediaStream=null;reattachMediaStream=null;webrtcDetectedBrowser=null;webrtcDetectedVersion=null;webrtcMinimumVersion=null;if(navigator.mozGetUserMedia||navigator.webkitGetUserMedia||(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/))){(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0?'m='+part:part).trim()+'\r\n';});};SDPUtils.matchPrefix=function(blob,prefix){return SDPUtils.splitLines(blob).filter(function(line){return line.indexOf(prefix)===0;});};SDPUtils.parseCandidate=function(line){var parts;if(line.indexOf('a=candidate:')===0){parts=line.substring(12).split(' ');}else{parts=line.substring(10).split(' ');} +var candidate={foundation:parts[0],component:parts[1],protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],port:parseInt(parts[5],10),type:parts[7]};for(var i=8;i-1){parts.attribute=line.substr(sp+1,colon-sp-1);parts.value=line.substr(colon+1);}else{parts.attribute=line.substr(sp+1);} +return parts;};SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var fpLine=lines.filter(function(line){return line.indexOf('a=fingerprint:')===0;})[0].substr(14);var dtlsParameters={role:'auto',fingerprints:[{algorithm:fpLine.split(' ')[0],value:fpLine.split(' ')[1]}]};return dtlsParameters;};SDPUtils.writeDtlsParameters=function(params,setupType){var sdp='a=setup:'+setupType+'\r\n';params.fingerprints.forEach(function(fp){sdp+='a=fingerprint:'+fp.algorithm+' '+fp.value+'\r\n';});return sdp;};SDPUtils.getIceParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var iceParameters={usernameFragment:lines.filter(function(line){return line.indexOf('a=ice-ufrag:')===0;})[0].substr(12),password:lines.filter(function(line){return line.indexOf('a=ice-pwd:')===0;})[0].substr(10)};return iceParameters;};SDPUtils.writeIceParameters=function(params){return'a=ice-ufrag:'+params.usernameFragment+'\r\n'+'a=ice-pwd:'+params.password+'\r\n';};SDPUtils.parseRtpParameters=function(mediaSection){var description={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]};var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');for(var i=3;i0?'9':'0';sdp+=' UDP/TLS/RTP/SAVPF ';sdp+=caps.codecs.map(function(codec){if(codec.preferredPayloadType!==undefined){return codec.preferredPayloadType;} +return codec.payloadType;}).join(' ')+'\r\n';sdp+='c=IN IP4 0.0.0.0\r\n';sdp+='a=rtcp:9 IN IP4 0.0.0.0\r\n';caps.codecs.forEach(function(codec){sdp+=SDPUtils.writeRtpMap(codec);sdp+=SDPUtils.writeFmtp(codec);sdp+=SDPUtils.writeRtcpFb(codec);});sdp+='a=rtcp-mux\r\n';return sdp;};SDPUtils.parseRtpEncodingParameters=function(mediaSection){var encodingParameters=[];var description=SDPUtils.parseRtpParameters(mediaSection);var hasRed=description.fecMechanisms.indexOf('RED')!==-1;var hasUlpfec=description.fecMechanisms.indexOf('ULPFEC')!==-1;var ssrcs=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='cname';});var primarySsrc=ssrcs.length>0&&ssrcs[0].ssrc;var secondarySsrc;var flows=SDPUtils.matchPrefix(mediaSection,'a=ssrc-group:FID').map(function(line){var parts=line.split(' ');parts.shift();return parts.map(function(part){return parseInt(part,10);});});if(flows.length>0&&flows[0].length>1&&flows[0][0]===primarySsrc){secondarySsrc=flows[0][1];} +description.codecs.forEach(function(codec){if(codec.name.toUpperCase()==='RTX'&&codec.parameters.apt){var encParam={ssrc:primarySsrc,codecPayloadType:parseInt(codec.parameters.apt,10),rtx:{payloadType:codec.payloadType,ssrc:secondarySsrc}};encodingParameters.push(encParam);if(hasRed){encParam=JSON.parse(JSON.stringify(encParam));encParam.fec={ssrc:secondarySsrc,mechanism:hasUlpfec?'red+ulpfec':'red'};encodingParameters.push(encParam);}}});if(encodingParameters.length===0&&primarySsrc){encodingParameters.push({ssrc:primarySsrc});} +var bandwidth=SDPUtils.matchPrefix(mediaSection,'b=');if(bandwidth.length){if(bandwidth[0].indexOf('b=TIAS:')===0){bandwidth=parseInt(bandwidth[0].substr(7),10);}else if(bandwidth[0].indexOf('b=AS:')===0){bandwidth=parseInt(bandwidth[0].substr(5),10);} +encodingParameters.forEach(function(params){params.maxBitrate=bandwidth;});} +return encodingParameters;};SDPUtils.writeSessionBoilerplate=function(){return'v=0\r\n'+'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n'+'s=-\r\n'+'t=0 0\r\n';};SDPUtils.writeMediaSection=function(transceiver,caps,type,stream){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),type==='offer'?'actpass':'active');sdp+='a=mid:'+transceiver.mid+'\r\n';if(transceiver.rtpSender&&transceiver.rtpReceiver){sdp+='a=sendrecv\r\n';}else if(transceiver.rtpSender){sdp+='a=sendonly\r\n';}else if(transceiver.rtpReceiver){sdp+='a=recvonly\r\n';}else{sdp+='a=inactive\r\n';} +if(transceiver.rtpSender){var msid='msid:'+stream.id+' '+ +transceiver.rtpSender.track.id+'\r\n';sdp+='a='+msid;sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' '+msid;} +sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';return sdp;};SDPUtils.getDirection=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);for(var i=0;i0&&typeof selector==='function'){return origGetStats(selector,successCallback);} +var fixChromeStats_=function(response){var standardReport={};var reports=response.result();reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name);});standardReport[standardStats.id]=standardStats;});return standardReport;};var makeMapStats=function(stats,legacyStats){var map=new Map(Object.keys(stats).map(function(key){return[key,stats[key]];}));legacyStats=legacyStats||stats;Object.keys(legacyStats).forEach(function(key){map[key]=legacyStats[key];});return map;};if(arguments.length>=2){var successCallbackWrapper_=function(response){args[1](makeMapStats(fixChromeStats_(response)));};return origGetStats.apply(this,[successCallbackWrapper_,arguments[0]]);} +return new Promise(function(resolve,reject){if(args.length===1&&typeof selector==='object'){origGetStats.apply(self,[function(response){resolve(makeMapStats(fixChromeStats_(response)));},reject]);}else{origGetStats.apply(self,[function(response){resolve(makeMapStats(fixChromeStats_(response),response.result()));},reject]);}}).then(successCallback,errorCallback);};return pc;};window.RTCPeerConnection.prototype=webkitRTCPeerConnection.prototype;if(webkitRTCPeerConnection.generateCertificate){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function(){return webkitRTCPeerConnection.generateCertificate;}});} +['createOffer','createAnswer'].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var self=this;if(arguments.length<1||(arguments.length===1&&typeof arguments[0]==='object')){var opts=arguments.length===1?arguments[0]:undefined;return new Promise(function(resolve,reject){nativeMethod.apply(self,[resolve,reject,opts]);});} +return nativeMethod.apply(this,arguments);};});if(browserDetails.version<51){['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){var args=arguments;var self=this;var promise=new Promise(function(resolve,reject){nativeMethod.apply(self,[args[0],resolve,reject]);});if(args.length<2){return promise;} +return promise.then(function(){args[1].apply(null,[]);},function(err){if(args.length>=3){args[2].apply(null,[err]);}});};});} +['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=webkitRTCPeerConnection.prototype[method];webkitRTCPeerConnection.prototype[method]=function(){arguments[0]=new((method==='addIceCandidate')?RTCIceCandidate:RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=RTCPeerConnection.prototype.addIceCandidate;RTCPeerConnection.prototype.addIceCandidate=function(){return arguments[0]===null?Promise.resolve():nativeAddIceCandidate.apply(this,arguments);};}};module.exports={shimMediaStream:chromeShim.shimMediaStream,shimOnTrack:chromeShim.shimOnTrack,shimSourceObject:chromeShim.shimSourceObject,shimPeerConnection:chromeShim.shimPeerConnection,shimGetUserMedia:require('./getusermedia')};},{"../utils.js":10,"./getusermedia":4}],4:[function(require,module,exports){'use strict';var logging=require('../utils.js').log;module.exports=function(){var constraintsToChrome_=function(c){if(typeof c!=='object'||c.mandatory||c.optional){return c;} +var cc={};Object.keys(c).forEach(function(key){if(key==='require'||key==='advanced'||key==='mediaSource'){return;} +var r=(typeof c[key]==='object')?c[key]:{ideal:c[key]};if(r.exact!==undefined&&typeof r.exact==='number'){r.min=r.max=r.exact;} +var oldname_=function(prefix,name){if(prefix){return prefix+name.charAt(0).toUpperCase()+name.slice(1);} +return(name==='deviceId')?'sourceId':name;};if(r.ideal!==undefined){cc.optional=cc.optional||[];var oc={};if(typeof r.ideal==='number'){oc[oldname_('min',key)]=r.ideal;cc.optional.push(oc);oc={};oc[oldname_('max',key)]=r.ideal;cc.optional.push(oc);}else{oc[oldname_('',key)]=r.ideal;cc.optional.push(oc);}} +if(r.exact!==undefined&&typeof r.exact!=='number'){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_('',key)]=r.exact;}else{['min','max'].forEach(function(mix){if(r[mix]!==undefined){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_(mix,key)]=r[mix];}});}});if(c.advanced){cc.optional=(cc.optional||[]).concat(c.advanced);} +return cc;};var shimConstraints_=function(constraints,func){constraints=JSON.parse(JSON.stringify(constraints));if(constraints&&constraints.audio){constraints.audio=constraintsToChrome_(constraints.audio);} +if(constraints&&typeof constraints.video==='object'){var face=constraints.video.facingMode;face=face&&((typeof face==='object')?face:{ideal:face});if((face&&(face.exact==='user'||face.exact==='environment'||face.ideal==='user'||face.ideal==='environment'))&&!(navigator.mediaDevices.getSupportedConstraints&&navigator.mediaDevices.getSupportedConstraints().facingMode)){delete constraints.video.facingMode;if(face.exact==='environment'||face.ideal==='environment'){return navigator.mediaDevices.enumerateDevices().then(function(devices){devices=devices.filter(function(d){return d.kind==='videoinput';});var back=devices.find(function(d){return d.label.toLowerCase().indexOf('back')!==-1;})||(devices.length&&devices[devices.length-1]);if(back){constraints.video.deviceId=face.exact?{exact:back.deviceId}:{ideal:back.deviceId};} +constraints.video=constraintsToChrome_(constraints.video);logging('chrome: '+JSON.stringify(constraints));return func(constraints);});}} +constraints.video=constraintsToChrome_(constraints.video);} +logging('chrome: '+JSON.stringify(constraints));return func(constraints);};var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError',ConstraintNotSatisfiedError:'OverconstrainedError'}[e.name]||e.name,message:e.message,constraint:e.constraintName,toString:function(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function(constraints,onSuccess,onError){shimConstraints_(constraints,function(c){navigator.webkitGetUserMedia(c,onSuccess,function(e){onError(shimError_(e));});});};navigator.getUserMedia=getUserMedia_;var getUserMediaPromise_=function(constraints){return new Promise(function(resolve,reject){navigator.getUserMedia(constraints,resolve,reject);});};if(!navigator.mediaDevices){navigator.mediaDevices={getUserMedia:getUserMediaPromise_,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:'audioinput',video:'videoinput'};return MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:''};}));});});}};} +if(!navigator.mediaDevices.getUserMedia){navigator.mediaDevices.getUserMedia=function(constraints){return getUserMediaPromise_(constraints);};}else{var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(cs){return shimConstraints_(cs,function(c){return origGetUserMedia(c).catch(function(e){return Promise.reject(shimError_(e));});});};} +if(typeof navigator.mediaDevices.addEventListener==='undefined'){navigator.mediaDevices.addEventListener=function(){logging('Dummy mediaDevices.addEventListener called.');};} +if(typeof navigator.mediaDevices.removeEventListener==='undefined'){navigator.mediaDevices.removeEventListener=function(){logging('Dummy mediaDevices.removeEventListener called.');};}};},{"../utils.js":10}],5:[function(require,module,exports){'use strict';var SDPUtils=require('sdp');var edgeShim={shimPeerConnection:function(){if(window.RTCIceGatherer){if(!window.RTCIceCandidate){window.RTCIceCandidate=function(args){return args;};} +if(!window.RTCSessionDescription){window.RTCSessionDescription=function(args){return args;};}} +window.RTCPeerConnection=function(config){var self=this;var _eventTarget=document.createDocumentFragment();['addEventListener','removeEventListener','dispatchEvent'].forEach(function(method){self[method]=_eventTarget[method].bind(_eventTarget);});this.onicecandidate=null;this.onaddstream=null;this.ontrack=null;this.onremovestream=null;this.onsignalingstatechange=null;this.oniceconnectionstatechange=null;this.onnegotiationneeded=null;this.ondatachannel=null;this.localStreams=[];this.remoteStreams=[];this.getLocalStreams=function(){return self.localStreams;};this.getRemoteStreams=function(){return self.remoteStreams;};this.localDescription=new RTCSessionDescription({type:'',sdp:''});this.remoteDescription=new RTCSessionDescription({type:'',sdp:''});this.signalingState='stable';this.iceConnectionState='new';this.iceGatheringState='new';this.iceOptions={gatherPolicy:'all',iceServers:[]};if(config&&config.iceTransportPolicy){switch(config.iceTransportPolicy){case'all':case'relay':this.iceOptions.gatherPolicy=config.iceTransportPolicy;break;case'none':throw new TypeError('iceTransportPolicy "none" not supported');default:break;}} +this.usingBundle=config&&config.bundlePolicy==='max-bundle';if(config&&config.iceServers){var iceServers=JSON.parse(JSON.stringify(config.iceServers));this.iceOptions.iceServers=iceServers.filter(function(server){if(server&&server.urls){var urls=server.urls;if(typeof urls==='string'){urls=[urls];} +urls=urls.filter(function(url){return url.indexOf('turn:')===0&&url.indexOf('transport=udp')!==-1&&url.indexOf('turn:[')===-1;})[0];return!!urls;} +return false;});} +this.transceivers=[];this._localIceCandidatesBuffer=[];};window.RTCPeerConnection.prototype._emitBufferedCandidates=function(){var self=this;var sections=SDPUtils.splitSections(self.localDescription.sdp);this._localIceCandidatesBuffer.forEach(function(event){var end=!event.candidate||Object.keys(event.candidate).length===0;if(end){for(var j=1;j-1){this.localStreams.splice(idx,1);this._maybeFireNegotiationNeeded();}};window.RTCPeerConnection.prototype.getSenders=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpSender;}).map(function(transceiver){return transceiver.rtpSender;});};window.RTCPeerConnection.prototype.getReceivers=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpReceiver;}).map(function(transceiver){return transceiver.rtpReceiver;});};window.RTCPeerConnection.prototype._getCommonCapabilities=function(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};localCapabilities.codecs.forEach(function(lCodec){for(var i=0;i0;sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver=self.transceivers[sdpMLineIndex];var iceGatherer=transceiver.iceGatherer;var iceTransport=transceiver.iceTransport;var dtlsTransport=transceiver.dtlsTransport;var localCapabilities=transceiver.localCapabilities;var remoteCapabilities=transceiver.remoteCapabilities;var rejected=mediaSection.split('\n',1)[0].split(' ',2)[1]==='0';if(!rejected){var remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);if(isIceLite){var cands=SDPUtils.matchPrefix(mediaSection,'a=candidate:').map(function(cand){return SDPUtils.parseCandidate(cand);}).filter(function(cand){return cand.component==='1';});if(cands.length){iceTransport.setRemoteCandidates(cands);}} +var remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);if(isIceLite){remoteDtlsParameters.role='server';} +if(!self.usingBundle||sdpMLineIndex===0){iceTransport.start(iceGatherer,remoteIceParameters,isIceLite?'controlling':'controlled');dtlsTransport.start(remoteDtlsParameters);} +var params=self._getCommonCapabilities(localCapabilities,remoteCapabilities);self._transceive(transceiver,params.codecs.length>0,false);}});} +this.localDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-local-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} +var hasCallback=arguments.length>1&&typeof arguments[1]==='function';if(hasCallback){var cb=arguments[1];window.setTimeout(function(){cb();if(self.iceGatheringState==='new'){self.iceGatheringState='gathering';} +self._emitBufferedCandidates();},0);} +var p=Promise.resolve();p.then(function(){if(!hasCallback){if(self.iceGatheringState==='new'){self.iceGatheringState='gathering';} +window.setTimeout(self._emitBufferedCandidates.bind(self),500);}});return p;};window.RTCPeerConnection.prototype.setRemoteDescription=function(description){var self=this;var stream=new MediaStream();var receiverList=[];var sections=SDPUtils.splitSections(description.sdp);var sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;this.usingBundle=SDPUtils.matchPrefix(sessionpart,'a=group:BUNDLE ').length>0;sections.forEach(function(mediaSection,sdpMLineIndex){var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].substr(2).split(' ');var kind=mline[0];var rejected=mline[1]==='0';var direction=SDPUtils.getDirection(mediaSection,sessionpart);var transceiver;var iceGatherer;var iceTransport;var dtlsTransport;var rtpSender;var rtpReceiver;var sendEncodingParameters;var recvEncodingParameters;var localCapabilities;var track;var remoteCapabilities=SDPUtils.parseRtpParameters(mediaSection);var remoteIceParameters;var remoteDtlsParameters;if(!rejected){remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);remoteDtlsParameters.role='client';} +recvEncodingParameters=SDPUtils.parseRtpEncodingParameters(mediaSection);var mid=SDPUtils.matchPrefix(mediaSection,'a=mid:');if(mid.length){mid=mid[0].substr(6);}else{mid=SDPUtils.generateIdentifier();} +var cname;var remoteSsrc=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(obj){return obj.attribute==='cname';})[0];if(remoteSsrc){cname=remoteSsrc.value;} +var isComplete=SDPUtils.matchPrefix(mediaSection,'a=end-of-candidates',sessionpart).length>0;var cands=SDPUtils.matchPrefix(mediaSection,'a=candidate:').map(function(cand){return SDPUtils.parseCandidate(cand);}).filter(function(cand){return cand.component==='1';});if(description.type==='offer'&&!rejected){var transports=self.usingBundle&&sdpMLineIndex>0?{iceGatherer:self.transceivers[0].iceGatherer,iceTransport:self.transceivers[0].iceTransport,dtlsTransport:self.transceivers[0].dtlsTransport}:self._createIceAndDtlsTransports(mid,sdpMLineIndex);if(isComplete){transports.iceTransport.setRemoteCandidates(cands);} +localCapabilities=RTCRtpReceiver.getCapabilities(kind);sendEncodingParameters=[{ssrc:(2*sdpMLineIndex+2)*1001}];rtpReceiver=new RTCRtpReceiver(transports.dtlsTransport,kind);track=rtpReceiver.track;receiverList.push([track,rtpReceiver]);stream.addTrack(track);if(self.localStreams.length>0&&self.localStreams[0].getTracks().length>=sdpMLineIndex){var localTrack;if(kind==='audio'){localTrack=self.localStreams[0].getAudioTracks()[0];}else if(kind==='video'){localTrack=self.localStreams[0].getVideoTracks()[0];} +if(localTrack){rtpSender=new RTCRtpSender(localTrack,transports.dtlsTransport);}} +self.transceivers[sdpMLineIndex]={iceGatherer:transports.iceGatherer,iceTransport:transports.iceTransport,dtlsTransport:transports.dtlsTransport,localCapabilities:localCapabilities,remoteCapabilities:remoteCapabilities,rtpSender:rtpSender,rtpReceiver:rtpReceiver,kind:kind,mid:mid,cname:cname,sendEncodingParameters:sendEncodingParameters,recvEncodingParameters:recvEncodingParameters};self._transceive(self.transceivers[sdpMLineIndex],false,direction==='sendrecv'||direction==='sendonly');}else if(description.type==='answer'&&!rejected){transceiver=self.transceivers[sdpMLineIndex];iceGatherer=transceiver.iceGatherer;iceTransport=transceiver.iceTransport;dtlsTransport=transceiver.dtlsTransport;rtpSender=transceiver.rtpSender;rtpReceiver=transceiver.rtpReceiver;sendEncodingParameters=transceiver.sendEncodingParameters;localCapabilities=transceiver.localCapabilities;self.transceivers[sdpMLineIndex].recvEncodingParameters=recvEncodingParameters;self.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities;self.transceivers[sdpMLineIndex].cname=cname;if((isIceLite||isComplete)&&cands.length){iceTransport.setRemoteCandidates(cands);} +if(!self.usingBundle||sdpMLineIndex===0){iceTransport.start(iceGatherer,remoteIceParameters,'controlling');dtlsTransport.start(remoteDtlsParameters);} +self._transceive(transceiver,direction==='sendrecv'||direction==='recvonly',direction==='sendrecv'||direction==='sendonly');if(rtpReceiver&&(direction==='sendrecv'||direction==='sendonly')){track=rtpReceiver.track;receiverList.push([track,rtpReceiver]);stream.addTrack(track);}else{delete transceiver.rtpReceiver;}}});this.remoteDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-remote-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} +if(stream.getTracks().length){self.remoteStreams.push(stream);window.setTimeout(function(){var event=new Event('addstream');event.stream=stream;self.dispatchEvent(event);if(self.onaddstream!==null){window.setTimeout(function(){self.onaddstream(event);},0);} +receiverList.forEach(function(item){var track=item[0];var receiver=item[1];var trackEvent=new Event('track');trackEvent.track=track;trackEvent.receiver=receiver;trackEvent.streams=[stream];self.dispatchEvent(event);if(self.ontrack!==null){window.setTimeout(function(){self.ontrack(trackEvent);},0);}});},0);} +if(arguments.length>1&&typeof arguments[1]==='function'){window.setTimeout(arguments[1],0);} +return Promise.resolve();};window.RTCPeerConnection.prototype.close=function(){this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport){transceiver.iceTransport.stop();} +if(transceiver.dtlsTransport){transceiver.dtlsTransport.stop();} +if(transceiver.rtpSender){transceiver.rtpSender.stop();} +if(transceiver.rtpReceiver){transceiver.rtpReceiver.stop();}});this._updateSignalingState('closed');};window.RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState;var event=new Event('signalingstatechange');this.dispatchEvent(event);if(this.onsignalingstatechange!==null){this.onsignalingstatechange(event);}};window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){var event=new Event('negotiationneeded');this.dispatchEvent(event);if(this.onnegotiationneeded!==null){this.onnegotiationneeded(event);}};window.RTCPeerConnection.prototype._updateConnectionState=function(){var self=this;var newState;var states={'new':0,closed:0,connecting:0,checking:0,connected:0,completed:0,failed:0};this.transceivers.forEach(function(transceiver){states[transceiver.iceTransport.state]++;states[transceiver.dtlsTransport.state]++;});states.connected+=states.completed;newState='new';if(states.failed>0){newState='failed';}else if(states.connecting>0||states.checking>0){newState='connecting';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0||states.completed>0){newState='connected';} +if(newState!==self.iceConnectionState){self.iceConnectionState=newState;var event=new Event('iceconnectionstatechange');this.dispatchEvent(event);if(this.oniceconnectionstatechange!==null){this.oniceconnectionstatechange(event);}}};window.RTCPeerConnection.prototype.createOffer=function(){var self=this;if(this._pendingOffer){throw new Error('createOffer called while there is a pending offer.');} +var offerOptions;if(arguments.length===1&&typeof arguments[0]!=='function'){offerOptions=arguments[0];}else if(arguments.length===3){offerOptions=arguments[2];} +var tracks=[];var numAudioTracks=0;var numVideoTracks=0;if(this.localStreams.length){numAudioTracks=this.localStreams[0].getAudioTracks().length;numVideoTracks=this.localStreams[0].getVideoTracks().length;} +if(offerOptions){if(offerOptions.mandatory||offerOptions.optional){throw new TypeError('Legacy mandatory/optional constraints not supported.');} +if(offerOptions.offerToReceiveAudio!==undefined){numAudioTracks=offerOptions.offerToReceiveAudio;} +if(offerOptions.offerToReceiveVideo!==undefined){numVideoTracks=offerOptions.offerToReceiveVideo;}} +if(this.localStreams.length){this.localStreams[0].getTracks().forEach(function(track){tracks.push({kind:track.kind,track:track,wantReceive:track.kind==='audio'?numAudioTracks>0:numVideoTracks>0});if(track.kind==='audio'){numAudioTracks--;}else if(track.kind==='video'){numVideoTracks--;}});} +while(numAudioTracks>0||numVideoTracks>0){if(numAudioTracks>0){tracks.push({kind:'audio',wantReceive:true});numAudioTracks--;} +if(numVideoTracks>0){tracks.push({kind:'video',wantReceive:true});numVideoTracks--;}} +var sdp=SDPUtils.writeSessionBoilerplate();var transceivers=[];tracks.forEach(function(mline,sdpMLineIndex){var track=mline.track;var kind=mline.kind;var mid=SDPUtils.generateIdentifier();var transports=self.usingBundle&&sdpMLineIndex>0?{iceGatherer:transceivers[0].iceGatherer,iceTransport:transceivers[0].iceTransport,dtlsTransport:transceivers[0].dtlsTransport}:self._createIceAndDtlsTransports(mid,sdpMLineIndex);var localCapabilities=RTCRtpSender.getCapabilities(kind);var rtpSender;var rtpReceiver;var sendEncodingParameters=[{ssrc:(2*sdpMLineIndex+1)*1001}];if(track){rtpSender=new RTCRtpSender(track,transports.dtlsTransport);} +if(mline.wantReceive){rtpReceiver=new RTCRtpReceiver(transports.dtlsTransport,kind);} +transceivers[sdpMLineIndex]={iceGatherer:transports.iceGatherer,iceTransport:transports.iceTransport,dtlsTransport:transports.dtlsTransport,localCapabilities:localCapabilities,remoteCapabilities:null,rtpSender:rtpSender,rtpReceiver:rtpReceiver,kind:kind,mid:mid,sendEncodingParameters:sendEncodingParameters,recvEncodingParameters:null};});if(this.usingBundle){sdp+='a=group:BUNDLE '+transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} +tracks.forEach(function(mline,sdpMLineIndex){var transceiver=transceivers[sdpMLineIndex];sdp+=SDPUtils.writeMediaSection(transceiver,transceiver.localCapabilities,'offer',self.localStreams[0]);});this._pendingOffer=transceivers;var desc=new RTCSessionDescription({type:'offer',sdp:sdp});if(arguments.length&&typeof arguments[0]==='function'){window.setTimeout(arguments[0],0,desc);} +return Promise.resolve(desc);};window.RTCPeerConnection.prototype.createAnswer=function(){var self=this;var sdp=SDPUtils.writeSessionBoilerplate();if(this.usingBundle){sdp+='a=group:BUNDLE '+this.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} +this.transceivers.forEach(function(transceiver){var commonCapabilities=self._getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);sdp+=SDPUtils.writeMediaSection(transceiver,commonCapabilities,'answer',self.localStreams[0]);});var desc=new RTCSessionDescription({type:'answer',sdp:sdp});if(arguments.length&&typeof arguments[0]==='function'){window.setTimeout(arguments[0],0,desc);} +return Promise.resolve(desc);};window.RTCPeerConnection.prototype.addIceCandidate=function(candidate){if(candidate===null){this.transceivers.forEach(function(transceiver){transceiver.iceTransport.addRemoteCandidate({});});}else{var mLineIndex=candidate.sdpMLineIndex;if(candidate.sdpMid){for(var i=0;i0?SDPUtils.parseCandidate(candidate.candidate):{};if(cand.protocol==='tcp'&&(cand.port===0||cand.port===9)){return;} +if(cand.component!=='1'){return;} +if(cand.type==='endOfCandidates'){cand={};} +transceiver.iceTransport.addRemoteCandidate(cand);var sections=SDPUtils.splitSections(this.remoteDescription.sdp);sections[mLineIndex+1]+=(cand.type?candidate.candidate.trim():'a=end-of-candidates')+'\r\n';this.remoteDescription.sdp=sections.join('');}} +if(arguments.length>1&&typeof arguments[1]==='function'){window.setTimeout(arguments[1],0);} +return Promise.resolve();};window.RTCPeerConnection.prototype.getStats=function(){var promises=[];this.transceivers.forEach(function(transceiver){['rtpSender','rtpReceiver','iceGatherer','iceTransport','dtlsTransport'].forEach(function(method){if(transceiver[method]){promises.push(transceiver[method].getStats());}});});var cb=arguments.length>1&&typeof arguments[1]==='function'&&arguments[1];return new Promise(function(resolve){var results=new Map();Promise.all(promises).then(function(res){res.forEach(function(result){Object.keys(result).forEach(function(id){results.set(id,result[id]);results[id]=result[id];});});if(cb){window.setTimeout(cb,0,results);} +resolve(results);});});};}};module.exports={shimPeerConnection:edgeShim.shimPeerConnection,shimGetUserMedia:require('./getusermedia')};},{"./getusermedia":6,"sdp":1}],6:[function(require,module,exports){'use strict';module.exports=function(){var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError'}[e.name]||e.name,message:e.message,constraint:e.constraint,toString:function(){return this.name;}};};var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return origGetUserMedia(c).catch(function(e){return Promise.reject(shimError_(e));});};};},{}],7:[function(require,module,exports){'use strict';var browserDetails=require('../utils').browserDetails;var firefoxShim={shimOnTrack:function(){if(typeof window==='object'&&window.RTCPeerConnection&&!('ontrack'in +window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function(){return this._ontrack;},set:function(f){if(this._ontrack){this.removeEventListener('track',this._ontrack);this.removeEventListener('addstream',this._ontrackpoly);} +this.addEventListener('track',this._ontrack=f);this.addEventListener('addstream',this._ontrackpoly=function(e){e.stream.getTracks().forEach(function(track){var event=new Event('track');event.track=track;event.receiver={track:track};event.streams=[e.stream];this.dispatchEvent(event);}.bind(this));}.bind(this));}});}},shimSourceObject:function(){if(typeof window==='object'){if(window.HTMLMediaElement&&!('srcObject'in window.HTMLMediaElement.prototype)){Object.defineProperty(window.HTMLMediaElement.prototype,'srcObject',{get:function(){return this.mozSrcObject;},set:function(stream){this.mozSrcObject=stream;}});}}},shimPeerConnection:function(){if(typeof window!=='object'||!(window.RTCPeerConnection||window.mozRTCPeerConnection)){return;} +if(!window.RTCPeerConnection){window.RTCPeerConnection=function(pcConfig,pcConstraints){if(browserDetails.version<38){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i=pos&&parseInt(match[pos],10);},detectBrowser:function(){var result={};result.browser=null;result.version=null;if(typeof window==='undefined'||!window.navigator){result.browser='Not a browser.';return result;} +if(navigator.mozGetUserMedia){result.browser='firefox';result.version=this.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1);}else if(navigator.webkitGetUserMedia){if(window.webkitRTCPeerConnection){result.browser='chrome';result.version=this.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2);}else{if(navigator.userAgent.match(/Version\/(\d+).(\d+)/)){result.browser='safari';result.version=this.extractVersion(navigator.userAgent,/AppleWebKit\/([0-9]+)\./,1);}else{result.browser='Unsupported webkit-based browser '+'with GUM support but no WebRTC support.';return result;}}}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){result.browser='edge';result.version=this.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2);}else{result.browser='Not a supported browser.';return result;} +return result;}};module.exports={log:utils.log,disableLog:utils.disableLog,browserDetails:utils.detectBrowser(),extractVersion:utils.extractVersion};},{}]},{},[2])(2)});AdapterJS.parseWebrtcDetectedBrowser();if(navigator.mozGetUserMedia){MediaStreamTrack.getSources=function(successCb){setTimeout(function(){var infos=[{kind:'audio',id:'default',label:'',facing:''},{kind:'video',id:'default',label:'',facing:''}];successCb(infos);},0);};createIceServer=function(url,username,password){console.warn('createIceServer is deprecated. It should be replaced with an application level implementation.');var iceServer=null;var urlParts=url.split(':');if(urlParts[0].indexOf('stun')===0){iceServer={urls:[url]};}else if(urlParts[0].indexOf('turn')===0){if(webrtcDetectedVersion<27){var turnUrlParts=url.split('?');if(turnUrlParts.length===1||turnUrlParts[1].indexOf('transport=udp')===0){iceServer={urls:[turnUrlParts[0]],credential:password,username:username};}}else{iceServer={urls:[url],credential:password,username:username};}} +return iceServer;};createIceServers=function(urls,username,password){console.warn('createIceServers is deprecated. It should be replaced with an application level implementation.');var iceServers=[];for(i=0;i=34){iceServers={'urls':urls,'credential':password,'username':username};}else{for(i=0;i'+' '+' '+' '+''+''+ +(AdapterJS.options.getAllCams?'':'')+'
';while(AdapterJS.WebRTCPlugin.plugin.firstChild){frag.appendChild(AdapterJS.WebRTCPlugin.plugin.firstChild);} +document.body.appendChild(frag);AdapterJS.WebRTCPlugin.plugin=document.getElementById(AdapterJS.WebRTCPlugin.pluginInfo.pluginId);}else{AdapterJS.WebRTCPlugin.plugin=document.createElement('object');AdapterJS.WebRTCPlugin.plugin.id=AdapterJS.WebRTCPlugin.pluginInfo.pluginId;if(isIE){AdapterJS.WebRTCPlugin.plugin.width='1px';AdapterJS.WebRTCPlugin.plugin.height='1px';}else{AdapterJS.WebRTCPlugin.plugin.width='0px';AdapterJS.WebRTCPlugin.plugin.height='0px';} +AdapterJS.WebRTCPlugin.plugin.type=AdapterJS.WebRTCPlugin.pluginInfo.type;AdapterJS.WebRTCPlugin.plugin.innerHTML=''+''+' '+ +(AdapterJS.options.getAllCams?'':'')+''+'';document.body.appendChild(AdapterJS.WebRTCPlugin.plugin);} +AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INJECTED;};AdapterJS.WebRTCPlugin.isPluginInstalled=function(comName,plugName,installedCb,notInstalledCb){if(!isIE){var pluginArray=navigator.plugins;for(var i=0;i=0){installedCb();return;}} +notInstalledCb();}else{try{var axo=new ActiveXObject(comName+'.'+plugName);}catch(e){notInstalledCb();return;} +installedCb();}};AdapterJS.WebRTCPlugin.defineWebRTCInterface=function(){if(AdapterJS.WebRTCPlugin.pluginState===AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY){console.error('AdapterJS - WebRTC interface has already been defined');return;} +AdapterJS.WebRTCPlugin.pluginState=AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING;AdapterJS.isDefined=function(variable){return variable!==null&&variable!==undefined;};createIceServer=function(url,username,password){var iceServer=null;var urlParts=url.split(':');if(urlParts[0].indexOf('stun')===0){iceServer={'url':url,'hasCredentials':false};}else if(urlParts[0].indexOf('turn')===0){iceServer={'url':url,'hasCredentials':true,'credential':password,'username':username};} +return iceServer;};createIceServers=function(urls,username,password){var iceServers=[];for(var i=0;i1){if(iceServers){servers.iceServers=iceServers;} +return AdapterJS.WebRTCPlugin.plugin.PeerConnection(servers);}else{var mandatory=(constraints&&constraints.mandatory)?constraints.mandatory:null;var optional=(constraints&&constraints.optional)?constraints.optional:null;return AdapterJS.WebRTCPlugin.plugin.PeerConnection(AdapterJS.WebRTCPlugin.pageId,iceServers,mandatory,optional);}};MediaStreamTrack=function(){};MediaStreamTrack.getSources=function(callback){AdapterJS.WebRTCPlugin.callWhenPluginReady(function(){AdapterJS.WebRTCPlugin.plugin.GetSources(callback);});};getUserMedia=function(constraints,successCallback,failureCallback){constraints.audio=constraints.audio||false;constraints.video=constraints.video||false;AdapterJS.WebRTCPlugin.callWhenPluginReady(function(){AdapterJS.WebRTCPlugin.plugin.getUserMedia(constraints,successCallback,failureCallback);});};window.navigator.getUserMedia=getUserMedia;if(!navigator.mediaDevices&&typeof Promise!=='undefined'){requestUserMedia=function(constraints){return new Promise(function(resolve,reject){getUserMedia(constraints,resolve,reject);});};navigator.mediaDevices={getUserMedia:requestUserMedia,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:'audioinput',video:'videoinput'};return MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],id:device.id,deviceId:device.id,groupId:''};}));});});}};} +attachMediaStream=function(element,stream){if(!element||!element.parentNode){return;} +var streamId;if(stream===null){streamId='';}else{if(typeof stream.enableSoundTracks!=='undefined'){stream.enableSoundTracks(true);} +streamId=stream.id;} +var elementId=element.id.length===0?Math.random().toString(36).slice(2):element.id;var nodeName=element.nodeName.toLowerCase();if(nodeName!=='object'){var tag;switch(nodeName){case'audio':tag=AdapterJS.WebRTCPlugin.TAGS.AUDIO;break;case'video':tag=AdapterJS.WebRTCPlugin.TAGS.VIDEO;break;default:tag=AdapterJS.WebRTCPlugin.TAGS.NONE;} +var frag=document.createDocumentFragment();var temp=document.createElement('div');var classHTML='';if(element.className){classHTML='class="'+element.className+'" ';}else if(element.attributes&&element.attributes['class']){classHTML='class="'+element.attributes['class'].value+'" ';} +temp.innerHTML=''+' '+' '+' '+' '+' '+'';while(temp.firstChild){frag.appendChild(temp.firstChild);} +var height='';var width='';if(element.clientWidth||element.clientHeight){width=element.clientWidth;height=element.clientHeight;} +else if(element.width||element.height){width=element.width;height=element.height;} +element.parentNode.insertBefore(frag,element);frag=document.getElementById(elementId);frag.width=width;frag.height=height;element.parentNode.removeChild(element);}else{var children=element.children;for(var i=0;i!==children.length;++i){if(children[i].name==='streamId'){children[i].value=streamId;break;}} +element.setStreamId(streamId);} +var newElement=document.getElementById(elementId);AdapterJS.forwardEventHandlers(newElement,element,Object.getPrototypeOf(element));return newElement;};reattachMediaStream=function(to,from){var stream=null;var children=from.children;for(var i=0;i!==children.length;++i){if(children[i].name==='streamId'){AdapterJS.WebRTCPlugin.WaitForPluginReady();stream=AdapterJS.WebRTCPlugin.plugin.getStreamWithId(AdapterJS.WebRTCPlugin.pageId,children[i].value);break;}} +if(stream!==null){return attachMediaStream(to,stream);}else{console.log('Could not find the stream associated with this element');}};window.attachMediaStream=attachMediaStream;window.reattachMediaStream=reattachMediaStream;window.getUserMedia=getUserMedia;AdapterJS.attachMediaStream=attachMediaStream;AdapterJS.reattachMediaStream=reattachMediaStream;AdapterJS.getUserMedia=getUserMedia;AdapterJS.forwardEventHandlers=function(destElem,srcElem,prototype){properties=Object.getOwnPropertyNames(prototype);for(var prop in properties){if(prop){propName=properties[prop];if(typeof propName.slice==='function'&&propName.slice(0,2)==='on'&&typeof srcElem[propName]==='function'){AdapterJS.addEvent(destElem,propName.slice(2),srcElem[propName]);}}} +var subPrototype=Object.getPrototypeOf(prototype);if(!!subPrototype){AdapterJS.forwardEventHandlers(destElem,srcElem,subPrototype);}};RTCIceCandidate=function(candidate){if(!candidate.sdpMid){candidate.sdpMid='';} +AdapterJS.WebRTCPlugin.WaitForPluginReady();return AdapterJS.WebRTCPlugin.plugin.ConstructIceCandidate(candidate.sdpMid,candidate.sdpMLineIndex,candidate.candidate);};AdapterJS.addEvent(document,'readystatechange',AdapterJS.WebRTCPlugin.injectPlugin);AdapterJS.WebRTCPlugin.injectPlugin();};AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb=AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb||function(){AdapterJS.addEvent(document,'readystatechange',AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv);AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv();};AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCbPriv=function(){if(AdapterJS.options.hidePluginInstallPrompt){return;} +var downloadLink=AdapterJS.WebRTCPlugin.pluginInfo.downloadLink;if(downloadLink){var popupString;if(AdapterJS.WebRTCPlugin.pluginInfo.portalLink){popupString='This website requires you to install the '+' '+AdapterJS.WebRTCPlugin.pluginInfo.companyName+' WebRTC Plugin'+' to work on this browser.';}else{popupString=AdapterJS.TEXT.PLUGIN.REQUIRE_INSTALLATION;} +AdapterJS.renderNotificationBar(popupString,AdapterJS.TEXT.PLUGIN.BUTTON,downloadLink);}else{AdapterJS.renderNotificationBar(AdapterJS.TEXT.PLUGIN.NOT_SUPPORTED);}};AdapterJS.WebRTCPlugin.isPluginInstalled(AdapterJS.WebRTCPlugin.pluginInfo.prefix,AdapterJS.WebRTCPlugin.pluginInfo.plugName,AdapterJS.WebRTCPlugin.defineWebRTCInterface,AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb);} \ No newline at end of file diff --git a/html5/verto/video_demo/verto.js b/html5/verto/video_demo/verto.js index e0ee4aec19..7b6adc13b5 100644 --- a/html5/verto/video_demo/verto.js +++ b/html5/verto/video_demo/verto.js @@ -101,14 +101,14 @@ function full_screen(name) { } $("#" + video_screen).resize(function(e) { - console.log("video size changed to " + $("#" + video_screen).width() + "x" + $("#" + video_screen).height()); + //console.log("video size changed to " + $("#" + video_screen).width() + "x" + $("#" + video_screen).height()); - if ($("#" + video_screen).width() > $(window).width()) { + //if ($("#" + video_screen).width() > $(window).width()) { //resize(false); - $("#" + video_screen).width("100%"); - $("#" + video_screen).height("100%"); - } - + //$("#" + video_screen).width("100%"); + //$("#" + video_screen).height("100%"); + //} + real_size(); }); @@ -128,13 +128,41 @@ function resize(up) { } + +$( window ).resize(function() { + real_size(); +}); + function real_size() { + - $("#" + video_screen).width(""); - $("#" + video_screen).height(""); + /* temasys hack */ + setTimeout(function() { + $("#" + video_screen).width(""); + $("#" + video_screen).height(""); + + var w = $("#" + video_screen).width(); + var h = $("#" + video_screen).height(); + + var new_w; + var new_h; + var aspect = 1920 / 1080; /*temasys doesn't provide video width hack aspect to wide screen*/ + + if (w > h) { + new_w = window.innerWidth; + new_h = Math.round(window.innerWidth / aspect); + } else { + new_h = window.innerHeight; + new_w = Math.round(window.innerHeight / aspect); + } + + $("#" + video_screen).width(new_w); + $("#" + video_screen).height(new_h); + }, 500); + + console.log("video size changed to fit screen"); - console.log("video size changed to natural default"); } @@ -202,8 +230,19 @@ function check_vid() { return use_vid; } +var DISABLE_SPEED_TEST = true; + function do_speed_test(fn) { + + + if (DISABLE_SPEED_TEST) { + if (fn) { + fn(); + } + return; + } + goto_page("bwtest"); vertoHandle.rpcClient.speedTest(1024 * 256, function(e, obj) { @@ -486,6 +525,7 @@ var callbacks = { check_vid_res(); $("#ansbtn").click(function() { + console.error("WTF", cur_call, d); cur_call.answer({ useStereo: $("#use_stereo").is(':checked'), callee_id_name: $("#cidname").val(), @@ -548,7 +588,7 @@ var callbacks = { } goto_page("incall"); - + real_size(); break; case $.verto.enum.state.hangup: $("#main_info").html("Call ended with cause: " + d.cause); diff --git a/src/switch_core_media.c b/src/switch_core_media.c index 5fb25f5772..1c27253ca9 100644 --- a/src/switch_core_media.c +++ b/src/switch_core_media.c @@ -8074,7 +8074,7 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess if (switch_channel_test_flag(smh->session->channel, CF_ICE)) { gen_ice(session, SWITCH_MEDIA_TYPE_AUDIO, ip, port); - switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=msid-semantic: WMS %s\r\na=end-of-candidates\r\n", smh->msid); + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=msid-semantic: WMS %s\r\n", smh->msid); } if (a_engine->codec_negotiated) { @@ -8267,6 +8267,7 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess } } + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=end-of-candidates\r\n"); switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u cname:%s\r\n", a_engine->ssrc, smh->cname); switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u msid:%s a0\r\n", a_engine->ssrc, smh->msid); @@ -8804,6 +8805,8 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess #ifdef GOOGLE_ICE switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ice-options:google-ice\r\n"); #endif + switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=end-of-candidates\r\n"); + } diff --git a/src/switch_rtp.c b/src/switch_rtp.c index 697c302d4a..944d9e9087 100644 --- a/src/switch_rtp.c +++ b/src/switch_rtp.c @@ -3192,7 +3192,7 @@ static int do_dtls(switch_rtp_t *rtp_session, switch_dtls_t *dtls) ret = SSL_get_error(dtls->ssl, ret); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_ERROR, "%s DTLS packet read err %d\n", rtp_type(rtp_session), ret); } - + if (dtls_states[dtls->state]) { r = dtls_states[dtls->state](rtp_session, dtls); } @@ -3396,8 +3396,11 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_dtls(switch_rtp_t *rtp_session, d //SSL_CTX_set_verify(dtls->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); SSL_CTX_set_verify(dtls->ssl_ctx, SSL_VERIFY_NONE, NULL); - SSL_CTX_set_cipher_list(dtls->ssl_ctx, "ALL"); - + SSL_CTX_set_cipher_list(dtls->ssl_ctx, "ECDH:!RC4:!SSLv3:RSA_WITH_AES_128_CBC_SHA"); + //SSL_CTX_set_cipher_list(dtls->ssl_ctx, "ECDHE-RSA-AES256-GCM-SHA384"); + //SSL_CTX_set_cipher_list(dtls->ssl_ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + //SSL_CTX_set_cipher_list(dtls->ssl_ctx, "SUITEB128"); + SSL_CTX_set_read_ahead(dtls->ssl_ctx, 1); #ifdef HAVE_OPENSSL_DTLS_SRTP //SSL_CTX_set_tlsext_use_srtp(dtls->ssl_ctx, "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32"); SSL_CTX_set_tlsext_use_srtp(dtls->ssl_ctx, "SRTP_AES128_CM_SHA1_80"); @@ -3456,11 +3459,11 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_dtls(switch_rtp_t *rtp_session, d SSL_set_verify(dtls->ssl, SSL_VERIFY_NONE, NULL); SSL_set_app_data(dtls->ssl, dtls); - BIO_ctrl(dtls->read_bio, BIO_CTRL_DGRAM_SET_MTU, 1400, NULL); - BIO_ctrl(dtls->write_bio, BIO_CTRL_DGRAM_SET_MTU, 1400, NULL); - SSL_set_mtu(dtls->ssl, 1400); - BIO_ctrl(dtls->write_bio, BIO_C_SET_BUFF_SIZE, 1400, NULL); - BIO_ctrl(dtls->read_bio, BIO_C_SET_BUFF_SIZE, 1400, NULL); + //BIO_ctrl(dtls->read_bio, BIO_CTRL_DGRAM_SET_MTU, 1400, NULL); + //BIO_ctrl(dtls->write_bio, BIO_CTRL_DGRAM_SET_MTU, 1400, NULL); + //SSL_set_mtu(dtls->ssl, 1400); + //BIO_ctrl(dtls->write_bio, BIO_C_SET_BUFF_SIZE, 1400, NULL); + //BIO_ctrl(dtls->read_bio, BIO_C_SET_BUFF_SIZE, 1400, NULL); @@ -4145,7 +4148,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_set_video_buffer_size(switch_rtp_t *r } if (!max_frames || frames >= max_frames) { - max_frames = frames + 8; + max_frames = frames * 10; } rtp_session->last_max_vb_frames = max_frames; @@ -4202,7 +4205,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_activate_jitter_buffer(switch_rtp_t * } if (max_queue_frames < queue_frames) { - max_queue_frames = queue_frames * 6; + max_queue_frames = queue_frames * 3; }