]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
FS-9242 convert to adapter.js
authorAnthony Minessale <anthm@freeswitch.org>
Wed, 8 Jun 2016 21:20:18 +0000 (16:20 -0500)
committerAnthony Minessale <anthm@freeswitch.org>
Tue, 27 Sep 2016 21:07:00 +0000 (16:07 -0500)
17 files changed:
html5/verto/js/Makefile
html5/verto/js/src/jquery.FSRTC.js
html5/verto/js/src/vendor/adapter-latest.js [new file with mode: 0644]
html5/verto/verto_communicator/src/index.html
html5/verto/verto_communicator/src/partials/preview.html
html5/verto/verto_communicator/src/storageService/services/splash_screen.js
html5/verto/verto_communicator/src/storageService/services/storage.js
html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js
html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js
html5/verto/verto_communicator/src/vertoControllers/controllers/SettingsController.js
html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js
html5/verto/verto_communicator/src/vertoDirectives/directives/videoTag.js
html5/verto/verto_communicator/src/vertoService/services/vertoService.js
html5/verto/video_demo/js/verto-min.js
html5/verto/video_demo/verto.js
src/switch_core_media.c
src/switch_rtp.c

index e2c8dab06837377906a01076e3eaf61e9d4b031a..05aa6617b010678503934684aceb3eaf6c34b635 100644 (file)
@@ -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
 
index 225c1b16ec1f0ef5995bc53ab5b0ea6aa2f98684..5cfd11bd057eb1c49e57f423479ab94d24dc9bbb 100644 (file)
             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';
         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) {
     };
 
     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;
     }
 
         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';
         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;
     }
 
         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) {
 
         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) {
         function onSuccess(stream) {
             self.localStream = stream;
 
-            self.peer = RTCPeerConnection({
+            self.peer = FSRTCPeerConnection({
                 type: self.type,
                 attachStream: self.localStream,
                 onICE: function(candidate) {
                constraints: {
                     audio: false,
                     video: {
-                       mandatory: self.options.videoParams,
-                       optional: []
+                       //mandatory: self.options.videoParams,
+                       //optional: []
                     },
                },
                localVideo: self.options.localVideo,
            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);
+                   }
                }
            }
 
             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");},
 
        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 (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 {
 
-       if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") {
-           if (!video.optional) {
-               video.optional = [];
-           }
+           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 (obj.options.useCamera !== "any") {
-               video.optional.push({sourceId: obj.options.useCamera});
-           }
+           if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") {
+               //if (!video.optional) {
+               //video.optional = [];
+               //}
 
-           if (bestFrameRate) {
-               video.optional.push({minFrameRate: bestFrameRate});
-               video.optional.push({maxFrameRate: bestFrameRate});
-           }
+               
+               if (obj.options.useCamera !== "any") {
+                   //video.optional.push({sourceId: obj.options.useCamera});
+                   video.deviceId = obj.options.useCamera;
+               }
 
-       } else {
-           console.log("Camera Disabled");
-           video = false;
-           useVideo = false;
+               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};
            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) {
     // 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];
-            }
-        }
-
-        var optional = {
-            optional: []
-        };
-
-        if (!moz) {
-            optional.optional = [{
-                DtlsSrtpKeyAgreement: true
-            },
-            {
-                RtpDataChannels: options.onChannelMessage ? true : false
-            }];
+            if (typeof(options.iceServers) === "boolean") {
+               config.iceServers = [default_ice];
+            } else {
+               config.iceServers = options.iceServers;
+           }
         }
 
-        var peer = new PeerConnection(iceServers, optional);
+        var peer = new window.RTCPeerConnection(config);
 
         openOffererChannel();
         var x = 0;
             }
            
             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);
-                    */
                 }
             }
         }
             //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() {
                 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)
             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);
                     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();
         }
         }
 
         function serializeSdp(sdp) {
-            //if (!moz) sdp = setBandwidth(sdp);
-            //sdp = getInteropSDP(sdp);
-            //console.debug(sdp);
             return sdp;
         }
 
         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();
         }
 
             };
         }
 
-        if (options.onAnswerSDP && moz && options.onChannelMessage) openAnswererChannel();
-
         function openAnswererChannel() {
             peer.ondatachannel = function(event) {
                 channel = event.channel;
                 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!
         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
                 }));
 
     // 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
         });
 
         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';
             }
 
        }
 
        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 (file)
index 0000000..e390add
--- /dev/null
@@ -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('<span style="display: inline-block; font-family: Helvetica, Arial,' +
+    'sans-serif; font-size: .9rem; padding: 4px; vertical-align: ' +
+    'middle; cursor: default;">' + text + '</span>');
+  if(buttonText && buttonLink) {
+    c.document.write('<button id="okay">' + buttonText + '</button><button id="cancel">Cancel</button>');
+    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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+   /* eslint-env node */
+  'use strict';
+  
+  // SDP helpers.
+  var SDPUtils = {};
+  
+  // Generate an alphanumeric identifier for cname or mids.
+  // TODO: use UUIDs instead? https://gist.github.com/jed/982883
+  SDPUtils.generateIdentifier = function() {
+    return Math.random().toString(36).substr(2, 10);
+  };
+  
+  // The RTCP CNAME used by all peerconnections from the same JS.
+  SDPUtils.localCName = SDPUtils.generateIdentifier();
+  
+  // Splits SDP into lines, dealing with both CRLF and LF.
+  SDPUtils.splitLines = function(blob) {
+    return blob.trim().split('\n').map(function(line) {
+      return line.trim();
+    });
+  };
+  // Splits SDP into sessionpart and mediasections. Ensures CRLF.
+  SDPUtils.splitSections = function(blob) {
+    var parts = blob.split('\nm=');
+    return parts.map(function(part, index) {
+      return (index > 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:<pt> 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 = '<object id="' +
+        AdapterJS.WebRTCPlugin.pluginInfo.pluginId + '" type="' +
+        AdapterJS.WebRTCPlugin.pluginInfo.type + '" ' + 'width="1" height="1">' +
+        '<param name="pluginId" value="' +
+        AdapterJS.WebRTCPlugin.pluginInfo.pluginId + '" /> ' +
+        '<param name="windowless" value="false" /> ' +
+        '<param name="pageId" value="' + AdapterJS.WebRTCPlugin.pageId + '" /> ' +
+        '<param name="onload" value="' + AdapterJS.WebRTCPlugin.pluginInfo.onload + '" />' +
+        '<param name="tag" value="' + AdapterJS.WebRTCPlugin.TAGS.NONE + '" />' +
+        // uncomment to be able to use virtual cams
+        (AdapterJS.options.getAllCams ? '<param name="forceGetAllCams" value="True" />':'') +
+
+        '</object>';
+      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 = '<param name="onload" value="' +
+        AdapterJS.WebRTCPlugin.pluginInfo.onload + '">' +
+        '<param name="pluginId" value="' +
+        AdapterJS.WebRTCPlugin.pluginInfo.pluginId + '">' +
+        '<param name="windowless" value="false" /> ' +
+        (AdapterJS.options.getAllCams ? '<param name="forceGetAllCams" value="True" />':'') +
+        '<param name="pageId" value="' + AdapterJS.WebRTCPlugin.pageId + '">' +
+        '<param name="tag" value="' + AdapterJS.WebRTCPlugin.TAGS.NONE + '" />';
+      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 <object> 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 = '<object id="' + elementId + '" ' + classHTML +
+          'type="' + AdapterJS.WebRTCPlugin.pluginInfo.type + '">' +
+          '<param name="pluginId" value="' + elementId + '" /> ' +
+          '<param name="pageId" value="' + AdapterJS.WebRTCPlugin.pageId + '" /> ' +
+          '<param name="windowless" value="true" /> ' +
+          '<param name="streamId" value="' + streamId + '" /> ' +
+          '<param name="tag" value="' + tag + '" /> ' +
+          '</object>';
+        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 <object> 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 ' +
+        ' <a href="' + AdapterJS.WebRTCPlugin.pluginInfo.portalLink +
+        '" target="_blank">' + AdapterJS.WebRTCPlugin.pluginInfo.companyName +
+        ' WebRTC Plugin</a>' +
+        ' 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
+  ///////////////////////////////////////////////////////////////////
+}
index 2f4e2af1f5520ce322d484d2a57409059357b871..16e11f5ceeafdb1b2e1a654ff9e353a6d2241f09 100644 (file)
     <script type="text/javascript" src="../js/src/jquery.jsonrpcclient.js"></script>
     <script type="text/javascript" src="../js/src/jquery.FSRTC.js"></script>
     <script type="text/javascript" src="../js/src/jquery.verto.js"></script>
+    <script type="text/javascript" src="../js/src/vendor/adapter-latest.js"></script>
 
     <script type="text/javascript" src="js/3rd-party/getScreenId.js"></script>
     <script type="text/javascript" src="js/3rd-party/md5.min.js"></script>
index 9c58c30d0bc2b3652db851f00edf81bf4c08d814..68b4523e72277e77106d6ee592a5345032e2e9b9 100644 (file)
@@ -7,7 +7,7 @@
       <div class="panel-body">
         <div class="preview-wrapper">
           <video id="videopreview" muted autoplay style="width: 100%;"></video>
-          <div id="mic-meter">
+          <div id="mic-meter" ng-if="audioContext">
             <div class="volumes">
               <div class="volume-segment"></div>
               <div class="volume-segment"></div>
index ae77d000e752e28cb30b7c7cd91d2ee0ebe96342..9aa4414bf5cb31dde8d22c8559bf545fa36c224d 100644 (file)
               '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);
index 501b7b7ab6071648d8ca5407267d900e110b3758..8aff5ee61a8e7b48ae51b8cf626038aecda19e54 100644 (file)
@@ -23,6 +23,7 @@
           mutedMic: false,
           preview: true,
           selectedVideo: null,
+          selectedVideoName: null,
           selectedAudio: null,
           selectedShare: null,
           selectedSpeaker: null,
index 50bb6ea75b5d93e4717e509f257fa277e5630bfc..7800ea3e6f05a6cf376ee816997febf9cb741a95 100644 (file)
@@ -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();
index 7a8945ec029faa4299266403b8c3085823e948a5..1379409cfeea7d3962cd57446d5b7d6e5e8684e3 100644 (file)
         $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 = {
 
           });
         };
+        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) {
           }
 
           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() {
 
         $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;
index c52cdb8c82faa78d6ff5b991013bbb9a8be61d8d..a7da6f4beb3bf823125c097026c2c37596ea12fc 100644 (file)
         });
 
         $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);
           }
index 6fd32f9de7a39409484fca81da7a66a0956e869c..65ab68d93b8cd1b5f1e8e0009365abdb4138c60b 100644 (file)
@@ -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 {
index e40adc879aa0ea9160c63baedfccdc15cdb55c3d..15fd7e1a01b102fa6b563300bd67042b0f7d844f 100644 (file)
   .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');
       });
     }
 
index b926c77f9ef2d8da233fd7dc48c62e97a3aa8d0b..404c0c4a9f2ee3d7665d09e6e78ba1c27e5dbf4a 100644 (file)
@@ -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);
index dbe001713759415a963abb649b902b228164334c..52f165af517a7613283def7ad6a15cbfc718078a 100644 (file)
@@ -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;i<elements.length;i++){if(index===3){newLine[index++]=payload;}
 if(elements[i]!==payload)newLine[index++]=elements[i];}
 return newLine.join(' ');}
-$.FSRTC=function(options){this.options=$.extend({useVideo:null,useStereo:false,userData:null,localVideo:null,screenShare:false,useCamera:"any",iceServers:false,videoParams:{},audioParams:{},callbacks:{onICEComplete:function(){},onICE:function(){},onOfferSDP:function(){}},},options);this.audioEnabled=true;this.videoEnabled=true;this.mediaData={SDP:null,profile:{},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,}};}
-if(self.options.useVideo){self.options.useVideo.style.display='none';}
-setCompat();checkCompat();};$.FSRTC.validRes=[];$.FSRTC.prototype.useVideo=function(obj,local){var self=this;if(obj){self.options.useVideo=obj;self.options.localVideo=local;if(moz){self.constraints.offerToReceiveVideo=true;}else{self.constraints.mandatory.OfferToReceiveVideo=true;}}else{self.options.useVideo=null;self.options.localVideo=null;if(moz){self.constraints.offerToReceiveVideo=false;}else{self.constraints.mandatory.OfferToReceiveVideo=false;}}
+$.FSRTC=function(options){this.options=$.extend({useVideo:null,useStereo:false,userData:null,localVideo:null,screenShare:false,useCamera:"any",iceServers:false,videoParams:{},audioParams:{},callbacks:{onICEComplete:function(){},onICE:function(){},onOfferSDP:function(){}},},options);this.audioEnabled=true;this.videoEnabled=true;this.mediaData={SDP:null,profile:{},candidateList:[]};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';}
+setCompat();checkCompat();};$.FSRTC.validRes=[];$.FSRTC.prototype.useVideo=function(obj,local){var self=this;if(obj){self.options.useVideo=obj;self.options.localVideo=local;self.constraints.offerToReceiveVideo=true;}else{self.options.useVideo=null;self.options.localVideo=null;self.constraints.offerToReceiveVideo=false;}
 if(self.options.useVideo){self.options.useVideo.style.display='none';}};$.FSRTC.prototype.useStereo=function(on){var self=this;self.options.useStereo=on;};$.FSRTC.prototype.stereoHack=function(sdp){var self=this;if(!self.options.useStereo){return sdp;}
 var sdpLines=sdp.split('\r\n');var opusIndex=findLine(sdpLines,'a=rtpmap','opus/48000'),opusPayload;if(!opusIndex){return sdp;}else{opusPayload=getCodecPayloadType(sdpLines[opusIndex]);}
 var fmtpLineIndex=findLine(sdpLines,'a=fmtp:'+opusPayload.toString());if(fmtpLineIndex===null){sdpLines[opusIndex]=sdpLines[opusIndex]+'\r\na=fmtp:'+opusPayload.toString()+" stereo=1; sprop-stereo=1"}else{sdpLines[fmtpLineIndex]=sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1');}
-sdp=sdpLines.join('\r\n');return sdp;};function setCompat(){$.FSRTC.moz=!!navigator.mozGetUserMedia;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;}
+sdp=sdpLines.join('\r\n');return sdp;};function setCompat(){}
+function checkCompat(){return true;}
 function onStreamError(self,e){console.log('There has been a problem retrieving the streams - did you allow access? Check Device Resolution',e);doCallback(self,"onError",e);}
 function onStreamSuccess(self,stream){console.log("Stream Success");doCallback(self,"onStream",stream);}
 function onICE(self,candidate){self.mediaData.candidate=candidate;self.mediaData.candidateList.push(self.mediaData.candidate);doCallback(self,"onICE");}
@@ -24,15 +22,15 @@ function onChannelError(self,e){console.error("Channel Error",e);doCallback(self
 function onICESDP(self,sdp){self.mediaData.SDP=self.stereoHack(sdp.sdp);console.log("ICE SDP");doCallback(self,"onICESDP");}
 function onAnswerSDP(self,sdp){self.answer.SDP=self.stereoHack(sdp.sdp);console.log("ICE ANSWER SDP");doCallback(self,"onAnswerSDP",self.answer.SDP);}
 function onMessage(self,msg){console.log("Message");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';}
-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.');}
-self.options.useAudio.play();self.remoteStream=stream;}
+var element=self.options.useAudio;console.log("REMOTE STREAM",stream,element);FSRTCattachMediaStream(element,stream);self.options.useAudio.play();self.remoteStream=stream;}
 function onOfferSDP(self,sdp){self.mediaData.SDP=self.stereoHack(sdp.sdp);console.log("Offer SDP");doCallback(self,"onOfferSDP");}
 $.FSRTC.prototype.answer=function(sdp,onSuccess,onError){this.peer.addAnswerSDP({type:"answer",sdp:sdp},onSuccess,onError);};$.FSRTC.prototype.stopPeer=function(){if(self.peer){console.log("stopping peer");self.peer.stop();}}
-$.FSRTC.prototype.stop=function(){var self=this;if(self.options.useVideo){self.options.useVideo.style.display='none';if(moz){self.options.useVideo['mozSrcObject']=null;}else{self.options.useVideo['src']='';}}
+$.FSRTC.prototype.stop=function(){var self=this;if(self.options.useVideo){self.options.useVideo.style.display='none';self.options.useVideo['src']='';}
 if(self.localStream){if(typeof self.localStream.stop=='function'){self.localStream.stop();}else{if(self.localStream.active){var tracks=self.localStream.getTracks();console.error(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}
 self.localStream=null;}
-if(self.options.localVideo){self.options.localVideo.style.display='none';if(moz){self.options.localVideo['mozSrcObject']=null;}else{self.options.localVideo['src']='';}}
+if(self.options.localVideo){self.options.localVideo.style.display='none';self.options.localVideo['src']='';}
 if(self.options.localVideoStream){if(typeof self.options.localVideoStream.stop=='function'){self.options.localVideoStream.stop();}else{if(self.options.localVideoStream.active){var tracks=self.options.localVideoStream.getTracks();console.error(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}}
 if(self.peer){console.log("stopping peer");self.peer.stop();}};$.FSRTC.prototype.getMute=function(){var self=this;return self.audioEnabled;}
 $.FSRTC.prototype.setMute=function(what){var self=this;var audioTracks=self.localStream.getAudioTracks();for(var i=0,len=audioTracks.length;i<len;i++){switch(what){case"on":audioTracks[i].enabled=true;break;case"off":audioTracks[i].enabled=false;break;case"toggle":audioTracks[i].enabled=!audioTracks[i].enabled;default:break;}
@@ -42,48 +40,43 @@ $.FSRTC.prototype.getVideoMute=function(){var self=this;return self.videoEnabled
 $.FSRTC.prototype.setVideoMute=function(what){var self=this;var videoTracks=self.localStream.getVideoTracks();for(var i=0,len=videoTracks.length;i<len;i++){switch(what){case"on":videoTracks[i].enabled=true;break;case"off":videoTracks[i].enabled=false;break;case"toggle":videoTracks[i].enabled=!videoTracks[i].enabled;default:break;}
 self.videoEnabled=videoTracks[i].enabled;}
 return!self.videoEnabled;}
-$.FSRTC.prototype.createAnswer=function(params){var self=this;self.type="answer";self.remoteSDP=params.sdp;console.debug("inbound sdp: ",params.sdp);function onSuccess(stream){self.localStream=stream;self.peer=RTCPeerConnection({type:self.type,attachStream:self.localStream,onICE:function(candidate){return onICE(self,candidate);},onICEComplete:function(){return onICEComplete(self);},onRemoteStream:function(stream){return onRemoteStream(self,stream);},onICESDP:function(sdp){return onICESDP(self,sdp);},onChannelError:function(e){return onChannelError(self,e);},constraints:self.constraints,iceServers:self.options.iceServers,offerSDP:{type:"offer",sdp:self.remoteSDP}});onStreamSuccess(self);}
+$.FSRTC.prototype.createAnswer=function(params){var self=this;self.type="answer";self.remoteSDP=params.sdp;console.debug("inbound sdp: ",params.sdp);function onSuccess(stream){self.localStream=stream;self.peer=FSRTCPeerConnection({type:self.type,attachStream:self.localStream,onICE:function(candidate){return onICE(self,candidate);},onICEComplete:function(){return onICEComplete(self);},onRemoteStream:function(stream){return onRemoteStream(self,stream);},onICESDP:function(sdp){return onICESDP(self,sdp);},onChannelError:function(e){return onChannelError(self,e);},constraints:self.constraints,iceServers:self.options.iceServers,offerSDP:{type:"offer",sdp:self.remoteSDP}});onStreamSuccess(self);}
 function onError(e){onStreamError(self,e);}
-var mediaParams=getMediaParams(self);console.log("Audio constraints",mediaParams.audio);console.log("Video constraints",mediaParams.video);if(self.options.useVideo&&self.options.localVideo){getUserMedia({constraints:{audio:false,video:{mandatory:self.options.videoParams,optional:[]},},localVideo:self.options.localVideo,onsuccess:function(e){self.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!");}});}
-getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,onsuccess:onSuccess,onerror:onError});};function getMediaParams(obj){var audio;if(obj.options.useMic&&obj.options.useMic==="none"){console.log("Microphone Disabled");audio=false;}else if(obj.options.videoParams&&obj.options.screenShare){console.error("SCREEN SHARE");audio=false;}else{audio={mandatory:{},optional:[]};if(obj.options.useMic!=="any"){audio.optional=[{sourceId:obj.options.useMic}]}
-if(obj.options.audioParams){for(var key in obj.options.audioParams){var con={};con[key]=obj.options.audioParams[key];audio.optional.push(con);}}}
-if(obj.options.useVideo&&obj.options.localVideo){getUserMedia({constraints:{audio:false,video:{mandatory:obj.options.videoParams,optional:[]},},localVideo:obj.options.localVideo,onsuccess:function(e){self.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!");}});}
-var video={};var bestFrameRate=obj.options.videoParams.vertoBestFrameRate;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});}}else{console.log("Camera Disabled");video=false;useVideo=false;}
+var mediaParams=getMediaParams(self);console.log("Audio constraints",mediaParams.audio);console.log("Video constraints",mediaParams.video);if(self.options.useVideo&&self.options.localVideo){getUserMedia({constraints:{audio:false,video:{},},localVideo:self.options.localVideo,onsuccess:function(e){self.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!");}});}
+getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,onsuccess:onSuccess,onerror:onError});};function getMediaParams(obj){var audio;if(obj.options.useMic&&obj.options.useMic==="none"){console.log("Microphone Disabled");audio=false;}else if(obj.options.videoParams&&obj.options.screenShare){console.error("SCREEN SHARE",obj.options.videoParams);audio=false;}else{audio={advanced:[]};if(obj.options.useMic!=="any"){audio.deviceId={exact:obj.options.useMic};}
+if(obj.options.audioParams){for(var key in obj.options.audioParams){var con={};if(obj.options.audioParams[key]){con.exact=key;audio.advanced.push(con);}}}}
+if(obj.options.useVideo&&obj.options.localVideo){getUserMedia({constraints:{audio:false,video:obj.options.videoParams},localVideo:obj.options.localVideo,onsuccess:function(e){self.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!");}});}
+var video={};var bestFrameRate=obj.options.videoParams.vertoBestFrameRate;var minFrameRate=obj.options.videoParams.minFrameRate||15;delete obj.options.videoParams.vertoBestFrameRate;if(obj.options.screenShare){video={mandatory:obj.options.videoParams};}else{video={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(obj.options.useCamera!=="any"){video.deviceId=obj.options.useCamera;}
+if(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};}
 $.FSRTC.prototype.call=function(profile){checkCompat();var self=this;var screen=false;self.type="offer";if(self.options.videoParams&&self.options.screenShare){screen=true;}
-function onSuccess(stream){self.localStream=stream;if(screen){if(moz){self.constraints.OfferToReceiveVideo=false;}else{self.constraints.mandatory.OfferToReceiveVideo=false;}}
-self.peer=RTCPeerConnection({type:self.type,attachStream:self.localStream,onICE:function(candidate){return onICE(self,candidate);},onICEComplete:function(){return onICEComplete(self);},onRemoteStream:screen?function(stream){}:function(stream){return onRemoteStream(self,stream);},onOfferSDP:function(sdp){return onOfferSDP(self,sdp);},onICESDP:function(sdp){return onICESDP(self,sdp);},onChannelError:function(e){return onChannelError(self,e);},constraints:self.constraints,iceServers:self.options.iceServers,});onStreamSuccess(self,stream);}
+function onSuccess(stream){self.localStream=stream;if(screen){self.constraints.offerToReceiveVideo=false;}
+self.peer=FSRTCPeerConnection({type:self.type,attachStream:self.localStream,onICE:function(candidate){return onICE(self,candidate);},onICEComplete:function(){return onICEComplete(self);},onRemoteStream:screen?function(stream){}:function(stream){return onRemoteStream(self,stream);},onOfferSDP:function(sdp){return onOfferSDP(self,sdp);},onICESDP:function(sdp){return onICESDP(self,sdp);},onChannelError:function(e){return onChannelError(self,e);},constraints:self.constraints,iceServers:self.options.iceServers,});onStreamSuccess(self,stream);}
 function onError(e){onStreamError(self,e);}
-var mediaParams=getMediaParams(self);console.log("Audio constraints",mediaParams.audio);console.log("Video constraints",mediaParams.video);if(mediaParams.audio||mediaParams.video){getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,onsuccess:onSuccess,onerror:onError});}else{onSuccess(null);}};window.moz=!!navigator.mozGetUserMedia;function RTCPeerConnection(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;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];}}
-var optional={optional:[]};if(!moz){optional.optional=[{DtlsSrtpKeyAgreement:true},{RtpDataChannels:options.onChannelMessage?true:false}];}
-var peer=new PeerConnection(iceServers,optional);openOffererChannel();var x=0;function ice_handler(){done=true;gathering=null;if(options.onICEComplete){options.onICEComplete();}
-if(options.type=="offer"){if((!moz||(!options.sentICESDP&&peer.localDescription.sdp.match(/a=candidate/))&&!x&&options.onICESDP)){options.onICESDP(peer.localDescription);}}else{if(!x&&options.onICESDP){options.onICESDP(peer.localDescription);}}}
+var mediaParams=getMediaParams(self);console.log("Audio constraints",mediaParams.audio);console.log("Video constraints",mediaParams.video);if(mediaParams.audio||mediaParams.video){getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,onsuccess:onSuccess,onerror:onError});}else{onSuccess(null);}};function FSRTCPeerConnection(options){var gathering=false,done=false;var config={};var default_ice={urls:['stun:stun.l.google.com:19302']};if(options.iceServers){if(typeof(options.iceServers)==="boolean"){config.iceServers=[default_ice];}else{config.iceServers=options.iceServers;}}
+var peer=new window.RTCPeerConnection(config);openOffererChannel();var x=0;function ice_handler(){done=true;gathering=null;if(options.onICEComplete){options.onICEComplete();}
+if(options.type=="offer"){options.onICESDP(peer.localDescription);}else{if(!x&&options.onICESDP){options.onICESDP(peer.localDescription);}}}
 peer.onicecandidate=function(event){if(done){return;}
 if(!gathering){gathering=setTimeout(ice_handler,1000);}
 if(event){if(event.candidate){options.onICE(event.candidate);}}else{done=true;if(gathering){clearTimeout(gathering);gathering=null;}
 ice_handler();}};if(options.attachStream)peer.addStream(options.attachStream);if(options.attachStreams&&options.attachStream.length){var streams=options.attachStreams;for(var i=0;i<streams.length;i++){peer.addStream(streams[i]);}}
-peer.onaddstream=function(event){var remoteMediaStream=event.stream;remoteMediaStream.onended=function(){if(options.onRemoteStreamEnded)options.onRemoteStreamEnded(remoteMediaStream);};if(options.onRemoteStream)options.onRemoteStream(remoteMediaStream);};var constraints=options.constraints||{offerToReceiveAudio:true,offerToReceiveVideo:true};function createOffer(){if(!options.onOfferSDP)return;peer.createOffer(function(sessionDescription){sessionDescription.sdp=serializeSdp(sessionDescription.sdp);peer.setLocalDescription(sessionDescription);options.onOfferSDP(sessionDescription);if(moz&&options.onICESDP&&sessionDescription.sdp.match(/a=candidate/)){options.onICESDP(sessionDescription);options.sentICESDP=1;}},onSdpError,constraints);}
-function createAnswer(){if(options.type!="answer")return;peer.setRemoteDescription(new SessionDescription(options.offerSDP),onSdpSuccess,onSdpError);peer.createAnswer(function(sessionDescription){sessionDescription.sdp=serializeSdp(sessionDescription.sdp);peer.setLocalDescription(sessionDescription);if(options.onAnswerSDP){options.onAnswerSDP(sessionDescription);}},onSdpError,constraints);}
-if((options.onChannelMessage&&!moz)||!options.onChannelMessage){createOffer();createAnswer();}
+peer.onaddstream=function(event){var remoteMediaStream=event.stream;remoteMediaStream.onended=function(){if(options.onRemoteStreamEnded)options.onRemoteStreamEnded(remoteMediaStream);};if(options.onRemoteStream)options.onRemoteStream(remoteMediaStream);};function createOffer(){if(!options.onOfferSDP)return;peer.createOffer(function(sessionDescription){sessionDescription.sdp=serializeSdp(sessionDescription.sdp);peer.setLocalDescription(sessionDescription);options.onOfferSDP(sessionDescription);},onSdpError,options.constraints);}
+function createAnswer(){if(options.type!="answer")return;peer.setRemoteDescription(new window.RTCSessionDescription(options.offerSDP),onSdpSuccess,onSdpError);peer.createAnswer(function(sessionDescription){sessionDescription.sdp=serializeSdp(sessionDescription.sdp);peer.setLocalDescription(sessionDescription);if(options.onAnswerSDP){options.onAnswerSDP(sessionDescription);}},onSdpError);}
+if((options.onChannelMessage)||!options.onChannelMessage){createOffer();createAnswer();}
 function setBandwidth(sdp){sdp=sdp.replace(/b=AS([^\r\n]+\r\n)/g,'');sdp=sdp.replace(/a=mid:data\r\n/g,'a=mid:data\r\nb=AS:1638400\r\n');return sdp;}
 function getInteropSDP(sdp){var chars='ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),extractedChars='';function getChars(){extractedChars+=chars[parseInt(Math.random()*40)]||'';if(extractedChars.length<40)getChars();return extractedChars;}
 if(options.onAnswerSDP)sdp=sdp.replace(/(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g,'');var inline=getChars()+'\r\n'+(extractedChars='');sdp=sdp.indexOf('a=crypto')==-1?sdp.replace(/c=IN/g,'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:'+inline+'c=IN'):sdp;return sdp;}
 function serializeSdp(sdp){return sdp;}
-var channel;function openOffererChannel(){if(!options.onChannelMessage||(moz&&!options.onOfferSDP))return;_openOffererChannel();if(!moz)return;navigator.mozGetUserMedia({audio:true,fake:true},function(stream){peer.addStream(stream);createOffer();},useless);}
-function _openOffererChannel(){channel=peer.createDataChannel(options.channel||'RTCDataChannel',moz?{}:{reliable:false});if(moz)channel.binaryType='blob';setChannelEvents();}
+var channel;function openOffererChannel(){if(!options.onChannelMessage)return;_openOffererChannel();return;}
+function _openOffererChannel(){channel=peer.createDataChannel(options.channel||'RTCDataChannel',{reliable:false});setChannelEvents();}
 function setChannelEvents(){channel.onmessage=function(event){if(options.onChannelMessage)options.onChannelMessage(event);};channel.onopen=function(){if(options.onChannelOpened)options.onChannelOpened(channel);};channel.onclose=function(event){if(options.onChannelClosed)options.onChannelClosed(event);console.warn('WebRTC DataChannel closed',event);};channel.onerror=function(event){if(options.onChannelError)options.onChannelError(event);console.error('WebRTC DataChannel error',event);};}
-if(options.onAnswerSDP&&moz&&options.onChannelMessage)openAnswererChannel();function openAnswererChannel(){peer.ondatachannel=function(event){channel=event.channel;channel.binaryType='blob';setChannelEvents();};if(!moz)return;navigator.mozGetUserMedia({audio:true,fake:true},function(stream){peer.addStream(stream);createAnswer();},useless);}
+function openAnswererChannel(){peer.ondatachannel=function(event){channel=event.channel;channel.binaryType='blob';setChannelEvents();};return;}
 function useless(){log('Error in fake:true');}
 function onSdpSuccess(){}
 function onSdpError(e){if(options.onChannelError){options.onChannelError(e);}
 console.error('sdp error:',e);}
-return{addAnswerSDP:function(sdp,cbSuccess,cbError){peer.setRemoteDescription(new SessionDescription(sdp),cbSuccess?cbSuccess:onSdpSuccess,cbError?cbError:onSdpError);},addICE:function(candidate){peer.addIceCandidate(new IceCandidate({sdpMLineIndex:candidate.sdpMLineIndex,candidate:candidate.candidate}));},peer:peer,channel:channel,sendData:function(message){if(channel){channel.send(message);}},stop:function(){peer.close();if(options.attachStream){if(typeof options.attachStream.stop=='function'){options.attachStream.stop();}else{options.attachStream.active=false;}}}};}
-var video_constraints={mandatory:{},optional:[]};function getUserMedia(options){var n=navigator,media;n.getMedia=n.webkitGetUserMedia||n.mozGetUserMedia;n.getMedia(options.constraints||{audio:true,video:video_constraints},streaming,options.onerror||function(e){console.error(e);});function streaming(stream){if(options.localVideo){options.localVideo[moz?'mozSrcObject':'src']=moz?stream:window.webkitURL.createObjectURL(stream);options.localVideo.style.display='block';}
+return{addAnswerSDP:function(sdp,cbSuccess,cbError){peer.setRemoteDescription(new window.RTCSessionDescription(sdp),cbSuccess?cbSuccess:onSdpSuccess,cbError?cbError:onSdpError);},addICE:function(candidate){peer.addIceCandidate(new window.RTCIceCandidate({sdpMLineIndex:candidate.sdpMLineIndex,candidate:candidate.candidate}));},peer:peer,channel:channel,sendData:function(message){if(channel){channel.send(message);}},stop:function(){peer.close();if(options.attachStream){if(typeof options.attachStream.stop=='function'){options.attachStream.stop();}else{options.attachStream.active=false;}}}};}
+var video_constraints={};function getUserMedia(options){var n=navigator,media;n.getMedia=n.getUserMedia;n.getMedia(options.constraints||{audio:true,video:video_constraints},streaming,options.onerror||function(e){console.error(e);});function streaming(stream){if(options.localVideo){options.localVideo['src']=window.URL.createObjectURL(stream);options.localVideo.style.display='block';}
 if(options.onsuccess){options.onsuccess(stream);}
 media=stream;}
 return media;}
@@ -91,10 +84,10 @@ $.FSRTC.resSupported=function(w,h){for(var i in $.FSRTC.validRes){if($.FSRTC.val
 return false;}
 $.FSRTC.bestResSupported=function(){var w=0,h=0;for(var i in $.FSRTC.validRes){if($.FSRTC.validRes[i][0]>w&&$.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('<span style="display: inline-block; font-family: Helvetica, Arial,'+'sans-serif; font-size: .9rem; padding: 4px; vertical-align: '+'middle; cursor: default;">'+text+'</span>');if(buttonText&&buttonLink){c.document.write('<button id="okay">'+buttonText+'</button><button id="cancel">Cancel</button>');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;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){'use strict';var SDPUtils={};SDPUtils.generateIdentifier=function(){return Math.random().toString(36).substr(2,10);};SDPUtils.localCName=SDPUtils.generateIdentifier();SDPUtils.splitLines=function(blob){return blob.trim().split('\n').map(function(line){return line.trim();});};SDPUtils.splitSections=function(blob){var parts=blob.split('\nm=');return parts.map(function(part,index){return(index>0?'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<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:break;}}
+return candidate;};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);sdp.push('rport');sdp.push(candidate.relatedPort);}
+if(candidate.tcpType&&candidate.protocol.toLowerCase()==='tcp'){sdp.push('tcptype');sdp.push(candidate.tcpType);}
+return'candidate:'+sdp.join(' ');};SDPUtils.parseRtpMap=function(line){var parts=line.substr(9).split(' ');var parsed={payloadType:parseInt(parts.shift(),10)};parts=parts[0].split('/');parsed.name=parts[0];parsed.clockRate=parseInt(parts[1],10);parsed.numChannels=parts.length===3?parseInt(parts[2],10):1;return parsed;};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';};SDPUtils.parseExtmap=function(line){var parts=line.substr(9).split(' ');return{id:parseInt(parts[0],10),uri:parts[1]};};SDPUtils.writeExtmap=function(headerExtension){return'a=extmap:'+(headerExtension.id||headerExtension.preferredId)+' '+headerExtension.uri+'\r\n';};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;};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;};SDPUtils.parseRtcpFb=function(line){var parts=line.substr(line.indexOf(' ')+1).split(' ');return{type:parts.shift(),parameter:parts.join(' ')};};SDPUtils.writeRtcpFb=function(codec){var lines='';var pt=codec.payloadType;if(codec.preferredPayloadType!==undefined){pt=codec.preferredPayloadType;}
+if(codec.rtcpFeedback&&codec.rtcpFeedback.length){codec.rtcpFeedback.forEach(function(fb){lines+='a=rtcp-fb:'+pt+' '+fb.type+
+(fb.parameter&&fb.parameter.length?' '+fb.parameter:'')+'\r\n';});}
+return lines;};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;};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;i<mline.length;i++){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+' ');codec.parameters=fmtps.length?SDPUtils.parseFmtp(fmtps[0]):{};codec.rtcpFeedback=SDPUtils.matchPrefix(mediaSection,'a=rtcp-fb:'+pt+' ').map(SDPUtils.parseRtcpFb);description.codecs.push(codec);switch(codec.name.toUpperCase()){case'RED':case'ULPFEC':description.fecMechanisms.push(codec.name.toUpperCase());break;default:break;}}}
+SDPUtils.matchPrefix(mediaSection,'a=extmap:').forEach(function(line){description.headerExtensions.push(SDPUtils.parseExtmap(line));});return description;};SDPUtils.writeRtpDescription=function(kind,caps){var sdp='';sdp+='m='+kind+' ';sdp+=caps.codecs.length>0?'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;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:}}
+if(sessionpart){return SDPUtils.getDirection(sessionpart);}
+return'sendrecv';};module.exports=SDPUtils;},{}],2:[function(require,module,exports){'use strict';(function(){var logging=require('./utils').log;var browserDetails=require('./utils').browserDetails;module.exports.browserDetails=browserDetails;module.exports.extractVersion=require('./utils').extractVersion;module.exports.disableLog=require('./utils').disableLog;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;switch(browserDetails.browser){case'opera':case'chrome':if(!chromeShim||!chromeShim.shimPeerConnection){logging('Chrome shim is not included in this adapter release.');return;}
+logging('adapter.js shimming chrome.');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.');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.');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.');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){'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){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)){Object.defineProperty(window.HTMLMediaElement.prototype,'srcObject',{get:function(){return this._srcObject;},set:function(stream){var self=this;this._srcObject=stream;if(this.src){URL.revokeObjectURL(this.src);}
+if(!stream){this.src='';return;}
+this.src=URL.createObjectURL(stream);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(){window.RTCPeerConnection=function(pcConfig,pcConstraints){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(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;};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<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){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;});};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){commonCapabilities.codecs.push(rCodec);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;});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;}}});return commonCapabilities;};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;if(end){if(iceGatherer.state===undefined){iceGatherer.state='completed';}
+event.candidate.candidate='candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates';}else{cand.component=iceTransport.component==='RTCP'?2:1;event.candidate.candidate=SDPUtils.writeCandidate(cand);}
+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';});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':break;default:break;}};iceTransport.onicestatechange=function(){self._updateConnectionState();};var dtlsTransport=new RTCDtlsTransport(iceTransport);dtlsTransport.ondtlsstatechange=function(){self._updateConnectionState();};dtlsTransport.onerror=function(){dtlsTransport.state='failed';self._updateConnectionState();};return{iceGatherer:iceGatherer,iceTransport:iceTransport,dtlsTransport:dtlsTransport};};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'){if(this._pendingOffer){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';});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;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):{};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<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;if(mozRTCPeerConnection.generateCertificate){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function(){return mozRTCPeerConnection.generateCertificate;}});}
+window.RTCSessionDescription=mozRTCSessionDescription;window.RTCIceCandidate=mozRTCIceCandidate;}
+['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);};});var nativeAddIceCandidate=RTCPeerConnection.prototype.addIceCandidate;RTCPeerConnection.prototype.addIceCandidate=function(){return arguments[0]===null?Promise.resolve():nativeAddIceCandidate.apply(this,arguments);};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);};}};module.exports={shimOnTrack:firefoxShim.shimOnTrack,shimSourceObject:firefoxShim.shimSourceObject,shimPeerConnection:firefoxShim.shimPeerConnection,shimGetUserMedia:require('./getusermedia')};},{"../utils":10,"./getusermedia":8}],8:[function(require,module,exports){'use strict';var logging=require('../utils').log;var browserDetails=require('../utils').browserDetails;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;}};};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));});};var getUserMediaPromise_=function(constraints){return new Promise(function(resolve,reject){getUserMedia_(constraints,resolve,reject);});};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){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);}
+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){'use strict';var safariShim={shimGetUserMedia:function(){navigator.getUserMedia=navigator.webkitGetUserMedia;}};module.exports={shimGetUserMedia:safariShim.shimGetUserMedia};},{}],10:[function(require,module,exports){'use strict';var logDisabled_=true;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);}}},extractVersion:function(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=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<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;};}
+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;};}
+if(attachMediaStream){attachMediaStream_base=attachMediaStream;attachMediaStream=function(element,stream){if((webrtcDetectedBrowser==='chrome'||webrtcDetectedBrowser==='opera')&&!stream){element.src='';}else{attachMediaStream_base(element,stream);}
+return element;};}
+if(reattachMediaStream){reattachMediaStream_base=reattachMediaStream;reattachMediaStream=function(to,from){reattachMediaStream_base(to,from);return to;};}
+window.attachMediaStream=attachMediaStream;window.reattachMediaStream=reattachMediaStream;window.getUserMedia=getUserMedia;AdapterJS.attachMediaStream=attachMediaStream;AdapterJS.reattachMediaStream=reattachMediaStream;AdapterJS.getUserMedia=getUserMedia;if(typeof Promise==='undefined'){requestUserMedia=null;}
+AdapterJS.maybeThroughWebRTCReady();}else{if(typeof console!=='object'||typeof console.log!=='function'){console={}||console;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){};}
+AdapterJS.parseWebrtcDetectedBrowser();isIE=webrtcDetectedBrowser==='IE';AdapterJS.WebRTCPlugin.WaitForPluginReady=function(){while(AdapterJS.WebRTCPlugin.pluginState!==AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY){}};AdapterJS.WebRTCPlugin.callWhenPluginReady=function(callback){if(AdapterJS.WebRTCPlugin.pluginState===AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY){callback();}else{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(){if(document.readyState!=='complete'){return;}
+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='<object id="'+
+AdapterJS.WebRTCPlugin.pluginInfo.pluginId+'" type="'+
+AdapterJS.WebRTCPlugin.pluginInfo.type+'" '+'width="1" height="1">'+'<param name="pluginId" value="'+
+AdapterJS.WebRTCPlugin.pluginInfo.pluginId+'" /> '+'<param name="windowless" value="false" /> '+'<param name="pageId" value="'+AdapterJS.WebRTCPlugin.pageId+'" /> '+'<param name="onload" value="'+AdapterJS.WebRTCPlugin.pluginInfo.onload+'" />'+'<param name="tag" value="'+AdapterJS.WebRTCPlugin.TAGS.NONE+'" />'+
+(AdapterJS.options.getAllCams?'<param name="forceGetAllCams" value="True" />':'')+'</object>';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='<param name="onload" value="'+
+AdapterJS.WebRTCPlugin.pluginInfo.onload+'">'+'<param name="pluginId" value="'+
+AdapterJS.WebRTCPlugin.pluginInfo.pluginId+'">'+'<param name="windowless" value="false" /> '+
+(AdapterJS.options.getAllCams?'<param name="forceGetAllCams" value="True" />':'')+'<param name="pageId" value="'+AdapterJS.WebRTCPlugin.pageId+'">'+'<param name="tag" value="'+AdapterJS.WebRTCPlugin.TAGS.NONE+'" />';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){if(!(servers===undefined||servers===null||Array.isArray(servers.iceServers))){throw new Error('Failed to construct \'RTCPeerConnection\': Malformed RTCConfiguration');}
+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');}}
+AdapterJS.WebRTCPlugin.WaitForPluginReady();var iceServers=null;if(servers&&Array.isArray(servers.iceServers)){iceServers=servers.iceServers;for(var i=0;i<iceServers.length;i++){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){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='<object id="'+elementId+'" '+classHTML+'type="'+AdapterJS.WebRTCPlugin.pluginInfo.type+'">'+'<param name="pluginId" value="'+elementId+'" /> '+'<param name="pageId" value="'+AdapterJS.WebRTCPlugin.pageId+'" /> '+'<param name="windowless" value="true" /> '+'<param name="streamId" value="'+streamId+'" /> '+'<param name="tag" value="'+tag+'" /> '+'</object>';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 '+' <a href="'+AdapterJS.WebRTCPlugin.pluginInfo.portalLink+'" target="_blank">'+AdapterJS.WebRTCPlugin.pluginInfo.companyName+' WebRTC Plugin</a>'+' 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
index e0ee4aec19b37467a2c2f3106862e1d5d52fe7e7..7b6adc13b58d43025e62824b8beb4783f5f2dc7f 100644 (file)
@@ -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() {
 
+    
+
+    /* temasys hack */
+    setTimeout(function() {
+       $("#" + video_screen).width("");
+       $("#" + video_screen).height("");
 
-    $("#" + 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);
index 5fb25f5772c9d7f1a68e26f091d97d5e0c66eb2e..1c27253ca9f0e6628c2b7c8bdfbfd566b223e9e8 100644 (file)
@@ -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");
+
                                }
 
                                
index 697c302d4a6650f5ea1ca50e3ea08c038a984ab2..944d9e90871b2f573050ed0c4d7c3881f327816a 100644 (file)
@@ -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;
        }